mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Added sentiments
This commit is contained in:
parent
d7ba4ecef7
commit
08338436ea
@ -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;
|
||||||
|
|
||||||
|
@ -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::{
|
||||||
|
@ -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
192
rtsim/src/data/sentiment.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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(
|
||||||
|
&[&char_id.0 as &dyn ToSql, &requesting_player_uuid],
|
||||||
|
|row| {
|
||||||
let y: i64 = row.get(0)?;
|
let y: i64 = row.get(0)?;
|
||||||
Ok(y)
|
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,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user