Persistence of auxiliary abilities.

This commit is contained in:
Sam 2021-11-26 23:19:46 -05:00
parent d86692c4fe
commit 4c3771a1a0
13 changed files with 186 additions and 49 deletions

View File

@ -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<AuxiliaryKey, [AuxiliaryAbility; MAX_ABILITIES]>) -> Self {
ActiveAbilities {
auxiliary_sets,
..Self::default()
}
}
pub fn change_ability(

View File

@ -115,6 +115,7 @@ pub enum ServerEvent {
comp::Inventory,
Option<comp::Waypoint>,
Vec<(comp::Pet, comp::Body, comp::Stats)>,
comp::ActiveAbilities,
),
},
ExitIngame {

View File

@ -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(())
}

View File

@ -42,6 +42,7 @@ pub fn handle_loaded_character_data(
comp::Inventory,
Option<comp::Waypoint>,
Vec<(comp::Pet, comp::Body, comp::Stats)>,
comp::ActiveAbilities,
),
) {
server

View File

@ -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::<Presence>().get(entity),
state.read_storage::<comp::SkillSet>().get(entity),
state.read_storage::<comp::Inventory>().get(entity),
state
.read_storage::<comp::ability::ActiveAbilities>()
.get(entity),
state.read_storage::<Uid>().get(entity),
state.read_storage::<comp::Player>().get(entity),
state.ecs().fetch_mut::<CharacterUpdater>(),
@ -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 */ },

View File

@ -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

View File

@ -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::<Vec<(comp::Pet, comp::Body, comp::Stats)>>();
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<PetPersistenceData>,
char_waypoint: Option<comp::Waypoint>,
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(())
}

View File

@ -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<Item = &'a skillset::Ski
})
.collect()
}
pub fn convert_active_abilities_to_database(
entity_id: CharacterId,
active_abilities: &ability::ActiveAbilities,
) -> AbilitySets {
let ability_sets = active_abilities
.auxiliary_sets
.iter()
.filter_map(|set| serde_json::to_string(&set).ok())
.collect::<Vec<String>>();
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::<Vec<String>>(&sets.ability_sets))
.flatten()
.filter_map(|set| serde_json::from_str::<(ability::AuxiliaryKey, [ability::AuxiliaryAbility; ability::MAX_ABILITIES])>(&set).ok())
.collect::<HashMap<ability::AuxiliaryKey, [ability::AuxiliaryAbility; ability::MAX_ABILITIES]>>();
ability::ActiveAbilities::new(ability_sets)
}

View File

@ -24,6 +24,7 @@ pub type CharacterUpdateData = (
comp::Inventory,
Vec<PetPersistenceData>,
Option<comp::Waypoint>,
comp::ability::ActiveAbilities,
);
pub type PetPersistenceData = (comp::Pet, comp::Body, comp::Stats);
@ -330,21 +331,25 @@ impl CharacterUpdater {
&'a comp::Inventory,
Vec<PetPersistenceData>,
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::<Vec<_>>();
@ -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");

View File

@ -29,6 +29,7 @@ pub type PersistedComponents = (
comp::Inventory,
Option<comp::Waypoint>,
Vec<PetPersistenceData>,
comp::ActiveAbilities,
);
pub type EditableComponents = (comp::Body,);

View File

@ -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,
}

View File

@ -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::<Uid>(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(

View File

@ -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<Self>>,
);
@ -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,
},