Add initiative system, and refactor systems to use new runstate

This commit is contained in:
Timothy Warren 2022-01-11 10:01:37 -05:00
parent 3def036868
commit f0ac291e6a
8 changed files with 151 additions and 102 deletions

View File

@ -1,7 +1,9 @@
mod animal_ai_system;
mod bystander_ai_system;
mod initiative_system;
mod monster_ai_system;
pub use animal_ai_system::AnimalAI;
pub use bystander_ai_system::BystanderAI;
pub use initiative_system::InitiativeSystem;
pub use monster_ai_system::MonsterAI;

View File

@ -2,7 +2,7 @@ use ::rltk::{DijkstraMap, DistanceAlg, Point};
use ::specs::prelude::*;
use crate::components::{
Carnivore, EntityMoved, Herbivore, Item, Position, Viewshed, WantsToMelee,
Carnivore, EntityMoved, Herbivore, Item, MyTurn, Position, Viewshed, WantsToMelee,
};
use crate::{Map, RunState};
@ -22,6 +22,7 @@ impl<'a> System<'a> for AnimalAI {
WriteStorage<'a, WantsToMelee>,
WriteStorage<'a, EntityMoved>,
WriteStorage<'a, Position>,
ReadStorage<'a, MyTurn>,
);
fn run(&mut self, data: Self::SystemData) {
@ -37,15 +38,12 @@ impl<'a> System<'a> for AnimalAI {
mut wants_to_melee,
mut entity_moved,
mut position,
turns,
) = data;
if *runstate != RunState::MonsterTurn {
return;
}
// Herbivores run away a lot
for (entity, mut viewshed, _herbivore, mut pos) in
(&entities, &mut viewshed, &herbivore, &mut position).join()
for (entity, mut viewshed, _herbivore, mut pos, _turn) in
(&entities, &mut viewshed, &herbivore, &mut position, &turns).join()
{
let mut run_away_from: Vec<usize> = Vec::new();
for other_tile in viewshed.visible_tiles.iter() {
@ -85,8 +83,8 @@ impl<'a> System<'a> for AnimalAI {
}
// Carnivores just want to eat everything
for (entity, mut viewshed, _carnivore, mut pos) in
(&entities, &mut viewshed, &carnivore, &mut position).join()
for (entity, mut viewshed, _carnivore, mut pos, _turn) in
(&entities, &mut viewshed, &carnivore, &mut position, &turns).join()
{
let mut run_towards: Vec<usize> = Vec::new();
let mut attacked = false;

View File

@ -1,7 +1,7 @@
use ::rltk::{Point, RandomNumberGenerator};
use specs::prelude::*;
use crate::components::{Bystander, EntityMoved, Name, Position, Quips, Viewshed};
use crate::components::{Bystander, EntityMoved, MyTurn, Name, Position, Quips, Viewshed};
use crate::game_log::GameLog;
use crate::{Map, RunState};
@ -22,6 +22,7 @@ impl<'a> System<'a> for BystanderAI {
WriteExpect<'a, GameLog>,
WriteStorage<'a, Quips>,
ReadStorage<'a, Name>,
ReadStorage<'a, MyTurn>,
);
fn run(&mut self, data: Self::SystemData) {
@ -38,14 +39,11 @@ impl<'a> System<'a> for BystanderAI {
mut gamelog,
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
if let Some(quip) = quips.get_mut(entity) {

View File

@ -0,0 +1,67 @@
use ::rltk::RandomNumberGenerator;
use ::specs::prelude::*;
use crate::components::{Attributes, Initiative, MyTurn, Position};
use crate::RunState;
pub struct InitiativeSystem {}
impl<'a> System<'a> for InitiativeSystem {
#[allow(clippy::type_complexity)]
type SystemData = (
WriteStorage<'a, Initiative>,
ReadStorage<'a, Position>,
WriteStorage<'a, MyTurn>,
Entities<'a>,
WriteExpect<'a, RandomNumberGenerator>,
ReadStorage<'a, Attributes>,
WriteExpect<'a, RunState>,
ReadExpect<'a, Entity>,
);
fn run(&mut self, data: Self::SystemData) {
let (
mut initiatives,
positions,
mut turns,
entities,
mut rng,
attributes,
mut runstate,
player,
) = data;
if *runstate != RunState::Ticking {
return;
} // We'll be adding Ticking in a moment
// Clear any remaining MyTurn we left by mistake
turns.clear();
// Roll initiative
for (entity, initiative, _pos) in (&entities, &mut initiatives, &positions).join() {
initiative.current -= 1;
if initiative.current < 1 {
// It's my turn
turns
.insert(entity, MyTurn {})
.expect("Unable to insert turn");
// Re-roll
initiative.current = 6 + rng.roll_dice(1, 6);
// Give a bonus for quickness
if let Some(attr) = attributes.get(entity) {
initiative.current -= attr.quickness.bonus;
}
// TODO: More initiative granting boosts/penalties will go here later
// if its the player, we want to go to an AwaitingInput state
if entity == *player {
*runstate = RunState::AwaitingInput;
}
}
}
}
}

View File

@ -1,7 +1,7 @@
use ::rltk::{Point, RGB};
use ::specs::prelude::*;
use crate::components::{Confusion, Monster, Position, Viewshed, WantsToMelee};
use crate::components::{Confusion, Monster, MyTurn, Position, Viewshed, WantsToMelee};
use crate::particle_system::ParticleBuilder;
use crate::{EntityMoved, Map, RunState};
@ -22,6 +22,7 @@ impl<'a> System<'a> for MonsterAI {
WriteStorage<'a, Confusion>,
WriteExpect<'a, ParticleBuilder>,
WriteStorage<'a, EntityMoved>,
ReadStorage<'a, MyTurn>,
);
fn run(&mut self, data: Self::SystemData) {
@ -38,14 +39,11 @@ impl<'a> System<'a> for MonsterAI {
mut confused,
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()
for (entity, mut viewshed, _monster, mut pos, _turn) in
(&entities, &mut viewshed, &monster, &mut position, &turns).join()
{
let mut can_act = true;

View File

@ -1,6 +1,6 @@
use ::specs::prelude::*;
use crate::components::{HungerClock, HungerState, SufferDamage};
use crate::components::{HungerClock, HungerState, MyTurn, SufferDamage};
use crate::game_log::GameLog;
use crate::RunState;
@ -15,66 +15,56 @@ impl<'a> System<'a> for HungerSystem {
ReadExpect<'a, RunState>,
WriteStorage<'a, SufferDamage>,
WriteExpect<'a, GameLog>,
ReadStorage<'a, MyTurn>,
);
fn run(&mut self, data: Self::SystemData) {
let (entities, mut hunger_clock, player_entity, runstate, mut inflict_damage, mut log) =
data;
let (
entities,
mut hunger_clock,
player_entity,
runstate,
mut inflict_damage,
mut log,
turns,
) = data;
for (entity, mut clock) in (&entities, &mut hunger_clock).join() {
let mut proceed = false;
for (entity, mut clock, _myturn) in (&entities, &mut hunger_clock, &turns).join() {
clock.duration -= 1;
match *runstate {
RunState::PlayerTurn => {
if entity == *player_entity {
proceed = true;
if clock.duration < 1 {
match clock.state {
HungerState::WellFed => {
clock.state = HungerState::Normal;
clock.duration = 200;
if entity == *player_entity {
log.append("You are no longer well fed.");
}
}
}
RunState::MonsterTurn => {
if entity != *player_entity {
proceed = true;
HungerState::Normal => {
clock.state = HungerState::Hungry;
clock.duration = 200;
if entity == *player_entity {
log.append("You are hungry.");
}
}
}
_ => proceed = false,
}
HungerState::Hungry => {
clock.state = HungerState::Starving;
clock.duration = 200;
if proceed {
clock.duration -= 1;
if clock.duration < 1 {
match clock.state {
HungerState::WellFed => {
clock.state = HungerState::Normal;
clock.duration = 200;
if entity == *player_entity {
log.append("You are no longer well fed.");
}
if entity == *player_entity {
log.append("You are starving!");
}
HungerState::Normal => {
clock.state = HungerState::Hungry;
clock.duration = 200;
if entity == *player_entity {
log.append("You are hungry.");
}
}
HungerState::Starving => {
// Inflict damage from hunger
if entity == *player_entity {
log.append("Your hunger pangs are getting painful!");
}
HungerState::Hungry => {
clock.state = HungerState::Starving;
clock.duration = 200;
if entity == *player_entity {
log.append("You are starving!");
}
}
HungerState::Starving => {
// Inflict damage from hunger
if entity == *player_entity {
log.append("Your hunger pangs are getting painful!");
}
SufferDamage::new_damage(&mut inflict_damage, entity, 1, false);
}
SufferDamage::new_damage(&mut inflict_damage, entity, 1, false);
}
}
}

View File

@ -62,8 +62,7 @@ const SHOW_MAPGEN_VISUALIZER: bool = false;
pub enum RunState {
AwaitingInput,
PreRun,
PlayerTurn,
MonsterTurn,
Ticking,
ShowInventory,
ShowDropItem,
ShowTargeting {
@ -107,15 +106,18 @@ impl State {
}
fn run_systems(&mut self) {
let mut mapindex = MapIndexingSystem {};
mapindex.run_now(&self.ecs);
let mut vis = VisibilitySystem {};
vis.run_now(&self.ecs);
let mut initiative = ai::InitiativeSystem {};
initiative.run_now(&self.ecs);
let mut mob = ai::MonsterAI {};
mob.run_now(&self.ecs);
let mut mapindex = MapIndexingSystem {};
mapindex.run_now(&self.ecs);
let mut animal = ai::AnimalAI {};
animal.run_now(&self.ecs);
@ -203,21 +205,15 @@ impl GameState for State {
RunState::AwaitingInput => {
newrunstate = player_input(self, ctx);
}
RunState::PlayerTurn => {
RunState::Ticking => {
self.run_systems();
self.ecs.maintain();
match *self.ecs.fetch::<RunState>() {
RunState::MagicMapReveal { .. } => {
newrunstate = RunState::MagicMapReveal { row: 0 }
}
_ => newrunstate = RunState::MonsterTurn,
}
}
RunState::MonsterTurn => {
self.run_systems();
self.ecs.maintain();
newrunstate = RunState::AwaitingInput;
newrunstate = match *self.ecs.fetch::<RunState>() {
RunState::AwaitingInput => RunState::AwaitingInput,
RunState::MagicMapReveal { .. } => RunState::MagicMapReveal { row: 0 },
_ => RunState::Ticking,
};
}
RunState::ShowInventory => {
let result = gui::show_inventory(self, ctx);
@ -245,7 +241,7 @@ impl GameState for State {
)
.expect("failed to add intent to use item");
newrunstate = RunState::PlayerTurn;
newrunstate = RunState::Ticking;
}
}
}
@ -265,7 +261,7 @@ impl GameState for State {
)
.expect("failed to add intent to drop item");
newrunstate = RunState::PlayerTurn;
newrunstate = RunState::Ticking;
}
}
}
@ -284,7 +280,7 @@ impl GameState for State {
)
.expect("Unable to insert intent to remove item");
newrunstate = RunState::PlayerTurn;
newrunstate = RunState::Ticking;
}
}
}
@ -306,7 +302,7 @@ impl GameState for State {
)
.expect("failed to add intent to use item");
newrunstate = RunState::PlayerTurn;
newrunstate = RunState::Ticking;
}
}
}
@ -361,7 +357,7 @@ impl GameState for State {
}
if row == map.height - 1 {
newrunstate = RunState::MonsterTurn;
newrunstate = RunState::Ticking;
} else {
newrunstate = RunState::MagicMapReveal { row: row + 1 };
}

View File

@ -59,7 +59,7 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> RunState
let mut ppos = ecs.write_resource::<Point>();
ppos.x = pos.x;
ppos.y = pos.y;
result = RunState::PlayerTurn;
result = RunState::Ticking;
} else if combat_stats.get(*potential_target).is_some() {
wants_to_melee
.insert(
@ -70,7 +70,7 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> RunState
)
.expect("Add target failed");
return RunState::PlayerTurn;
return RunState::Ticking;
}
if let Some(door) = doors.get_mut(*potential_target) {
@ -100,7 +100,7 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> RunState
result = match map.tiles[destination_idx] {
TileType::DownStairs => RunState::NextLevel,
TileType::UpStairs => RunState::PreviousLevel,
_ => RunState::PlayerTurn,
_ => RunState::Ticking,
}
}
}
@ -218,7 +218,7 @@ fn skip_turn(ecs: &mut World) -> RunState {
pools.hit_points.current = i32::min(pools.hit_points.current + 1, pools.hit_points.max);
}
RunState::PlayerTurn
RunState::Ticking
}
fn use_consumable_hotkey(gs: &mut State, key: i32) -> RunState {
@ -259,10 +259,10 @@ fn use_consumable_hotkey(gs: &mut State, key: i32) -> RunState {
)
.expect("Unable to insert intent to use item.");
return RunState::PlayerTurn;
return RunState::Ticking;
}
RunState::PlayerTurn
RunState::Ticking
}
pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState {
@ -366,5 +366,5 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState {
},
}
RunState::PlayerTurn
RunState::Ticking
}