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