1use chrono::{DateTime, Local};
2use enum_map::{Enum, EnumArray, EnumMap};
3use num_derive::FromPrimitive;
4use num_traits::FromPrimitive;
5use strum::{EnumCount, EnumIter};
6
7use super::{
8 items::Equipment, AttributeType, CCGet, Class, EnumMapGet, Item, SFError,
9 ServerTime,
10};
11use crate::{
12 misc::soft_into,
13 simulate::{
14 constants::{LIGHT_ENEMIES, SHADOW_ENEMIES},
15 Monster,
16 },
17};
18
19#[derive(Debug, Default, Clone)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21pub struct Portal {
23 pub finished: u16,
25 pub can_fight: bool,
29 pub enemy_level: u32,
32 pub enemy_hp_percentage: u8,
34 pub player_hp_bonus: u16,
36}
37
38impl Portal {
39 pub(crate) fn update(
40 &mut self,
41 data: &[i64],
42 server_time: ServerTime,
43 ) -> Result<(), SFError> {
44 self.finished = data.csiget(0, "portal fights", 10_000)?;
45 self.enemy_hp_percentage = data.csiget(1, "portal hp", 0)?;
46
47 let current_day = chrono::Datelike::ordinal(&server_time.current());
48 let last_portal_day: u32 = data.csiget(2, "portal day", 0)?;
49 self.can_fight = last_portal_day != current_day;
50
51 Ok(())
52 }
53}
54
55#[derive(Debug, Default, Clone)]
56#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
57pub struct Dungeons {
60 pub next_free_fight: Option<DateTime<Local>>,
62 pub light: EnumMap<LightDungeon, DungeonProgress>,
64 pub shadow: EnumMap<ShadowDungeon, DungeonProgress>,
67 pub portal: Option<Portal>,
68 pub companions: Option<EnumMap<CompanionClass, Companion>>,
71}
72
73impl Dungeons {
74 pub fn progress(&self, dungeon: impl Into<Dungeon>) -> DungeonProgress {
76 let dungeon: Dungeon = dungeon.into();
77 match dungeon {
78 Dungeon::Light(dungeon) => *self.light.get(dungeon),
79 Dungeon::Shadow(dungeon) => *self.shadow.get(dungeon),
80 }
81 }
82
83 pub fn current_enemy(
88 &self,
89 dungeon: impl Into<Dungeon> + Copy,
90 ) -> Option<&'static Monster> {
91 dungeon_enemy(dungeon, self.progress(dungeon))
92 }
93}
94
95#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
96#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
97pub enum DungeonProgress {
99 #[default]
100 Locked,
102 Open {
104 finished: u16,
106 },
107 Finished,
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
112#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
113#[allow(missing_docs)]
114pub enum DungeonType {
117 Light,
118 Shadow,
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
122#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
123#[allow(missing_docs)]
124pub enum Dungeon {
127 Light(LightDungeon),
128 Shadow(ShadowDungeon),
129}
130
131#[derive(
132 Debug,
133 Clone,
134 Copy,
135 PartialEq,
136 Eq,
137 EnumCount,
138 EnumIter,
139 Enum,
140 FromPrimitive,
141 Hash,
142)]
143#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
144#[allow(missing_docs)]
145pub enum LightDungeon {
149 DesecratedCatacombs = 0,
150 MinesOfGloria = 1,
151 RuinsOfGnark = 2,
152 CutthroatGrotto = 3,
153 EmeraldScaleAltar = 4,
154 ToxicTree = 5,
155 MagmaStream = 6,
156 FrostBloodTemple = 7,
157 PyramidsofMadness = 8,
158 BlackSkullFortress = 9,
159 CircusOfHorror = 10,
160 Hell = 11,
161 The13thFloor = 12,
162 Easteros = 13,
163 Tower = 14,
164 TimeHonoredSchoolofMagic = 15,
165 Hemorridor = 16,
166 NordicGods = 18,
167 MountOlympus = 19,
168 TavernoftheDarkDoppelgangers = 20,
169 DragonsHoard = 21,
170 HouseOfHorrors = 22,
171 ThirdLeagueOfSuperheroes = 23,
172 DojoOfChildhoodHeroes = 24,
173 MonsterGrotto = 25,
174 CityOfIntrigues = 26,
175 SchoolOfMagicExpress = 27,
176 AshMountain = 28,
177 PlayaGamesHQ = 29,
178 TrainingCamp = 30,
179 Sandstorm = 31,
180}
181
182impl From<LightDungeon> for Dungeon {
183 fn from(val: LightDungeon) -> Self {
184 Dungeon::Light(val)
185 }
186}
187
188#[derive(
189 Debug,
190 Clone,
191 Copy,
192 PartialEq,
193 Eq,
194 EnumCount,
195 EnumIter,
196 Enum,
197 FromPrimitive,
198 Hash,
199)]
200#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
201#[allow(missing_docs)]
202pub enum ShadowDungeon {
205 DesecratedCatacombs = 0,
206 MinesOfGloria = 1,
207 RuinsOfGnark = 2,
208 CutthroatGrotto = 3,
209 EmeraldScaleAltar = 4,
210 ToxicTree = 5,
211 MagmaStream = 6,
212 FrostBloodTemple = 7,
213 PyramidsOfMadness = 8,
214 BlackSkullFortress = 9,
215 CircusOfHorror = 10,
216 Hell = 11,
217 The13thFloor = 12,
218 Easteros = 13,
219 Twister = 14,
220 TimeHonoredSchoolOfMagic = 15,
221 Hemorridor = 16,
222 ContinuousLoopofIdols = 17,
223 NordicGods = 18,
224 MountOlympus = 19,
225 TavernOfTheDarkDoppelgangers = 20,
226 DragonsHoard = 21,
227 HouseOfHorrors = 22,
228 ThirdLeagueofSuperheroes = 23,
229 DojoOfChildhoodHeroes = 24,
230 MonsterGrotto = 25,
231 CityOfIntrigues = 26,
232 SchoolOfMagicExpress = 27,
233 AshMountain = 28,
234 PlayaGamesHQ = 29,
235}
236
237impl From<ShadowDungeon> for Dungeon {
238 fn from(val: ShadowDungeon) -> Self {
239 Dungeon::Shadow(val)
240 }
241}
242
243fn update_progress<T: FromPrimitive + EnumArray<DungeonProgress>>(
244 data: &[i64],
245 dungeons: &mut EnumMap<T, DungeonProgress>,
246) {
247 for (dungeon_id, progress) in data.iter().copied().enumerate() {
248 let Some(dungeon_typ) = FromPrimitive::from_usize(dungeon_id) else {
249 continue;
250 };
251 let dungeon = dungeons.get_mut(dungeon_typ);
252 *dungeon = match progress {
253 -1 => DungeonProgress::Locked,
254 x => {
255 let stage = soft_into(x, "dungeon progress", 0);
256 if stage == 10 || stage == 100 && dungeon_id == 14 {
257 DungeonProgress::Finished
258 } else {
259 DungeonProgress::Open { finished: stage }
260 }
261 }
262 };
263 }
264}
265
266impl Dungeons {
267 #[must_use]
269 pub fn can_companion_equip(
270 &self,
271 companion: CompanionClass,
272 item: &Item,
273 ) -> bool {
274 if self.companions.is_none() {
276 return false;
277 }
278 item.can_be_equipped_by_companion(companion)
279 }
280
281 pub(crate) fn update_progress(
282 &mut self,
283 data: &[i64],
284 dungeon_type: DungeonType,
285 ) {
286 match dungeon_type {
287 DungeonType::Light => update_progress(data, &mut self.light),
288 DungeonType::Shadow => {
289 update_progress(data, &mut self.shadow);
290 for (dungeon, limit) in [
291 (ShadowDungeon::ContinuousLoopofIdols, 21),
292 (ShadowDungeon::Twister, 1000),
293 ] {
294 let d = self.shadow.get_mut(dungeon);
295 if let DungeonProgress::Open { finished, .. } = d {
296 if *finished >= limit {
297 *d = DungeonProgress::Finished;
298 }
299 }
300 }
301 }
302 };
303 }
304}
305
306#[derive(
307 Debug, Clone, Copy, PartialEq, Eq, EnumCount, Enum, EnumIter, Hash,
308)]
309#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
310pub enum CompanionClass {
313 Warrior = 0,
315 Mage = 1,
317 Scout = 2,
319}
320
321impl From<CompanionClass> for Class {
322 fn from(value: CompanionClass) -> Self {
323 match value {
324 CompanionClass::Warrior => Class::Warrior,
325 CompanionClass::Mage => Class::Mage,
326 CompanionClass::Scout => Class::Scout,
327 }
328 }
329}
330
331#[derive(Debug, Default, Clone)]
332#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
333pub struct Companion {
336 pub level: i64,
339 pub equipment: Equipment,
341 pub attributes: EnumMap<AttributeType, u32>,
343}
344
345pub fn dungeon_enemy(
346 dungeon: impl Into<Dungeon>,
347 progress: DungeonProgress,
348) -> Option<&'static Monster> {
349 let stage = match progress {
350 DungeonProgress::Open { finished } => finished,
351 DungeonProgress::Locked | DungeonProgress::Finished => return None,
352 };
353
354 let dungeon: Dungeon = dungeon.into();
355 match dungeon {
356 Dungeon::Light(dungeon) => {
357 LIGHT_ENEMIES.get(dungeon).get(stage as usize)
358 }
359 Dungeon::Shadow(dungeon) => {
360 SHADOW_ENEMIES.get(dungeon).get(stage as usize)
361 }
362 }
363}