diff --git a/common/sys/src/stats.rs b/common/sys/src/stats.rs index 5de01c0dbe..943e6d0e0f 100644 --- a/common/sys/src/stats.rs +++ b/common/sys/src/stats.rs @@ -7,8 +7,8 @@ use common::{ resources::DeltaTime, span, }; +use hashbrown::HashSet; use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage}; -use std::collections::HashSet; const ENERGY_REGEN_ACCEL: f32 = 10.0; diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index ce0f57a066..e7cdf99f01 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -28,9 +28,9 @@ use common_net::{ }; use common_sys::state::BlockChange; use comp::item::Reagent; +use hashbrown::HashSet; use rand::prelude::*; use specs::{join::Join, saveload::MarkerAllocator, Entity as EcsEntity, WorldExt}; -use std::collections::HashSet; use tracing::error; use vek::Vec3; diff --git a/server/src/persistence/character.rs b/server/src/persistence/character.rs index b9281ab569..3088a008e2 100644 --- a/server/src/persistence/character.rs +++ b/server/src/persistence/character.rs @@ -15,6 +15,7 @@ use crate::{ convert_body_from_database, convert_body_to_database_json, convert_character_from_database, convert_inventory_from_database_items, convert_items_to_database_items, convert_loadout_from_database_items, + convert_skill_groups_to_database, convert_skills_to_database, convert_stats_from_database, convert_stats_to_database, convert_waypoint_from_database_json, }, @@ -58,7 +59,9 @@ pub fn load_character_data( char_id: CharacterId, connection: VelorenTransaction, ) -> CharacterDataResult { - use schema::{body::dsl::*, character::dsl::*, item::dsl::*}; + use schema::{ + body::dsl::*, character::dsl::*, item::dsl::*, skill_group::dsl::*, + }; let character_containers = get_pseudo_containers(connection, char_id)?; @@ -100,9 +103,22 @@ pub fn load_character_data( }, }); + let skill_data = schema::skill::dsl::skill + .filter(schema::skill::dsl::character_id.eq(char_id)) + .load::(&*connection)?; + + let skill_group_data = skill_group + .filter(schema::skill_group::dsl::character_id.eq(char_id)) + .load::(&*connection)?; + Ok(( convert_body_from_database(&char_body)?, - convert_stats_from_database(&stats_data, character_data.alias), + convert_stats_from_database( + &stats_data, + character_data.alias, + &skill_data, + &skill_group_data, + ), convert_inventory_from_database_items(&inventory_items, &loadout_items)?, char_waypoint, )) @@ -172,7 +188,7 @@ pub fn create_character( check_character_limit(uuid, connection)?; - use schema::{body, character}; + use schema::{body, character, skill_group}; let (body, stats, inventory, waypoint) = persisted_components; @@ -218,6 +234,8 @@ pub fn create_character( ))); } + let skill_set = stats.skill_set.clone(); + // Insert stats record let db_stats = convert_stats_to_database(character_id, &stats, &waypoint)?; let stats_count = diesel::insert_into(schema::stats::table) @@ -266,6 +284,18 @@ pub fn create_character( ))); } + let db_skill_groups = convert_skill_groups_to_database(character_id, skill_set.skill_groups); + let skill_groups_count = diesel::insert_into(skill_group::table) + .values(&db_skill_groups) + .execute(&*connection)?; + + if skill_groups_count != 1 { + return Err(Error::OtherError(format!( + "Error inserting into skill_group table for char_id {}", + character_id + ))); + } + // Insert default inventory and loadout item records let mut inserts = Vec::new(); @@ -305,7 +335,9 @@ pub fn delete_character( char_id: CharacterId, connection: VelorenTransaction, ) -> CharacterListResult { - use schema::{body::dsl::*, character::dsl::*, stats::dsl::*}; + use schema::{ + body::dsl::*, character::dsl::*, skill::dsl::*, skill_group::dsl::*, stats::dsl::*, + }; // Load the character to delete - ensures that the requesting player // owns the character @@ -317,6 +349,13 @@ pub fn delete_character( ) .first::(&*connection)?; + // Delete skills + diesel::delete(skill_group.filter(schema::skill_group::dsl::character_id.eq(char_id))) + .execute(&*connection)?; + + diesel::delete(skill.filter(schema::skill::dsl::character_id.eq(char_id))) + .execute(&*connection)?; + // Delete character let character_count = diesel::delete( character @@ -529,7 +568,7 @@ pub fn update( char_waypoint: Option, connection: VelorenTransaction, ) -> Result>, Error> { - use super::schema::item::dsl::*; + use super::schema::{item::dsl::*, skill_group::dsl::*}; let pseudo_containers = get_pseudo_containers(connection, char_id)?; @@ -591,6 +630,20 @@ pub fn update( } } + let char_skill_set = char_stats.skill_set.clone(); + + let db_skill_groups = convert_skill_groups_to_database(char_id, char_skill_set.skill_groups); + + diesel::replace_into(skill_group) + .values(&db_skill_groups) + .execute(&*connection)?; + + let db_skills = convert_skills_to_database(char_id, char_skill_set.skills); + + diesel::replace_into(schema::skill::dsl::skill) + .values(&db_skills) + .execute(&*connection)?; + let db_stats = convert_stats_to_database(char_id, &char_stats, &char_waypoint)?; let stats_count = diesel::update(schema::stats::dsl::stats.filter(schema::stats::dsl::stats_id.eq(char_id))) diff --git a/server/src/persistence/character/conversions.rs b/server/src/persistence/character/conversions.rs index f0e7416ae3..63d9bdc647 100644 --- a/server/src/persistence/character/conversions.rs +++ b/server/src/persistence/character/conversions.rs @@ -1,6 +1,6 @@ use crate::persistence::{ character::EntityId, - models::{Body, Character, Item, Stats}, + models::{Body, Character, Item, Skill, SkillGroup, Stats}, }; use crate::persistence::{ @@ -15,11 +15,13 @@ use common::{ loadout_builder::LoadoutBuilder, slot::InvSlotId, }, + skills, Body as CompBody, Waypoint, *, }, resources::Time, }; use core::{convert::TryFrom, num::NonZeroU64}; +use hashbrown::HashMap; use itertools::{Either, Itertools}; use std::sync::Arc; @@ -342,7 +344,12 @@ pub fn convert_character_from_database(character: &Character) -> common::charact } } -pub fn convert_stats_from_database(stats: &Stats, alias: String) -> common::comp::Stats { +pub fn convert_stats_from_database( + stats: &Stats, + alias: String, + skills: &[Skill], + skill_groups: &[SkillGroup], +) -> common::comp::Stats { let mut new_stats = common::comp::Stats::empty(); new_stats.name = alias; new_stats.level.set_level(stats.level as u32); @@ -356,6 +363,10 @@ pub fn convert_stats_from_database(stats: &Stats, alias: String) -> common::comp new_stats.endurance = stats.endurance as u32; new_stats.fitness = stats.fitness as u32; new_stats.willpower = stats.willpower as u32; + new_stats.skill_set = skills::SkillSet { + skill_groups: convert_skill_groups_from_database(skill_groups), + skills: convert_skills_from_database(skills), + }; new_stats } @@ -369,3 +380,72 @@ fn get_item_from_asset(item_definition_id: &str) -> Result Vec { + let mut new_skill_groups = Vec::new(); + for skill_group in skill_groups.iter() { + let skill_group_type = + serde_json::de::from_str::(&skill_group.skill_group_type) + .map_err(|err| { + Error::ConversionError(format!( + "Error de-serializing skill group: {} err: {}", + skill_group.skill_group_type, err + )) + }) + .unwrap(); + let new_skill_group = skills::SkillGroup { + skill_group_type, + exp: skill_group.exp as u16, + available_sp: skill_group.available_sp as u16, + }; + new_skill_groups.push(new_skill_group); + } + new_skill_groups +} + +fn convert_skills_from_database(skills: &[Skill]) -> HashMap { + let mut new_skills = HashMap::new(); + for skill in skills.iter() { + let new_skill = serde_json::de::from_str::(&skill.skill_type) + .map_err(|err| { + Error::ConversionError(format!( + "Error de-serializing skill: {} err: {}", + skill.skill_type, err + )) + }) + .unwrap(); + new_skills.insert(new_skill, skill.level.map(|l| l as u16)); + } + new_skills +} + +pub fn convert_skill_groups_to_database( + character_id: CharacterId, + skill_groups: Vec, +) -> Vec { + let db_skill_groups: Vec<_> = skill_groups + .into_iter() + .map(|sg| SkillGroup { + character_id, + skill_group_type: serde_json::to_string(&sg.skill_group_type).unwrap(), + exp: sg.exp as i32, + available_sp: sg.available_sp as i32, + }) + .collect(); + db_skill_groups +} + +pub fn convert_skills_to_database( + character_id: CharacterId, + skills: HashMap, +) -> Vec { + let db_skills: Vec<_> = skills + .iter() + .map(|(s, l)| Skill { + character_id, + skill_type: serde_json::to_string(&s).unwrap(), + level: l.map(|l| l as i32), + }) + .collect(); + db_skills +} diff --git a/server/src/persistence/models.rs b/server/src/persistence/models.rs index e826ec0f91..d6cea8b91d 100644 --- a/server/src/persistence/models.rs +++ b/server/src/persistence/models.rs @@ -1,6 +1,6 @@ extern crate serde_json; -use super::schema::{body, character, entity, item, stats}; +use super::schema::{body, character, entity, item, skill, skill_group, stats}; #[derive(Debug, Insertable, PartialEq)] #[table_name = "entity"] @@ -57,3 +57,22 @@ pub struct Body { pub variant: String, pub body_data: String, } + +#[derive(Associations, Identifiable, Insertable, Queryable, Debug)] +#[primary_key(character_id, skill_type)] +#[table_name = "skill"] +pub struct Skill { + pub character_id: i64, + pub skill_type: String, + pub level: Option, +} + +#[derive(Associations, Identifiable, Insertable, Queryable, Debug)] +#[primary_key(character_id, skill_group_type)] +#[table_name = "skill_group"] +pub struct SkillGroup { + pub character_id: i64, + pub skill_group_type: String, + pub exp: i32, + pub available_sp: i32, +} diff --git a/server/src/persistence/schema.rs b/server/src/persistence/schema.rs index 8838656ecf..00701c1fea 100644 --- a/server/src/persistence/schema.rs +++ b/server/src/persistence/schema.rs @@ -42,6 +42,24 @@ table! { } } +table! { + skill (character_id, skill_type) { + character_id -> BigInt, + #[sql_name = "skill"] + skill_type -> Text, + level -> Nullable, + } +} + +table! { + skill_group (character_id, skill_group_type) { + character_id -> BigInt, + skill_group_type -> Text, + exp -> Integer, + available_sp -> Integer, + } +} + joinable!(character -> body (character_id)); joinable!(character -> stats (character_id));