blastmud/blastmud_game/src/models/user.rs
2023-10-01 16:34:25 +11:00

226 lines
6.1 KiB
Rust

use super::{
item::{BuffImpact, Item, SkillType, StatType},
journal::JournalState,
};
#[double]
use crate::db::DBTrans;
use crate::{message_handler::ListenerSession, DResult};
use chrono::{DateTime, Utc};
use mockall_double::double;
use once_cell::sync::OnceCell;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct UserTermData {
pub accepted_terms: BTreeMap<String, DateTime<Utc>>,
pub terms_complete: bool, // Recalculated on accept and login.
pub last_presented_term: Option<String>,
}
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
#[serde(default)]
pub struct UserExperienceData {
pub spent_xp: u64, // Since last chargen complete.
pub journals: JournalState,
pub xp_change_for_this_reroll: i64,
pub crafted_items: BTreeMap<String, u64>,
}
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
pub enum UserFlag {
Staff,
}
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum WristpadHack {
Superdork,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)]
pub struct WristpadHackData {
pub name: &'static str,
pub buff: Vec<BuffImpact>,
}
pub fn wristpad_hack_data() -> &'static BTreeMap<WristpadHack, WristpadHackData> {
static D: OnceCell<BTreeMap<WristpadHack, WristpadHackData>> = OnceCell::new();
D.get_or_init(|| {
vec![(
WristpadHack::Superdork,
WristpadHackData {
name: "Superdork",
buff: vec![
BuffImpact::ChangeStat {
stat: StatType::Brains,
magnitude: 3.0,
},
BuffImpact::ChangeStat {
stat: StatType::Cool,
magnitude: -1.0,
},
],
},
)]
.into_iter()
.collect()
})
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(default)]
pub struct User {
pub username: String,
pub password_hash: String, // bcrypted.
pub email: String,
pub player_item_id: i64,
pub registered_at: Option<DateTime<Utc>>,
pub banned_until: Option<DateTime<Utc>>,
pub abandoned_at: Option<DateTime<Utc>>,
pub chargen_last_completed_at: Option<DateTime<Utc>>,
pub terms: UserTermData,
pub experience: UserExperienceData,
pub raw_skills: BTreeMap<SkillType, f64>,
pub raw_stats: BTreeMap<StatType, f64>,
pub wristpad_hacks: Vec<WristpadHack>,
pub last_skill_improve: BTreeMap<SkillType, DateTime<Utc>>,
pub last_page_from: Option<String>,
pub credits: u64,
pub danger_code: Option<String>,
pub user_flags: Vec<UserFlag>,
// Reminder: Consider backwards compatibility when updating this.
}
static NO_BUFFS: Vec<BuffImpact> = vec![];
pub fn xp_to_hack_slots(xp: u64) -> u64 {
if xp >= 1000000 {
14
} else if xp >= 500000 {
13
} else if xp >= 100000 {
12
} else if xp >= 70000 {
11
} else if xp >= 40000 {
10
} else if xp >= 20000 {
9
} else if xp >= 10000 {
8
} else if xp >= 8000 {
7
} else if xp >= 6000 {
6
} else if xp >= 4000 {
5
} else if xp >= 2000 {
4
} else if xp >= 1500 {
3
} else if xp >= 1000 {
2
} else if xp >= 500 {
1
} else {
0
}
}
impl User {
pub fn wristpad_hack_buffs<'a>(self: &'a Self) -> impl Iterator<Item = &'a BuffImpact> + 'a {
self.wristpad_hacks.iter().flat_map(|h| {
wristpad_hack_data()
.get(h)
.map(|hd| &hd.buff)
.unwrap_or_else(|| &NO_BUFFS)
})
}
pub async fn adjust_xp_for_reroll(
self: &mut User,
item: &mut Item,
change: i64,
trans: &DBTrans,
sess: &ListenerSession,
) -> DResult<()> {
self.experience.xp_change_for_this_reroll += change;
self.xp_adjusted(item, change, trans, sess).await
}
pub async fn xp_adjusted(
self: &mut User,
item: &mut Item,
change: i64,
trans: &DBTrans,
sess: &ListenerSession,
) -> DResult<()> {
let old_slots = xp_to_hack_slots(item.total_xp);
if change >= 0 {
item.total_xp += change as u64;
} else if (-change) as u64 <= item.total_xp {
item.total_xp -= (-change) as u64;
} else {
item.total_xp = 0;
}
let new_slots = xp_to_hack_slots(item.total_xp);
if new_slots > old_slots {
trans
.queue_for_session(
sess,
Some("You just earned a new hack slot on your wristpad!\n"),
)
.await?;
}
Ok(())
}
}
impl Default for UserTermData {
fn default() -> Self {
UserTermData {
accepted_terms: BTreeMap::new(),
terms_complete: false,
last_presented_term: None,
}
}
}
impl Default for UserExperienceData {
fn default() -> Self {
UserExperienceData {
spent_xp: 0,
journals: Default::default(),
xp_change_for_this_reroll: 0,
crafted_items: BTreeMap::new(),
}
}
}
impl Default for User {
fn default() -> Self {
User {
username: "unknown".to_owned(),
password_hash: "unknown".to_owned(),
email: "unknown".to_owned(),
player_item_id: 0,
registered_at: None,
banned_until: None,
abandoned_at: None,
chargen_last_completed_at: None,
terms: UserTermData::default(),
experience: UserExperienceData::default(),
raw_skills: BTreeMap::new(),
raw_stats: BTreeMap::new(),
wristpad_hacks: vec![],
last_skill_improve: BTreeMap::new(),
last_page_from: None,
credits: 500,
danger_code: None,
user_flags: vec![],
}
}
}