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 }