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