Add chapter: 18

This commit is contained in:
Kasper Juul Hermansen 2022-01-28 21:21:29 +01:00
parent 0e2c91dd12
commit cb17a9c356
Signed by: kjuulh
GPG Key ID: DCD9397082D97069
12 changed files with 286 additions and 49 deletions

File diff suppressed because one or more lines are too long

View File

@ -173,3 +173,8 @@ pub struct DefenseBonus {
pub struct WantsToRemoveItem { pub struct WantsToRemoveItem {
pub item: Entity, pub item: Entity,
} }
#[derive(Component, Serialize, Deserialize, Clone)]
pub struct ParticleLifetime {
pub lifetime_ms: f32,
}

View File

@ -1,9 +1,9 @@
use rltk::console; use rltk::console;
use specs::prelude::*; use specs::prelude::*;
use crate::{Map, Name, Position, RunState};
use crate::components::{CombatStats, Player, SufferDamage}; use crate::components::{CombatStats, Player, SufferDamage};
use crate::gamelog::GameLog; use crate::gamelog::GameLog;
use crate::{Map, Name, Position, RunState};
pub struct DamageSystem {} pub struct DamageSystem {}
@ -13,7 +13,7 @@ impl<'a> System<'a> for DamageSystem {
WriteStorage<'a, SufferDamage>, WriteStorage<'a, SufferDamage>,
ReadStorage<'a, Position>, ReadStorage<'a, Position>,
WriteExpect<'a, Map>, WriteExpect<'a, Map>,
Entities<'a> Entities<'a>,
); );
fn run(&mut self, data: Self::SystemData) { fn run(&mut self, data: Self::SystemData) {

View File

@ -1,14 +1,14 @@
use rltk::{Rltk}; use rltk::Rltk;
use rltk::{Point, VirtualKeyCode};
use rltk::RGB; use rltk::RGB;
use rltk::{Point, VirtualKeyCode};
use specs::prelude::*; use specs::prelude::*;
use crate::{CombatStats, InBackpack, RunState, State, Viewshed};
use crate::{Equipped, Map};
use crate::gamelog::GameLog; use crate::gamelog::GameLog;
use crate::Name; use crate::Name;
use crate::Player; use crate::Player;
use crate::Position; use crate::Position;
use crate::{CombatStats, InBackpack, RunState, State, Viewshed};
use crate::{Equipped, Map};
pub fn draw_ui(ecs: &World, ctx: &mut Rltk) { pub fn draw_ui(ecs: &World, ctx: &mut Rltk) {
ctx.draw_box( ctx.draw_box(
@ -603,17 +603,40 @@ pub fn remove_item_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Opti
} }
#[derive(PartialEq, Copy, Clone)] #[derive(PartialEq, Copy, Clone)]
pub enum GameOverResult { NoSelection, QuitToMenu } pub enum GameOverResult {
NoSelection,
QuitToMenu,
}
pub fn game_over(ctx: &mut Rltk) -> GameOverResult { pub fn game_over(ctx: &mut Rltk) -> GameOverResult {
ctx.print_color_centered(15, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "Your journey has ended!"); ctx.print_color_centered(
ctx.print_color_centered(17, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), "One day, we'll tell you all about how you did."); 15,
ctx.print_color_centered(18, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), "That day, sadly, is not in this chapter.."); RGB::named(rltk::YELLOW),
RGB::named(rltk::BLACK),
"Your journey has ended!",
);
ctx.print_color_centered(
17,
RGB::named(rltk::WHITE),
RGB::named(rltk::BLACK),
"One day, we'll tell you all about how you did.",
);
ctx.print_color_centered(
18,
RGB::named(rltk::WHITE),
RGB::named(rltk::BLACK),
"That day, sadly, is not in this chapter..",
);
ctx.print_color_centered(20, RGB::named(rltk::MAGENTA), RGB::named(rltk::BLACK), "Press any key to return to the menu."); ctx.print_color_centered(
20,
RGB::named(rltk::MAGENTA),
RGB::named(rltk::BLACK),
"Press any key to return to the menu.",
);
match ctx.key { match ctx.key {
None => GameOverResult::NoSelection, None => GameOverResult::NoSelection,
Some(_) => GameOverResult::QuitToMenu Some(_) => GameOverResult::QuitToMenu,
} }
} }

View File

@ -1,6 +1,7 @@
use specs::prelude::*; use specs::prelude::*;
use crate::gamelog::GameLog; use crate::gamelog::GameLog;
use crate::particle_system::ParticleBuilder;
use crate::{ use crate::{
AreaOfEffect, CombatStats, Confusion, Consumable, Equippable, Equipped, InBackpack, AreaOfEffect, CombatStats, Confusion, Consumable, Equippable, Equipped, InBackpack,
InflictsDamage, Map, Name, Position, ProvidesHealing, Ranged, SufferDamage, WantsToDropItem, InflictsDamage, Map, Name, Position, ProvidesHealing, Ranged, SufferDamage, WantsToDropItem,
@ -69,6 +70,8 @@ impl<'a> System<'a> for ItemUseSystem {
ReadStorage<'a, Equippable>, ReadStorage<'a, Equippable>,
WriteStorage<'a, Equipped>, WriteStorage<'a, Equipped>,
WriteStorage<'a, InBackpack>, WriteStorage<'a, InBackpack>,
WriteExpect<'a, ParticleBuilder>,
ReadStorage<'a, Position>,
); );
fn run(&mut self, data: Self::SystemData) { fn run(&mut self, data: Self::SystemData) {
@ -90,6 +93,8 @@ impl<'a> System<'a> for ItemUseSystem {
equippable, equippable,
mut equipped, mut equipped,
mut backpack, mut backpack,
mut particle_builder,
positions,
) = data; ) = data;
for (entity, use_item) in (&entities, &wants_use).join() { for (entity, use_item) in (&entities, &wants_use).join() {
@ -118,8 +123,17 @@ impl<'a> System<'a> for ItemUseSystem {
for mob in map.tile_content[idx].iter() { for mob in map.tile_content[idx].iter() {
targets.push(*mob); targets.push(*mob);
} }
particle_builder.request(
tile_idx.x,
tile_idx.y,
rltk::RGB::named(rltk::ORANGE),
rltk::RGB::named(rltk::BLACK),
rltk::to_cp437('░'),
200.,
);
} }
}
} }
} }
} }
@ -172,6 +186,17 @@ impl<'a> System<'a> for ItemUseSystem {
"You use {} on {}, inflicting {} hp.", "You use {} on {}, inflicting {} hp.",
item_name.name, mob_name.name, item_damages.damage item_name.name, mob_name.name, item_damages.damage
)); ));
if let Some(pos) = positions.get(*mob) {
particle_builder.request(
pos.x,
pos.y,
rltk::RGB::named(rltk::RED),
rltk::RGB::named(rltk::BLACK),
rltk::to_cp437('‼'),
200.,
);
}
} }
used_item = true; used_item = true;
@ -192,6 +217,16 @@ impl<'a> System<'a> for ItemUseSystem {
)); ));
} }
used_item = true; used_item = true;
if let Some(pos) = positions.get(*target) {
particle_builder.request(
pos.x,
pos.y,
rltk::RGB::named(rltk::GREEN),
rltk::RGB::named(rltk::BLACK),
rltk::to_cp437('♥'),
200.,
);
}
} }
} }
} }
@ -209,6 +244,17 @@ impl<'a> System<'a> for ItemUseSystem {
"You use {} on {}, confusing them.", "You use {} on {}, confusing them.",
item_name.name, mob_name.name item_name.name, mob_name.name
)); ));
if let Some(pos) = positions.get(*mob) {
particle_builder.request(
pos.x,
pos.y,
rltk::RGB::named(rltk::MAGENTA),
rltk::RGB::named(rltk::BLACK),
rltk::to_cp437('?'),
200.,
);
}
} }
} }
} }

View File

@ -14,7 +14,10 @@ use player::*;
use visibility_system::*; use visibility_system::*;
use crate::gamelog::GameLog; use crate::gamelog::GameLog;
use crate::inventory_system::{ItemCollectionSystem, ItemDropSystem, ItemRemoveSystem, ItemUseSystem}; use crate::inventory_system::{
ItemCollectionSystem, ItemDropSystem, ItemRemoveSystem, ItemUseSystem,
};
use crate::particle_system::ParticleSpawnSystem;
mod components; mod components;
mod damage_system; mod damage_system;
@ -25,6 +28,7 @@ mod map;
mod map_indexing_system; mod map_indexing_system;
mod melee_combat_system; mod melee_combat_system;
mod monster_ai_system; mod monster_ai_system;
mod particle_system;
mod player; mod player;
mod random_table; mod random_table;
mod rect; mod rect;
@ -221,6 +225,9 @@ impl State {
let mut remove_items = ItemRemoveSystem {}; let mut remove_items = ItemRemoveSystem {};
remove_items.run_now(&self.ecs); remove_items.run_now(&self.ecs);
let mut particle_spawn = ParticleSpawnSystem {};
particle_spawn.run_now(&self.ecs);
self.ecs.maintain(); self.ecs.maintain();
} }
} }
@ -234,6 +241,7 @@ impl GameState for State {
} }
ctx.cls(); ctx.cls();
particle_system::cull_dead_particles(&mut self.ecs, ctx);
match new_run_state { match new_run_state {
RunState::MainMenu { .. } => {} RunState::MainMenu { .. } => {}
@ -392,7 +400,9 @@ impl GameState for State {
gui::GameOverResult::NoSelection => {} gui::GameOverResult::NoSelection => {}
gui::GameOverResult::QuitToMenu => { gui::GameOverResult::QuitToMenu => {
self.game_over_cleanup(); self.game_over_cleanup();
new_run_state = RunState::MainMenu { menu_selection: gui::MainMenuSelection::NewGame }; new_run_state = RunState::MainMenu {
menu_selection: gui::MainMenuSelection::NewGame,
};
} }
} }
} }
@ -417,6 +427,7 @@ fn main() -> rltk::BError {
let mut gs = State { ecs: World::new() }; let mut gs = State { ecs: World::new() };
gs.ecs.insert(rltk::RandomNumberGenerator::new()); gs.ecs.insert(rltk::RandomNumberGenerator::new());
gs.ecs.insert(particle_system::ParticleBuilder::new());
gs.ecs.register::<Position>(); gs.ecs.register::<Position>();
gs.ecs.register::<Renderable>(); gs.ecs.register::<Renderable>();
@ -446,6 +457,7 @@ fn main() -> rltk::BError {
gs.ecs.register::<MeleePowerBonus>(); gs.ecs.register::<MeleePowerBonus>();
gs.ecs.register::<DefenseBonus>(); gs.ecs.register::<DefenseBonus>();
gs.ecs.register::<WantsToRemoveItem>(); gs.ecs.register::<WantsToRemoveItem>();
gs.ecs.register::<ParticleLifetime>();
gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new()); gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new());

View File

@ -1,7 +1,7 @@
use std::cmp::{max, min}; use std::cmp::{max, min};
use std::collections::HashSet; use std::collections::HashSet;
use rltk::{Algorithm2D, BaseMap, FontCharType, Point, RandomNumberGenerator, RGB, Rltk}; use rltk::{Algorithm2D, BaseMap, FontCharType, Point, RandomNumberGenerator, Rltk, RGB};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specs::prelude::*; use specs::prelude::*;
@ -246,32 +246,42 @@ pub fn draw_map(ecs: &World, ctx: &mut Rltk) {
} }
fn wall_glyph(map: &Map, x: i32, y: i32) -> FontCharType { fn wall_glyph(map: &Map, x: i32, y: i32) -> FontCharType {
if x < 1 || x > map.width - 2 || y < 1 || y > map.height - 2 as i32 { return 35; } if x < 1 || x > map.width - 2 || y < 1 || y > map.height - 2 as i32 {
return 35;
}
let mut mask: u8 = 0; let mut mask: u8 = 0;
if is_revealed_and_wall(map, x, y - 1) { mask += 1; } if is_revealed_and_wall(map, x, y - 1) {
if is_revealed_and_wall(map, x, y + 1) { mask += 2; } mask += 1;
if is_revealed_and_wall(map, x - 1, y) { mask += 4; } }
if is_revealed_and_wall(map, x + 1, y) { mask += 8; } if is_revealed_and_wall(map, x, y + 1) {
mask += 2;
}
if is_revealed_and_wall(map, x - 1, y) {
mask += 4;
}
if is_revealed_and_wall(map, x + 1, y) {
mask += 8;
}
match mask { match mask {
0 => { 9 } // Pillar because we can't see neighbors 0 => 9, // Pillar because we can't see neighbors
1 => { 186 } // Wall only to the north 1 => 186, // Wall only to the north
2 => { 186 } // Wall only to the south 2 => 186, // Wall only to the south
3 => { 186 } // Wall to the north and south 3 => 186, // Wall to the north and south
4 => { 205 } // Wall only to the west 4 => 205, // Wall only to the west
5 => { 188 } // Wall to the north and west 5 => 188, // Wall to the north and west
6 => { 187 } // Wall to the south and west 6 => 187, // Wall to the south and west
7 => { 185 } // Wall to the north, south and west 7 => 185, // Wall to the north, south and west
8 => { 205 } // Wall only to the east 8 => 205, // Wall only to the east
9 => { 200 } // Wall to the north and east 9 => 200, // Wall to the north and east
10 => { 201 } // Wall to the south and east 10 => 201, // Wall to the south and east
11 => { 204 } // Wall to the north, south and east 11 => 204, // Wall to the north, south and east
12 => { 205 } // Wall to the east and west 12 => 205, // Wall to the east and west
13 => { 202 } // Wall to the east, west, and south 13 => 202, // Wall to the east, west, and south
14 => { 203 } // Wall to the east, west, and north 14 => 203, // Wall to the east, west, and north
15 => { 206 } // ╬ Wall on all sides 15 => 206, // ╬ Wall on all sides
_ => { 35 } // We missed one? _ => 35, // We missed one?
} }
} }

View File

@ -2,11 +2,13 @@ use specs::prelude::*;
use crate::components::{CombatStats, Name, SufferDamage, WantsToMelee}; use crate::components::{CombatStats, Name, SufferDamage, WantsToMelee};
use crate::gamelog::GameLog; use crate::gamelog::GameLog;
use crate::{DefenseBonus, Equipped, MeleePowerBonus}; use crate::particle_system::ParticleBuilder;
use crate::{DefenseBonus, Equipped, MeleePowerBonus, Position};
pub struct MeleeCombatSystem {} pub struct MeleeCombatSystem {}
impl<'a> System<'a> for MeleeCombatSystem { impl<'a> System<'a> for MeleeCombatSystem {
#[allow(clippy::complexity)]
type SystemData = ( type SystemData = (
Entities<'a>, Entities<'a>,
WriteExpect<'a, GameLog>, WriteExpect<'a, GameLog>,
@ -17,6 +19,8 @@ impl<'a> System<'a> for MeleeCombatSystem {
ReadStorage<'a, MeleePowerBonus>, ReadStorage<'a, MeleePowerBonus>,
ReadStorage<'a, DefenseBonus>, ReadStorage<'a, DefenseBonus>,
ReadStorage<'a, Equipped>, ReadStorage<'a, Equipped>,
WriteExpect<'a, ParticleBuilder>,
ReadStorage<'a, Position>,
); );
fn run(&mut self, data: Self::SystemData) { fn run(&mut self, data: Self::SystemData) {
@ -30,6 +34,8 @@ impl<'a> System<'a> for MeleeCombatSystem {
melee_bonus, melee_bonus,
defense_bonus, defense_bonus,
equipped, equipped,
mut particle_builder,
positions,
) = data; ) = data;
for (entity, wants_melee, name, stats) in for (entity, wants_melee, name, stats) in
@ -58,6 +64,17 @@ impl<'a> System<'a> for MeleeCombatSystem {
} }
} }
if let Some(pos) = positions.get(wants_melee.target) {
particle_builder.request(
pos.x,
pos.y,
rltk::RGB::named(rltk::ORANGE),
rltk::RGB::named(rltk::BLACK),
rltk::to_cp437('‼'),
200.0,
);
}
let damage = i32::max( let damage = i32::max(
0, 0,
(stats.power + offensive_bonus) - (target_stats.defense + defensive_bonus), (stats.power + offensive_bonus) - (target_stats.defense + defensive_bonus),

View File

@ -3,9 +3,10 @@ use specs::prelude::*;
use crate::{ use crate::{
components::{Monster, Position, Viewshed, WantsToMelee}, components::{Monster, Position, Viewshed, WantsToMelee},
map::Map, Confusion,
Confusion, RunState, map::Map, RunState,
}; };
use crate::particle_system::ParticleBuilder;
pub struct MonsterAI {} pub struct MonsterAI {}
@ -22,6 +23,7 @@ impl<'a> System<'a> for MonsterAI {
WriteStorage<'a, Position>, WriteStorage<'a, Position>,
WriteStorage<'a, WantsToMelee>, WriteStorage<'a, WantsToMelee>,
WriteStorage<'a, Confusion>, WriteStorage<'a, Confusion>,
WriteExpect<'a, ParticleBuilder>
); );
fn run(&mut self, data: Self::SystemData) { fn run(&mut self, data: Self::SystemData) {
@ -36,6 +38,7 @@ impl<'a> System<'a> for MonsterAI {
mut position, mut position,
mut wants_to_melee, mut wants_to_melee,
mut confused, mut confused,
mut particle_builder
) = data; ) = data;
if *runstate != RunState::MonsterTurn { if *runstate != RunState::MonsterTurn {
@ -53,6 +56,8 @@ impl<'a> System<'a> for MonsterAI {
confused.remove(entity); confused.remove(entity);
} }
can_act = false; can_act = false;
particle_builder.request(pos.x, pos.y, rltk::RGB::named(rltk::MAGENTA), rltk::RGB::named(rltk::BLACK), rltk::to_cp437('?'), 200.);
} }
if !can_act { if !can_act {

119
src/particle_system.rs Normal file
View File

@ -0,0 +1,119 @@
use rltk::{Rltk, RGB};
use specs::prelude::*;
use crate::{ParticleLifetime, Position, Renderable};
pub fn cull_dead_particles(ecs: &mut World, ctx: &Rltk) {
let mut dead_particles: Vec<Entity> = Vec::new();
{
let mut particles = ecs.write_storage::<ParticleLifetime>();
let mut entities = ecs.entities();
for (entity, mut particle) in (&entities, &mut particles).join() {
particle.lifetime_ms -= ctx.frame_time_ms;
if particle.lifetime_ms < 0. {
dead_particles.push(entity);
}
}
}
for dead_particle in dead_particles.iter() {
ecs.delete_entity(*dead_particle)
.expect("Particle will not die");
}
}
struct ParticleRequest {
x: i32,
y: i32,
fg: RGB,
bg: RGB,
glyph: rltk::FontCharType,
lifetime: f32,
}
pub struct ParticleBuilder {
requests: Vec<ParticleRequest>,
}
impl ParticleBuilder {
pub fn new() -> ParticleBuilder {
ParticleBuilder {
requests: Vec::new(),
}
}
pub fn request(
&mut self,
x: i32,
y: i32,
fg: RGB,
bg: RGB,
glyph: rltk::FontCharType,
lifetime: f32,
) {
self.requests.push(ParticleRequest {
x,
y,
fg,
bg,
glyph,
lifetime,
});
}
}
pub struct ParticleSpawnSystem {}
impl<'a> System<'a> for ParticleSpawnSystem {
type SystemData = (
Entities<'a>,
WriteStorage<'a, Position>,
WriteStorage<'a, Renderable>,
WriteStorage<'a, ParticleLifetime>,
WriteExpect<'a, ParticleBuilder>,
);
fn run(&mut self, data: Self::SystemData) {
let (
entities,
mut positions,
mut renderables,
mut particle_lifetimes,
mut particle_builder,
) = data;
for new_particle in particle_builder.requests.iter() {
let p = entities.create();
positions
.insert(
p,
Position {
x: new_particle.x,
y: new_particle.y,
},
)
.expect("Unable to insert position");
renderables
.insert(
p,
Renderable {
fg: new_particle.fg,
bg: new_particle.bg,
glyph: new_particle.glyph,
render_order: 0,
},
)
.expect("Unable to insert renderable");
particle_lifetimes
.insert(
p,
ParticleLifetime {
lifetime_ms: new_particle.lifetime,
},
)
.expect("Unable to insert particle lifetime");
}
particle_builder.requests.clear();
}
}

View File

@ -76,7 +76,8 @@ pub fn save_game(ecs: &mut World) {
Equipped, Equipped,
MeleePowerBonus, MeleePowerBonus,
DefenseBonus, DefenseBonus,
WantsToRemoveItem WantsToRemoveItem,
ParticleLifetime
); );
} }
@ -155,7 +156,8 @@ pub fn load_game(ecs: &mut World) {
Equipped, Equipped,
MeleePowerBonus, MeleePowerBonus,
DefenseBonus, DefenseBonus,
WantsToRemoveItem WantsToRemoveItem,
ParticleLifetime
); );
} }

View File

@ -249,7 +249,6 @@ fn dagger(ecs: &mut World, x: i32, y: i32) {
.build(); .build();
} }
fn longsword(ecs: &mut World, x: i32, y: i32) { fn longsword(ecs: &mut World, x: i32, y: i32) {
ecs.create_entity() ecs.create_entity()
.with(Position { x, y }) .with(Position { x, y })
@ -292,7 +291,6 @@ fn shield(ecs: &mut World, x: i32, y: i32) {
.build(); .build();
} }
fn tower_shield(ecs: &mut World, x: i32, y: i32) { fn tower_shield(ecs: &mut World, x: i32, y: i32) {
ecs.create_entity() ecs.create_entity()
.with(Position { x, y }) .with(Position { x, y })