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