1use crate::persistence::{
2 character::EntityId,
3 error::PersistenceError,
4 json_models::{
5 self, CharacterPosition, DatabaseAbilitySet, DatabaseItemProperties, GenericBody,
6 HumanoidBody,
7 },
8 models::{AbilitySets, Character, Item, SkillGroup},
9};
10use common::{
11 character::CharacterId,
12 comp::{
13 ActiveAbilities, Body as CompBody, Hardcore, Inventory, MapMarker, Stats, Waypoint, body,
14 inventory::{
15 item::{Item as VelorenItem, MaterialStatManifest, tool::AbilityMap},
16 loadout::{Loadout, LoadoutError},
17 loadout_builder::LoadoutBuilder,
18 recipe_book::RecipeBook,
19 slot::InvSlotId,
20 },
21 item,
22 skillset::{self, SkillGroupKind, SkillSet, skills::Skill},
23 },
24 resources::Time,
25};
26use core::{convert::TryFrom, num::NonZeroU64};
27use hashbrown::HashMap;
28use lazy_static::lazy_static;
29use std::{collections::VecDeque, str::FromStr, sync::Arc};
30use tracing::{trace, warn};
31
32#[derive(Debug)]
33pub struct ItemModelPair {
34 pub comp: Arc<item::ItemId>,
35 pub model: Item,
36}
37
38lazy_static! {
42 pub static ref MATERIAL_STATS_MANIFEST: MaterialStatManifest =
43 MaterialStatManifest::load().cloned();
44 pub static ref ABILITY_MAP: AbilityMap = AbilityMap::load().cloned();
45}
46
47pub fn convert_items_to_database_items(
56 loadout_container_id: EntityId,
57 inventory: &Inventory,
58 inventory_container_id: EntityId,
59 overflow_items_container_id: EntityId,
60 recipe_book_container_id: EntityId,
61 next_id: &mut i64,
62) -> Vec<ItemModelPair> {
63 let loadout = inventory
64 .loadout_items_with_persistence_key()
65 .map(|(slot, item)| (slot.to_string(), item, loadout_container_id));
66
67 let overflow_items = inventory.overflow_items().enumerate().map(|(i, item)| {
68 (
69 serde_json::to_string(&i).expect("failed to serialize index of overflow item"),
70 Some(item),
71 overflow_items_container_id,
72 )
73 });
74
75 let recipe_book = inventory
76 .persistence_recipes_iter_with_index()
77 .map(|(i, item)| {
78 (
79 serde_json::to_string(&i)
80 .expect("failed to serialize index of recipe from recipe book"),
81 Some(item),
82 recipe_book_container_id,
83 )
84 });
85 let inventory = inventory.slots_with_id().map(|(pos, item)| {
87 (
88 serde_json::to_string(&pos).expect("failed to serialize InvSlotId"),
89 item.as_ref(),
90 inventory_container_id,
91 )
92 });
93
94 let mut bfs_queue: VecDeque<_> = inventory
97 .chain(loadout)
98 .chain(overflow_items)
99 .chain(recipe_book)
100 .collect();
101 let mut upserts = Vec::new();
102 let mut depth = HashMap::new();
103 depth.insert(inventory_container_id, 0);
104 depth.insert(loadout_container_id, 0);
105 depth.insert(overflow_items_container_id, 0);
106 depth.insert(recipe_book_container_id, 0);
107 while let Some((position, item, parent_container_item_id)) = bfs_queue.pop_front() {
108 if let Some(item) = item {
110 let new_item_id = NonZeroU64::new(u64::try_from(*next_id).expect(
112 "We are willing to crash if the next entity id overflows (or is otherwise \
113 negative).",
114 ))
115 .expect("next_id should not be zero, either");
116
117 let comp = item.get_item_id_for_database();
120 let item_id = comp.load()
121 .and_then(|item_id| Some(if item_id >= new_item_id {
151 match comp.compare_exchange(Some(item_id), Some(new_item_id)) {
153 Ok(_) => {
154 let item_id = *next_id;
155 *next_id += 1;
157 item_id
158 },
159 Err(item_id) => {
160 EntityId::try_from(item_id?.get())
165 .expect("We always choose legal EntityIds as item ids")
166 },
167 }
168 } else { EntityId::try_from(item_id.get()).expect("We always choose legal EntityIds as item ids") }))
169 .unwrap_or_else(|| {
173 match comp.compare_exchange(None, Some(new_item_id)) {
175 Ok(_) => {
176 let item_id = *next_id;
177 *next_id += 1;
178 item_id
179 },
180 Err(item_id) => {
181 EntityId::try_from(item_id.expect("TODO: Fix handling of reset to None when we have concurrent writers.").get())
182 .expect("We always choose legal EntityIds as item ids")
183 },
184 }
185 });
186
187 depth.insert(item_id, depth[&parent_container_item_id] + 1);
188
189 for (i, component) in item.components().iter().enumerate() {
190 bfs_queue.push_back((format!("component_{}", i), Some(component), item_id));
195 }
196
197 let item_properties = json_models::item_properties_to_db_model(item);
198
199 let upsert = ItemModelPair {
200 model: Item {
201 item_definition_id: item.persistence_item_id(),
202 position,
203 parent_container_item_id,
204 item_id,
205 stack_size: if item.is_stackable() {
206 item.amount().into()
207 } else {
208 1
209 },
210 properties: serde_json::to_string(&item_properties)
211 .expect("Failed to convert item properties to a json string."),
212 },
213 comp,
216 };
217 upserts.push(upsert);
218 }
219 }
220 upserts.sort_by_key(|pair| (depth[&pair.model.item_id], pair.model.item_id));
221 trace!("upserts: {:#?}", upserts);
222 upserts
223}
224
225pub fn convert_body_to_database_json(
226 comp_body: &CompBody,
227) -> Result<(&str, String), PersistenceError> {
228 Ok(match comp_body {
229 CompBody::Humanoid(body) => (
230 "humanoid",
231 serde_json::to_string(&HumanoidBody::from(body))?,
232 ),
233 CompBody::QuadrupedLow(body) => (
234 "quadruped_low",
235 serde_json::to_string(&GenericBody::from(body))?,
236 ),
237 CompBody::QuadrupedMedium(body) => (
238 "quadruped_medium",
239 serde_json::to_string(&GenericBody::from(body))?,
240 ),
241 CompBody::QuadrupedSmall(body) => (
242 "quadruped_small",
243 serde_json::to_string(&GenericBody::from(body))?,
244 ),
245 CompBody::BirdMedium(body) => (
246 "bird_medium",
247 serde_json::to_string(&GenericBody::from(body))?,
248 ),
249 CompBody::Crustacean(body) => (
250 "crustacean",
251 serde_json::to_string(&GenericBody::from(body))?,
252 ),
253 _ => {
254 return Err(PersistenceError::ConversionError(format!(
255 "Unsupported body type for persistence: {:?}",
256 comp_body
257 )));
258 },
259 })
260}
261
262pub fn convert_waypoint_to_database_json(
263 waypoint: Option<Waypoint>,
264 map_marker: Option<MapMarker>,
265) -> Option<String> {
266 if waypoint.is_some() || map_marker.is_some() {
267 let charpos = CharacterPosition {
268 waypoint: waypoint.map(|w| w.get_pos()),
269 map_marker: map_marker.map(|m| m.0),
270 };
271 Some(
272 serde_json::to_string(&charpos)
273 .map_err(|err| {
274 PersistenceError::ConversionError(format!("Error encoding waypoint: {:?}", err))
275 })
276 .ok()?,
277 )
278 } else {
279 None
280 }
281}
282
283pub fn convert_waypoint_from_database_json(
284 position: &str,
285) -> Result<(Option<Waypoint>, Option<MapMarker>), PersistenceError> {
286 let character_position =
287 serde_json::de::from_str::<CharacterPosition>(position).map_err(|err| {
288 PersistenceError::ConversionError(format!(
289 "Error de-serializing waypoint: {} err: {}",
290 position, err
291 ))
292 })?;
293 Ok((
294 character_position
295 .waypoint
296 .map(|pos| Waypoint::new(pos, Time(0.0))),
297 character_position.map_marker.map(MapMarker),
298 ))
299}
300
301fn get_mutable_item<'a, 'b, T>(
308 index: usize,
309 inventory_items: &'a [Item],
310 item_indices: &'a HashMap<i64, usize>,
311 inventory: &'b mut T,
312 get_mut_item: &'a impl Fn(&'b mut T, &str) -> Option<&'b mut VelorenItem>,
313) -> Result<&'a mut VelorenItem, PersistenceError>
314where
315 'b: 'a,
316{
317 let parent_id = inventory_items[index].parent_container_item_id;
325 if inventory_items[index].position.contains("component_") {
326 if let Some(parent) = item_indices.get(&parent_id).map(move |i| {
327 get_mutable_item(
328 *i,
329 inventory_items,
330 item_indices,
331 inventory,
332 get_mut_item,
334 )
335 }) {
336 let position = &inventory_items[index].position;
338 let component_index = position
339 .split('_')
340 .nth(1)
341 .and_then(|s| s.parse::<usize>().ok())
342 .ok_or_else(|| {
343 PersistenceError::ConversionError(format!(
344 "Failed to parse position stored in database: {position}."
345 ))
346 })?;
347 parent?
350 .persistence_access_mutable_component(component_index)
351 .ok_or_else(|| {
352 PersistenceError::ConversionError(format!(
353 "Component in position {component_index} doesn't exist on parent item \
354 {parent_id}."
355 ))
356 })
357 } else {
358 Err(PersistenceError::ConversionError(format!(
359 "Parent item with id {parent_id} does not exist in database."
360 )))
361 }
362 } else {
363 get_mut_item(inventory, &inventory_items[index].position).ok_or_else(|| {
364 PersistenceError::ConversionError(format!(
365 "Unable to retrieve parent veloren item {parent_id} of component from inventory."
366 ))
367 })
368 }
369}
370
371pub fn convert_inventory_from_database_items(
377 inventory_container_id: i64,
378 inventory_items: &[Item],
379 loadout_container_id: i64,
380 loadout_items: &[Item],
381 overflow_items_container_id: i64,
382 overflow_items: &[Item],
383 recipe_book_items: &[Item],
384) -> Result<Inventory, PersistenceError> {
385 let loadout = convert_loadout_from_database_items(loadout_container_id, loadout_items)?;
392 let overflow_items =
393 convert_overflow_items_from_database_items(overflow_items_container_id, overflow_items)?;
394 let recipe_book = convert_recipe_book_from_database_items(recipe_book_items)?;
395 let mut inventory = Inventory::with_loadout_humanoid(loadout).with_recipe_book(recipe_book);
396 let mut item_indices = HashMap::new();
397
398 let mut failed_inserts = HashMap::new();
399
400 for (i, db_item) in inventory_items.iter().enumerate() {
404 item_indices.insert(db_item.item_id, i);
405
406 let mut item = get_item_from_asset(db_item.item_definition_id.as_str())?;
407 let item_properties =
408 serde_json::de::from_str::<DatabaseItemProperties>(&db_item.properties)?;
409 json_models::apply_db_item_properties(&mut item, &item_properties);
410
411 let comp = item.get_item_id_for_database();
413
414 comp.store(Some(NonZeroU64::try_from(db_item.item_id as u64).map_err(
416 |_| PersistenceError::ConversionError("Item with zero item_id".to_owned()),
417 )?));
418
419 if db_item.stack_size == 1 || item.is_stackable() {
421 item.set_amount(u32::try_from(db_item.stack_size).map_err(|_| {
425 PersistenceError::ConversionError(format!(
426 "Invalid item stack size for stackable={}: {}",
427 item.is_stackable(),
428 &db_item.stack_size
429 ))
430 })?)
431 .map_err(|_| {
432 PersistenceError::ConversionError("Error setting amount for item".to_owned())
433 })?;
434 }
435
436 let slot = |s: &str| {
440 serde_json::from_str::<InvSlotId>(s).map_err(|_| {
441 PersistenceError::ConversionError(format!(
442 "Failed to parse item position: {:?}",
443 &db_item.position
444 ))
445 })
446 };
447
448 if db_item.parent_container_item_id == inventory_container_id {
449 match slot(&db_item.position) {
450 Ok(slot) => {
451 let insert_res = inventory.insert_at(slot, item);
452
453 match insert_res {
454 Ok(None) => {
455 },
457 Ok(Some(_item)) => {
458 return Err(PersistenceError::ConversionError(
464 "Inserted an item into the same slot twice".to_string(),
465 ));
466 },
467 Err(item) => {
468 failed_inserts.insert(db_item.position.clone(), item);
471 },
472 }
473 },
474 Err(err) => {
475 return Err(err);
476 },
477 }
478 } else if let Some(&j) = item_indices.get(&db_item.parent_container_item_id) {
479 get_mutable_item(
480 j,
481 inventory_items,
482 &item_indices,
483 &mut (&mut inventory, &mut failed_inserts),
484 &|(inv, f_i): &mut (&mut Inventory, &mut HashMap<String, VelorenItem>), s| {
485 slot(s)
488 .ok()
489 .and_then(|slot| inv.slot_mut(slot))
490 .and_then(|a| a.as_mut())
491 .or_else(|| f_i.get_mut(s))
492 },
493 )?
494 .persistence_access_add_component(item);
495 } else {
496 return Err(PersistenceError::ConversionError(format!(
497 "Couldn't find parent item {} before item {} in inventory",
498 db_item.parent_container_item_id, db_item.item_id
499 )));
500 }
501 }
502
503 if let Err(inv_error) = inventory.push_all(
506 overflow_items
507 .into_iter()
508 .chain(failed_inserts.into_values()),
509 ) {
510 inventory.persistence_push_overflow_items(inv_error.returned_items());
511 }
512
513 inventory.persistence_update_all_item_states(&ABILITY_MAP, &MATERIAL_STATS_MANIFEST);
516
517 Ok(inventory)
518}
519
520pub fn convert_loadout_from_database_items(
521 loadout_container_id: i64,
522 database_items: &[Item],
523) -> Result<Loadout, PersistenceError> {
524 let loadout_builder = LoadoutBuilder::empty();
525 let mut loadout = loadout_builder.build();
526 let mut item_indices = HashMap::new();
527
528 for (i, db_item) in database_items.iter().enumerate() {
532 item_indices.insert(db_item.item_id, i);
533
534 let mut item = get_item_from_asset(db_item.item_definition_id.as_str())?;
535 let item_properties =
536 serde_json::de::from_str::<DatabaseItemProperties>(&db_item.properties)?;
537 json_models::apply_db_item_properties(&mut item, &item_properties);
538
539 let comp = item.get_item_id_for_database();
541 comp.store(Some(NonZeroU64::try_from(db_item.item_id as u64).map_err(
542 |_| PersistenceError::ConversionError("Item with zero item_id".to_owned()),
543 )?));
544
545 let convert_error = |err| match err {
546 LoadoutError::InvalidPersistenceKey => PersistenceError::ConversionError(format!(
547 "Invalid persistence key: {}",
548 &db_item.position
549 )),
550 LoadoutError::NoParentAtSlot => PersistenceError::ConversionError(format!(
551 "No parent item at slot: {}",
552 &db_item.position
553 )),
554 };
555
556 if db_item.parent_container_item_id == loadout_container_id {
557 loadout
558 .set_item_at_slot_using_persistence_key(&db_item.position, item)
559 .map_err(convert_error)?;
560 } else if let Some(&j) = item_indices.get(&db_item.parent_container_item_id) {
561 get_mutable_item(j, database_items, &item_indices, &mut loadout, &|l, s| {
562 l.get_mut_item_at_slot_using_persistence_key(s).ok()
563 })?
564 .persistence_access_add_component(item);
565 } else {
566 return Err(PersistenceError::ConversionError(format!(
567 "Couldn't find parent item {} before item {} in loadout",
568 db_item.parent_container_item_id, db_item.item_id
569 )));
570 }
571 }
572
573 loadout.persistence_update_all_item_states(&ABILITY_MAP, &MATERIAL_STATS_MANIFEST);
576
577 Ok(loadout)
578}
579
580pub fn convert_overflow_items_from_database_items(
581 overflow_items_container_id: i64,
582 database_items: &[Item],
583) -> Result<Vec<VelorenItem>, PersistenceError> {
584 let mut overflow_items_with_database_position = HashMap::new();
585 let mut item_indices = HashMap::new();
586
587 for (i, db_item) in database_items.iter().enumerate() {
591 item_indices.insert(db_item.item_id, i);
592
593 let mut item = get_item_from_asset(db_item.item_definition_id.as_str())?;
594 let item_properties =
595 serde_json::de::from_str::<DatabaseItemProperties>(&db_item.properties)?;
596 json_models::apply_db_item_properties(&mut item, &item_properties);
597
598 let comp = item.get_item_id_for_database();
600
601 comp.store(Some(NonZeroU64::try_from(db_item.item_id as u64).map_err(
603 |_| PersistenceError::ConversionError("Item with zero item_id".to_owned()),
604 )?));
605
606 if db_item.stack_size == 1 || item.is_stackable() {
608 item.set_amount(u32::try_from(db_item.stack_size).map_err(|_| {
612 PersistenceError::ConversionError(format!(
613 "Invalid item stack size for stackable={}: {}",
614 item.is_stackable(),
615 &db_item.stack_size
616 ))
617 })?)
618 .map_err(|_| {
619 PersistenceError::ConversionError("Error setting amount for item".to_owned())
620 })?;
621 }
622
623 if db_item.parent_container_item_id == overflow_items_container_id {
624 match overflow_items_with_database_position.insert(db_item.position.clone(), item) {
625 None => {
626 },
628 Some(_item) => {
629 return Err(PersistenceError::ConversionError(
632 "Inserted an item into the same overflow slot twice".to_string(),
633 ));
634 },
635 }
636 } else if let Some(&j) = item_indices.get(&db_item.parent_container_item_id) {
637 get_mutable_item(
638 j,
639 database_items,
640 &item_indices,
641 &mut overflow_items_with_database_position,
642 &|o_i, s| o_i.get_mut(s),
643 )?
644 .persistence_access_add_component(item);
645 } else {
646 return Err(PersistenceError::ConversionError(format!(
647 "Couldn't find parent item {} before item {} in overflow items",
648 db_item.parent_container_item_id, db_item.item_id
649 )));
650 }
651 }
652
653 let overflow_items = overflow_items_with_database_position
654 .into_values()
655 .collect::<Vec<_>>();
656
657 Ok(overflow_items)
658}
659
660fn get_item_from_asset(item_definition_id: &str) -> Result<common::comp::Item, PersistenceError> {
661 common::comp::Item::new_from_asset(item_definition_id).map_err(|err| {
662 PersistenceError::AssetError(format!(
663 "Error loading item asset: {} - {}",
664 item_definition_id, err
665 ))
666 })
667}
668
669macro_rules! deserialize_body {
671 ($body_data:expr, $body_variant:tt, $body_type:tt) => {{
672 let json_model = serde_json::de::from_str::<GenericBody>($body_data)?;
673 CompBody::$body_variant(common::comp::$body_type::Body {
674 species: common::comp::$body_type::Species::from_str(&json_model.species)
675 .map_err(|_| {
676 PersistenceError::ConversionError(format!(
677 "Missing species: {}",
678 json_model.species
679 ))
680 })?
681 .to_owned(),
682 body_type: common::comp::$body_type::BodyType::from_str(&json_model.body_type)
683 .map_err(|_| {
684 PersistenceError::ConversionError(format!(
685 "Missing body type: {}",
686 json_model.species
687 ))
688 })?
689 .to_owned(),
690 })
691 }};
692}
693pub fn convert_body_from_database(
694 variant: &str,
695 body_data: &str,
696) -> Result<CompBody, PersistenceError> {
697 Ok(match variant {
698 "humanoid" => {
701 let json_model = serde_json::de::from_str::<HumanoidBody>(body_data)?;
702 CompBody::Humanoid(body::humanoid::Body {
703 species: body::humanoid::ALL_SPECIES
704 .get(json_model.species as usize)
705 .ok_or_else(|| {
706 PersistenceError::ConversionError(format!(
707 "Missing species: {}",
708 json_model.species
709 ))
710 })?
711 .to_owned(),
712 body_type: body::humanoid::ALL_BODY_TYPES
713 .get(json_model.body_type as usize)
714 .ok_or_else(|| {
715 PersistenceError::ConversionError(format!(
716 "Missing body_type: {}",
717 json_model.body_type
718 ))
719 })?
720 .to_owned(),
721 hair_style: json_model.hair_style,
722 beard: json_model.beard,
723 eyes: json_model.eyes,
724 accessory: json_model.accessory,
725 hair_color: json_model.hair_color,
726 skin: json_model.skin,
727 eye_color: json_model.eye_color,
728 })
729 },
730 "quadruped_low" => {
731 deserialize_body!(body_data, QuadrupedLow, quadruped_low)
732 },
733 "quadruped_medium" => {
734 deserialize_body!(body_data, QuadrupedMedium, quadruped_medium)
735 },
736 "quadruped_small" => {
737 deserialize_body!(body_data, QuadrupedSmall, quadruped_small)
738 },
739 "bird_medium" => {
740 deserialize_body!(body_data, BirdMedium, bird_medium)
741 },
742 "crustacean" => {
743 deserialize_body!(body_data, Crustacean, crustacean)
744 },
745 _ => {
746 return Err(PersistenceError::ConversionError(format!(
747 "{} is not a supported body type for deserialization",
748 variant
749 )));
750 },
751 })
752}
753
754pub fn convert_character_from_database(character: &Character) -> common::character::Character {
755 common::character::Character {
756 id: Some(CharacterId(character.character_id)),
757 alias: String::from(&character.alias),
758 }
759}
760
761pub fn convert_stats_from_database(alias: String, body: CompBody) -> Stats {
762 let mut new_stats = Stats::empty(body);
763 new_stats.name = alias;
764 new_stats
765}
766
767pub fn convert_hardcore_from_database(hardcore: i64) -> Result<Option<Hardcore>, PersistenceError> {
768 match hardcore {
769 0 => Ok(None),
770 1 => Ok(Some(common::comp::Hardcore)),
771 _ => Err(PersistenceError::ConversionError(format!(
772 "Invalid hardcore field: {hardcore}"
773 ))),
774 }
775}
776
777pub fn convert_hardcore_to_database(hardcore: Option<Hardcore>) -> i64 {
778 if hardcore.is_some() { 1 } else { 0 }
779}
780
781pub fn convert_skill_set_from_database(
785 skill_groups: &[SkillGroup],
786) -> (SkillSet, Option<skillset::SkillsPersistenceError>) {
787 let (skillless_skill_groups, deserialized_skills) =
788 convert_skill_groups_from_database(skill_groups);
789 SkillSet::load_from_database(skillless_skill_groups, deserialized_skills)
790}
791
792fn convert_skill_groups_from_database(
793 skill_groups: &[SkillGroup],
794) -> (
795 HashMap<SkillGroupKind, skillset::SkillGroup>,
798 HashMap<SkillGroupKind, Result<Vec<Skill>, skillset::SkillsPersistenceError>>,
800) {
801 let mut new_skill_groups = HashMap::new();
802 let mut deserialized_skills = HashMap::new();
803 for skill_group in skill_groups.iter() {
804 let skill_group_kind = json_models::db_string_to_skill_group(&skill_group.skill_group_kind);
805 let mut new_skill_group = skillset::SkillGroup {
806 skill_group_kind,
807 earned_exp: 0,
809 available_exp: 0,
810 available_sp: 0,
811 earned_sp: 0,
812 ordered_skills: Vec::new(),
815 };
816
817 let skill_group_exp = skill_group.earned_exp.clamp(0, i64::from(u32::MAX)) as u32;
821 new_skill_group.add_experience(skill_group_exp);
822
823 use skillset::SkillsPersistenceError;
824
825 let skills_result = if skill_group.spent_exp != i64::from(new_skill_group.spent_exp()) {
826 Err(SkillsPersistenceError::SpentExpMismatch)
829 } else if Some(&skill_group.hash_val) != skillset::SKILL_GROUP_HASHES.get(&skill_group_kind)
830 {
831 Err(SkillsPersistenceError::HashMismatch)
834 } else {
835 match serde_json::from_str::<Vec<Skill>>(&skill_group.skills) {
837 Ok(skills) => Ok(skills),
839 Err(err) => {
841 warn!(
842 "Skills failed to correctly deserialized\nError: {:#?}\nRaw JSON: {:#?}",
843 err, &skill_group.skills
844 );
845 Err(SkillsPersistenceError::DeserializationFailure)
846 },
847 }
848 };
849
850 deserialized_skills.insert(skill_group_kind, skills_result);
851
852 new_skill_groups.insert(skill_group_kind, new_skill_group);
853 }
854 (new_skill_groups, deserialized_skills)
855}
856
857pub fn convert_skill_groups_to_database<'a, I: Iterator<Item = &'a skillset::SkillGroup>>(
858 entity_id: CharacterId,
859 skill_groups: I,
860) -> Vec<SkillGroup> {
861 let skill_group_hashes = &skillset::SKILL_GROUP_HASHES;
862 skill_groups
863 .into_iter()
864 .map(|sg| SkillGroup {
865 entity_id: entity_id.0,
866 skill_group_kind: json_models::skill_group_to_db_string(sg.skill_group_kind),
867 earned_exp: i64::from(sg.earned_exp),
868 spent_exp: i64::from(sg.spent_exp()),
869 skills: serde_json::to_string(&sg.ordered_skills).unwrap_or_else(|_| "".to_string()),
871 hash_val: skill_group_hashes
872 .get(&sg.skill_group_kind)
873 .cloned()
874 .unwrap_or_default(),
875 })
876 .collect()
877}
878
879pub fn convert_active_abilities_to_database(
880 entity_id: CharacterId,
881 active_abilities: &ActiveAbilities,
882) -> AbilitySets {
883 let ability_sets = json_models::active_abilities_to_db_model(active_abilities);
884 AbilitySets {
885 entity_id: entity_id.0,
886 ability_sets: serde_json::to_string(&ability_sets).unwrap_or_default(),
887 }
888}
889
890pub fn convert_active_abilities_from_database(ability_sets: &AbilitySets) -> ActiveAbilities {
891 let ability_sets = serde_json::from_str::<Vec<DatabaseAbilitySet>>(&ability_sets.ability_sets)
892 .unwrap_or_else(|err| {
893 common_base::dev_panic!(format!(
894 "Failed to parse ability sets. Error: {:#?}\nAbility sets:\n{:#?}",
895 err, ability_sets.ability_sets
896 ));
897 Vec::new()
898 });
899 json_models::active_abilities_from_db_model(ability_sets)
900}
901
902pub fn convert_recipe_book_from_database_items(
903 database_items: &[Item],
904) -> Result<RecipeBook, PersistenceError> {
905 let mut recipes_groups = Vec::new();
906
907 for db_item in database_items.iter() {
908 let item = get_item_from_asset(db_item.item_definition_id.as_str())?;
909
910 let comp = item.get_item_id_for_database();
912 comp.store(Some(NonZeroU64::try_from(db_item.item_id as u64).map_err(
913 |_| PersistenceError::ConversionError("Item with zero item_id".to_owned()),
914 )?));
915
916 recipes_groups.push(item);
917 }
918
919 let recipe_book = RecipeBook::recipe_book_from_persistence(recipes_groups);
920
921 Ok(recipe_book)
922}