sf_api/gamestate/
character.rs

1use std::fmt::Debug;
2
3use chrono::{DateTime, Local};
4use enum_map::EnumMap;
5use num_derive::FromPrimitive;
6use num_traits::FromPrimitive;
7
8use super::{Mirror, NormalCost, RelationEntry, SFError, ScrapBook};
9use crate::{PlayerId, command::*, gamestate::items::*, misc::*};
10
11#[derive(Debug, Clone, Default)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13/// Everything, that can be considered part of the character and not the rest
14/// of the world
15pub struct Character {
16    /// This is the unique identifier of this character. Can be used to compare
17    /// against places, that also have `player_ids` to make sure a Hall of
18    /// Fame entry or similar is not the player
19    pub player_id: PlayerId,
20    /// The name of this character
21    pub name: String,
22    /// The current level of this character
23    pub level: u16,
24    /// The amount of silver a player has. 100 silver = 1 gold
25    pub silver: u64,
26    /// The amount of moshrooms a player has
27    pub mushrooms: u32,
28
29    /// The class of this character
30    pub class: Class,
31
32    /// The race of this character. Has some effects on attributes, which is
33    /// why this is not in portrait
34    pub race: Race,
35    /// Everything that determines the players looks except for the race
36    pub portrait: Portrait,
37    /// The description of this character
38    pub description: String,
39
40    /// The amount of experience already earned in the current level
41    pub experience: u64,
42    /// The amount of experience required to level up.
43    /// `next_level_xp - experience` is the amount of xp missing to level up
44    pub next_level_xp: u64,
45    /// The amount of honor earned through the arena
46    pub honor: u32,
47    /// The rank in the hall of fame
48    pub rank: u32,
49
50    /// All the items this character has stored. These are all the slots right
51    /// next to the portrait in the web ui
52    pub inventory: Inventory,
53    /// All items the character has currently equipped (on the body)
54    pub equipment: Equipment,
55
56    /// If the character has a manequin, this will contain all the equipment
57    /// stored in it
58    pub manequin: Option<Equipment>,
59    /// The potions currently active
60    pub active_potions: [Option<Potion>; 3],
61
62    /// The total armor of our character. Basically all equipped armor combined
63    pub armor: u64,
64
65    /// The min amount of damage the weapon claims it can do without any bonus
66    pub min_damage: u32,
67    /// The max amount of damage the weapon claims it can do without any bonus
68    pub max_damage: u32,
69
70    /// The base attributes without any equipment, or other boosts
71    pub attribute_basis: EnumMap<AttributeType, u32>,
72    /// All bonus attributes from equipment/pets/potions
73    pub attribute_additions: EnumMap<AttributeType, u32>,
74    /// The amount of times an attribute has been bought already.
75    /// Important to calculate the price of the next attribute to buy
76    pub attribute_times_bought: EnumMap<AttributeType, u32>,
77
78    /// The mount this character has rented
79    pub mount: Option<Mount>,
80    /// The point at which the mount will end. Note that this might be None,
81    /// whilst mount is Some
82    pub mount_end: Option<DateTime<Local>>,
83    /// The silver you get for buying a dragon
84    pub mount_dragon_refund: u64,
85
86    /// Whether this character has the mirror completed, or is still collecting
87    /// pieces
88    pub mirror: Mirror,
89    /// If the scrapbook has been unlocked, it can be found here
90    pub scrapbook: Option<ScrapBook>,
91
92    /// A list of other characters, that the set some sort of special relation
93    /// to. Either good, or bad
94    pub relations: Vec<RelationEntry>,
95}
96
97/// All the exclusively cosmetic info necessary to build a player image, that is
98/// otherwise useless. As these values might change their based on each other,
99/// some of them are not fully parsed (to a more descriptive enum)
100#[derive(Debug, Default, Clone, PartialEq, Eq)]
101#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
102#[allow(missing_docs)]
103pub struct Portrait {
104    /// The gender (m/w)
105    pub gender: Gender,
106    pub hair_color: u8,
107    pub hair: u8,
108    pub mouth: u8,
109    pub brows: u8,
110    pub eyes: u8,
111    pub beards: u8,
112    pub nose: u8,
113    pub ears: u8,
114    pub extra: u8,
115    pub horns: u8,
116    /// Influencers get a special portrait. Otherwise this should be 0
117    pub special_portrait: i64,
118}
119
120impl Portrait {
121    pub(crate) fn parse(data: &[i64]) -> Result<Portrait, SFError> {
122        Ok(Self {
123            mouth: data.csiget(0, "mouth", 1)?,
124            hair_color: data.csimget(1, "hair color", 100, |a| a / 100)?,
125            hair: data.csimget(1, "hair", 1, |a| a % 100)?,
126            brows: data.csimget(2, "brows", 1, |a| a % 100)?,
127            eyes: data.csiget(3, "eyes", 1)?,
128            beards: data.csimget(4, "beards", 1, |a| a % 100)?,
129            nose: data.csiget(5, "nose", 1)?,
130            ears: data.csiget(6, "ears", 1)?,
131            extra: data.csiget(7, "extra", 1)?,
132            horns: data.csimget(8, "horns", 1, |a| a % 100)?,
133            special_portrait: data.cget(9, "special portrait")?,
134            gender: Gender::from_i64(data.csimget(11, "gender", 1, |a| a % 2)?)
135                .unwrap_or_default(),
136        })
137    }
138}
139
140#[derive(Debug, Clone, Default, Copy, PartialEq, Eq, FromPrimitive, Hash)]
141#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
142#[allow(missing_docs)]
143pub enum Gender {
144    #[default]
145    Female = 0,
146    Male,
147}
148
149#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, FromPrimitive, Hash)]
150#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
151#[allow(missing_docs)]
152pub enum Class {
153    #[default]
154    Warrior = 0,
155    Mage,
156    Scout,
157    Assassin,
158    BattleMage,
159    Berserker,
160    DemonHunter,
161    Druid,
162    Bard,
163    Necromancer,
164    Paladin,
165    PlagueDoctor,
166}
167
168#[allow(clippy::enum_glob_use)]
169impl Class {
170    #[must_use]
171    #[allow(clippy::enum_glob_use)]
172    pub fn main_attribute(&self) -> AttributeType {
173        use Class::*;
174        match self {
175            Paladin | BattleMage | Berserker | Warrior => {
176                AttributeType::Strength
177            }
178            Assassin | DemonHunter | Scout | PlagueDoctor => {
179                AttributeType::Dexterity
180            }
181            Druid | Bard | Necromancer | Mage => AttributeType::Intelligence,
182        }
183    }
184
185    #[must_use]
186    pub(crate) fn weapon_multiplier(self) -> f64 {
187        use Class::*;
188        match self {
189            PlagueDoctor | Paladin | Warrior | Assassin | BattleMage
190            | Berserker => 2.0,
191            Scout => 2.5,
192            Mage | DemonHunter | Druid | Bard | Necromancer => 4.5,
193        }
194    }
195
196    #[must_use]
197    pub(crate) fn life_multiplier(self, is_companion: bool) -> f64 {
198        use Class::*;
199
200        match self {
201            Warrior if is_companion => 6.1,
202            Paladin => 6.0,
203            Warrior | BattleMage | Druid => 5.0,
204            PlagueDoctor | Scout | Assassin | Berserker | DemonHunter
205            | Necromancer => 4.0,
206            Mage | Bard => 2.0,
207        }
208    }
209
210    #[must_use]
211    pub(crate) fn armor_factor(self) -> f64 {
212        use Class::*;
213        match self {
214            Berserker => 0.5,
215            Paladin | Warrior | Mage | Scout | DemonHunter | Druid
216            | Assassin => 1.0,
217            Bard | Necromancer => 2.0,
218            PlagueDoctor => 2.5,
219            BattleMage => 5.0,
220        }
221    }
222
223    #[must_use]
224    pub(crate) fn max_damage_reduction(self) -> f64 {
225        use Class::*;
226        match self {
227            Bard | BattleMage | DemonHunter | Warrior => 0.5,
228            Paladin => 0.45,
229            PlagueDoctor | Druid | Assassin | Berserker | Scout => 0.25,
230            Necromancer => 0.2,
231            Mage => 0.1,
232        }
233    }
234
235    #[must_use]
236    pub fn can_wear_shield(self) -> bool {
237        matches!(self, Self::Paladin | Self::Warrior)
238    }
239
240    #[must_use]
241    pub(crate) fn damage_factor(self, against: Class) -> f64 {
242        use Class::*;
243        match self {
244            Druid if against == Class::DemonHunter => 0.33 + 0.15,
245            Druid if against == Class::Mage => 0.33 + 0.33,
246            Druid => 0.33,
247            Necromancer if against == Class::DemonHunter => 0.56 + 0.1,
248            Necromancer => 0.56,
249            Assassin => 0.625,
250            Paladin => 0.83,
251            Warrior | Mage | Scout | BattleMage | DemonHunter => 1.0,
252            Bard => 1.125,
253            Berserker | PlagueDoctor => 1.25,
254        }
255    }
256}
257
258#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, Hash)]
259#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
260#[allow(missing_docs)]
261pub enum DruidMask {
262    Cat = 4,
263    Bear = 5,
264}
265
266#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, Hash)]
267#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
268#[allow(missing_docs)]
269pub enum BardInstrument {
270    Harp = 1,
271    Lute,
272    Flute,
273}
274
275#[derive(Debug, PartialEq, Eq, Default, Clone, Copy, FromPrimitive, Hash)]
276#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
277#[allow(missing_docs)]
278pub enum Race {
279    #[default]
280    Human = 1,
281    Elf,
282    Dwarf,
283    Gnome,
284    Orc,
285    DarkElf,
286    Goblin,
287    Demon,
288}
289
290impl Race {
291    #[must_use]
292    pub fn stat_modifiers(self) -> EnumMap<AttributeType, i32> {
293        let raw = match self {
294            Race::Human => [0, 0, 0, 0, 0],
295            Race::Elf => [-1, 2, 0, -1, 0],
296            Race::Dwarf => [0, -2, -1, 2, 1],
297            Race::Gnome => [-2, 3, -1, -1, 1],
298            Race::Orc => [1, 0, -1, 0, 0],
299            Race::DarkElf => [-2, 2, 1, -1, 0],
300            Race::Goblin => [-2, 2, 0, -1, 1],
301            Race::Demon => [3, -1, 0, 1, -3],
302        };
303        EnumMap::from_array(raw)
304    }
305}
306
307#[derive(Debug, Copy, Clone, FromPrimitive, PartialEq, Eq, Hash)]
308#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
309#[allow(missing_docs)]
310pub enum Mount {
311    Cow = 1,
312    Horse = 2,
313    Tiger = 3,
314    Dragon = 4,
315}
316
317impl Mount {
318    /// Returns the cost of this mount
319    #[must_use]
320    pub fn cost(&self) -> NormalCost {
321        match self {
322            Mount::Cow => NormalCost {
323                silver: 100,
324                mushrooms: 0,
325            },
326            Mount::Horse => NormalCost {
327                silver: 500,
328                mushrooms: 0,
329            },
330            Mount::Tiger => NormalCost {
331                silver: 1000,
332                mushrooms: 1,
333            },
334            Mount::Dragon => NormalCost {
335                silver: 0,
336                mushrooms: 25,
337            },
338        }
339    }
340}