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::Throwable)
90        | Weapon(ToolKind::Empty)
91        | Weapon(ToolKind::Natural)
92        | Weapon(ToolKind::Shovel) => panic!(
93            "Tried to add unsupported skill group to database: {:?}",
94            skill_group
95        ),
96    };
97    skill_group_string.to_string()
98}
99
100pub fn db_string_to_skill_group(skill_group_string: &str) -> comp::skillset::SkillGroupKind {
101    use comp::{item::tool::ToolKind, skillset::SkillGroupKind::*};
102    match skill_group_string {
103        "General" => General,
104        "Weapon Sword" => Weapon(ToolKind::Sword),
105        "Weapon Axe" => Weapon(ToolKind::Axe),
106        "Weapon Hammer" => Weapon(ToolKind::Hammer),
107        "Weapon Bow" => Weapon(ToolKind::Bow),
108        "Weapon Staff" => Weapon(ToolKind::Staff),
109        "Weapon Sceptre" => Weapon(ToolKind::Sceptre),
110        "Weapon Pick" => Weapon(ToolKind::Pick),
111
112        _ => panic!(
113            "Tried to convert an unsupported string from the database: {}",
114            skill_group_string
115        ),
116    }
117}
118
119#[derive(Serialize, Deserialize)]
120pub struct DatabaseAbilitySet {
121    mainhand: String,
122    offhand: String,
123    abilities: Vec<String>,
124}
125
126fn aux_ability_to_string(ability: comp::ability::AuxiliaryAbility) -> String {
127    use common::comp::ability::AuxiliaryAbility;
128    match ability {
129        AuxiliaryAbility::MainWeapon(index) => format!("Main Weapon:index:{}", index),
130        AuxiliaryAbility::OffWeapon(index) => format!("Off Weapon:index:{}", index),
131        AuxiliaryAbility::Glider(index) => format!("Glider:index:{}", index),
132        AuxiliaryAbility::Empty => String::from("Empty"),
133    }
134}
135
136fn aux_ability_from_string(ability: &str) -> comp::ability::AuxiliaryAbility {
137    use common::comp::ability::AuxiliaryAbility;
138    let mut parts = ability.split(":index:");
139    match parts.next() {
140        Some("Main Weapon") => match parts
141            .next()
142            .map(|index| index.parse::<usize>().map_err(|_| index))
143        {
144            Some(Ok(index)) => AuxiliaryAbility::MainWeapon(index),
145            Some(Err(error)) => {
146                dev_panic!(format!(
147                    "Conversion from database to ability set failed. Unable to parse index for \
148                     mainhand abilities: {}",
149                    error
150                ));
151                AuxiliaryAbility::Empty
152            },
153            None => {
154                dev_panic!(String::from(
155                    "Conversion from database to ability set failed. Unable to find an index for \
156                     mainhand abilities"
157                ));
158                AuxiliaryAbility::Empty
159            },
160        },
161        Some("Off Weapon") => match parts
162            .next()
163            .map(|index| index.parse::<usize>().map_err(|_| index))
164        {
165            Some(Ok(index)) => AuxiliaryAbility::OffWeapon(index),
166            Some(Err(error)) => {
167                dev_panic!(format!(
168                    "Conversion from database to ability set failed. Unable to parse index for \
169                     offhand abilities: {}",
170                    error
171                ));
172                AuxiliaryAbility::Empty
173            },
174            None => {
175                dev_panic!(String::from(
176                    "Conversion from database to ability set failed. Unable to find an index for \
177                     offhand abilities"
178                ));
179                AuxiliaryAbility::Empty
180            },
181        },
182        Some("Glider") => match parts
183            .next()
184            .map(|index| index.parse::<usize>().map_err(|_| index))
185        {
186            Some(Ok(index)) => AuxiliaryAbility::Glider(index),
187            Some(Err(error)) => {
188                dev_panic!(format!(
189                    "Conversion from database to ability set failed. Unable to parse index for \
190                     offhand abilities: {}",
191                    error
192                ));
193                AuxiliaryAbility::Empty
194            },
195            None => {
196                dev_panic!(String::from(
197                    "Conversion from database to ability set failed. Unable to find an index for \
198                     offhand abilities"
199                ));
200                AuxiliaryAbility::Empty
201            },
202        },
203        Some("Empty") => AuxiliaryAbility::Empty,
204        unknown => {
205            dev_panic!(format!(
206                "Conversion from database to ability set failed. Unknown auxiliary ability: {:#?}",
207                unknown
208            ));
209            AuxiliaryAbility::Empty
210        },
211    }
212}
213
214fn tool_kind_to_string(tool: Option<comp::item::tool::ToolKind>) -> String {
215    use common::comp::item::tool::ToolKind::*;
216    String::from(match tool {
217        Some(Sword) => "Sword",
218        Some(Axe) => "Axe",
219        Some(Hammer) => "Hammer",
220        Some(Bow) => "Bow",
221        Some(Staff) => "Staff",
222        Some(Sceptre) => "Sceptre",
223        Some(Dagger) => "Dagger",
224        Some(Shield) => "Shield",
225        Some(Spear) => "Spear",
226        Some(Blowgun) => "Blowgun",
227        Some(Pick) => "Pick",
228        Some(Shovel) => "Shovel",
229
230        // Toolkinds that are not anticipated to have many active abilities (if any at all)
231        Some(Farming) => "Farming",
232        Some(Debug) => "Debug",
233        Some(Natural) => "Natural",
234        Some(Instrument) => "Instrument",
235        Some(Throwable) => "Throwable",
236        Some(Empty) => "Empty",
237        None => "None",
238    })
239}
240
241fn tool_kind_from_string(tool: String) -> Option<comp::item::tool::ToolKind> {
242    use common::comp::item::tool::ToolKind::*;
243    match tool.as_str() {
244        "Sword" => Some(Sword),
245        "Axe" => Some(Axe),
246        "Hammer" => Some(Hammer),
247        "Bow" => Some(Bow),
248        "Staff" => Some(Staff),
249        "Sceptre" => Some(Sceptre),
250        "Dagger" => Some(Dagger),
251        "Shield" => Some(Shield),
252        "Spear" => Some(Spear),
253        "Blowgun" => Some(Blowgun),
254        "Pick" => Some(Pick),
255        "Farming" => Some(Farming),
256        "Debug" => Some(Debug),
257        "Natural" => Some(Natural),
258        "Empty" => Some(Empty),
259        "None" => None,
260        unknown => {
261            dev_panic!(format!(
262                "Conversion from database to ability set failed. Unknown toolkind: {:#?}",
263                unknown
264            ));
265            None
266        },
267    }
268}
269
270pub fn active_abilities_to_db_model(
271    active_abilities: &comp::ability::ActiveAbilities,
272) -> Vec<DatabaseAbilitySet> {
273    active_abilities
274        .auxiliary_sets
275        .iter()
276        .map(|((mainhand, offhand), abilities)| DatabaseAbilitySet {
277            mainhand: tool_kind_to_string(*mainhand),
278            offhand: tool_kind_to_string(*offhand),
279            abilities: abilities
280                .iter()
281                .map(|ability| aux_ability_to_string(*ability))
282                .collect(),
283        })
284        .collect::<Vec<_>>()
285}
286
287pub fn active_abilities_from_db_model(
288    ability_sets: Vec<DatabaseAbilitySet>,
289) -> comp::ability::ActiveAbilities {
290    let ability_sets = ability_sets
291        .into_iter()
292        .map(
293            |DatabaseAbilitySet {
294                 mainhand,
295                 offhand,
296                 abilities,
297             }| {
298                let mut auxiliary_abilities =
299                    vec![comp::ability::AuxiliaryAbility::Empty; comp::ability::BASE_ABILITY_LIMIT];
300                for (empty, ability) in auxiliary_abilities.iter_mut().zip(abilities.into_iter()) {
301                    *empty = aux_ability_from_string(&ability);
302                }
303                (
304                    (
305                        tool_kind_from_string(mainhand),
306                        tool_kind_from_string(offhand),
307                    ),
308                    auxiliary_abilities,
309                )
310            },
311        )
312        .collect::<HashMap<_, _>>();
313    comp::ability::ActiveAbilities::from_auxiliary(
314        ability_sets,
315        Some(comp::ability::BASE_ABILITY_LIMIT),
316    )
317}
318
319/// Struct containing item properties in the format that they get persisted to
320/// the database. Adding new fields is generally safe as long as they are
321/// optional. Renaming or removing old fields will require a migration.
322#[derive(Serialize, Deserialize)]
323pub struct DatabaseItemProperties {
324    #[serde(skip_serializing_if = "Option::is_none")]
325    durability: Option<NonZeroU32>,
326}
327
328pub fn item_properties_to_db_model(item: &comp::Item) -> DatabaseItemProperties {
329    DatabaseItemProperties {
330        durability: item.persistence_durability(),
331    }
332}
333
334pub fn apply_db_item_properties(item: &mut comp::Item, properties: &DatabaseItemProperties) {
335    let DatabaseItemProperties { durability } = properties;
336    item.persistence_set_durability(*durability);
337}
338
339#[cfg(test)]
340pub mod tests {
341    #[test]
342    fn test_default_item_properties() {
343        use super::DatabaseItemProperties;
344        const DEFAULT_ITEM_PROPERTIES: &str = "{}";
345        let _ = serde_json::de::from_str::<DatabaseItemProperties>(DEFAULT_ITEM_PROPERTIES).expect(
346            "Default value should always load to ensure that changes to item properties is always \
347             forward compatible with migration V50.",
348        );
349    }
350}