1#![allow(
12 clippy::cast_possible_wrap,
13 clippy::cast_sign_loss,
14 clippy::cast_precision_loss,
15 clippy::cast_possible_truncation
16)]
17use std::sync::Arc;
18
19use enum_map::{Enum, EnumMap};
20use fastrand::Rng;
21use fighter::InBattleFighter;
22use strum::EnumIter;
23
24pub use crate::simulate::{
25 damage::DamageRange,
26 fighter::Fighter,
27 upgradeable::{PlayerFighterSquad, UpgradeableFighter},
28};
29use crate::{
30 command::AttributeType, gamestate::character::Class,
31 simulate::fighter::FighterIdent,
32};
33
34pub(crate) mod constants;
35mod damage;
36mod fighter;
37mod upgradeable;
38
39#[derive(Debug, Clone)]
41pub struct Weapon {
42 pub rune_value: i32,
44 pub rune_type: Option<Element>,
46 pub damage: DamageRange,
48}
49
50#[derive(Debug, Clone, Default)]
51pub struct FightSimulationResult {
52 pub win_ratio: f64,
54 pub won_fights: u32,
57}
58
59#[must_use]
92pub fn simulate_battle(
93 left: &[Fighter],
94 right: &[Fighter],
95 iterations: u32,
96 is_arena_battle: bool,
97) -> FightSimulationResult {
98 if left.is_empty() || right.is_empty() {
99 return FightSimulationResult::default();
100 }
101
102 simulate_fight(left, right, iterations, is_arena_battle)
103}
104
105fn simulate_fight(
106 left: &[Fighter],
107 right: &[Fighter],
108 iterations: u32,
109 is_arena_battle: bool,
110) -> FightSimulationResult {
111 let mut cache = InBattleCache(Vec::new());
112 let mut won_fights = 0;
113 for _ in 0..iterations {
114 let fight_result =
115 perform_single_fight(left, right, is_arena_battle, &mut cache);
116 if fight_result == FightOutcome::SimulationBroken {
117 break;
118 }
119 if fight_result == FightOutcome::LeftSideWin {
120 won_fights += 1;
121 }
122 }
123
124 let win_ratio = f64::from(won_fights) / f64::from(iterations);
125 FightSimulationResult {
126 win_ratio,
127 won_fights,
128 }
129}
130
131struct InBattleCache(Vec<((FighterIdent, FighterIdent), InBattleFighter)>);
132
133impl InBattleCache {
134 pub fn get_or_insert(
135 &mut self,
136 this: &Fighter,
137 other: &Fighter,
138 is_arena_battle: bool,
139 ) -> InBattleFighter {
140 if self.0.len() > 10 {
141 return InBattleFighter::new(this, other, is_arena_battle);
142 }
143 let ident = (this.ident, other.ident);
144 if let Some(existing) = self.0.iter().find(|a| a.0 == ident) {
145 return existing.1.clone();
146 }
147 let new = InBattleFighter::new(this, other, is_arena_battle);
148 self.0.push((ident, new.clone()));
149 new
150 }
151}
152
153fn perform_single_fight(
154 left: &[Fighter],
155 right: &[Fighter],
156 is_arena_battle: bool,
157 cache: &mut InBattleCache,
158) -> FightOutcome {
159 let mut rng = Rng::new();
160
161 let mut left_side = left.iter().peekable();
162 let mut left_in_battle: Option<InBattleFighter> = None;
163
164 let mut right_side = right.iter().peekable();
165 let mut right_in_battle: Option<InBattleFighter> = None;
166
167 for _ in 0..500 {
168 let Some(left) = left_side.peek_mut() else {
169 return FightOutcome::RightSideWin;
170 };
171 let Some(right) = right_side.peek_mut() else {
172 return FightOutcome::LeftSideWin;
173 };
174
175 let (left_fighter, right_fighter) =
176 match (&mut left_in_battle, &mut right_in_battle) {
177 (Some(left), Some(right)) => {
178 (left, right)
180 }
181 (None, None) => {
182 (
184 left_in_battle.insert(cache.get_or_insert(
185 left,
186 right,
187 is_arena_battle,
188 )),
189 right_in_battle.insert(cache.get_or_insert(
190 right,
191 left,
192 is_arena_battle,
193 )),
194 )
195 }
196 (None, Some(r)) => {
197 r.update_opponent(right, left, is_arena_battle);
198 (
199 left_in_battle.insert(cache.get_or_insert(
200 left,
201 right,
202 is_arena_battle,
203 )),
204 r,
205 )
206 }
207 (Some(l), None) => {
208 l.update_opponent(left, right, is_arena_battle);
209 (
210 l,
211 right_in_battle.insert(cache.get_or_insert(
212 right,
213 left,
214 is_arena_battle,
215 )),
216 )
217 }
218 };
219
220 let res = perform_fight(left_fighter, right_fighter, &mut rng);
224
225 match res {
226 FightOutcome::LeftSideWin => {
227 right_side.next();
228 right_in_battle = None;
229 }
230 FightOutcome::RightSideWin => {
231 left_side.next();
232 left_in_battle = None;
233 }
234 FightOutcome::SimulationBroken => {
235 return FightOutcome::SimulationBroken;
236 }
237 }
238 }
239
240 FightOutcome::SimulationBroken
241}
242
243fn perform_fight<'a>(
244 char_side: &'a mut InBattleFighter,
245 dungeon_side: &'a mut InBattleFighter,
246 rng: &mut Rng,
247) -> FightOutcome {
248 let char_side_starts =
249 char_side.reaction > dungeon_side.reaction || rng.bool();
250
251 let (attacker, defender) = if char_side_starts {
252 (char_side, dungeon_side)
253 } else {
254 (dungeon_side, char_side)
255 };
256
257 let round = &mut 0u32;
258
259 if attacker.attack_before_fight(defender, round, rng) {
260 return outcome_from_bool(char_side_starts);
261 }
262
263 if defender.attack_before_fight(attacker, round, rng) {
264 return outcome_from_bool(!char_side_starts);
265 }
266
267 for _ in 0..1_000_000 {
270 let skip_round =
271 defender.will_skips_opponent_round(attacker, round, rng);
272 if !skip_round && attacker.attack(defender, round, rng) {
273 return outcome_from_bool(char_side_starts);
274 }
275
276 let skip_round =
277 attacker.will_skips_opponent_round(defender, round, rng);
278 if !skip_round && defender.attack(attacker, round, rng) {
279 return outcome_from_bool(!char_side_starts);
280 }
281 }
282 FightOutcome::SimulationBroken
284}
285
286fn outcome_from_bool(result: bool) -> FightOutcome {
287 if result {
288 FightOutcome::LeftSideWin
289 } else {
290 FightOutcome::RightSideWin
291 }
292}
293
294#[derive(Debug, Clone, Copy, PartialEq, Eq)]
295enum FightOutcome {
296 LeftSideWin,
297 RightSideWin,
298 SimulationBroken,
299}
300
301#[derive(Debug, Clone, Copy, Enum, EnumIter, Hash, PartialEq, Eq)]
302pub enum Element {
303 Fire,
304 Cold,
305 Lightning,
306}
307
308#[derive(Debug, Clone, PartialEq, Eq)]
309pub struct Monster {
310 pub name: &'static str,
311 pub level: u16,
312 pub class: Class,
313 pub attributes: EnumMap<AttributeType, u32>,
314 pub hp: u64,
315 pub min_dmg: u32,
316 pub max_dmg: u32,
317 pub armor: u32,
318 pub runes: Option<MonsterRunes>,
319}
320
321#[derive(Debug, Clone, PartialEq, Eq)]
322pub struct MonsterRunes {
323 pub damage_type: Element,
324 pub damage: i32,
325 pub resistances: EnumMap<Element, i32>,
326}