use super::{ get_player_item_or_fail, parsing::parse_count, search_items_for_user, user_error, ItemSearchParams, UResult, UserError, UserVerb, UserVerbRef, VerbContext, }; use crate::{ models::item::{BuffCause, Item, LocationActionType}, regular_tasks::queued_command::{ queue_command, QueueCommand, QueueCommandHandler, QueuedCommandContext, }, services::{comms::broadcast_to_room, skills::calculate_total_stats_skills_for_user}, static_content::possession_type::{possession_data, WearData}, }; use async_trait::async_trait; use std::time; async fn check_removeable(ctx: &mut QueuedCommandContext<'_>, item: &Item) -> UResult<()> { if item.location != ctx.item.refstr() { user_error(format!( "You try to remove {} but realise you no longer have it.", &item.display_for_sentence(ctx.explicit().await?, 1, false) ))? } if item.action_type != LocationActionType::Worn { user_error("You realise you're not wearing it!".to_owned())?; } let poss_data = item .possession_type .as_ref() .and_then(|pt| possession_data().get(&pt)) .ok_or_else(|| { UserError( "That item no longer exists in the game so can't be handled. Ask staff for help." .to_owned(), ) })?; let wear_data = poss_data.wear_data.as_ref().ok_or_else(|| { UserError( "You seem to be wearing something that isn't clothes! Ask staff for help.".to_owned(), ) })?; let other_clothes = ctx .trans .find_by_action_and_location(&ctx.item.refstr(), &LocationActionType::Worn) .await?; if let Some(my_worn_since) = item.action_type_started { for part in &wear_data.covers_parts { if let Some(other_item) = other_clothes.iter().find(|other_item| { match other_item .possession_type .as_ref() .and_then(|pt| possession_data().get(&pt)) .and_then(|pd| pd.wear_data.as_ref()) { None => false, Some(WearData { covers_parts, .. }) => { covers_parts.contains(&part) && other_item .action_type_started .map(|other_worn_since| other_worn_since < my_worn_since) .unwrap_or(false) } } }) { user_error(format!( "You can't do that without first removing your {} from your {}.", &other_item.display_for_sentence(ctx.explicit().await?, 1, false), part.display(ctx.item.sex.clone()) ))?; } } } Ok(()) } pub struct QueueHandler; #[async_trait] impl QueueCommandHandler for QueueHandler { async fn start_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult { if ctx.item.death_data.is_some() { user_error( "You try to remove it, but your ghostly hands slip through it uselessly".to_owned(), )?; } let item_id = match ctx.command { QueueCommand::Remove { possession_id } => possession_id, _ => user_error("Unexpected command".to_owned())?, }; let item = match ctx .trans .find_item_by_type_code("possession", &item_id) .await? { None => user_error("Item not found".to_owned())?, Some(it) => it, }; check_removeable(ctx, &item).await?; let msg = format!( "{} fumbles around trying to take off {}\n", &ctx.item.display_for_sentence(true, 1, true), &item.display_for_sentence(true, 1, false) ); broadcast_to_room(ctx.trans, &ctx.item.location, None, &msg).await?; Ok(time::Duration::from_secs(1)) } #[allow(unreachable_patterns)] async fn finish_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<()> { if ctx.item.death_data.is_some() { user_error( "You try to remove it, but your ghostly hands slip through it uselessly".to_owned(), )?; } let item_id = match ctx.command { QueueCommand::Remove { possession_id } => possession_id, _ => user_error("Unexpected command".to_owned())?, }; let item = match ctx .trans .find_item_by_type_code("possession", &item_id) .await? { None => user_error("Item not found".to_owned())?, Some(it) => it, }; check_removeable(ctx, &item).await?; let msg = format!( "{} removes {}\n", &ctx.item.display_for_sentence(true, 1, true), &item.display_for_sentence(true, 1, false) ); broadcast_to_room(ctx.trans, &ctx.item.location, None, &msg).await?; let mut item_mut = (*item).clone(); item_mut.action_type = LocationActionType::Normal; item_mut.action_type_started = None; let poss_data = item.possession_type.as_ref() .and_then(|pt| possession_data().get(&pt)) .ok_or_else(|| UserError( "That item no longer exists in the game so can't be handled. Ask staff for help.".to_owned()))?; let wear_data = poss_data.wear_data.as_ref().ok_or_else(|| { UserError( "You seem to be wearing something that isn't clothes! Ask staff for help." .to_owned(), ) })?; ctx.trans.save_item_model(&item_mut).await?; if wear_data.dodge_penalty != 0.0 { ctx.item.temporary_buffs = ctx .item .temporary_buffs .clone() .into_iter() .filter(|buf| { buf.cause != BuffCause::ByItem { item_code: item_mut.item_code.clone(), item_type: item_mut.item_type.clone(), } }) .collect(); if ctx.item.item_type == "player" { if let Some(usr) = ctx.trans.find_by_username(&ctx.item.item_code).await? { calculate_total_stats_skills_for_user(ctx.item, &usr); } } } Ok(()) } } pub struct Verb; #[async_trait] impl UserVerb for Verb { async fn handle( self: &Self, ctx: &mut VerbContext, _verb: &str, mut remaining: &str, ) -> UResult<()> { let player_item = get_player_item_or_fail(ctx).await?; let mut get_limit = Some(1); if remaining == "all" || remaining.starts_with("all ") { remaining = remaining[3..].trim(); get_limit = None; } else if let (Some(n), remaining2) = parse_count(remaining) { get_limit = Some(n); remaining = remaining2; } let targets = search_items_for_user( ctx, &ItemSearchParams { include_contents: true, item_type_only: Some("possession"), item_action_type_only: Some(&LocationActionType::Worn), limit: get_limit.unwrap_or(100), ..ItemSearchParams::base(&player_item, &remaining) }, ) .await?; if player_item.death_data.is_some() { user_error("The dead don't undress themselves".to_owned())?; } let mut did_anything: bool = false; let mut player_item_mut = (*player_item).clone(); for target in targets .iter() .filter(|t| t.action_type.is_visible_in_look()) { if target.item_type != "possession" { user_error("You can't remove that!".to_owned())?; } did_anything = true; queue_command( ctx, &mut player_item_mut, &QueueCommand::Remove { possession_id: target.item_code.clone(), }, ) .await?; } if !did_anything { user_error("I didn't find anything matching.".to_owned())?; } else { ctx.trans.save_item_model(&player_item_mut).await?; } Ok(()) } } static VERB_INT: Verb = Verb; pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;