1use std::collections::HashSet;
2
3use chrono::{DateTime, Local};
4use log::warn;
5use num_derive::FromPrimitive;
6use num_traits::FromPrimitive;
7use strum::EnumIter;
8
9use super::{
10 ArrSkip, CCGet, CGet, IdleBuildingType, LightDungeon, Mount, ShopType,
11 character::Class, items::*, tavern::Location, unlockables::HabitatType,
12};
13use crate::{command::AttributeType, error::SFError};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19#[non_exhaustive]
20#[allow(missing_docs)]
21pub enum WheelRewardType {
22 Mushrooms,
23 Stone,
24 StoneXL,
25 Wood,
26 WoodXL,
27 Experience,
28 ExperienceXL,
29 Silver,
30 SilverXL,
31 Arcane,
32 Souls,
33 Item,
34 PetItem(PetItem),
35 Unknown,
36}
37
38#[derive(Debug, Clone, Copy)]
40#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
41pub struct WheelReward {
42 pub typ: WheelRewardType,
44 pub amount: i64,
46}
47
48impl WheelReward {
49 pub(crate) fn parse(
50 data: &[i64],
51 upgraded: bool,
52 ) -> Result<WheelReward, SFError> {
53 let raw_typ = data.cget(0, "wheel reward typ")?;
54 let mut amount = data.cget(1, "wheel reward amount")?;
55 let typ = match raw_typ {
57 0 => WheelRewardType::Mushrooms,
58 1 => {
59 if upgraded {
60 WheelRewardType::Arcane
61 } else {
62 WheelRewardType::Wood
63 }
64 }
65 2 => WheelRewardType::ExperienceXL,
66 3 => {
67 if upgraded {
68 let res = WheelRewardType::PetItem(
69 PetItem::parse(amount).ok_or_else(|| {
70 SFError::ParsingError(
71 "pet wheel reward type",
72 amount.to_string(),
73 )
74 })?,
75 );
76 amount = 1;
77 res
78 } else {
79 WheelRewardType::Stone
80 }
81 }
82 4 => WheelRewardType::SilverXL,
83 5 => {
84 amount = 1;
88 WheelRewardType::Item
89 }
90 6 => WheelRewardType::WoodXL,
91 7 => WheelRewardType::Experience,
92 8 => WheelRewardType::StoneXL,
93 9 => {
94 if upgraded {
95 WheelRewardType::Souls
96 } else {
97 WheelRewardType::Silver
98 }
99 }
100 x => {
101 warn!("unknown wheel reward type: {x}");
102 WheelRewardType::Unknown
103 }
104 };
105 Ok(WheelReward { typ, amount })
106 }
107}
108
109#[derive(Debug, Clone)]
111#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
112pub struct CalendarReward {
113 pub typ: CalendarRewardType,
117 pub amount: i64,
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
124#[allow(missing_docs)]
125pub enum CalendarRewardType {
126 Silver,
127 Mushrooms,
128 Experience,
129 Wood,
130 Stone,
131 Souls,
132 Arcane,
133 Runes,
134 Item,
135 Attribute(AttributeType),
136 Fruit(HabitatType),
137 Level,
138 Potion(PotionType),
139 TenQuicksandGlasses,
140 LevelUp,
141 Unknown,
142}
143
144impl CalendarReward {
145 pub(crate) fn parse(data: &[i64]) -> Result<CalendarReward, SFError> {
146 let amount = data.cget(1, "c reward amount")?;
147 let typ = data.cget(0, "c reward typ")?;
148 let typ = match typ {
149 1 => CalendarRewardType::Silver,
150 2 => CalendarRewardType::Mushrooms,
151 3 => CalendarRewardType::Experience,
152 4 => CalendarRewardType::Wood,
153 5 => CalendarRewardType::Stone,
154 6 => CalendarRewardType::Souls,
155 7 => CalendarRewardType::Arcane,
156 8 => CalendarRewardType::Runes,
157 10 => CalendarRewardType::Item,
158 11 => CalendarRewardType::Attribute(AttributeType::Strength),
159 12 => CalendarRewardType::Attribute(AttributeType::Dexterity),
160 13 => CalendarRewardType::Attribute(AttributeType::Intelligence),
161 14 => CalendarRewardType::Attribute(AttributeType::Constitution),
162 15 => CalendarRewardType::Attribute(AttributeType::Luck),
163 x @ 16..=20 => {
164 if let Some(typ) = HabitatType::from_typ_id(x - 15) {
165 CalendarRewardType::Fruit(typ)
166 } else {
167 warn!("unknown pet class in c rewards");
168 CalendarRewardType::Unknown
169 }
170 }
171 21 => CalendarRewardType::LevelUp,
172 22 => CalendarRewardType::Potion(PotionType::EternalLife),
173 23 => CalendarRewardType::TenQuicksandGlasses,
174 24 => CalendarRewardType::Potion(PotionType::Strength),
175 25 => CalendarRewardType::Potion(PotionType::Dexterity),
176 26 => CalendarRewardType::Potion(PotionType::Intelligence),
177 27 => CalendarRewardType::Potion(PotionType::Constitution),
178 28 => CalendarRewardType::Potion(PotionType::Luck),
179 x => {
180 warn!("Unknown calendar reward: {x}");
181 CalendarRewardType::Unknown
182 }
183 };
184
185 Ok(CalendarReward { typ, amount })
186 }
187}
188
189#[derive(Debug, Clone, Default)]
191#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
192pub struct TimedSpecials {
193 pub events: Events,
195 pub tasks: Tasks,
197 pub calendar: Calendar,
199 pub wheel: Wheel,
201 pub advent_calendar: Option<Reward>,
203}
204
205#[derive(Debug, Clone, Default)]
207#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
208pub struct Events {
209 pub active: HashSet<Event>,
211 pub ends: Option<DateTime<Local>>,
213}
214
215#[derive(Debug, Clone, Default)]
217#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
218#[doc(alias = "DailyLoginBonus")]
219pub struct Calendar {
220 pub collected: usize,
224 pub rewards: Vec<CalendarReward>,
226 pub next_possible: Option<DateTime<Local>>,
229}
230
231#[derive(Debug, Clone, Default)]
233#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
234pub struct Tasks {
235 pub daily: DailyTasks,
237 pub event: EventTasks,
239}
240
241#[derive(Debug, Clone, Default)]
243#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
244pub struct DailyTasks {
245 pub tasks: Vec<Task>,
247 pub rewards: [RewardChest; 3],
249}
250
251#[derive(Debug, Clone, Default)]
253#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
254pub struct EventTasks {
255 pub theme: EventTaskTheme,
257 pub start: Option<DateTime<Local>>,
259 pub end: Option<DateTime<Local>>,
261 pub tasks: Vec<Task>,
263 pub rewards: [RewardChest; 3],
265}
266
267macro_rules! impl_tasks {
268 ($t:ty) => {
269 impl $t {
270 #[must_use]
272 pub fn completed(&self) -> usize {
273 self.tasks.iter().filter(|a| a.is_completed()).count()
274 }
275
276 #[must_use]
278 pub fn earned_points(&self) -> u32 {
279 self.tasks
280 .iter()
281 .filter(|a| a.is_completed())
282 .map(|a| a.point_reward)
283 .sum()
284 }
285
286 #[must_use]
288 pub fn total_points(&self) -> u32 {
289 self.tasks.iter().map(|a| a.point_reward).sum()
290 }
291
292 #[must_use]
295 pub fn get_available(&self, task_type: TaskType) -> Option<&Task> {
296 self.tasks
297 .iter()
298 .find(|task| task.typ == task_type && !task.is_completed())
299 }
300
301 #[must_use]
303 pub fn get_uncompleted(&self) -> Vec<&Task> {
304 self.tasks
305 .iter()
306 .filter(|task| !task.is_completed())
307 .collect()
308 }
309
310 #[must_use]
312 pub fn can_open_chest(&self, index: usize) -> bool {
313 let Some(chest) = self.rewards.get(index) else {
315 return false;
316 };
317
318 if chest.opened {
320 return false;
321 }
322
323 self.earned_points() >= chest.required_points
325 }
326 }
327 };
328}
329
330impl_tasks!(DailyTasks);
331impl_tasks!(EventTasks);
332
333impl Task {
334 #[must_use]
336 pub fn is_completed(&self) -> bool {
337 self.current >= self.target
338 }
339}
340
341#[derive(Debug, Clone, Default)]
343#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
344pub struct Wheel {
345 pub lucky_coins: u32,
347 pub spins_today: u8,
349 pub next_free_spin: Option<DateTime<Local>>,
351 pub result: Option<WheelReward>,
353}
354
355#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, Default, Hash)]
357#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
358#[non_exhaustive]
359#[allow(missing_docs)]
360pub enum EventTaskTheme {
361 Gambler = 2,
363 RankClimber = 3,
364 ShoppingSpree = 4,
365 TimeSkipper = 5,
366 RuffianReset = 6,
367 PartTimeNudist = 7,
368 Scrimper = 8,
369 Scholar = 9,
370 Maximizer = 10,
371 UnderworldFigure = 11,
372 EggHunt = 12,
373 SummerCollectifun = 13,
374 Walpurgis = 14,
375 PetTrainer = 15,
376 FortressMaster = 16,
377 LegendaryDungeon = 17,
378 Hellevator = 18,
379 #[default]
380 Unknown = 245,
381}
382
383#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
385#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
386#[non_exhaustive]
387#[allow(missing_docs)]
388pub enum TaskType {
389 AddSocketToItem,
390 BlacksmithDismantle,
391 BuyHourGlasses,
392 BuyOfferFromArenaManager,
393 ClaimSoulsFromExtractor,
394 CollectGoldFromPit,
395 ConsumeThirstForAdventure,
396 ConsumeThirstFromUnderworld,
397 DefeatGambler,
398 DrinkBeer,
399 EarnMoneyCityGuard,
400 EarnMoneyFromHoFFights,
401 EarnMoneySellingItems,
402 EnterDemonPortal,
403 FeedPets,
404
405 FightGuildHydra,
406 FightGuildPortal,
407 FightInDungeons,
408 FightInPetHabitat,
409 FightMonsterInLegendaryDungeon,
410 FightOtherPets,
411
412 FillMushroomsInAdventuromatic,
413 FindGemInFortress,
414 GainArcaneFromDismantle,
415 GainEpic,
416 GainHonorExpeditions,
417 GainHonorFortress,
418 GainHonorInArena,
419 GainHonorInHoF,
420 GainLegendaryFromLegendaryDungeon,
421 GainMetalFromDismantle,
422 GainSilver,
423 GainSilverFromFightsInHoF,
424 GainXP,
425 GainXpFromAcademy,
426 GainXpFromAdventuromatic,
427 GainXpFromArenaFights,
428 GainXpFromQuests,
429 GetLuckyCoinsFromFlyingTube,
430 GuildReadyFight,
431 LureHeroesIntoUnderworld,
432 PlayGameOfDice,
433 RequestNewGoods,
434 SacrificeRunes,
435 SkipGameOfDiceWait,
436 SkipQuest,
437 SpendGoldInShop,
438 SpendGoldOnUpgrades,
439 SpinWheelOfFortune,
440 ThrowEpicInToilet,
441 ThrowItemInCauldron,
442 ThrowItemInToilet,
443 TravelTo(Location),
444
445 Upgrade(AttributeType),
446 UpgradeAnyAttribute,
447 UpgradeArenaManager,
448 UpgradeItemAttributes,
449
450 WinFightsPlayerPet,
451 WinFightsAgainst(Class),
452 WinFightsBackToBack,
453 WinFightsBareHands,
454 WinFightsInArena,
455 WinFightsInHoF,
456 WinFightsNoChestplate,
457 WinFightsNoEpicsLegendaries,
458 WinFightsNoGear,
459
460 LeaseMount,
461 DefeatMonstersLightDungeon(LightDungeon),
462 BuyWeaponInWeaponsShop,
463 FightHigherRankedPlayer,
464 AddFriend,
465 ClaimNewCustomerPack,
466 JoinOrCreateGuild,
467 UpgradeAnyGuildSkill,
468 CityGuardHours,
469 DrinkPotion(PotionType),
470
471 BuyFromShop(ShopType),
472 FindFruitsOnExpedition,
473 BrewPotions,
474 CollectWood,
475 CollectStone,
476 CommandFortressBattle,
477 FightHellevator,
478 BuyHellevatorTreats,
479 DefeatHellevatorFloors,
480 EnterLegendaryDungeon,
481 OpenLegendaryDungeonCrateChests,
482 FeedPetType(HabitatType),
483 SpendCardsHellevator,
484 OpenAdventCalendar,
485 UpgradeArenaManagerBuilding(IdleBuildingType),
486 EarnMoneyFromExpeditions,
487
488 Unknown,
489}
490
491impl TaskType {
492 pub(crate) fn parse(num: i64) -> TaskType {
493 match num {
494 1 => TaskType::DrinkBeer,
495 2 => TaskType::ConsumeThirstForAdventure,
496 3 => TaskType::WinFightsInArena,
497 4 => TaskType::SpinWheelOfFortune,
498 5 => TaskType::FightGuildHydra,
499 6 => TaskType::FightGuildPortal,
500 7 => TaskType::FeedPets,
501 8 => TaskType::FightOtherPets,
502 9 => TaskType::BlacksmithDismantle,
503 10 => TaskType::ThrowItemInToilet,
504 11 => TaskType::PlayGameOfDice,
505 12 => TaskType::LureHeroesIntoUnderworld,
506 13 => TaskType::EnterDemonPortal,
507 14 => TaskType::DefeatGambler,
508 15 => TaskType::Upgrade(AttributeType::Strength),
509 16 => TaskType::Upgrade(AttributeType::Dexterity),
510 17 => TaskType::Upgrade(AttributeType::Intelligence),
511 18 => TaskType::ConsumeThirstFromUnderworld,
512 19 => TaskType::GuildReadyFight,
513 20 => TaskType::FindGemInFortress,
514 21 => TaskType::ThrowItemInCauldron,
515 22 => TaskType::FightInPetHabitat,
516 23 => TaskType::UpgradeArenaManager,
517 24 => TaskType::SacrificeRunes,
518 25..=45 => {
519 let Some(location) = FromPrimitive::from_i64(num - 24) else {
520 return TaskType::Unknown;
521 };
522 TaskType::TravelTo(location)
523 }
524 46 => TaskType::ThrowEpicInToilet,
525 47 => TaskType::BuyOfferFromArenaManager,
526 48 => TaskType::WinFightsAgainst(Class::Warrior),
527 49 => TaskType::WinFightsAgainst(Class::Mage),
528 50 => TaskType::WinFightsAgainst(Class::Scout),
529 51 => TaskType::WinFightsAgainst(Class::Assassin),
530 52 => TaskType::WinFightsAgainst(Class::Druid),
531 53 => TaskType::WinFightsAgainst(Class::Bard),
532 54 => TaskType::WinFightsAgainst(Class::BattleMage),
533 55 => TaskType::WinFightsAgainst(Class::Berserker),
534 56 => TaskType::WinFightsAgainst(Class::DemonHunter),
535 57 => TaskType::WinFightsBareHands,
536 58 => TaskType::WinFightsPlayerPet,
537 59 => TaskType::GetLuckyCoinsFromFlyingTube,
538 60 => TaskType::Upgrade(AttributeType::Luck),
539 61 => TaskType::GainHonorInArena,
540 62 => TaskType::GainHonorInHoF,
541 63 => TaskType::GainHonorFortress,
542 64 => TaskType::GainHonorExpeditions,
543 65 => TaskType::SpendGoldInShop,
544 66 => TaskType::SpendGoldOnUpgrades,
545 67 => TaskType::RequestNewGoods,
546 68 => TaskType::BuyHourGlasses,
547 69 => TaskType::SkipQuest,
548 70 => TaskType::SkipGameOfDiceWait,
549 71 => TaskType::WinFightsInHoF,
550 72 => TaskType::WinFightsBackToBack,
551 73 => TaskType::SpendCardsHellevator,
552 74 => TaskType::GainSilverFromFightsInHoF,
553 75 => TaskType::WinFightsNoChestplate,
554 76 => TaskType::WinFightsNoGear,
555 77 => TaskType::WinFightsNoEpicsLegendaries,
556 78 => TaskType::EarnMoneyCityGuard,
557 79 => TaskType::EarnMoneyFromHoFFights,
558 80 => TaskType::EarnMoneySellingItems,
559 81 => TaskType::CollectGoldFromPit,
560 82 => TaskType::GainXpFromQuests,
561 83 => TaskType::GainXpFromAcademy,
562 84 => TaskType::GainXpFromArenaFights,
563 85 => TaskType::GainXpFromAdventuromatic,
564 86 => TaskType::GainArcaneFromDismantle,
565 87 => TaskType::GainMetalFromDismantle,
566 88 => TaskType::UpgradeItemAttributes,
567 89 => TaskType::AddSocketToItem,
568 90 => TaskType::ClaimSoulsFromExtractor,
569 91 => TaskType::FillMushroomsInAdventuromatic,
570 92 => TaskType::WinFightsAgainst(Class::Necromancer),
571 93 => TaskType::GainLegendaryFromLegendaryDungeon,
572 94 => TaskType::FightMonsterInLegendaryDungeon,
573 95 => TaskType::FightInDungeons,
574 96 => TaskType::UpgradeAnyAttribute,
575 97 => TaskType::GainSilver,
576 98 => TaskType::GainXP,
577 99 => TaskType::GainEpic,
578 100 => TaskType::BuyFromShop(ShopType::Magic),
579 101 => TaskType::BuyFromShop(ShopType::Weapon),
580 102 => TaskType::FindFruitsOnExpedition,
581 103 => TaskType::BrewPotions,
582 104 => TaskType::CollectWood,
583 105 => TaskType::CollectStone,
584 106 => TaskType::CommandFortressBattle,
585 107 => TaskType::FightHellevator,
586 108 => TaskType::BuyHellevatorTreats,
587 109 => TaskType::DefeatHellevatorFloors,
588 110 => TaskType::EnterLegendaryDungeon,
589 111 => TaskType::OpenLegendaryDungeonCrateChests,
590 112 => TaskType::FeedPetType(HabitatType::Shadow),
591 113 => TaskType::FeedPetType(HabitatType::Light),
592 114 => TaskType::FeedPetType(HabitatType::Earth),
593 115 => TaskType::FeedPetType(HabitatType::Fire),
594 116 => TaskType::FeedPetType(HabitatType::Water),
595 117 => {
596 TaskType::DefeatMonstersLightDungeon(LightDungeon::TrainingCamp)
597 }
598 118 => TaskType::ClaimNewCustomerPack,
599 119 => TaskType::JoinOrCreateGuild,
600 120 => TaskType::UpgradeAnyGuildSkill,
601 121 => TaskType::AddFriend,
602 122 => TaskType::DrinkPotion(PotionType::Constitution),
603 123 => TaskType::DrinkPotion(PotionType::Strength),
604 124 => TaskType::DrinkPotion(PotionType::Dexterity),
605 125 => TaskType::DrinkPotion(PotionType::Intelligence),
606 126 => TaskType::DrinkPotion(PotionType::EternalLife),
607 127 => TaskType::LeaseMount,
608 128 => TaskType::FightHigherRankedPlayer,
609 129 => TaskType::CityGuardHours,
610 130 => TaskType::BuyWeaponInWeaponsShop,
611 131 => TaskType::Upgrade(AttributeType::Constitution),
612 132 => TaskType::WinFightsAgainst(Class::Paladin),
613 133 => {
614 TaskType::UpgradeArenaManagerBuilding(IdleBuildingType::Seat)
615 }
616 134 => TaskType::OpenAdventCalendar,
617 135 => TaskType::EarnMoneyFromExpeditions,
618 136 => TaskType::WinFightsAgainst(Class::PlagueDoctor),
619
620 ..=0 | 137.. => TaskType::Unknown,
621 }
622 }
623}
624
625#[derive(Debug, Clone, Copy, PartialEq, Eq)]
627#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
628pub struct Task {
629 pub typ: TaskType,
631 pub current: u64,
633 pub target: u64,
635 pub point_reward: u32,
637}
638
639impl Task {
640 pub(crate) fn parse(data: &[i64]) -> Result<Task, SFError> {
641 let raw_typ = data.cget(0, "task typ")?;
642 let typ = TaskType::parse(raw_typ);
643
644 if typ == TaskType::Unknown {
645 warn!("Unknown task: {data:?} {raw_typ}");
646 }
647 Ok(Task {
648 typ,
649 current: data.csiget(1, "current ti", 0)?,
650 target: data.csiget(2, "target ti", u64::MAX)?,
651 point_reward: data.csiget(3, "reward ti", 0)?,
652 })
653 }
654}
655
656#[derive(Debug, Clone, Default)]
658#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
659pub struct RewardChest {
660 pub opened: bool,
662 pub required_points: u32,
664 pub rewards: Vec<Reward>,
666}
667
668#[derive(Debug, Clone)]
670#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
671pub struct Reward {
672 pub typ: RewardType,
674 pub amount: u64,
676}
677
678#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)]
679#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
680pub enum RewardType {
681 HellevatorPoints,
682 HellevatorCards,
683 Mushrooms,
684 Silver,
685 LuckyCoins,
686 Wood,
687 Stone,
688 Arcane,
689 Metal,
690 Souls,
691 Fruit(HabitatType),
692 LegendaryGem,
693 GoldFidget,
694 SilverFidget,
695 BronzeFidget,
696 Gem,
697 FruitBasket,
698 XP,
699 Egg,
700 QuicksandGlass,
701 Honor,
702 Beer,
703 Frame,
704 Mount(Mount),
705 Unknown,
706}
707
708impl RewardType {
709 #[must_use]
710 pub(crate) fn parse(val: i64) -> RewardType {
711 match val {
712 1 => RewardType::HellevatorPoints,
713 2 => RewardType::HellevatorCards,
714 3 => RewardType::Mushrooms,
715 4 => RewardType::Silver,
716 5 => RewardType::LuckyCoins,
717 6 => RewardType::Wood,
718 7 => RewardType::Stone,
719 8 => RewardType::Arcane,
720 9 => RewardType::Metal,
721 10 => RewardType::Souls,
722 11 => RewardType::Fruit(HabitatType::Shadow),
723 12 => RewardType::Fruit(HabitatType::Light),
724 13 => RewardType::Fruit(HabitatType::Earth),
725 14 => RewardType::Fruit(HabitatType::Fire),
726 15 => RewardType::Fruit(HabitatType::Water),
727 16 => RewardType::LegendaryGem,
728 17 => RewardType::GoldFidget,
729 18 => RewardType::SilverFidget,
730 19 => RewardType::BronzeFidget,
731 20..=22 => RewardType::Gem,
732 23 => RewardType::FruitBasket,
733 24 => RewardType::XP,
734 25 => RewardType::Egg,
735 26 => RewardType::QuicksandGlass,
736 27 => RewardType::Honor,
737 28 => RewardType::Beer,
738 29 => RewardType::Frame,
739 30 => RewardType::Mount(Mount::Cow),
740 31 => RewardType::Mount(Mount::Horse),
741 32 => RewardType::Mount(Mount::Tiger),
742 33 => RewardType::Mount(Mount::Dragon),
743 x => {
744 warn!("Unknown reward type: {x}");
745 RewardType::Unknown
746 }
747 }
748 }
749}
750
751impl Reward {
752 pub(crate) fn parse(data: &[i64]) -> Result<Reward, SFError> {
753 Ok(Reward {
754 typ: RewardType::parse(data.cget(0, "reward typ")?),
755 amount: data.csiget(1, "reward amount", 0)?,
756 })
757 }
758}
759
760impl RewardChest {
761 pub(crate) fn parse(data: &[i64]) -> Result<RewardChest, SFError> {
762 let opened = data.cget(0, "rchest opened")? != 0;
763 let required_points = data.ciget(1, "reward chest required points")?;
764 let reward_count: usize = data.ciget(2, "reward chest count")?;
765 let mut rewards = Vec::new();
766 for pos in 0..reward_count {
767 let data = data.skip(3 + pos * 2, "rchest rewards")?;
768 rewards.push(Reward::parse(data)?);
769 }
770 Ok(RewardChest {
771 opened,
772 required_points,
773 rewards,
774 })
775 }
776}
777
778#[derive(Debug, Clone, Copy, FromPrimitive, PartialEq, Eq, Hash, EnumIter)]
780#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
781#[allow(missing_docs)]
782pub enum Event {
783 ExceptionalXPEvent = 0,
784 GloriousGoldGalore,
785 TidyToiletTime,
786 AssemblyOfAwesomeAnimals,
787 FantasticFortressFestivity,
788 DaysOfDoomedSouls,
789 WitchesDance,
790 SandsOfTimeSpecial,
791 ForgeFrenzyFestival,
792 EpicShoppingSpreeExtravaganza,
793 EpicQuestExtravaganza,
794 EpicGoodLuckExtravaganza,
795 OneBeerTwoBeerFreeBeer,
796 PieceworkParty,
797 LuckyDay,
798 CrazyMushroomHarvest,
799 HolidaySale,
800 ValentinesBlessing,
801 BlackGemRush,
802 RumbleForRiches,
803}
804
805pub(crate) fn parse_rewards(vals: &[i64]) -> [RewardChest; 3] {
806 let mut start = 0;
807 core::array::from_fn(|_| -> Result<RewardChest, SFError> {
808 let vals = vals.skip(start, "multi reward chest")?;
809 let chest = RewardChest::parse(vals)?;
810 let consumed = 3 + chest.rewards.len() * 2;
811 start += consumed;
812 Ok(chest)
813 })
814 .map(|res| match res {
815 Ok(res) => res,
816 Err(err) => {
817 warn!("Bad task rewards: {err}");
818 RewardChest::default()
819 }
820 })
821}