Skip to main content

veloren_server/persistence/
json_models.rs

1use common::comp;
2use common_base::dev_panic;
3use hashbrown::HashMap;
4use serde::{Deserialize, Serialize};
5use std::{num::NonZeroU32, string::ToString};
6use vek::{Vec2, Vec3};
7
8#[derive(Serialize, Deserialize)]
9pub struct HumanoidBody {
10    pub species: u8,
11    pub body_type: u8,
12    pub hair_style: u8,
13    pub beard: u8,
14    pub eyes: u8,
15    pub accessory: u8,
16    pub hair_color: u8,
17    pub skin: u8,
18    pub eye_color: u8,
19    #[serde(default = "default_height_scale")]
20    pub height_scale: u8,
21}
22
23fn default_height_scale() -> u8 { u8::MAX / 2 }
24
25impl From<&comp::humanoid::Body> for HumanoidBody {
26    fn from(body: &comp::humanoid::Body) -> Self {
27        HumanoidBody {
28            species: body.species as u8,
29            body_type: body.body_type as u8,
30            hair_style: body.hair_style,
31            beard: body.beard,
32            eyes: body.eyes,
33            accessory: body.accessory,
34            hair_color: body.hair_color,
35            skin: body.skin,
36            eye_color: body.eye_color,
37            height_scale: body.height_scale,
38        }
39    }
40}
41
42/// A serializable model used to represent a generic Body. Since all variants
43/// of Body except Humanoid (currently) have the same struct layout, a single
44/// struct is used for persistence conversions.
45#[derive(Serialize, Deserialize)]
46pub struct GenericBody {
47    pub species: String,
48    pub body_type: String,
49}
50
51macro_rules! generic_body_from_impl {
52    ($body_type:ty) => {
53        impl From<&$body_type> for GenericBody {
54            fn from(body: &$body_type) -> Self {
55                GenericBody {
56                    species: body.species.to_string(),
57                    body_type: body.body_type.to_string(),
58                }
59            }
60        }
61    };
62}
63
64generic_body_from_impl!(comp::quadruped_low::Body);
65generic_body_from_impl!(comp::quadruped_medium::Body);
66generic_body_from_impl!(comp::quadruped_small::Body);
67generic_body_from_impl!(comp::bird_medium::Body);
68generic_body_from_impl!(comp::crustacean::Body);
69
70#[derive(Serialize, Deserialize)]
71pub struct CharacterPosition {
72    pub waypoint: Option<Vec3<f32>>,
73    pub map_marker: Option<Vec2<i32>>,
74}
75
76pub fn skill_group_to_db_string(skill_group: comp::skillset::SkillGroupKind) -> String {
77    use comp::{item::tool::ToolKind, skillset::SkillGroupKind::*};
78    let skill_group_string = match skill_group {
79        General => "General",
80        Weapon(ToolKind::Sword) => "Weapon Sword",
81        Weapon(ToolKind::Axe) => "Weapon Axe",
82        Weapon(ToolKind::Hammer) => "Weapon Hammer",
83        Weapon(ToolKind::Bow) => "Weapon Bow",
84        Weapon(ToolKind::Staff) => "Weapon Staff",
85        Weapon(ToolKind::Sceptre) => "Weapon Sceptre",
86        Weapon(ToolKind::Pick) => "Weapon Pick",
87        Weapon(ToolKind::Dagger)
88        | Weapon(ToolKind::Shield)
89        | Weapon(ToolKind::Spear)
90        | Weapon(ToolKind::Blowgun)
91        | Weapon(ToolKind::Debug)
92        | Weapon(ToolKind::Farming)
93        | Weapon(ToolKind::Instrument)
94        | Weapon(ToolKind::Throwable)
95        | Weapon(ToolKind::Empty)
96        | Weapon(ToolKind::Natural)
97        | Weapon(ToolKind::Shovel) => panic!(
98            "Tried to add unsupported skill group to database: {:?}",
99            skill_group
100        ),
101    };
102    skill_group_string.to_string()
103}
104
105pub fn db_string_to_skill_group(skill_group_string: &str) -> comp::skillset::SkillGroupKind {
106    use comp::{item::tool::ToolKind, skillset::SkillGroupKind::*};
107    match skill_group_string {
108        "General" => General,
109        "Weapon Sword" => Weapon(ToolKind::Sword),
110        "Weapon Axe" => Weapon(ToolKind::Axe),
111        "Weapon Hammer" => Weapon(ToolKind::Hammer),
112        "Weapon Bow" => Weapon(ToolKind::Bow),
113        "Weapon Staff" => Weapon(ToolKind::Staff),
114        "Weapon Sceptre" => Weapon(ToolKind::Sceptre),
115        "Weapon Pick" => Weapon(ToolKind::Pick),
116
117        _ => panic!(
118            "Tried to convert an unsupported string from the database: {}",
119            skill_group_string
120        ),
121    }
122}
123
124#[derive(Serialize, Deserialize)]
125pub struct DatabaseAbilitySet {
126    mainhand: String,
127    offhand: String,
128    abilities: Vec<String>,
129}
130
131fn aux_ability_to_string(ability: comp::ability::AuxiliaryAbility) -> String {
132    use common::comp::ability::AuxiliaryAbility;
133    match ability {
134        AuxiliaryAbility::MainWeapon(index) => format!("Main Weapon:index:{}", index),
135        AuxiliaryAbility::OffWeapon(index) => format!("Off Weapon:index:{}", index),
136        AuxiliaryAbility::Glider(index) => format!("Glider:index:{}", index),
137        AuxiliaryAbility::Empty => String::from("Empty"),
138    }
139}
140
141fn aux_ability_from_string(ability: &str) -> comp::ability::AuxiliaryAbility {
142    use common::comp::ability::AuxiliaryAbility;
143    let mut parts = ability.split(":index:");
144    match parts.next() {
145        Some("Main Weapon") => match parts
146            .next()
147            .map(|index| index.parse::<usize>().map_err(|_| index))
148        {
149            Some(Ok(index)) => AuxiliaryAbility::MainWeapon(index),
150            Some(Err(error)) => {
151                dev_panic!(format!(
152                    "Conversion from database to ability set failed. Unable to parse index for \
153                     mainhand abilities: {}",
154                    error
155                ));
156                AuxiliaryAbility::Empty
157            },
158            None => {
159                dev_panic!(String::from(
160                    "Conversion from database to ability set failed. Unable to find an index for \
161                     mainhand abilities"
162                ));
163                AuxiliaryAbility::Empty
164            },
165        },
166        Some("Off Weapon") => match parts
167            .next()
168            .map(|index| index.parse::<usize>().map_err(|_| index))
169        {
170            Some(Ok(index)) => AuxiliaryAbility::OffWeapon(index),
171            Some(Err(error)) => {
172                dev_panic!(format!(
173                    "Conversion from database to ability set failed. Unable to parse index for \
174                     offhand abilities: {}",
175                    error
176                ));
177                AuxiliaryAbility::Empty
178            },
179            None => {
180                dev_panic!(String::from(
181                    "Conversion from database to ability set failed. Unable to find an index for \
182                     offhand abilities"
183                ));
184                AuxiliaryAbility::Empty
185            },
186        },
187        Some("Glider") => match parts
188            .next()
189            .map(|index| index.parse::<usize>().map_err(|_| index))
190        {
191            Some(Ok(index)) => AuxiliaryAbility::Glider(index),
192            Some(Err(error)) => {
193                dev_panic!(format!(
194                    "Conversion from database to ability set failed. Unable to parse index for \
195                     offhand abilities: {}",
196                    error
197                ));
198                AuxiliaryAbility::Empty
199            },
200            None => {
201                dev_panic!(String::from(
202                    "Conversion from database to ability set failed. Unable to find an index for \
203                     offhand abilities"
204                ));
205                AuxiliaryAbility::Empty
206            },
207        },
208        Some("Empty") => AuxiliaryAbility::Empty,
209        unknown => {
210            dev_panic!(format!(
211                "Conversion from database to ability set failed. Unknown auxiliary ability: {:#?}",
212                unknown
213            ));
214            AuxiliaryAbility::Empty
215        },
216    }
217}
218
219fn tool_kind_to_string(tool: Option<comp::item::tool::ToolKind>) -> String {
220    use common::comp::item::tool::ToolKind::*;
221    String::from(match tool {
222        Some(Sword) => "Sword",
223        Some(Axe) => "Axe",
224        Some(Hammer) => "Hammer",
225        Some(Bow) => "Bow",
226        Some(Staff) => "Staff",
227        Some(Sceptre) => "Sceptre",
228        Some(Dagger) => "Dagger",
229        Some(Shield) => "Shield",
230        Some(Spear) => "Spear",
231        Some(Blowgun) => "Blowgun",
232        Some(Pick) => "Pick",
233        Some(Shovel) => "Shovel",
234
235        // Toolkinds that are not anticipated to have many active abilities (if any at all)
236        Some(Farming) => "Farming",
237        Some(Debug) => "Debug",
238        Some(Natural) => "Natural",
239        Some(Instrument) => "Instrument",
240        Some(Throwable) => "Throwable",
241        Some(Empty) => "Empty",
242        None => "None",
243    })
244}
245
246fn tool_kind_from_string(tool: String) -> Option<comp::item::tool::ToolKind> {
247    use common::comp::item::tool::ToolKind::*;
248    match tool.as_str() {
249        "Sword" => Some(Sword),
250        "Axe" => Some(Axe),
251        "Hammer" => Some(Hammer),
252        "Bow" => Some(Bow),
253        "Staff" => Some(Staff),
254        "Sceptre" => Some(Sceptre),
255        "Dagger" => Some(Dagger),
256        "Shield" => Some(Shield),
257        "Spear" => Some(Spear),
258        "Blowgun" => Some(Blowgun),
259        "Pick" => Some(Pick),
260        "Farming" => Some(Farming),
261        "Debug" => Some(Debug),
262        "Natural" => Some(Natural),
263        "Empty" => Some(Empty),
264        "None" => None,
265        unknown => {
266            dev_panic!(format!(
267                "Conversion from database to ability set failed. Unknown toolkind: {:#?}",
268                unknown
269            ));
270            None
271        },
272    }
273}
274
275pub fn active_abilities_to_db_model(
276    active_abilities: &comp::ability::ActiveAbilities,
277) -> Vec<DatabaseAbilitySet> {
278    active_abilities
279        .auxiliary_sets
280        .iter()
281        .map(|((mainhand, offhand), abilities)| DatabaseAbilitySet {
282            mainhand: tool_kind_to_string(*mainhand),
283            offhand: tool_kind_to_string(*offhand),
284            abilities: abilities
285                .iter()
286                .map(|ability| aux_ability_to_string(*ability))
287                .collect(),
288        })
289        .collect::<Vec<_>>()
290}
291
292pub fn active_abilities_from_db_model(
293    ability_sets: Vec<DatabaseAbilitySet>,
294) -> comp::ability::ActiveAbilities {
295    let ability_sets = ability_sets
296        .into_iter()
297        .map(
298            |DatabaseAbilitySet {
299                 mainhand,
300                 offhand,
301                 abilities,
302             }| {
303                let mut auxiliary_abilities =
304                    vec![comp::ability::AuxiliaryAbility::Empty; comp::ability::BASE_ABILITY_LIMIT];
305                for (empty, ability) in auxiliary_abilities.iter_mut().zip(abilities) {
306                    *empty = aux_ability_from_string(&ability);
307                }
308                (
309                    (
310                        tool_kind_from_string(mainhand),
311                        tool_kind_from_string(offhand),
312                    ),
313                    auxiliary_abilities,
314                )
315            },
316        )
317        .collect::<HashMap<_, _>>();
318    comp::ability::ActiveAbilities::from_auxiliary(
319        ability_sets,
320        Some(comp::ability::BASE_ABILITY_LIMIT),
321    )
322}
323
324/// Struct containing item properties in the format that they get persisted to
325/// the database. Adding new fields is generally safe as long as they are
326/// optional. Renaming or removing old fields will require a migration.
327#[derive(Serialize, Deserialize)]
328pub struct DatabaseItemProperties {
329    #[serde(skip_serializing_if = "Option::is_none")]
330    durability: Option<NonZeroU32>,
331}
332
333pub fn item_properties_to_db_model(item: &comp::Item) -> DatabaseItemProperties {
334    DatabaseItemProperties {
335        durability: item.persistence_durability(),
336    }
337}
338
339pub fn apply_db_item_properties(item: &mut comp::Item, properties: &DatabaseItemProperties) {
340    let DatabaseItemProperties { durability } = properties;
341    item.persistence_set_durability(*durability);
342}
343
344#[cfg(test)]
345pub mod tests {
346    #[test]
347    fn test_default_item_properties() {
348        use super::DatabaseItemProperties;
349        const DEFAULT_ITEM_PROPERTIES: &str = "{}";
350        let _ = serde_json::de::from_str::<DatabaseItemProperties>(DEFAULT_ITEM_PROPERTIES).expect(
351            "Default value should always load to ensure that changes to item properties is always \
352             forward compatible with migration V50.",
353        );
354    }
355}