2023-05-16 22:02:42 +10:00
|
|
|
use super::{
|
2023-09-23 23:55:29 +10:00
|
|
|
item::{BuffImpact, Item, SkillType, StatType},
|
2023-06-07 22:38:46 +10:00
|
|
|
journal::JournalState,
|
2023-05-16 22:02:42 +10:00
|
|
|
};
|
2023-09-23 23:55:29 +10:00
|
|
|
#[double]
|
|
|
|
use crate::db::DBTrans;
|
|
|
|
use crate::{message_handler::ListenerSession, DResult};
|
2023-06-07 22:38:46 +10:00
|
|
|
use chrono::{DateTime, Utc};
|
2023-09-23 23:55:29 +10:00
|
|
|
use mockall_double::double;
|
|
|
|
use once_cell::sync::OnceCell;
|
2023-06-07 22:38:46 +10:00
|
|
|
use serde::{Deserialize, Serialize};
|
2022-12-26 01:30:59 +11:00
|
|
|
use std::collections::BTreeMap;
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
|
|
pub struct UserTermData {
|
|
|
|
pub accepted_terms: BTreeMap<String, DateTime<Utc>>,
|
2022-12-27 00:20:09 +11:00
|
|
|
pub terms_complete: bool, // Recalculated on accept and login.
|
2022-12-26 01:30:59 +11:00
|
|
|
pub last_presented_term: Option<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
2023-05-16 22:02:42 +10:00
|
|
|
#[serde(default)]
|
2022-12-26 01:30:59 +11:00
|
|
|
pub struct UserExperienceData {
|
|
|
|
pub spent_xp: u64, // Since last chargen complete.
|
2023-05-16 22:02:42 +10:00
|
|
|
pub journals: JournalState,
|
2022-12-26 01:30:59 +11:00
|
|
|
pub xp_change_for_this_reroll: i64,
|
2023-06-07 22:38:46 +10:00
|
|
|
pub crafted_items: BTreeMap<String, u64>,
|
2022-12-26 01:30:59 +11:00
|
|
|
}
|
|
|
|
|
2023-08-05 01:49:46 +10:00
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
|
|
|
|
pub enum UserFlag {
|
|
|
|
Staff,
|
|
|
|
}
|
|
|
|
|
2023-09-23 23:55:29 +10:00
|
|
|
#[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()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-12-26 01:30:59 +11:00
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
2023-01-20 23:08:40 +11:00
|
|
|
#[serde(default)]
|
2022-12-26 01:30:59 +11:00
|
|
|
pub struct User {
|
|
|
|
pub username: String,
|
|
|
|
pub password_hash: String, // bcrypted.
|
2022-12-27 16:08:27 +11:00
|
|
|
pub email: String,
|
2022-12-27 00:20:09 +11:00
|
|
|
pub player_item_id: i64,
|
2022-12-26 01:30:59 +11:00
|
|
|
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,
|
2023-01-20 23:08:40 +11:00
|
|
|
pub raw_skills: BTreeMap<SkillType, f64>,
|
|
|
|
pub raw_stats: BTreeMap<StatType, f64>,
|
2023-09-23 23:55:29 +10:00
|
|
|
pub wristpad_hacks: Vec<WristpadHack>,
|
2023-01-20 23:08:40 +11:00
|
|
|
pub last_skill_improve: BTreeMap<SkillType, DateTime<Utc>>,
|
2023-02-26 17:01:05 +11:00
|
|
|
pub last_page_from: Option<String>,
|
2023-01-30 22:28:43 +11:00
|
|
|
pub credits: u64,
|
2023-06-07 22:38:46 +10:00
|
|
|
pub danger_code: Option<String>,
|
2023-08-05 01:49:46 +10:00
|
|
|
pub user_flags: Vec<UserFlag>,
|
2023-01-30 22:28:43 +11:00
|
|
|
// Reminder: Consider backwards compatibility when updating this.
|
2022-12-26 01:30:59 +11:00
|
|
|
}
|
|
|
|
|
2023-09-23 23:55:29 +10:00
|
|
|
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(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-26 01:30:59 +11:00
|
|
|
impl Default for UserTermData {
|
|
|
|
fn default() -> Self {
|
|
|
|
UserTermData {
|
|
|
|
accepted_terms: BTreeMap::new(),
|
2022-12-27 00:20:09 +11:00
|
|
|
terms_complete: false,
|
2023-06-07 22:38:46 +10:00
|
|
|
last_presented_term: None,
|
2022-12-26 01:30:59 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for UserExperienceData {
|
|
|
|
fn default() -> Self {
|
|
|
|
UserExperienceData {
|
|
|
|
spent_xp: 0,
|
2023-05-16 22:02:42 +10:00
|
|
|
journals: Default::default(),
|
2022-12-26 01:30:59 +11:00
|
|
|
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(),
|
2022-12-27 16:08:27 +11:00
|
|
|
email: "unknown".to_owned(),
|
2022-12-26 01:30:59 +11:00
|
|
|
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(),
|
2023-09-23 23:55:29 +10:00
|
|
|
wristpad_hacks: vec![],
|
2023-01-20 23:08:40 +11:00
|
|
|
last_skill_improve: BTreeMap::new(),
|
2023-02-26 17:01:05 +11:00
|
|
|
last_page_from: None,
|
2023-06-07 22:38:46 +10:00
|
|
|
credits: 500,
|
|
|
|
danger_code: None,
|
2023-08-05 01:49:46 +10:00
|
|
|
user_flags: vec![],
|
2022-12-26 01:30:59 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|