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 AttributeType, CCGet, Class, EnumMapGet, Item, SFError, ServerTime,
9 items::Equipment,
10};
11use crate::{
12 misc::soft_into,
13 simulate::{
14 Monster,
15 constants::{LIGHT_ENEMIES, SHADOW_ENEMIES},
16 },
17};
18
19#[derive(Debug, Default, Clone)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22pub 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)]
58#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
59pub 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)]
97#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
98pub enum DungeonProgress {
99 #[default]
101 Locked,
102 Open {
104 finished: u16,
106 },
107 Finished,
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
114#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
115#[allow(missing_docs)]
116pub enum DungeonType {
117 Light,
118 Shadow,
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
124#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
125#[allow(missing_docs)]
126pub enum Dungeon {
127 Light(LightDungeon),
128 Shadow(ShadowDungeon),
129}
130
131#[derive(
135 Debug,
136 Clone,
137 Copy,
138 PartialEq,
139 Eq,
140 EnumCount,
141 EnumIter,
142 Enum,
143 FromPrimitive,
144 Hash,
145)]
146#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
147#[allow(missing_docs)]
148pub 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 ArcadeOfTheOldPixelIcons = 32,
181 TheServerRoom = 33,
182 WorkshopOfTheHunters = 34,
183 RetroTVLegends = 35,
184 MeetingRoom = 36,
185}
186
187impl From<LightDungeon> for Dungeon {
188 fn from(val: LightDungeon) -> Self {
189 Dungeon::Light(val)
190 }
191}
192
193#[derive(
196 Debug,
197 Clone,
198 Copy,
199 PartialEq,
200 Eq,
201 EnumCount,
202 EnumIter,
203 Enum,
204 FromPrimitive,
205 Hash,
206)]
207#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
208#[allow(missing_docs)]
209pub enum ShadowDungeon {
210 DesecratedCatacombs = 0,
211 MinesOfGloria = 1,
212 RuinsOfGnark = 2,
213 CutthroatGrotto = 3,
214 EmeraldScaleAltar = 4,
215 ToxicTree = 5,
216 MagmaStream = 6,
217 FrostBloodTemple = 7,
218 PyramidsOfMadness = 8,
219 BlackSkullFortress = 9,
220 CircusOfHorror = 10,
221 Hell = 11,
222 The13thFloor = 12,
223 Easteros = 13,
224 Twister = 14,
225 TimeHonoredSchoolOfMagic = 15,
226 Hemorridor = 16,
227 ContinuousLoopofIdols = 17,
228 NordicGods = 18,
229 MountOlympus = 19,
230 TavernOfTheDarkDoppelgangers = 20,
231 DragonsHoard = 21,
232 HouseOfHorrors = 22,
233 ThirdLeagueofSuperheroes = 23,
234 DojoOfChildhoodHeroes = 24,
235 MonsterGrotto = 25,
236 CityOfIntrigues = 26,
237 SchoolOfMagicExpress = 27,
238 AshMountain = 28,
239 PlayaGamesHQ = 29,
240 ArcadeOfTheOldPixelIcons = 32,
242 TheServerRoom = 33,
243 WorkshopOfTheHunters = 34,
244 RetroTVLegends = 35,
245 MeetingRoom = 36,
246}
247
248impl From<ShadowDungeon> for Dungeon {
249 fn from(val: ShadowDungeon) -> Self {
250 Dungeon::Shadow(val)
251 }
252}
253
254fn update_progress<T: FromPrimitive + EnumArray<DungeonProgress>>(
255 data: &[i64],
256 dungeons: &mut EnumMap<T, DungeonProgress>,
257) {
258 for (dungeon_id, progress) in data.iter().copied().enumerate() {
259 let Some(dungeon_typ) = FromPrimitive::from_usize(dungeon_id) else {
260 continue;
261 };
262 let dungeon = dungeons.get_mut(dungeon_typ);
263 *dungeon = match progress {
264 -1 => DungeonProgress::Locked,
265 x => {
266 let stage = soft_into(x, "dungeon progress", 0);
267 if stage == 10 || stage == 100 && dungeon_id == 14 {
268 DungeonProgress::Finished
269 } else {
270 DungeonProgress::Open { finished: stage }
271 }
272 }
273 };
274 }
275}
276
277impl Dungeons {
278 #[must_use]
280 pub fn can_companion_equip(
281 &self,
282 companion: CompanionClass,
283 item: &Item,
284 ) -> bool {
285 if self.companions.is_none() {
287 return false;
288 }
289 item.can_be_equipped_by_companion(companion)
290 }
291
292 pub(crate) fn update_progress(
293 &mut self,
294 data: &[i64],
295 dungeon_type: DungeonType,
296 ) {
297 match dungeon_type {
298 DungeonType::Light => update_progress(data, &mut self.light),
299 DungeonType::Shadow => {
300 update_progress(data, &mut self.shadow);
301 for (dungeon, limit) in [
302 (ShadowDungeon::ContinuousLoopofIdols, 21),
303 (ShadowDungeon::Twister, 1000),
304 ] {
305 let d = self.shadow.get_mut(dungeon);
306 if let DungeonProgress::Open { finished, .. } = d
307 && *finished >= limit
308 {
309 *d = DungeonProgress::Finished;
310 }
311 }
312 }
313 }
314 }
315}
316
317#[derive(
320 Debug, Clone, Copy, PartialEq, Eq, EnumCount, Enum, EnumIter, Hash,
321)]
322#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
323pub enum CompanionClass {
324 Warrior = 0,
326 Mage = 1,
328 Scout = 2,
330}
331
332impl From<CompanionClass> for Class {
333 fn from(value: CompanionClass) -> Self {
334 match value {
335 CompanionClass::Warrior => Class::Warrior,
336 CompanionClass::Mage => Class::Mage,
337 CompanionClass::Scout => Class::Scout,
338 }
339 }
340}
341
342#[derive(Debug, Default, Clone)]
345#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
346pub struct Companion {
347 pub level: i64,
350 pub equipment: Equipment,
352 pub attributes: EnumMap<AttributeType, u32>,
354}
355
356pub fn dungeon_enemy(
357 dungeon: impl Into<Dungeon>,
358 progress: DungeonProgress,
359) -> Option<&'static Monster> {
360 let stage = match progress {
361 DungeonProgress::Open { finished } => finished,
362 DungeonProgress::Locked | DungeonProgress::Finished => return None,
363 };
364
365 let dungeon: Dungeon = dungeon.into();
366 match dungeon {
367 Dungeon::Light(dungeon) => {
368 LIGHT_ENEMIES.get(dungeon).get(stage as usize)
369 }
370 Dungeon::Shadow(dungeon) => {
371 SHADOW_ENEMIES.get(dungeon).get(stage as usize)
372 }
373 }
374}