From aac6e0ad021f879635ead66ffbbe5f12b642e124 Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Mon, 31 Jul 2023 05:07:41 +0100 Subject: [PATCH] Speed with TURN_COST and slight randomisation to reduce predictability --- src/ai/energy_system.rs | 56 +++++++++++++++++++++--- src/ai/mod.rs | 4 +- src/ai/turn_status.rs | 45 +++++++++++++++++++ src/bystander_ai_system.rs | 13 +++--- src/components.rs | 3 ++ src/hunger_system.rs | 41 +++++------------ src/inventory_system.rs | 2 +- src/main.rs | 68 ++++++++++++++++++---------- src/monster_ai_system.rs | 90 +++++++++++--------------------------- src/player.rs | 8 ++-- src/saveload_system.rs | 2 + src/spawner.rs | 13 +++--- 12 files changed, 200 insertions(+), 145 deletions(-) create mode 100644 src/ai/turn_status.rs diff --git a/src/ai/energy_system.rs b/src/ai/energy_system.rs index 68238dd..a855b44 100644 --- a/src/ai/energy_system.rs +++ b/src/ai/energy_system.rs @@ -1,15 +1,16 @@ -use crate::{Energy, Position, RunState, TakingTurn}; +use crate::{Clock, Energy, Position, RunState, TakingTurn}; use rltk::prelude::*; use specs::prelude::*; pub struct EnergySystem {} -const NORMAL_SPEED: i32 = 12; +pub const NORMAL_SPEED: i32 = 12; const TURN_COST: i32 = NORMAL_SPEED * 4; impl<'a> System<'a> for EnergySystem { #[allow(clippy::type_complexity)] type SystemData = ( + ReadStorage<'a, Clock>, WriteStorage<'a, Energy>, ReadStorage<'a, Position>, WriteStorage<'a, TakingTurn>, @@ -20,29 +21,70 @@ impl<'a> System<'a> for EnergySystem { ); fn run(&mut self, data: Self::SystemData) { - let (mut energies, positions, mut turns, entities, mut rng, mut runstate, player) = data; - - if *runstate != RunState::MonsterTurn { + let (clock, mut energies, positions, mut turns, entities, mut rng, mut runstate, player) = data; + // If not ticking, do nothing. + if *runstate != RunState::Ticking { return; } - + for (_entity, _clock, energy) in (&entities, &clock, &mut energies).join() { + energy.current += NORMAL_SPEED; + if energy.current >= TURN_COST { + energy.current -= TURN_COST; + crate::gamelog::record_event("turns", 1); + } + } + // Clear TakingTurn{} from every entity. turns.clear(); - for (entity, energy, _pos) in (&entities, &mut energies, &positions).join() { + // Every entity has a POTENTIAL equal to their speed. let mut energy_potential: i32 = energy.speed; + if entity == *player { + console::log(format!( + "TICK for Player with speed {}: [current energy: {}]", + energy.speed, energy.current + )); + } + // Increment current energy by NORMAL_SPEED for every + // whole number of NORMAL_SPEEDS in their POTENTIAL. while energy_potential >= NORMAL_SPEED { energy_potential -= NORMAL_SPEED; energy.current += NORMAL_SPEED; + if entity == *player { + console::log(format!( + "Gained 12 energy. [current: {}, potential: {}]", + energy.current, energy_potential + )); + } } + // Roll a NORMAL_SPEED-sided die. If less than their + // remaining POTENTIAL, increment current energy by + // NORMAL_SPEED. + // i.e. An entity with a speed of 3/4ths NORMAL_SPEED + // will gain NORMAL_SPEED energy in 75% of ticks. if energy_potential > 0 { if rng.roll_dice(1, NORMAL_SPEED) <= energy_potential { energy.current += NORMAL_SPEED; + if entity == *player { + console::log(format!( + "Rolled for remainder! Gained 12 energy. [current energy: {}]", + energy.current + )); + } } } + // TURN_COST is equal to 4 * NORMAL_SPEED. If the current entity + // has enough energy, they take a turn and decrement their energy + // by TURN_COST. If the current entity is the player, await input. if energy.current >= TURN_COST { turns.insert(entity, TakingTurn {}).expect("Unable to insert turn."); energy.current -= TURN_COST; if entity == *player { + if entity == *player { + console::log(format!( + "Player has >=TURN_COST energy, granting a turn. [remaining energy: {}]", + energy.current + )); + } *runstate = RunState::AwaitingInput; } } diff --git a/src/ai/mod.rs b/src/ai/mod.rs index efa6f52..b4d44db 100644 --- a/src/ai/mod.rs +++ b/src/ai/mod.rs @@ -1,2 +1,4 @@ mod energy_system; -pub use energy_system::EnergySystem; +pub use energy_system::{EnergySystem, NORMAL_SPEED}; +mod turn_status; +pub use turn_status::TurnStatusSystem; diff --git a/src/ai/turn_status.rs b/src/ai/turn_status.rs new file mode 100644 index 0000000..0bd7854 --- /dev/null +++ b/src/ai/turn_status.rs @@ -0,0 +1,45 @@ +use crate::{gamelog, Confusion, Name, ParticleBuilder, Position, RunState, TakingTurn}; +use rltk::prelude::*; +use specs::prelude::*; + +pub struct TurnStatusSystem {} + +impl<'a> System<'a> for TurnStatusSystem { + #[allow(clippy::type_complexity)] + type SystemData = ( + WriteStorage<'a, TakingTurn>, + WriteStorage<'a, Confusion>, + Entities<'a>, + ReadExpect<'a, RunState>, + ReadStorage<'a, Name>, + ReadStorage<'a, Position>, + WriteExpect<'a, ParticleBuilder>, + ); + + fn run(&mut self, data: Self::SystemData) { + let (mut turns, mut confusion, entities, runstate, names, positions, mut particle_builder) = data; + if *runstate != RunState::Ticking { + return; + } + let mut remove_turn: Vec = Vec::new(); + let mut remove_confusion: Vec = Vec::new(); + for (entity, _turn, confused, name, pos) in (&entities, &mut turns, &mut confusion, &names, &positions).join() { + confused.turns -= 1; + if confused.turns < 1 { + remove_confusion.push(entity); + gamelog::Logger::new().npc_name(&name.name).colour(WHITE).append("snaps out of it.").log(); + particle_builder.request(pos.x, pos.y, RGB::named(LIGHT_BLUE), RGB::named(BLACK), to_cp437('!'), 200.0); + } else { + remove_turn.push(entity); + gamelog::Logger::new().npc_name(&name.name).colour(WHITE).append("is confused.").log(); + particle_builder.request(pos.x, pos.y, RGB::named(MAGENTA), RGB::named(BLACK), to_cp437('?'), 200.0); + } + } + for e in remove_turn { + turns.remove(e); + } + for e in remove_confusion { + confusion.remove(e); + } + } +} diff --git a/src/bystander_ai_system.rs b/src/bystander_ai_system.rs index 15c5610..4856169 100644 --- a/src/bystander_ai_system.rs +++ b/src/bystander_ai_system.rs @@ -1,4 +1,4 @@ -use super::{gamelog, Bystander, EntityMoved, Map, Name, Point, Position, Quips, RunState, Viewshed}; +use super::{gamelog, Bystander, EntityMoved, Map, Name, Point, Position, Quips, TakingTurn, Viewshed}; use specs::prelude::*; pub struct BystanderAI {} @@ -7,7 +7,6 @@ impl<'a> System<'a> for BystanderAI { #[allow(clippy::type_complexity)] type SystemData = ( WriteExpect<'a, Map>, - ReadExpect<'a, RunState>, Entities<'a>, WriteStorage<'a, Viewshed>, ReadStorage<'a, Bystander>, @@ -17,12 +16,12 @@ impl<'a> System<'a> for BystanderAI { ReadExpect<'a, Point>, WriteStorage<'a, Quips>, ReadStorage<'a, Name>, + ReadStorage<'a, TakingTurn>, ); fn run(&mut self, data: Self::SystemData) { let ( mut map, - runstate, entities, mut viewshed, bystander, @@ -32,13 +31,11 @@ impl<'a> System<'a> for BystanderAI { player_pos, mut quips, names, + turns, ) = data; - if *runstate != RunState::MonsterTurn { - return; - } - - for (entity, mut viewshed, _bystander, mut pos) in (&entities, &mut viewshed, &bystander, &mut position).join() + for (entity, mut viewshed, _bystander, mut pos, _turn) in + (&entities, &mut viewshed, &bystander, &mut position, &turns).join() { // Possibly quip let quip = quips.get_mut(entity); diff --git a/src/components.rs b/src/components.rs index 3ff6930..d8143db 100644 --- a/src/components.rs +++ b/src/components.rs @@ -52,6 +52,9 @@ pub struct Energy { pub speed: i32, } +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct Clock {} + #[derive(Component, Debug, Serialize, Deserialize, Clone)] pub struct TakingTurn {} diff --git a/src/hunger_system.rs b/src/hunger_system.rs index 90cc604..7ac3a1a 100644 --- a/src/hunger_system.rs +++ b/src/hunger_system.rs @@ -1,40 +1,19 @@ -use super::{gamelog, HungerClock, HungerState, RunState, SufferDamage}; +use super::{gamelog, HungerClock, HungerState, SufferDamage}; use specs::prelude::*; pub struct HungerSystem {} impl<'a> System<'a> for HungerSystem { #[allow(clippy::type_complexity)] - type SystemData = ( - Entities<'a>, - WriteStorage<'a, HungerClock>, - ReadExpect<'a, Entity>, - ReadExpect<'a, RunState>, - WriteStorage<'a, SufferDamage>, - ); + type SystemData = + (Entities<'a>, WriteStorage<'a, HungerClock>, ReadExpect<'a, Entity>, WriteStorage<'a, SufferDamage>); fn run(&mut self, data: Self::SystemData) { - let (entities, mut hunger_clock, player_entity, runstate, mut inflict_damage) = data; + let (entities, mut hunger_clock, player_entity, mut inflict_damage) = data; for (entity, mut clock) in (&entities, &mut hunger_clock).join() { - let mut proceed = false; - - match *runstate { - RunState::PlayerTurn => { - if entity == *player_entity { - proceed = true; - } - } - RunState::MonsterTurn => { - if entity != *player_entity { - proceed = true; - } - } - _ => proceed = false, - } - - if !proceed { - return; + if entity == *player_entity { + rltk::console::log(format!("HUNGER TICK for Player [current clock: {}]", clock.duration)); } clock.duration -= 1; if clock.duration > 0 { @@ -44,28 +23,28 @@ impl<'a> System<'a> for HungerSystem { match clock.state { HungerState::Satiated => { clock.state = HungerState::Normal; - clock.duration = 300; + clock.duration = 1200; if entity == *player_entity { gamelog::Logger::new().append("You are no longer satiated.").log(); } } HungerState::Normal => { clock.state = HungerState::Hungry; - clock.duration = 100; + clock.duration = 400; if entity == *player_entity { gamelog::Logger::new().colour(rltk::RED).append("You feel hungry.").log(); } } HungerState::Hungry => { clock.state = HungerState::Weak; - clock.duration = 50; + clock.duration = 200; if entity == *player_entity { gamelog::Logger::new().colour(rltk::RED).append("You feel weak with hunger.").log(); } } HungerState::Weak => { clock.state = HungerState::Fainting; - clock.duration = 50; + clock.duration = 200; if entity == *player_entity { gamelog::Logger::new().colour(rltk::RED).append("You feel hungry enough to faint.").log(); } diff --git a/src/inventory_system.rs b/src/inventory_system.rs index 7e33674..4be2947 100644 --- a/src/inventory_system.rs +++ b/src/inventory_system.rs @@ -217,7 +217,7 @@ impl<'a> System<'a> for ItemUseSystem { let hc = hunger_clock.get_mut(target); if let Some(hc) = hc { hc.state = HungerState::Satiated; - hc.duration = 50; + hc.duration = 200; } } } diff --git a/src/main.rs b/src/main.rs index 57ab534..5c6c264 100644 --- a/src/main.rs +++ b/src/main.rs @@ -50,8 +50,7 @@ pub const SHOW_MAPGEN: bool = false; pub enum RunState { AwaitingInput, PreRun, - PlayerTurn, - MonsterTurn, + Ticking, ShowInventory, ShowDropItem, ShowRemoveItem, @@ -120,10 +119,12 @@ impl State { } fn run_systems(&mut self) { + let mut mapindex = MapIndexingSystem {}; let mut vis = VisibilitySystem {}; + let mut energy = ai::EnergySystem {}; + let mut turn_status_system = ai::TurnStatusSystem {}; let mut mob = MonsterAI {}; let mut bystanders = bystander_ai_system::BystanderAI {}; - let mut mapindex = MapIndexingSystem {}; let mut trigger_system = trigger_system::TriggerSystem {}; let mut melee_system = MeleeCombatSystem {}; let mut damage_system = DamageSystem {}; @@ -134,10 +135,12 @@ impl State { let mut hunger_clock = hunger_system::HungerSystem {}; let mut particle_system = particle_system::ParticleSpawnSystem {}; + mapindex.run_now(&self.ecs); vis.run_now(&self.ecs); + energy.run_now(&self.ecs); + turn_status_system.run_now(&self.ecs); mob.run_now(&self.ecs); bystanders.run_now(&self.ecs); - mapindex.run_now(&self.ecs); trigger_system.run_now(&self.ecs); inventory_system.run_now(&self.ecs); item_use_system.run_now(&self.ecs); @@ -151,6 +154,11 @@ impl State { self.ecs.maintain(); } + fn run_map_index(&mut self) { + let mut mapindex = MapIndexingSystem {}; + mapindex.run_now(&self.ecs); + } + fn entities_to_remove_on_level_change(&mut self) -> Vec { let entities = self.ecs.entities(); let player = self.ecs.read_storage::(); @@ -270,23 +278,36 @@ impl GameState for State { new_runstate = RunState::AwaitingInput; } RunState::AwaitingInput => { - new_runstate = player_input(self, ctx); - } - RunState::PlayerTurn => { - self.run_systems(); - self.ecs.maintain(); - gamelog::record_event("turns", 1); - match *self.ecs.fetch::() { - RunState::MagicMapReveal { row, cursed } => { - new_runstate = RunState::MagicMapReveal { row: row, cursed: cursed } + self.run_map_index(); + // Sanity-checking that the player actually *should* + // be taking a turn before giving them one. If they + // don't have a turn component, go back to ticking. + let mut can_act = false; + { + let player_entity = self.ecs.fetch::(); + let turns = self.ecs.read_storage::(); + if let Some(_) = turns.get(*player_entity) { + can_act = true; } - _ => new_runstate = RunState::MonsterTurn, + } + if can_act { + new_runstate = player_input(self, ctx); + } else { + new_runstate = RunState::Ticking; } } - RunState::MonsterTurn => { - self.run_systems(); - self.ecs.maintain(); - new_runstate = RunState::AwaitingInput; + RunState::Ticking => { + while new_runstate == RunState::Ticking { + self.run_systems(); + self.ecs.maintain(); + match *self.ecs.fetch::() { + RunState::AwaitingInput => new_runstate = RunState::AwaitingInput, + RunState::MagicMapReveal { row, cursed } => { + new_runstate = RunState::MagicMapReveal { row: row, cursed: cursed } + } + _ => new_runstate = RunState::Ticking, + } + } } RunState::ShowInventory => { let result = gui::show_inventory(self, ctx); @@ -315,7 +336,7 @@ impl GameState for State { intent .insert(*self.ecs.fetch::(), WantsToUseItem { item: item_entity, target: None }) .expect("Unable to insert intent."); - new_runstate = RunState::PlayerTurn; + new_runstate = RunState::Ticking; } } } @@ -331,7 +352,7 @@ impl GameState for State { intent .insert(*self.ecs.fetch::(), WantsToDropItem { item: item_entity }) .expect("Unable to insert intent"); - new_runstate = RunState::PlayerTurn; + new_runstate = RunState::Ticking; } } } @@ -346,7 +367,7 @@ impl GameState for State { intent .insert(*self.ecs.fetch::(), WantsToRemoveItem { item: item_entity }) .expect("Unable to insert intent"); - new_runstate = RunState::PlayerTurn; + new_runstate = RunState::Ticking; } } } @@ -360,7 +381,7 @@ impl GameState for State { intent .insert(*self.ecs.fetch::(), WantsToUseItem { item, target: result.1 }) .expect("Unable to insert intent."); - new_runstate = RunState::PlayerTurn; + new_runstate = RunState::Ticking; } } } @@ -447,7 +468,7 @@ impl GameState for State { } if row as usize == map.height as usize - 1 { - new_runstate = RunState::MonsterTurn; + new_runstate = RunState::Ticking; } else { new_runstate = RunState::MagicMapReveal { row: row + 1, cursed: cursed }; } @@ -515,6 +536,7 @@ fn main() -> rltk::BError { gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); + gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); diff --git a/src/monster_ai_system.rs b/src/monster_ai_system.rs index 8d7ae86..b5f0afe 100644 --- a/src/monster_ai_system.rs +++ b/src/monster_ai_system.rs @@ -1,6 +1,4 @@ -use super::{ - gamelog, Confusion, EntityMoved, Map, Monster, Name, ParticleBuilder, Position, RunState, Viewshed, WantsToMelee, -}; +use super::{EntityMoved, Map, Monster, Position, TakingTurn, Viewshed, WantsToMelee}; use rltk::Point; use specs::prelude::*; @@ -12,16 +10,13 @@ impl<'a> System<'a> for MonsterAI { WriteExpect<'a, Map>, ReadExpect<'a, Point>, ReadExpect<'a, Entity>, - ReadExpect<'a, RunState>, Entities<'a>, WriteStorage<'a, Viewshed>, ReadStorage<'a, Monster>, WriteStorage<'a, Position>, WriteStorage<'a, WantsToMelee>, - WriteStorage<'a, Confusion>, - ReadStorage<'a, Name>, - WriteExpect<'a, ParticleBuilder>, WriteStorage<'a, EntityMoved>, + ReadStorage<'a, TakingTurn>, ); fn run(&mut self, data: Self::SystemData) { @@ -29,71 +24,38 @@ impl<'a> System<'a> for MonsterAI { mut map, player_pos, player_entity, - runstate, entities, mut viewshed, monster, mut position, mut wants_to_melee, - mut confused, - name, - mut particle_builder, mut entity_moved, + turns, ) = data; - if *runstate != RunState::MonsterTurn { - return; - } - - for (entity, mut viewshed, _monster, mut pos) in (&entities, &mut viewshed, &monster, &mut position).join() { - let mut can_act = true; - - // Check confusion - let is_confused = confused.get_mut(entity); - if let Some(i_am_confused) = is_confused { - i_am_confused.turns -= 1; - let entity_name = name.get(entity).unwrap(); - let mut fg = rltk::RGB::named(rltk::MAGENTA); - let mut glyph = rltk::to_cp437('?'); - if i_am_confused.turns < 1 { - confused.remove(entity); - gamelog::Logger::new() - .npc_name(&entity_name.name) - .colour(rltk::WHITE) - .append("snaps out of it.") - .log(); - fg = rltk::RGB::named(rltk::MEDIUMSLATEBLUE); - glyph = rltk::to_cp437('!'); - } else { - gamelog::Logger::new().npc_name(&entity_name.name).colour(rltk::WHITE).append("is confused.").log(); - } - particle_builder.request(pos.x, pos.y, fg, rltk::RGB::named(rltk::BLACK), glyph, 200.0); - can_act = false; - } - - if can_act { - let distance = rltk::DistanceAlg::Pythagoras.distance2d(Point::new(pos.x, pos.y), *player_pos); - if distance < 1.5 { - wants_to_melee - .insert(entity, WantsToMelee { target: *player_entity }) - .expect("Unable to insert attack."); - } else if viewshed.visible_tiles.contains(&*player_pos) { - // If the player is visible, but the path is obstructed, this will currently search - // the entire map (i.e. Will do a huge ASTAR to find an alternate route), and the - // mob will follow that path until it leaves vision, then lose sight of the player - // and stop. - let path = - rltk::a_star_search(map.xy_idx(pos.x, pos.y), map.xy_idx(player_pos.x, player_pos.y), &*map); - if path.success && path.steps.len() > 1 { - let mut idx = map.xy_idx(pos.x, pos.y); - map.blocked[idx] = false; - pos.x = (path.steps[1] as i32) % map.width; - pos.y = (path.steps[1] as i32) / map.width; - idx = map.xy_idx(pos.x, pos.y); - map.blocked[idx] = true; - viewshed.dirty = true; - entity_moved.insert(entity, EntityMoved {}).expect("Unable to insert marker"); - } + for (entity, mut viewshed, _monster, mut pos, _turn) in + (&entities, &mut viewshed, &monster, &mut position, &turns).join() + { + let distance = rltk::DistanceAlg::Pythagoras.distance2d(Point::new(pos.x, pos.y), *player_pos); + if distance < 1.5 { + wants_to_melee + .insert(entity, WantsToMelee { target: *player_entity }) + .expect("Unable to insert attack."); + } else if viewshed.visible_tiles.contains(&*player_pos) { + // If the player is visible, but the path is obstructed, this will currently search + // the entire map (i.e. Will do a huge ASTAR to find an alternate route), and the + // mob will follow that path until it leaves vision, then lose sight of the player + // and stop. + let path = rltk::a_star_search(map.xy_idx(pos.x, pos.y), map.xy_idx(player_pos.x, player_pos.y), &*map); + if path.success && path.steps.len() > 1 { + let mut idx = map.xy_idx(pos.x, pos.y); + map.blocked[idx] = false; + pos.x = (path.steps[1] as i32) % map.width; + pos.y = (path.steps[1] as i32) / map.width; + idx = map.xy_idx(pos.x, pos.y); + map.blocked[idx] = true; + viewshed.dirty = true; + entity_moved.insert(entity, EntityMoved {}).expect("Unable to insert marker"); } } } diff --git a/src/player.rs b/src/player.rs index 6ed7144..5116f72 100644 --- a/src/player.rs +++ b/src/player.rs @@ -66,7 +66,7 @@ pub fn try_door(i: i32, j: i32, ecs: &mut World) -> RunState { render_data.glyph = rltk::to_cp437('+'); // Nethack open door, maybe just use '/' instead. door_pos = Some(Point::new(pos.x + delta_x, pos.y + delta_y)); } - result = RunState::PlayerTurn; + result = RunState::Ticking; } else { gamelog::Logger::new().append("It's already closed.").log(); } @@ -139,7 +139,7 @@ pub fn open(i: i32, j: i32, ecs: &mut World) -> RunState { render_data.glyph = rltk::to_cp437('▓'); // Nethack open door, maybe just use '/' instead. door_pos = Some(Point::new(pos.x + delta_x, pos.y + delta_y)); } - result = RunState::PlayerTurn; + result = RunState::Ticking; } else { gamelog::Logger::new().append("It's already open.").log(); } @@ -266,7 +266,7 @@ pub fn kick(i: i32, j: i32, ecs: &mut World) -> RunState { } gamelog::record_event("kick_count", 1); - return RunState::PlayerTurn; + return RunState::Ticking; } pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> bool { @@ -483,7 +483,7 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { }, } if result { - return RunState::PlayerTurn; + return RunState::Ticking; } else { return RunState::AwaitingInput; } diff --git a/src/saveload_system.rs b/src/saveload_system.rs index bf9f90a..672eb72 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -53,6 +53,7 @@ pub fn save_game(ecs: &mut World) { BlocksTile, BlocksVisibility, Bystander, + Clock, Confusion, Consumable, Cursed, @@ -159,6 +160,7 @@ pub fn load_game(ecs: &mut World) { BlocksTile, BlocksVisibility, Bystander, + Clock, Confusion, Consumable, Cursed, diff --git a/src/spawner.rs b/src/spawner.rs index 49fb102..77efe3c 100644 --- a/src/spawner.rs +++ b/src/spawner.rs @@ -1,7 +1,7 @@ use super::{ - gamesystem, gamesystem::attr_bonus, random_table::RandomTable, raws, Attribute, Attributes, Energy, HungerClock, - HungerState, Map, Name, Player, Pool, Pools, Position, Rect, Renderable, SerializeMe, Skill, Skills, TileType, - Viewshed, + ai::NORMAL_SPEED, gamesystem, gamesystem::attr_bonus, random_table::RandomTable, raws, Attribute, Attributes, + Clock, Energy, HungerClock, HungerState, Map, Name, Player, Pool, Pools, Position, Rect, Renderable, SerializeMe, + Skill, Skills, TileType, Viewshed, }; use rltk::{RandomNumberGenerator, RGB}; use specs::prelude::*; @@ -24,7 +24,8 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { let cha = gamesystem::roll_4d6(&mut rng); std::mem::drop(rng); - // d8 hit die - but always maxxed at level 1, so player doesn't have to roll. + // We only create the player once, so create the Clock here for counting turns too. + ecs.create_entity().with(Clock {}).with(Energy { current: 0, speed: NORMAL_SPEED }).build(); let player = ecs .create_entity() .with(Position { x: player_x, y: player_y }) @@ -37,7 +38,7 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { .with(Player {}) .with(Viewshed { visible_tiles: Vec::new(), range: 12, dirty: true }) .with(Name { name: "you".to_string(), plural: "you".to_string() }) - .with(HungerClock { state: HungerState::Satiated, duration: 50 }) + .with(HungerClock { state: HungerState::Satiated, duration: 200 }) .with(Attributes { strength: Attribute { base: str, modifiers: 0, bonus: attr_bonus(str) }, dexterity: Attribute { base: dex, modifiers: 0, bonus: attr_bonus(dex) }, @@ -54,7 +55,7 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { bac: 10, }) .with(skills) - .with(Energy { current: 0, speed: 12 }) + .with(Energy { current: 0, speed: NORMAL_SPEED }) .marked::>() .build();