From 4c3771a1a0ab2065b15ca1bfd0036995d7df6425 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 26 Nov 2021 23:19:46 -0500 Subject: [PATCH] Persistence of auxiliary abilities. --- common/src/comp/ability.rs | 11 ++- common/src/event.rs | 1 + server/src/character_creator.rs | 10 ++- server/src/events/entity_creation.rs | 1 + server/src/events/player.rs | 12 ++- server/src/migrations/V46__ability_sets.sql | 12 +++ server/src/persistence/character.rs | 90 ++++++++++++++++--- .../src/persistence/character/conversions.rs | 28 +++++- server/src/persistence/character_updater.rs | 34 ++++--- server/src/persistence/mod.rs | 1 + server/src/persistence/models.rs | 5 ++ server/src/state_ext.rs | 13 +-- server/src/sys/persistence.rs | 17 +++- 13 files changed, 186 insertions(+), 49 deletions(-) create mode 100644 server/src/migrations/V46__ability_sets.sql diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 6f1a020ae6..46cf0ce717 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -62,12 +62,11 @@ impl Default for ActiveAbilities { } impl ActiveAbilities { - pub fn new(_inv: Option<&Inventory>, _skill_set: Option<&SkillSet>) -> Self { - // Maybe hook into loading saved variants when they exist here? - // let mut pool = Self::default(); - // pool.auto_update(inv, skill_set); - // pool - Self::default() + pub fn new(auxiliary_sets: HashMap) -> Self { + ActiveAbilities { + auxiliary_sets, + ..Self::default() + } } pub fn change_ability( diff --git a/common/src/event.rs b/common/src/event.rs index d1e8215a11..a712721cee 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -115,6 +115,7 @@ pub enum ServerEvent { comp::Inventory, Option, Vec<(comp::Pet, comp::Body, comp::Stats)>, + comp::ActiveAbilities, ), }, ExitIngame { diff --git a/server/src/character_creator.rs b/server/src/character_creator.rs index 54a27cb808..84b9833c99 100644 --- a/server/src/character_creator.rs +++ b/server/src/character_creator.rs @@ -69,7 +69,15 @@ pub fn create_character( entity, player_uuid, character_alias, - (body, stats, skill_set, inventory, waypoint, Vec::new()), + ( + body, + stats, + skill_set, + inventory, + waypoint, + Vec::new(), + Default::default(), + ), ); Ok(()) } diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index d449977484..bd8967b7bf 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -42,6 +42,7 @@ pub fn handle_loaded_character_data( comp::Inventory, Option, Vec<(comp::Pet, comp::Body, comp::Stats)>, + comp::ActiveAbilities, ), ) { server diff --git a/server/src/events/player.rs b/server/src/events/player.rs index 83112b3568..55113c75a4 100644 --- a/server/src/events/player.rs +++ b/server/src/events/player.rs @@ -202,6 +202,7 @@ fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity { Some(presence), Some(skill_set), Some(inventory), + Some(active_abilities), Some(player_uid), Some(player_info), mut character_updater, @@ -210,6 +211,9 @@ fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity { state.read_storage::().get(entity), state.read_storage::().get(entity), state.read_storage::().get(entity), + state + .read_storage::() + .get(entity), state.read_storage::().get(entity), state.read_storage::().get(entity), state.ecs().fetch_mut::(), @@ -251,7 +255,13 @@ fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity { character_updater.add_pending_logout_update( char_id, - (skill_set.clone(), inventory.clone(), pets, waypoint), + ( + skill_set.clone(), + inventory.clone(), + pets, + waypoint, + active_abilities.clone(), + ), ); }, PresenceKind::Spectator => { /* Do nothing, spectators do not need persisting */ }, diff --git a/server/src/migrations/V46__ability_sets.sql b/server/src/migrations/V46__ability_sets.sql new file mode 100644 index 0000000000..bbf0ed5914 --- /dev/null +++ b/server/src/migrations/V46__ability_sets.sql @@ -0,0 +1,12 @@ +-- Creates new ability_set table +CREATE TABLE "ability_set" ( + "entity_id" INT NOT NULL, + "ability_sets" TEXT NOT NULL, + PRIMARY KEY("entity_id"), + FOREIGN KEY("entity_id") REFERENCES "character"("character_id") +); + +-- Inserts starting ability sets for everyone +INSERT INTO ability_set +SELECT c.character_id, 'Empty' +FROM character c diff --git a/server/src/persistence/character.rs b/server/src/persistence/character.rs index 5668adcec5..c642207eca 100644 --- a/server/src/persistence/character.rs +++ b/server/src/persistence/character.rs @@ -8,10 +8,10 @@ extern crate rusqlite; use super::{error::PersistenceError, models::*}; use crate::{ - comp, - comp::Inventory, + comp::{self, Inventory}, persistence::{ character::conversions::{ + convert_active_abilities_from_database, convert_active_abilities_to_database, 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, @@ -234,6 +234,20 @@ pub fn load_character_data( }) .collect::>(); + let mut stmt = connection.prepare_cached( + " + SELECT ability_sets + FROM ability_set + WHERE entity_id = ?1", + )?; + + let ability_set_data = stmt.query_row(&[char_id], |row| { + Ok(AbilitySets { + entity_id: char_id, + ability_sets: row.get(0)?, + }) + })?; + Ok(( convert_body_from_database(&body_data.variant, &body_data.body_data)?, convert_stats_from_database(character_data.alias), @@ -246,6 +260,7 @@ pub fn load_character_data( )?, char_waypoint, pets, + convert_active_abilities_from_database(&ability_set_data), )) } @@ -327,14 +342,14 @@ pub fn create_character( uuid: &str, character_alias: &str, persisted_components: PersistedComponents, - transactionn: &mut Transaction, + transaction: &mut Transaction, ) -> CharacterCreationResult { - check_character_limit(uuid, transactionn)?; + check_character_limit(uuid, transaction)?; - let (body, _stats, skill_set, inventory, waypoint, _) = persisted_components; + let (body, _stats, skill_set, inventory, waypoint, _, active_abilities) = persisted_components; // Fetch new entity IDs for character, inventory and loadout - let mut new_entity_ids = get_new_entity_ids(transactionn, |next_id| next_id + 3)?; + let mut new_entity_ids = get_new_entity_ids(transaction, |next_id| next_id + 3)?; // Create pseudo-container items for character let character_id = new_entity_ids.next().unwrap(); @@ -365,7 +380,7 @@ pub fn create_character( }, ]; - let mut stmt = transactionn.prepare_cached( + let mut stmt = transaction.prepare_cached( " INSERT INTO item (item_id, parent_container_item_id, @@ -386,7 +401,7 @@ pub fn create_character( } drop(stmt); - let mut stmt = transactionn.prepare_cached( + let mut stmt = transaction.prepare_cached( " INSERT INTO body (body_id, variant, @@ -402,7 +417,7 @@ pub fn create_character( ])?; drop(stmt); - let mut stmt = transactionn.prepare_cached( + let mut stmt = transaction.prepare_cached( " INSERT INTO character (character_id, player_uuid, @@ -421,7 +436,7 @@ pub fn create_character( let db_skill_groups = convert_skill_groups_to_database(character_id, skill_set.skill_groups()); - let mut stmt = transactionn.prepare_cached( + let mut stmt = transaction.prepare_cached( " INSERT INTO skill_group (entity_id, skill_group_kind, @@ -444,10 +459,25 @@ pub fn create_character( } drop(stmt); + let ability_sets = convert_active_abilities_to_database(character_id, &active_abilities); + + let mut stmt = transaction.prepare_cached( + " + INSERT INTO skill_group (entity_id, + ability_sets) + VALUES (?1, ?2)", + )?; + + stmt.execute(&[ + &character_id as &dyn ToSql, + &ability_sets.ability_sets as &dyn ToSql, + ])?; + drop(stmt); + // Insert default inventory and loadout item records let mut inserts = Vec::new(); - get_new_entity_ids(transactionn, |mut next_id| { + get_new_entity_ids(transaction, |mut next_id| { let inserts_ = convert_items_to_database_items( loadout_container_id, &inventory, @@ -458,7 +488,7 @@ pub fn create_character( next_id })?; - let mut stmt = transactionn.prepare_cached( + let mut stmt = transaction.prepare_cached( " INSERT INTO item (item_id, parent_container_item_id, @@ -479,7 +509,7 @@ pub fn create_character( } drop(stmt); - load_character_list(uuid, transactionn).map(|list| (character_id, list)) + load_character_list(uuid, transaction).map(|list| (character_id, list)) } pub fn edit_character( @@ -579,6 +609,17 @@ pub fn delete_character( delete_pets(transaction, char_id, Rc::new(pet_ids))?; } + // Delete ability sets + let mut stmt = transaction.prepare_cached( + " + DELETE + FROM ability_set + WHERE entity_id = ?1", + )?; + + stmt.execute(&[&char_id])?; + drop(stmt); + // Delete character let mut stmt = transaction.prepare_cached( " @@ -892,6 +933,7 @@ pub fn update( inventory: comp::Inventory, pets: Vec, char_waypoint: Option, + active_abilities: comp::ability::ActiveAbilities, transaction: &mut Transaction, ) -> Result<(), PersistenceError> { // Run pet persistence @@ -1035,5 +1077,27 @@ pub fn update( ))); } + let ability_sets = convert_active_abilities_to_database(char_id, &active_abilities); + + let mut stmt = transaction.prepare_cached( + " + UPDATE ability_set + SET ability_sets = ?1 + WHERE entity_id = ?2 + ", + )?; + + let ability_sets_count = stmt.execute(&[ + &ability_sets.ability_sets as &dyn ToSql, + &char_id as &dyn ToSql, + ])?; + + if ability_sets_count != 1 { + return Err(PersistenceError::OtherError(format!( + "Error updating ability_set table for char_id {}", + char_id + ))); + } + Ok(()) } diff --git a/server/src/persistence/character/conversions.rs b/server/src/persistence/character/conversions.rs index a962ea3ce8..4e0132acac 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::{Character, Item, SkillGroup}, + models::{AbilitySets, Character, Item, SkillGroup}, }; use crate::persistence::{ @@ -602,3 +602,29 @@ pub fn convert_skill_groups_to_database<'a, I: Iterator AbilitySets { + let ability_sets = active_abilities + .auxiliary_sets + .iter() + .filter_map(|set| serde_json::to_string(&set).ok()) + .collect::>(); + AbilitySets { + entity_id, + ability_sets: serde_json::to_string(&ability_sets).unwrap_or_default(), + } +} + +pub fn convert_active_abilities_from_database( + ability_sets: &AbilitySets, +) -> ability::ActiveAbilities { + let ability_sets = core::iter::once(ability_sets) + .flat_map(|sets| serde_json::from_str::>(&sets.ability_sets)) + .flatten() + .filter_map(|set| serde_json::from_str::<(ability::AuxiliaryKey, [ability::AuxiliaryAbility; ability::MAX_ABILITIES])>(&set).ok()) + .collect::>(); + ability::ActiveAbilities::new(ability_sets) +} diff --git a/server/src/persistence/character_updater.rs b/server/src/persistence/character_updater.rs index f5d590de87..349ac82ce8 100644 --- a/server/src/persistence/character_updater.rs +++ b/server/src/persistence/character_updater.rs @@ -24,6 +24,7 @@ pub type CharacterUpdateData = ( comp::Inventory, Vec, Option, + comp::ability::ActiveAbilities, ); pub type PetPersistenceData = (comp::Pet, comp::Body, comp::Stats); @@ -330,21 +331,25 @@ impl CharacterUpdater { &'a comp::Inventory, Vec, Option<&'a comp::Waypoint>, + &'a comp::ability::ActiveAbilities, ), >, ) { let updates = updates - .map(|(character_id, skill_set, inventory, pets, waypoint)| { - ( - character_id, + .map( + |(character_id, skill_set, inventory, pets, waypoint, active_abilities)| { ( - skill_set.clone(), - inventory.clone(), - pets, - waypoint.cloned(), - ), - ) - }) + character_id, + ( + skill_set.clone(), + inventory.clone(), + pets, + waypoint.cloned(), + active_abilities.clone(), + ), + ) + }, + ) .chain(self.pending_logout_updates.drain()) .collect::>(); @@ -382,18 +387,19 @@ fn execute_batch_update( let mut transaction = connection.connection.transaction()?; transaction.set_drop_behavior(DropBehavior::Rollback); trace!("Transaction started for character batch update"); - updates - .into_iter() - .try_for_each(|(character_id, (stats, inventory, pets, waypoint))| { + updates.into_iter().try_for_each( + |(character_id, (stats, inventory, pets, waypoint, active_abilities))| { super::character::update( character_id, stats, inventory, pets, waypoint, + active_abilities, &mut transaction, ) - })?; + }, + )?; transaction.commit()?; trace!("Commit for character batch update completed"); diff --git a/server/src/persistence/mod.rs b/server/src/persistence/mod.rs index 8964be8f53..0d78329275 100644 --- a/server/src/persistence/mod.rs +++ b/server/src/persistence/mod.rs @@ -29,6 +29,7 @@ pub type PersistedComponents = ( comp::Inventory, Option, Vec, + comp::ActiveAbilities, ); pub type EditableComponents = (comp::Body,); diff --git a/server/src/persistence/models.rs b/server/src/persistence/models.rs index 42d6468af7..1f339316fb 100644 --- a/server/src/persistence/models.rs +++ b/server/src/persistence/models.rs @@ -35,3 +35,8 @@ pub struct Pet { pub body_variant: String, pub body_data: String, } + +pub struct AbilitySets { + pub entity_id: i64, + pub ability_sets: String, +} diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 50c2c8ac65..e46c2b072b 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -238,10 +238,8 @@ impl StateExt for State { .unwrap_or(0), )) .with(stats) - .with(comp::ActiveAbilities::new( - Some(&inventory), - Some(&skill_set), - )) + // TODO: Figure out way to have this start with sane defaults + .with(comp::ActiveAbilities::default()) .with(skill_set) .maybe_with(health) .with(poise) @@ -500,7 +498,7 @@ impl StateExt for State { } fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents) { - let (body, stats, skill_set, inventory, waypoint, pets) = components; + let (body, stats, skill_set, inventory, waypoint, pets, active_abilities) = components; if let Some(player_uid) = self.read_component_copied::(entity) { // Notify clients of a player list update @@ -530,10 +528,7 @@ impl StateExt for State { self.write_component_ignore_entity_dead(entity, comp::Energy::new(body, energy_level)); self.write_component_ignore_entity_dead(entity, comp::Poise::new(body)); self.write_component_ignore_entity_dead(entity, stats); - self.write_component_ignore_entity_dead( - entity, - comp::ActiveAbilities::new(Some(&inventory), Some(&skill_set)), - ); + self.write_component_ignore_entity_dead(entity, active_abilities); self.write_component_ignore_entity_dead(entity, skill_set); self.write_component_ignore_entity_dead(entity, inventory); self.write_component_ignore_entity_dead( diff --git a/server/src/sys/persistence.rs b/server/src/sys/persistence.rs index addbc00cf3..f483f7be00 100644 --- a/server/src/sys/persistence.rs +++ b/server/src/sys/persistence.rs @@ -2,7 +2,7 @@ use crate::{persistence::character_updater, presence::Presence, sys::SysSchedule use common::{ comp::{ pet::{is_tameable, Pet}, - Alignment, Body, Inventory, SkillSet, Stats, Waypoint, + ActiveAbilities, Alignment, Body, Inventory, SkillSet, Stats, Waypoint, }, uid::Uid, }; @@ -25,6 +25,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Waypoint>, ReadStorage<'a, Pet>, ReadStorage<'a, Stats>, + ReadStorage<'a, ActiveAbilities>, WriteExpect<'a, character_updater::CharacterUpdater>, Write<'a, SysScheduler>, ); @@ -45,6 +46,7 @@ impl<'a> System<'a> for Sys { player_waypoints, pets, stats, + active_abilities, mut updater, mut scheduler, ): Self::SystemData, @@ -57,11 +59,18 @@ impl<'a> System<'a> for Sys { &player_inventories, &uids, player_waypoints.maybe(), + &active_abilities, ) .join() .filter_map( - |(presence, skill_set, inventory, player_uid, waypoint)| match presence.kind - { + |( + presence, + skill_set, + inventory, + player_uid, + waypoint, + active_abilities, + )| match presence.kind { PresenceKind::Character(id) => { let pets = (&alignments, &bodies, &stats, &pets) .join() @@ -78,7 +87,7 @@ impl<'a> System<'a> for Sys { }) .collect(); - Some((id, skill_set, inventory, pets, waypoint)) + Some((id, skill_set, inventory, pets, waypoint, active_abilities)) }, PresenceKind::Spectator => None, },