Skip to main content

sf_api/gamestate/
underworld.rs

1#![allow(clippy::module_name_repetitions)]
2use std::time::Duration;
3
4use chrono::{DateTime, Local};
5use enum_map::{Enum, EnumMap};
6use num_derive::FromPrimitive;
7use strum::{EnumIter, IntoEnumIterator};
8
9use super::{ArrSkip, CCGet, CFPGet, CSTGet, EnumMapGet, SFError, ServerTime};
10
11/// The information about a characters underworld
12#[derive(Debug, Default, Clone)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14pub struct Underworld {
15    /// All the buildings, that the underworld can have. If they are not yet
16    /// build, they are level 0
17    pub buildings: EnumMap<UnderworldBuildingType, UnderworldBuilding>,
18    /// Information about all the buildable units in the underworld
19    pub units: EnumMap<UnderworldUnitType, UnderworldUnit>,
20    /// All information about the production of resources in the underworld
21    pub production: EnumMap<UnderworldResourceType, UnderworldProduction>,
22    /// The `last_collectable` value in `UnderworldProduction` is always out of
23    /// date. Refer to the `Fortress.last_collectable_updated` for more
24    /// information
25    pub last_collectable_update: Option<DateTime<Local>>,
26
27    // Both XP&silver are not really resources, so I just have this here,
28    // instead of in a resource info struct like in fortress
29    /// The current souls in the underworld
30    pub souls_current: u64,
31    /// The maximum amount of souls, that you can store in the underworld.  If
32    /// `current == limit`, you will not be able to collect resources from
33    /// the building
34    pub souls_limit: u64,
35
36    /// The building, that is currently being upgraded
37    pub upgrade_building: Option<UnderworldBuildingType>,
38    /// The time at which the upgrade is finished
39    pub upgrade_finish: Option<DateTime<Local>>,
40    /// The time the building upgrade began
41    pub upgrade_begin: Option<DateTime<Local>>,
42
43    /// The level of characters you need to lure to get the full reward
44    pub lure_level: u16,
45    /// The amount of players, that have been lured into the underworld today
46    pub lured_today: u16,
47    /// The suggested enemy to attack in the underworld. Must be populated with
48    /// the `UpdateLureSuggestion` command.
49    pub lure_suggestion: Option<LureSuggestion>,
50}
51
52/// The ident by which we can fetch more information about the lure enemy using
53/// the `ViewLureSuggestion` command.
54/// (Don't ask me why this process is so convoluted)
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
57pub struct LureSuggestion(pub(crate) u32);
58
59/// The price an upgrade, or building something in the underworld costs. These
60/// are always for one upgrade/build, which is important for unit builds
61#[derive(Debug, Default, Clone, Copy)]
62#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
63pub struct UnderworldCost {
64    /// The time it takes to complete one build/upgrade
65    pub time: Duration,
66    /// The price in silver this costs
67    pub silver: u64,
68    /// The price in sould this costs
69    pub souls: u64,
70}
71
72impl UnderworldCost {
73    pub(crate) fn parse(data: &[i64]) -> Result<UnderworldCost, SFError> {
74        Ok(UnderworldCost {
75            time: Duration::from_secs(data.csiget(0, "u time cost", 0)?),
76            // Guessing here
77            silver: data.csiget(1, "u silver cost", u64::MAX)?,
78            souls: data.csiget(2, "u sould cost", u64::MAX)?,
79        })
80    }
81}
82
83impl Underworld {
84    pub(crate) fn update_building_prices(
85        &mut self,
86        data: &[i64],
87    ) -> Result<(), SFError> {
88        for (pos, typ) in UnderworldBuildingType::iter().enumerate() {
89            self.buildings.get_mut(typ).upgrade_cost = UnderworldCost::parse(
90                data.skip(pos * 3, "underworld building prices")?,
91            )?;
92        }
93        Ok(())
94    }
95
96    pub(crate) fn update_underworld_unit_prices(
97        &mut self,
98        data: &[i64],
99    ) -> Result<(), SFError> {
100        for (pos, typ) in UnderworldUnitType::iter().enumerate() {
101            self.units.get_mut(typ).upgrade_next_lvl =
102                data.csiget(pos * 3, "uunit next lvl", 0)?;
103            self.units.get_mut(typ).upgrade_cost.silver =
104                data.csiget(1 + pos * 3, "uunit upgrade gold", 0)?;
105            self.units.get_mut(typ).upgrade_cost.souls =
106                data.csiget(2 + pos * 3, "uunit upgrade gold", 0)?;
107        }
108        Ok(())
109    }
110
111    pub(crate) fn update(
112        &mut self,
113        data: &[i64],
114        server_time: ServerTime,
115    ) -> Result<(), SFError> {
116        for (pos, typ) in UnderworldBuildingType::iter().enumerate() {
117            self.buildings.get_mut(typ).level =
118                data.csiget(448 + pos, "building level", 0)?;
119        }
120
121        for (i, typ) in UnderworldUnitType::iter().enumerate() {
122            let start = 146 + i * 148;
123            self.units.get_mut(typ).upgraded_amount =
124                data.csiget(start, "uunit upgrade level", 0)?;
125            self.units.get_mut(typ).count =
126                data.csiget(start + 1, "uunit count", 0)?;
127            self.units.get_mut(typ).total_attributes =
128                data.csiget(start + 2, "uunit atr bonus", 0)?;
129            self.units.get_mut(typ).level =
130                data.csiget(start + 3, "uunit level", 0)?;
131        }
132
133        #[allow(clippy::enum_glob_use)]
134        {
135            use UnderworldResourceType::*;
136            self.production.get_mut(Souls).last_collectable =
137                data.csiget(459, "uu souls in building", 0)?;
138            self.production.get_mut(Souls).limit =
139                data.csiget(460, "uu sould max in building", 0)?;
140            self.souls_limit = data.csiget(461, "uu souls max saved", 0)?;
141            self.production.get_mut(Souls).per_hour =
142                data.csiget(463, "uu souls per hour", 0)?;
143
144            self.production.get_mut(Silver).last_collectable =
145                data.csiget(464, "uu gold in building", 0)?;
146            self.production.get_mut(Silver).limit =
147                data.csiget(465, "uu max gold in building", 0)?;
148            self.production.get_mut(Silver).per_hour =
149                data.csiget(466, "uu gold ", 0)?;
150
151            self.production.get_mut(ThirstForAdventure).last_collectable =
152                data.csiget(473, "uu alu in building", 0)?;
153            self.production.get_mut(ThirstForAdventure).limit =
154                data.csiget(474, "uu max stored alu", 0)?;
155            self.production.get_mut(ThirstForAdventure).per_hour =
156                data.csiget(475, "uu alu per day", 0)?;
157        }
158
159        self.last_collectable_update =
160            data.cstget(467, "uw resource time", server_time)?;
161        self.upgrade_building =
162            data.cfpget(468, "u building upgrade", |x| x - 1)?;
163        self.upgrade_finish = data.cstget(469, "u expand end", server_time)?;
164        self.upgrade_begin =
165            data.cstget(470, "u upgrade begin", server_time)?;
166        self.lure_level = data.csiget(471, "uu lure lvl", 0)?;
167        self.lured_today = data.csiget(472, "u battles today", 0)?;
168        Ok(())
169    }
170}
171
172/// The type of a producible resource in the underworld
173#[derive(Debug, Clone, Copy, strum::EnumCount, Enum, PartialEq)]
174#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
175#[allow(missing_docs)]
176pub enum UnderworldResourceType {
177    Souls = 0,
178    Silver = 1,
179    #[doc(alias = "ALU")]
180    ThirstForAdventure = 2,
181}
182
183/// Information about the producion of a resource in the fortress.  Note that
184/// experience will not have some of these fields
185#[derive(Debug, Default, Clone)]
186#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
187pub struct UnderworldProduction {
188    /// The amount the production building has already produced, that you can
189    /// collect. Note that this value will be out of date by some amount of
190    /// time. If you need the exact current amount collectable, look at
191    /// `last_collectable_update`
192    pub last_collectable: u64,
193    /// The maximum amount of this resource, that this building can store. If
194    /// `building_collectable == building_limit` the production stops
195    pub limit: u64,
196    /// The amount of this resource the corresponding production building
197    /// produces per hour. The adventuromatics amount will be per day here
198    pub per_hour: u64,
199}
200
201/// The type of building in the underworld
202#[derive(
203    Debug,
204    Clone,
205    Copy,
206    FromPrimitive,
207    strum::EnumCount,
208    Enum,
209    EnumIter,
210    PartialEq,
211)]
212#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
213#[allow(missing_docs)]
214pub enum UnderworldBuildingType {
215    HeartOfDarkness = 0,
216    Gate = 1,
217    GoldPit = 2,
218    SoulExtractor = 3,
219    GoblinPit = 4,
220    TortureChamber = 5,
221    GladiatorTrainer = 6,
222    TrollBlock = 7,
223    Adventuromatic = 8,
224    Keeper = 9,
225}
226
227/// The type of unit in the underworld
228#[derive(Debug, Clone, Copy, strum::EnumCount, Enum, EnumIter, PartialEq)]
229#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
230#[allow(missing_docs)]
231pub enum UnderworldUnitType {
232    Goblin = 0,
233    Troll = 1,
234    Keeper = 2,
235}
236
237/// Information about the current building state of a building
238#[derive(Debug, Default, Clone, Copy)]
239#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
240pub struct UnderworldBuilding {
241    /// The current level of this building. If this is 0, it has not yet been
242    /// built
243    pub level: u8,
244    /// The amount of resources it costs to upgrade to the next level
245    pub upgrade_cost: UnderworldCost,
246}
247
248/// Information about a single type of unit
249#[derive(Debug, Default, Clone)]
250#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
251pub struct UnderworldUnit {
252    /// The current (battle) level this unit has
253    pub level: u16,
254    /// The amount of units the character has of this type
255    pub count: u16,
256    /// The total amount of attributes this unit has
257    pub total_attributes: u32,
258
259    /// The amount of times this unit has been upgraded already
260    pub upgraded_amount: u16,
261
262    /// The price to pay for this unit to be upgraded once
263    pub upgrade_cost: UnderworldCost,
264    /// The level this unit will have, when the upgrade has been bought
265    pub upgrade_next_lvl: u16,
266}