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