Added sentiments

This commit is contained in:
Joshua Barretto 2023-04-06 12:29:20 +01:00
parent d7ba4ecef7
commit 08338436ea
8 changed files with 259 additions and 48 deletions

View File

@ -5,7 +5,8 @@ use serde::{Deserialize, Serialize};
/// The limit on how many characters that a player can have /// The limit on how many characters that a player can have
pub const MAX_CHARACTERS_PER_PLAYER: usize = 8; pub const MAX_CHARACTERS_PER_PLAYER: usize = 8;
pub type CharacterId = i64; #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct CharacterId(pub i64);
pub const MAX_NAME_LENGTH: usize = 20; pub const MAX_NAME_LENGTH: usize = 20;

View File

@ -1,6 +1,7 @@
pub mod faction; pub mod faction;
pub mod nature; pub mod nature;
pub mod npc; pub mod npc;
pub mod sentiment;
pub mod site; pub mod site;
pub use self::{ pub use self::{

View File

@ -1,6 +1,7 @@
use crate::{ai::Action, gen::name}; use crate::{ai::Action, data::sentiment::Sentiments, gen::name};
pub use common::rtsim::{NpcId, Profession}; pub use common::rtsim::{NpcId, Profession};
use common::{ use common::{
character::CharacterId,
comp, comp,
grid::Grid, grid::Grid,
rtsim::{ rtsim::{
@ -91,7 +92,10 @@ pub struct Npc {
pub faction: Option<FactionId>, pub faction: Option<FactionId>,
pub riding: Option<Riding>, pub riding: Option<Riding>,
#[serde(default)]
pub personality: Personality, pub personality: Personality,
#[serde(default)]
pub sentiments: Sentiments,
// Unpersisted state // Unpersisted state
#[serde(skip)] #[serde(skip)]
@ -124,6 +128,7 @@ impl Clone for Npc {
riding: self.riding.clone(), riding: self.riding.clone(),
body: self.body, body: self.body,
personality: self.personality, personality: self.personality,
sentiments: self.sentiments.clone(),
// Not persisted // Not persisted
chunk_pos: None, chunk_pos: None,
current_site: Default::default(), current_site: Default::default(),
@ -144,6 +149,7 @@ impl Npc {
wpos, wpos,
body, body,
personality: Personality::default(), personality: Personality::default(),
sentiments: Sentiments::default(),
profession: None, profession: None,
home: None, home: None,
faction: None, faction: None,
@ -274,7 +280,7 @@ pub struct Npcs {
#[serde(skip, default = "construct_npc_grid")] #[serde(skip, default = "construct_npc_grid")]
pub npc_grid: Grid<GridCell>, pub npc_grid: Grid<GridCell>,
#[serde(skip)] #[serde(skip)]
pub character_map: HashMap<Vec2<i32>, Vec<(common::character::CharacterId, Vec3<f32>)>>, pub character_map: HashMap<Vec2<i32>, Vec<(CharacterId, Vec3<f32>)>>,
} }
impl Default for Npcs { impl Default for Npcs {

192
rtsim/src/data/sentiment.rs Normal file
View File

@ -0,0 +1,192 @@
use common::{
character::CharacterId,
rtsim::{Actor, FactionId, NpcId},
};
use rand::prelude::*;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
// Factions have a larger 'social memory' than individual NPCs and so we allow
// them to have more sentiments
pub const FACTION_MAX_SENTIMENTS: usize = 1024;
pub const NPC_MAX_SENTIMENTS: usize = 128;
/// The target that a sentiment is felt toward.
// NOTE: More could be added to this! For example:
// - Animal species (dislikes spiders?)
// - Kind of food (likes meat?)
// - Occupations (hatred of hunters or chefs?)
// - Ideologies (dislikes democracy, likes monarchy?)
// - etc.
#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum Target {
Character(CharacterId),
Npc(NpcId),
Faction(FactionId),
}
impl From<NpcId> for Target {
fn from(npc: NpcId) -> Self { Self::Npc(npc) }
}
impl From<FactionId> for Target {
fn from(faction: FactionId) -> Self { Self::Faction(faction) }
}
impl From<CharacterId> for Target {
fn from(character: CharacterId) -> Self { Self::Character(character) }
}
impl From<Actor> for Target {
fn from(actor: Actor) -> Self {
match actor {
Actor::Character(character) => Self::Character(character),
Actor::Npc(npc) => Self::Npc(npc),
}
}
}
#[derive(Clone, Default, Serialize, Deserialize)]
pub struct Sentiments {
#[serde(rename = "m")]
map: HashMap<Target, Sentiment>,
}
impl Sentiments {
/// Return the sentiment that is felt toward the given target.
pub fn toward(&self, target: impl Into<Target>) -> Sentiment {
self.map.get(&target.into()).copied().unwrap_or_default()
}
pub fn change_by(&mut self, target: impl Into<Target>, change: f32) {
let target = target.into();
self.map.entry(target).or_default().change_by(change);
}
/// Progressively decay the sentiment back to a neutral sentiment.
///
/// Note that sentiment get decay gets slower the harsher the sentiment is.
/// You can calculate the **average** number of ticks required for a
/// sentiment to decay with the following formula:
///
/// ```
/// ticks_until_neutrality = ((sentiment_value * 127 * 32) ^ 2) / 2
/// ```
///
/// For example, a positive (see [`Sentiment::POSITIVE`]) sentiment has a
/// value of `0.2`, so we get
///
/// ```
/// ticks_until_neutrality = ((0.1 * 127 * 32) ^ 2) / 2 = ~82,580 ticks
/// ```
///
/// Assuming a TPS of 30, that's ~46 minutes.
///
/// Some 'common' sentiment decay times are as follows:
///
/// - `POSITIVE`/`NEGATIVE`: ~46 minutes
/// - `ALLY`/`RIVAL`: ~6.9 hours
/// - `FRIEND`/`ENEMY`: ~27.5 hours
/// - `HERO`/`VILLAIN`: ~48.9 hours
pub fn decay(&mut self, rng: &mut impl Rng) {
self.map.retain(|_, sentiment| {
sentiment.decay(rng);
// We can eliminate redundant sentiments that don't need remembering
!sentiment.is_redundant()
});
}
/// Clean up sentiments to avoid them growing too large
pub fn cleanup(&mut self, max_sentiments: usize) {
if self.map.len() > max_sentiments {
let mut sentiments = self.map
.iter()
// For each sentiment, calculate how valuable it is for us to remember.
// For now, we just use the absolute value of the sentiment but later on we might want to favour
// sentiments toward factions and other 'larger' groups over, say, sentiments toward players/other NPCs
.map(|(tgt, sentiment)| (*tgt, sentiment.positivity.unsigned_abs()))
.collect::<Vec<_>>();
sentiments.sort_unstable_by_key(|(_, value)| *value);
// Remove the superfluous sentiments
for (tgt, _) in &sentiments[0..self.map.len() - max_sentiments] {
self.map.remove(tgt);
}
}
}
}
#[derive(Copy, Clone, Default, Serialize, Deserialize)]
pub struct Sentiment {
/// How positive the sentiment is.
///
/// Using i8 to reduce on-disk memory footprint.
/// Semantically, this value is -1 <= x <= 1.
#[serde(rename = "p")]
positivity: i8,
}
impl Sentiment {
/// Substantial positive sentiments: NPC may go out of their way to help
/// actors associated with the target, greet them, etc.
pub const ALLY: f32 = 0.3;
/// Very negative sentiments: NPC may confront the actor, get aggressive
/// with them, or even use force against them.
pub const ENEMY: f32 = -0.6;
/// Very positive sentiments: NPC may join the actor as a companion,
/// encourage them to join their faction, etc.
pub const FRIEND: f32 = 0.6;
/// Extremely positive sentiments: NPC may switch sides to join the actor's
/// faction, protect them at all costs, turn against friends for them,
/// etc. Verging on cult-like behaviour.
pub const HERO: f32 = 0.8;
/// Minor negative sentiments: NPC might be less willing to provide
/// information, give worse trade deals, etc.
pub const NEGATIVE: f32 = -0.1;
/// Minor positive sentiments: NPC might be more willing to provide
/// information, give better trade deals, etc.
pub const POSITIVE: f32 = 0.1;
/// Substantial positive sentiments: NPC may reject attempts to trade or
/// avoid actors associated with the target, insult them, but will not
/// use physical force.
pub const RIVAL: f32 = -0.3;
/// Extremely negative sentiments: NPC may aggressively persue or hunt down
/// the actor, organise others around them to do the same, and will
/// generally try to harm the actor in any way they can.
pub const VILLAIN: f32 = -0.8;
fn value(&self) -> f32 { self.positivity as f32 / 127.0 }
fn change_by(&mut self, change: f32) {
// There's a bit of ceremony here for two reasons:
// 1) Very small changes should not be rounded to 0
// 2) Sentiment should never (over/under)flow
if change != 0.0 {
let abs = (change * 127.0).abs().clamp(1.0, 127.0) as i8;
self.positivity = if change > 0.0 {
self.positivity.saturating_add(abs)
} else {
self.positivity.saturating_sub(abs)
};
}
}
fn decay(&mut self, rng: &mut impl Rng) {
if self.positivity != 0 {
// TODO: Make dt-independent so we can slow tick rates
if rng.gen_range(0..self.positivity.unsigned_abs() as u32 * 1024) == 0 {
self.positivity -= self.positivity.signum();
}
}
}
/// Return `true` if the sentiment can be forgotten without changing
/// anything (i.e: is entirely neutral, the default stance).
fn is_redundant(&self) -> bool { self.positivity == 0 }
/// Returns `true` if the sentiment has reached the given threshold.
pub fn is(&self, val: f32) -> bool {
if val > 0.0 {
self.value() >= val
} else {
self.value() <= val
}
}
}

View File

@ -139,7 +139,7 @@ pub fn load_character_data(
)?; )?;
let (body_data, character_data) = stmt.query_row( let (body_data, character_data) = stmt.query_row(
&[requesting_player_uuid.clone(), char_id.to_string()], &[requesting_player_uuid.clone(), char_id.0.to_string()],
|row| { |row| {
let character_data = Character { let character_data = Character {
character_id: row.get(0)?, character_id: row.get(0)?,
@ -168,7 +168,7 @@ pub fn load_character_data(
warn!( warn!(
"Error reading waypoint from database for character ID "Error reading waypoint from database for character ID
{}, error: {}", {}, error: {}",
char_id, e char_id.0, e
); );
(None, None) (None, None)
}, },
@ -187,9 +187,9 @@ pub fn load_character_data(
)?; )?;
let skill_group_data = stmt let skill_group_data = stmt
.query_map(&[char_id], |row| { .query_map(&[char_id.0], |row| {
Ok(SkillGroup { Ok(SkillGroup {
entity_id: char_id, entity_id: char_id.0,
skill_group_kind: row.get(0)?, skill_group_kind: row.get(0)?,
earned_exp: row.get(1)?, earned_exp: row.get(1)?,
spent_exp: row.get(2)?, spent_exp: row.get(2)?,
@ -212,7 +212,7 @@ pub fn load_character_data(
)?; )?;
let db_pets = stmt let db_pets = stmt
.query_map(&[char_id], |row| { .query_map(&[char_id.0], |row| {
Ok(Pet { Ok(Pet {
database_id: row.get(0)?, database_id: row.get(0)?,
name: row.get(1)?, name: row.get(1)?,
@ -240,7 +240,7 @@ pub fn load_character_data(
} else { } else {
warn!( warn!(
"Failed to deserialize pet_id: {} for character_id {}", "Failed to deserialize pet_id: {} for character_id {}",
db_pet.database_id, char_id db_pet.database_id, char_id.0
); );
None None
} }
@ -254,9 +254,9 @@ pub fn load_character_data(
WHERE entity_id = ?1", WHERE entity_id = ?1",
)?; )?;
let ability_set_data = stmt.query_row(&[char_id], |row| { let ability_set_data = stmt.query_row(&[char_id.0], |row| {
Ok(AbilitySets { Ok(AbilitySets {
entity_id: char_id, entity_id: char_id.0,
ability_sets: row.get(0)?, ability_sets: row.get(0)?,
}) })
})?; })?;
@ -329,7 +329,7 @@ pub fn load_character_list(player_uuid_: &str, connection: &Connection) -> Chara
FROM body FROM body
WHERE body_id = ?1", WHERE body_id = ?1",
)?; )?;
let db_body = stmt.query_row(&[char.id], |row| { let db_body = stmt.query_row(&[char.id.map(|c| c.0)], |row| {
Ok(Body { Ok(Body {
body_id: row.get(0)?, body_id: row.get(0)?,
variant: row.get(1)?, variant: row.get(1)?,
@ -342,7 +342,7 @@ pub fn load_character_list(player_uuid_: &str, connection: &Connection) -> Chara
let loadout_container_id = get_pseudo_container_id( let loadout_container_id = get_pseudo_container_id(
connection, connection,
character_data.character_id, CharacterId(character_data.character_id),
LOADOUT_PSEUDO_CONTAINER_POSITION, LOADOUT_PSEUDO_CONTAINER_POSITION,
)?; )?;
@ -470,7 +470,8 @@ pub fn create_character(
])?; ])?;
drop(stmt); drop(stmt);
let db_skill_groups = convert_skill_groups_to_database(character_id, skill_set.skill_groups()); let db_skill_groups =
convert_skill_groups_to_database(CharacterId(character_id), skill_set.skill_groups());
let mut stmt = transaction.prepare_cached( let mut stmt = transaction.prepare_cached(
" "
@ -495,7 +496,8 @@ pub fn create_character(
} }
drop(stmt); drop(stmt);
let ability_sets = convert_active_abilities_to_database(character_id, &active_abilities); let ability_sets =
convert_active_abilities_to_database(CharacterId(character_id), &active_abilities);
let mut stmt = transaction.prepare_cached( let mut stmt = transaction.prepare_cached(
" "
@ -547,7 +549,7 @@ pub fn create_character(
} }
drop(stmt); drop(stmt);
load_character_list(uuid, transaction).map(|list| (character_id, list)) load_character_list(uuid, transaction).map(|list| (CharacterId(character_id), list))
} }
pub fn edit_character( pub fn edit_character(
@ -570,7 +572,7 @@ pub fn edit_character(
warn!( warn!(
"Character edit rejected due to failed validation - Character ID: {} \ "Character edit rejected due to failed validation - Character ID: {} \
Alias: {}", Alias: {}",
character_id, character_alias character_id.0, character_alias
); );
return Err(PersistenceError::CharacterDataError); return Err(PersistenceError::CharacterDataError);
} else { } else {
@ -587,14 +589,14 @@ pub fn edit_character(
stmt.execute(&[ stmt.execute(&[
&body_variant.to_string(), &body_variant.to_string(),
&body_data, &body_data,
&character_id as &dyn ToSql, &character_id.0 as &dyn ToSql,
])?; ])?;
drop(stmt); drop(stmt);
let mut stmt = let mut stmt =
transaction.prepare_cached("UPDATE character SET alias = ?1 WHERE character_id = ?2")?; transaction.prepare_cached("UPDATE character SET alias = ?1 WHERE character_id = ?2")?;
stmt.execute(&[&character_alias, &character_id as &dyn ToSql])?; stmt.execute(&[&character_alias, &character_id.0 as &dyn ToSql])?;
drop(stmt); drop(stmt);
char_list.map(|list| (character_id, list)) char_list.map(|list| (character_id, list))
@ -616,10 +618,13 @@ pub fn delete_character(
AND player_uuid = ?2", AND player_uuid = ?2",
)?; )?;
let result = stmt.query_row(&[&char_id as &dyn ToSql, &requesting_player_uuid], |row| { let result = stmt.query_row(
let y: i64 = row.get(0)?; &[&char_id.0 as &dyn ToSql, &requesting_player_uuid],
Ok(y) |row| {
})?; let y: i64 = row.get(0)?;
Ok(y)
},
)?;
drop(stmt); drop(stmt);
if result != 1 { if result != 1 {
@ -636,7 +641,7 @@ pub fn delete_character(
WHERE entity_id = ?1", WHERE entity_id = ?1",
)?; )?;
stmt.execute(&[&char_id])?; stmt.execute(&[&char_id.0])?;
drop(stmt); drop(stmt);
let pet_ids = get_pet_ids(char_id, transaction)? let pet_ids = get_pet_ids(char_id, transaction)?
@ -655,7 +660,7 @@ pub fn delete_character(
WHERE entity_id = ?1", WHERE entity_id = ?1",
)?; )?;
stmt.execute(&[&char_id])?; stmt.execute(&[&char_id.0])?;
drop(stmt); drop(stmt);
// Delete character // Delete character
@ -666,7 +671,7 @@ pub fn delete_character(
WHERE character_id = ?1", WHERE character_id = ?1",
)?; )?;
stmt.execute(&[&char_id])?; stmt.execute(&[&char_id.0])?;
drop(stmt); drop(stmt);
// Delete body // Delete body
@ -677,7 +682,7 @@ pub fn delete_character(
WHERE body_id = ?1", WHERE body_id = ?1",
)?; )?;
stmt.execute(&[&char_id])?; stmt.execute(&[&char_id.0])?;
drop(stmt); drop(stmt);
// Delete all items, recursively walking all containers starting from the // Delete all items, recursively walking all containers starting from the
@ -701,14 +706,14 @@ pub fn delete_character(
WHERE EXISTS (SELECT 1 FROM parents WHERE parents.item_id = item.item_id)", WHERE EXISTS (SELECT 1 FROM parents WHERE parents.item_id = item.item_id)",
)?; )?;
let deleted_item_count = stmt.execute(&[&char_id])?; let deleted_item_count = stmt.execute(&[&char_id.0])?;
drop(stmt); drop(stmt);
if deleted_item_count < 3 { if deleted_item_count < 3 {
return Err(PersistenceError::OtherError(format!( return Err(PersistenceError::OtherError(format!(
"Error deleting from item table for char_id {} (expected at least 3 deletions, found \ "Error deleting from item table for char_id {} (expected at least 3 deletions, found \
{})", {})",
char_id, deleted_item_count char_id.0, deleted_item_count
))); )));
} }
@ -822,7 +827,7 @@ fn get_pseudo_container_id(
#[allow(clippy::needless_question_mark)] #[allow(clippy::needless_question_mark)]
let res = stmt.query_row( let res = stmt.query_row(
&[ &[
character_id.to_string(), character_id.0.to_string(),
pseudo_container_position.to_string(), pseudo_container_position.to_string(),
], ],
|row| Ok(row.get(0)?), |row| Ok(row.get(0)?),
@ -850,7 +855,7 @@ fn update_pets(
pets: Vec<PetPersistenceData>, pets: Vec<PetPersistenceData>,
transaction: &mut Transaction, transaction: &mut Transaction,
) -> Result<(), PersistenceError> { ) -> Result<(), PersistenceError> {
debug!("Updating {} pets for character {}", pets.len(), char_id); debug!("Updating {} pets for character {}", pets.len(), char_id.0);
let db_pets = get_pet_ids(char_id, transaction)?; let db_pets = get_pet_ids(char_id, transaction)?;
if !db_pets.is_empty() { if !db_pets.is_empty() {
@ -907,7 +912,7 @@ fn update_pets(
VALUES (?1, ?2, ?3)", VALUES (?1, ?2, ?3)",
)?; )?;
stmt.execute(&[&pet_entity_id as &dyn ToSql, &char_id, &stats.name])?; stmt.execute(&[&pet_entity_id as &dyn ToSql, &char_id.0, &stats.name])?;
drop(stmt); drop(stmt);
pet.get_database_id() pet.get_database_id()
@ -917,7 +922,10 @@ fn update_pets(
Ok(()) Ok(())
} }
fn get_pet_ids(char_id: i64, transaction: &mut Transaction) -> Result<Vec<i64>, PersistenceError> { fn get_pet_ids(
char_id: CharacterId,
transaction: &mut Transaction,
) -> Result<Vec<i64>, PersistenceError> {
#[rustfmt::skip] #[rustfmt::skip]
let mut stmt = transaction.prepare_cached(" let mut stmt = transaction.prepare_cached("
SELECT pet_id SELECT pet_id
@ -927,7 +935,7 @@ fn get_pet_ids(char_id: i64, transaction: &mut Transaction) -> Result<Vec<i64>,
#[allow(clippy::needless_question_mark)] #[allow(clippy::needless_question_mark)]
let db_pets = stmt let db_pets = stmt
.query_map(&[&char_id], |row| Ok(row.get(0)?))? .query_map(&[&char_id.0], |row| Ok(row.get(0)?))?
.map(|x| x.unwrap()) .map(|x| x.unwrap())
.collect::<Vec<i64>>(); .collect::<Vec<i64>>();
drop(stmt); drop(stmt);
@ -948,7 +956,10 @@ fn delete_pets(
let delete_count = stmt.execute(&[&pet_ids])?; let delete_count = stmt.execute(&[&pet_ids])?;
drop(stmt); drop(stmt);
debug!("Deleted {} pets for character id {}", delete_count, char_id); debug!(
"Deleted {} pets for character id {}",
delete_count, char_id.0
);
#[rustfmt::skip] #[rustfmt::skip]
let mut stmt = transaction.prepare_cached(" let mut stmt = transaction.prepare_cached("
@ -960,7 +971,7 @@ fn delete_pets(
let delete_count = stmt.execute(&[&pet_ids])?; let delete_count = stmt.execute(&[&pet_ids])?;
debug!( debug!(
"Deleted {} pet bodies for character id {}", "Deleted {} pet bodies for character id {}",
delete_count, char_id delete_count, char_id.0
); );
Ok(()) Ok(())
@ -996,7 +1007,7 @@ pub fn update(
})?; })?;
// Next, delete any slots we aren't upserting. // Next, delete any slots we aren't upserting.
trace!("Deleting items for character_id {}", char_id); trace!("Deleting items for character_id {}", char_id.0);
let mut existing_item_ids: Vec<_> = vec![ let mut existing_item_ids: Vec<_> = vec![
Value::from(pseudo_containers.inventory_container_id), Value::from(pseudo_containers.inventory_container_id),
Value::from(pseudo_containers.loadout_container_id), Value::from(pseudo_containers.loadout_container_id),
@ -1040,7 +1051,7 @@ pub fn update(
trace!( trace!(
"Upserting items {:?} for character_id {}", "Upserting items {:?} for character_id {}",
upserted_items, upserted_items,
char_id char_id.0
); );
// When moving inventory items around, foreign key constraints on // When moving inventory items around, foreign key constraints on
@ -1111,12 +1122,12 @@ pub fn update(
", ",
)?; )?;
let waypoint_count = stmt.execute(&[&db_waypoint as &dyn ToSql, &char_id])?; let waypoint_count = stmt.execute(&[&db_waypoint as &dyn ToSql, &char_id.0])?;
if waypoint_count != 1 { if waypoint_count != 1 {
return Err(PersistenceError::OtherError(format!( return Err(PersistenceError::OtherError(format!(
"Error updating character table for char_id {}", "Error updating character table for char_id {}",
char_id char_id.0
))); )));
} }
@ -1132,13 +1143,13 @@ pub fn update(
let ability_sets_count = stmt.execute(&[ let ability_sets_count = stmt.execute(&[
&ability_sets.ability_sets as &dyn ToSql, &ability_sets.ability_sets as &dyn ToSql,
&char_id as &dyn ToSql, &char_id.0 as &dyn ToSql,
])?; ])?;
if ability_sets_count != 1 { if ability_sets_count != 1 {
return Err(PersistenceError::OtherError(format!( return Err(PersistenceError::OtherError(format!(
"Error updating ability_set table for char_id {}", "Error updating ability_set table for char_id {}",
char_id, char_id.0,
))); )));
} }

View File

@ -608,7 +608,7 @@ pub fn convert_body_from_database(
pub fn convert_character_from_database(character: &Character) -> common::character::Character { pub fn convert_character_from_database(character: &Character) -> common::character::Character {
common::character::Character { common::character::Character {
id: Some(character.character_id), id: Some(CharacterId(character.character_id)),
alias: String::from(&character.alias), alias: String::from(&character.alias),
} }
} }
@ -704,7 +704,7 @@ pub fn convert_skill_groups_to_database<'a, I: Iterator<Item = &'a skillset::Ski
skill_groups skill_groups
.into_iter() .into_iter()
.map(|sg| SkillGroup { .map(|sg| SkillGroup {
entity_id, entity_id: entity_id.0,
skill_group_kind: json_models::skill_group_to_db_string(sg.skill_group_kind), skill_group_kind: json_models::skill_group_to_db_string(sg.skill_group_kind),
earned_exp: i64::from(sg.earned_exp), earned_exp: i64::from(sg.earned_exp),
spent_exp: i64::from(sg.spent_exp()), spent_exp: i64::from(sg.spent_exp()),
@ -724,7 +724,7 @@ pub fn convert_active_abilities_to_database(
) -> AbilitySets { ) -> AbilitySets {
let ability_sets = json_models::active_abilities_to_db_model(active_abilities); let ability_sets = json_models::active_abilities_to_db_model(active_abilities);
AbilitySets { AbilitySets {
entity_id, entity_id: entity_id.0,
ability_sets: serde_json::to_string(&ability_sets).unwrap_or_default(), ability_sets: serde_json::to_string(&ability_sets).unwrap_or_default(),
} }
} }

View File

@ -139,7 +139,7 @@ impl CharacterLoader {
if result.is_err() { if result.is_err() {
error!( error!(
?result, ?result,
"Error loading character data for character_id: {}", character_id "Error loading character data for character_id: {}", character_id.0
); );
} }
CharacterScreenResponseKind::CharacterData(Box::new(result)) CharacterScreenResponseKind::CharacterData(Box::new(result))

View File

@ -242,7 +242,7 @@ impl CharacterUpdater {
warn!( warn!(
"Ignoring request to add pending logout update for character ID {} as there is a \ "Ignoring request to add pending logout update for character ID {} as there is a \
disconnection of all clients in progress", disconnection of all clients in progress",
update_data.0 update_data.0.0
); );
return; return;
} }
@ -251,7 +251,7 @@ impl CharacterUpdater {
warn!( warn!(
"Ignoring request to add pending logout update for character ID {} as there is \ "Ignoring request to add pending logout update for character ID {} as there is \
already a pending delete for this character", already a pending delete for this character",
update_data.0 update_data.0.0
); );
return; return;
} }