212 lines
7.3 KiB
Rust
212 lines
7.3 KiB
Rust
use std::collections::BTreeMap;
|
|
|
|
use super::{
|
|
follow::cancel_follow_by_leader, get_player_item_or_fail, get_user_or_fail, look,
|
|
rent::recursively_destroy_or_move_item, user_error, UResult, UserError, UserVerb, UserVerbRef,
|
|
VerbContext,
|
|
};
|
|
use crate::{
|
|
models::task::{Task, TaskDetails, TaskMeta},
|
|
regular_tasks::{TaskHandler, TaskRunContext},
|
|
services::{
|
|
combat::{corpsify_item, handle_death},
|
|
destroy_container,
|
|
skills::calculate_total_stats_skills_for_user,
|
|
},
|
|
DResult,
|
|
};
|
|
use ansi::ansi;
|
|
use async_trait::async_trait;
|
|
use chrono::Utc;
|
|
use rand::{distributions::Alphanumeric, Rng};
|
|
use std::time;
|
|
|
|
async fn verify_code(
|
|
ctx: &mut VerbContext<'_>,
|
|
input: &str,
|
|
base_command: &str,
|
|
action_text: &str,
|
|
) -> UResult<bool> {
|
|
let user_dat = ctx
|
|
.user_dat
|
|
.as_mut()
|
|
.ok_or(UserError("Please log in first".to_owned()))?;
|
|
let code = match &user_dat.danger_code {
|
|
None => {
|
|
let new_code = rand::thread_rng()
|
|
.sample_iter(&Alphanumeric)
|
|
.take(8)
|
|
.map(char::from)
|
|
.collect::<String>();
|
|
user_dat.danger_code = Some(new_code.clone());
|
|
new_code
|
|
}
|
|
Some(code) => code.clone(),
|
|
};
|
|
let input_tr = input.trim();
|
|
if input_tr == "" || !input_tr.starts_with("code ") {
|
|
ctx.trans
|
|
.queue_for_session(
|
|
ctx.session,
|
|
Some(&format!(
|
|
ansi!("To verify you want to {}, type <bold>delete {} code {}<reset>\n"),
|
|
action_text, base_command, code
|
|
)),
|
|
)
|
|
.await?;
|
|
ctx.trans.save_user_model(&user_dat).await?;
|
|
return Ok(false);
|
|
}
|
|
if input_tr["code ".len()..].trim() != code {
|
|
ctx.trans
|
|
.queue_for_session(
|
|
ctx.session,
|
|
Some(&format!(
|
|
ansi!(
|
|
"Your confirmation code didn't match! \
|
|
To verify you want to {}, type <bold>delete {} code {}<reset>\n"
|
|
),
|
|
action_text, base_command, code
|
|
)),
|
|
)
|
|
.await?;
|
|
ctx.trans.save_user_model(&user_dat).await?;
|
|
return Ok(false);
|
|
}
|
|
Ok(true)
|
|
}
|
|
|
|
async fn reset_stats(ctx: &mut VerbContext<'_>) -> UResult<()> {
|
|
let mut player_item = (*get_player_item_or_fail(ctx).await?).clone();
|
|
let user_dat = ctx
|
|
.user_dat
|
|
.as_mut()
|
|
.ok_or(UserError("Please log in first".to_owned()))?;
|
|
handle_death(&ctx.trans, &mut player_item).await?;
|
|
corpsify_item(&ctx.trans, &player_item).await?;
|
|
player_item.death_data = None;
|
|
player_item.location = "room/repro_xv_chargen".to_owned();
|
|
player_item.total_xp = ((player_item.total_xp as i64)
|
|
- user_dat.experience.xp_change_for_this_reroll)
|
|
.max(0) as u64;
|
|
player_item.urges = Some(Default::default());
|
|
user_dat.experience.xp_change_for_this_reroll = 0;
|
|
user_dat.raw_stats = BTreeMap::new();
|
|
user_dat.raw_skills = BTreeMap::new();
|
|
user_dat.wristpad_hacks = vec![];
|
|
user_dat.scan_codes = vec![];
|
|
calculate_total_stats_skills_for_user(&mut player_item, &user_dat);
|
|
ctx.trans.save_user_model(&user_dat).await?;
|
|
ctx.trans.save_item_model(&player_item).await?;
|
|
look::VERB.handle(ctx, "look", "").await?;
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct DestroyUserHandler;
|
|
pub static DESTROY_USER_HANDLER: &'static (dyn TaskHandler + Sync + Send) = &DestroyUserHandler;
|
|
|
|
#[async_trait]
|
|
impl TaskHandler for DestroyUserHandler {
|
|
async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
|
|
let username = match &ctx.task.details {
|
|
TaskDetails::DestroyUser { username } => username.clone(),
|
|
_ => {
|
|
return Ok(None);
|
|
}
|
|
};
|
|
let _user = match ctx.trans.find_by_username(&username).await? {
|
|
None => return Ok(None),
|
|
Some(u) => u,
|
|
};
|
|
let player_item = match ctx
|
|
.trans
|
|
.find_item_by_type_code("player", &username.to_lowercase())
|
|
.await?
|
|
{
|
|
None => return Ok(None),
|
|
Some(p) => p,
|
|
};
|
|
cancel_follow_by_leader(&ctx.trans, &player_item.refstr()).await?;
|
|
destroy_container(&ctx.trans, &player_item).await?;
|
|
for dynzone in ctx
|
|
.trans
|
|
.find_dynzone_for_owner(&format!("player/{}", &username.to_lowercase()))
|
|
.await?
|
|
{
|
|
recursively_destroy_or_move_item(&ctx.trans, &dynzone).await?;
|
|
}
|
|
ctx.trans.delete_user(&username).await?;
|
|
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
pub struct Verb;
|
|
#[async_trait]
|
|
impl UserVerb for Verb {
|
|
async fn handle(
|
|
self: &Self,
|
|
ctx: &mut VerbContext,
|
|
_verb: &str,
|
|
remaining: &str,
|
|
) -> UResult<()> {
|
|
let rtrim = remaining.trim();
|
|
let username = get_user_or_fail(ctx)?.username.clone();
|
|
if rtrim.starts_with("character forever") {
|
|
if !verify_code(ctx, &rtrim["character forever".len()..], "character forever",
|
|
"permanently destroy your character (after a one week reflection period), making the name available for other players").await? {
|
|
return Ok(());
|
|
}
|
|
|
|
ctx.trans
|
|
.upsert_task(&Task {
|
|
meta: TaskMeta {
|
|
task_code: username.clone(),
|
|
next_scheduled: Utc::now() + chrono::Duration::days(7),
|
|
..Default::default()
|
|
},
|
|
details: TaskDetails::DestroyUser { username },
|
|
})
|
|
.await?;
|
|
|
|
ctx.trans
|
|
.queue_for_session(
|
|
ctx.session,
|
|
Some(
|
|
"Puny human, your permanent destruction has been scheduled \
|
|
for one week from now! If you change your mind, just log \
|
|
in again before the week is up. After one week, your username \
|
|
will be available for others to take, and you will need to start \
|
|
again with a new character. This character will count towards the \
|
|
character limit for the week, but will not once it is deleted. \
|
|
Goodbye forever!\n",
|
|
),
|
|
)
|
|
.await?;
|
|
ctx.trans.queue_for_session(ctx.session, None).await?;
|
|
} else if rtrim.starts_with("stats") {
|
|
if !verify_code(ctx, &rtrim["stats".len()..], "stats",
|
|
"kill your character, reset your stats and non-journal XP, and pick new stats to reclone with").await? {
|
|
return Ok(());
|
|
}
|
|
|
|
reset_stats(ctx).await?;
|
|
} else {
|
|
user_error(
|
|
ansi!("Try <bold>delete character forever<reset> or <bold>delete stats<reset>")
|
|
.to_owned(),
|
|
)?
|
|
}
|
|
let user_dat = ctx
|
|
.user_dat
|
|
.as_mut()
|
|
.ok_or(UserError("Please log in first".to_owned()))?;
|
|
user_dat.danger_code = None;
|
|
ctx.trans.save_user_model(&user_dat).await?;
|
|
Ok(())
|
|
}
|
|
}
|
|
static VERB_INT: Verb = Verb;
|
|
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;
|