/ src / combat / mod.rs
mod.rs
  1  use std::{
  2      ops::DerefMut,
  3      time::{Duration, Instant},
  4  };
  5  
  6  use rand::{thread_rng, Rng};
  7  use specs::{
  8      Component, DenseVecStorage, DispatcherBuilder, Entities, Entity, Join, World, WorldExt,
  9  };
 10  
 11  use crate::{
 12      experience::{GainExperience, KillExperience},
 13      flow::{GameFlow, GameState},
 14      monster::Monster,
 15      movement::Position,
 16      player::Player,
 17      turn::Turn,
 18  };
 19  
 20  pub mod view;
 21  
 22  #[derive(Component, Clone)]
 23  pub struct Attacked;
 24  
 25  #[derive(Component, Clone)]
 26  pub struct CombatStats {
 27      pub max_hp: i64,
 28      pub hp: i64,
 29      pub defense: i64,
 30      pub power: i64,
 31  }
 32  
 33  #[derive(Clone, Default, PartialEq, Eq)]
 34  pub enum CombatFlowState {
 35      #[default]
 36      Tossing,
 37      Tossed {
 38          attacker_score: i64,
 39          defending_score: i64,
 40      },
 41      HpDiff {
 42          defending_diff: i64,
 43      },
 44  }
 45  
 46  #[derive(Clone)]
 47  pub struct CombatFlow {
 48      pub attacker: Entity,
 49      pub defending: Entity,
 50      started: Instant,
 51      pub state: CombatFlowState,
 52  }
 53  
 54  #[derive(Clone, Default)]
 55  pub enum CombatState {
 56      #[default]
 57      NoCombat,
 58      Combat(CombatFlow),
 59  }
 60  
 61  impl CombatStats {
 62      pub fn hp_ratio(&self) -> f64 {
 63          self.hp as f64 / self.max_hp as f64
 64      }
 65  }
 66  
 67  struct CombatSystem;
 68  
 69  impl<'a> specs::System<'a> for CombatSystem {
 70      type SystemData = (
 71          Entities<'a>,
 72          specs::Write<'a, CombatState>,
 73          specs::Read<'a, Turn>,
 74          specs::WriteStorage<'a, Player>,
 75          specs::WriteStorage<'a, Monster>,
 76          specs::WriteStorage<'a, CombatStats>,
 77          specs::WriteStorage<'a, Attacked>,
 78          specs::ReadStorage<'a, Position>,
 79          specs::Read<'a, GameFlow>,
 80      );
 81  
 82      fn run(
 83          &mut self,
 84          (
 85              entities,
 86              mut combat_state,
 87              turn,
 88              player,
 89              monsters,
 90              mut stats,
 91              mut attacked,
 92              positions,
 93              game_state,
 94          ): Self::SystemData,
 95      ) {
 96          let GameState::Running = game_state.state else {
 97              return;
 98          };
 99  
100          match combat_state.deref_mut() {
101              CombatState::NoCombat => {
102                  for (e, _) in (&entities, &player).join() {
103                      attacked.remove(e);
104                  }
105  
106                  let (_, player_pos, player_entity) = (&player, &positions, &entities)
107                      .join()
108                      .next()
109                      .expect("Player entity must exist");
110  
111                  let mut has_collision = None;
112                  for (entity, _, pos) in (&entities, &monsters, &positions).join() {
113                      let player_collision = pos.x == player_pos.x && pos.y == player_pos.y;
114                      if player_collision {
115                          has_collision = Some(entity);
116                          break;
117                      }
118                  }
119  
120                  if let Some(monster_entity) = has_collision {
121                      let (attacker, defending) = match *turn {
122                          Turn::Game => (monster_entity, player_entity),
123                          Turn::Player => (player_entity, monster_entity),
124                      };
125  
126                      *combat_state = CombatState::Combat(CombatFlow {
127                          attacker,
128                          defending,
129                          started: Instant::now(),
130                          state: CombatFlowState::Tossing,
131                      });
132                  }
133              }
134              CombatState::Combat(CombatFlow {
135                  started,
136                  state,
137                  attacker,
138                  defending,
139              }) if (Instant::now() - *started) > Duration::from_secs(1) => match state {
140                  CombatFlowState::Tossing => {
141                      let mut rng = thread_rng();
142                      let attacker_score: i64 = rng.gen_range(0..=8);
143                      let defending_score: i64 = rng.gen_range(0..=8);
144                      *state = CombatFlowState::Tossed {
145                          attacker_score,
146                          defending_score,
147                      };
148                      *started = Instant::now();
149                  }
150                  CombatFlowState::Tossed {
151                      attacker_score,
152                      defending_score,
153                  } => {
154                      let attacker_stats = stats.get(*attacker).unwrap();
155                      let defending_stats = stats.get(*defending).unwrap();
156  
157                      let attack = *attacker_score + attacker_stats.power;
158                      let defense = *defending_score + defending_stats.defense;
159  
160                      let diff = if attack > defense {
161                          defense - attack
162                      } else {
163                          0
164                      };
165  
166                      *state = CombatFlowState::HpDiff {
167                          defending_diff: diff,
168                      };
169                      *started = Instant::now();
170                  }
171                  CombatFlowState::HpDiff { defending_diff } => {
172                      let defending_stats = stats.get_mut(*defending).unwrap();
173                      attacked.insert(*attacker, Attacked).unwrap();
174  
175                      defending_stats.hp += *defending_diff;
176  
177                      *combat_state = CombatState::NoCombat;
178                  }
179              },
180              _ => {}
181          };
182      }
183  }
184  
185  struct DeathSystem;
186  
187  impl<'a> specs::System<'a> for DeathSystem {
188      type SystemData = (
189          Entities<'a>,
190          specs::ReadStorage<'a, Player>,
191          specs::ReadStorage<'a, Monster>,
192          specs::ReadStorage<'a, CombatStats>,
193          specs::ReadStorage<'a, KillExperience>,
194          specs::WriteStorage<'a, GainExperience>,
195          specs::Read<'a, GameFlow>,
196      );
197  
198      fn run(
199          &mut self,
200          (entities, player, monsters, stats, kill_experience, mut gain_experience, game_flow): Self::SystemData,
201      ) {
202          let GameState::Running = game_flow.state else {
203              return;
204          };
205  
206          let (player_entity, _) = (&entities, &player).join().next().unwrap();
207  
208          gain_experience
209              .insert(player_entity, GainExperience::new(0))
210              .unwrap();
211          for (entity, entity_stats, _) in (&entities, &stats, &monsters).join() {
212              if entity_stats.hp <= 0 {
213                  if let Some(kill_exp) = kill_experience.get(entity) {
214                      let gain = gain_experience.get_mut(player_entity).unwrap();
215                      gain.exp_count += GainExperience::from(kill_exp.clone()).exp_count;
216                  }
217  
218                  entities
219                      .delete(entity)
220                      .expect("monster deletion should succeed");
221              }
222          }
223      }
224  }
225  
226  pub fn register(dispatcher: &mut DispatcherBuilder, world: &mut World) -> anyhow::Result<()> {
227      world.insert(CombatState::default());
228  
229      world.register::<CombatStats>();
230      world.register::<Attacked>();
231  
232      dispatcher.add(
233          CombatSystem,
234          "combat_system",
235          &["player_move_system", "monster_move_system"],
236      );
237      dispatcher.add(DeathSystem, "death_system", &["combat_system"]);
238      Ok(())
239  }