use super::{ VerbContext, UserVerb, UserVerbRef, UResult, ItemSearchParams, user_error, get_player_item_or_fail, search_item_for_user, parsing, }; use crate::{ static_content::possession_type::{ possession_data, }, regular_tasks::queued_command::{ QueueCommandHandler, QueueCommand, queue_command }, models::item::{ SkillType, }, services::{ comms::broadcast_to_room, skills::skill_check_and_grind, effect::run_effects, check_consent, }, language, }; use async_trait::async_trait; use std::time; pub struct QueueHandler; #[async_trait] impl QueueCommandHandler for QueueHandler { async fn start_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand) -> UResult { let player_item = get_player_item_or_fail(ctx).await?; if player_item.death_data.is_some() { user_error("You try to use it, but your ghostly hands slip through it uselessly".to_owned())?; } let (item_id, target_type_code) = match command { QueueCommand::Use { possession_id, target_id } => (possession_id, target_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 }; if item.location != format!("player/{}", player_item.item_code) { user_error(format!("You try to use {} but realise you no longer have it", item.display_for_sentence( !ctx.session_dat.less_explicit_mode, 1, false ) ))? } let (target_type, target_code) = match target_type_code.split_once("/") { None => user_error("Couldn't handle use command (invalid target)".to_owned())?, Some(spl) => spl }; let is_self_use = target_type == "player" && target_code == player_item.item_code; let target = if is_self_use { player_item.clone() } else { match ctx.trans.find_item_by_type_code(&target_type, &target_code).await? { None => user_error(format!("Couldn't handle use command (target {} missing)", target_type_code))?, Some(it) => it } }; if !is_self_use && target.location != player_item.location && target.location != format!("player/{}", player_item.item_code) { let target_name = target.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false); user_error(format!("You try to use {} on {}, but realise {} is no longer here", item.display_for_sentence( !ctx.session_dat.less_explicit_mode, 1, false), target_name, target_name ))? } let msg_exp = format!("{} prepares to use {} {} on {}\n", &player_item.display_for_sentence(true, 1, true), &player_item.pronouns.possessive, &item.display_for_sentence(true, 1, false), &if is_self_use { player_item.pronouns.intensive.clone() } else { player_item.display_for_sentence(true, 1, false) }); let msg_nonexp = format!("{} prepares to use {} {} on {}\n", &player_item.display_for_sentence(false, 1, true), &player_item.pronouns.possessive, &item.display_for_sentence(false, 1, false), &if is_self_use { player_item.pronouns.intensive.clone() } else { player_item.display_for_sentence(true, 1, false) }); broadcast_to_room(ctx.trans, &player_item.location, None, &msg_exp, Some(&msg_nonexp)).await?; let mut draw_level: f64 = *player_item.total_skills.get(&SkillType::Quickdraw).to_owned().unwrap_or(&8.0); let mut player_item_mut = (*player_item).clone(); let skill_result = skill_check_and_grind(ctx.trans, &mut player_item_mut, &SkillType::Quickdraw, draw_level).await?; if skill_result < -0.5 { draw_level -= 2.0; } else if skill_result < -0.25 { draw_level -= 1.0; } else if skill_result > 0.5 { draw_level += 2.0; } else if skill_result > 0.25 { draw_level += 1.0; } ctx.trans.save_item_model(&player_item_mut).await?; let wait_ticks = (12.0 - (draw_level / 2.0)).min(8.0).max(1.0); Ok(time::Duration::from_millis((wait_ticks * 500.0).round() as u64)) } #[allow(unreachable_patterns)] async fn finish_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand) -> UResult<()> { let player_item = get_player_item_or_fail(ctx).await?; if player_item.death_data.is_some() { user_error("You try to use it, but your ghostly hands slip through it uselessly".to_owned())?; } let (ref item_id, ref target_type_code) = match command { QueueCommand::Use { possession_id, target_id } => (possession_id, target_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 }; if item.location != format!("player/{}", player_item.item_code) { user_error(format!("You try to use {} but realise you no longer have it", item.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false) ))? } let (ref target_type, ref target_code) = match target_type_code.split_once("/") { None => user_error("Couldn't handle use command (invalid target)".to_owned())?, Some(ref sp) => sp.clone() }; let target = match ctx.trans.find_item_by_type_code(&target_type, &target_code).await? { None => user_error("Couldn't handle use command (target missing)".to_owned())?, Some(it) => it }; if target.location != player_item.location && target.location != format!("player/{}", player_item.item_code) { let target_name = target.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false); user_error(format!("You try to use {} on {}, but realise {} is no longer here", item.display_for_sentence( !ctx.session_dat.less_explicit_mode, 1, false), target_name, target_name ))? } let use_data = match item.possession_type.as_ref() .and_then(|poss_type| possession_data().get(&poss_type)) .and_then(|poss_data| poss_data.use_data.as_ref()) { None => user_error("You can't use that!".to_owned())?, Some(d) => d }; if let Some(consent_type) = use_data.needs_consent_check.as_ref() { if !check_consent(ctx.trans, "use", consent_type, &player_item, &target).await? { user_error(format!("{} doesn't allow {} from you", &target.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, true), consent_type.to_str()))? } } if let Some(charge_data) = item.possession_type.as_ref() .and_then(|poss_type| possession_data().get(&poss_type)) .and_then(|poss_data| poss_data.charge_data.as_ref()) { if item.charges < 1 { user_error( format!("{} has no {} {} left", item.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, true), &language::pluralise(charge_data.charge_name_prefix), charge_data.charge_name_suffix ))?; } } if let Some(err) = (use_data.errorf)(&item, &target) { user_error(err)?; } if ctx.trans.check_task_by_type_code( "DelayedHealth", &format!("{}/{}/{}", &target.item_type, &target.item_code, use_data.task_ref) ).await? { user_error(format!("You see no reason to use {} on {}", item.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false), target.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false) ))?; } let is_self_use = target_type == &"player" && target_code == &player_item.item_code; let mut player_mut = (*player_item).clone(); let skillcheck = skill_check_and_grind(&ctx.trans, &mut player_mut, &use_data.uses_skill, use_data.diff_level).await?; let (effects, skilllvl) = if skillcheck <= -0.5 { // 0-1 how bad was the crit fail? (&use_data.crit_fail_effects, (-0.5-skillcheck) * 2.0) } else if skillcheck < 0.0 { (&use_data.fail_effects, -skillcheck * 2.0) } else { (&use_data.success_effects, skillcheck) }; let mut target_mut = if is_self_use { None } else { Some((*target).clone()) }; run_effects(ctx.trans, &effects, &mut player_mut, &item, &mut target_mut, skilllvl, use_data.task_ref).await?; if let Some(target_mut_save) = target_mut { ctx.trans.save_item_model(&target_mut_save).await?; } ctx.trans.save_item_model(&player_mut).await?; let mut item_mut = (*item).clone(); let mut save_item = false; if item.possession_type.as_ref() .and_then(|poss_type| possession_data().get(&poss_type)) .and_then(|poss_data| poss_data.charge_data.as_ref()).is_some() { item_mut.charges -= 1; save_item = true; } if item_mut.charges == 0 { if let Some((new_poss, new_poss_dat)) = item.possession_type.as_ref() .and_then(|pt| possession_data().get(pt)) .and_then(|poss_data| poss_data.becomes_on_spent.as_ref()) .and_then(|poss_type| possession_data().get(&poss_type) .map(|poss_dat| (poss_type, poss_dat))) { item_mut.possession_type = Some(new_poss.clone()); item_mut.display = new_poss_dat.display.to_owned(); item_mut.display_less_explicit = new_poss_dat.display_less_explicit.map(|d| d.to_owned()); item_mut.details = Some(new_poss_dat.details.to_owned()); item_mut.details_less_explicit = new_poss_dat.details_less_explicit.map(|d| d.to_owned()); item_mut.aliases = new_poss_dat.aliases.iter().map(|al| (*al).to_owned()).collect(); item_mut.health = new_poss_dat.max_health; item_mut.weight = new_poss_dat.weight; save_item = true; } } if save_item { ctx.trans.save_item_model(&item_mut).await?; } Ok(()) } } pub struct Verb; #[async_trait] impl UserVerb for Verb { async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, remaining: &str) -> UResult<()> { let player_item = get_player_item_or_fail(ctx).await?; if player_item.death_data.is_some() { user_error("You try to use it, but your ghostly hands slip through it uselessly".to_owned())?; } let (what_name, whom_name) = parsing::parse_on_or_default(remaining, "me"); let item = search_item_for_user(ctx, &ItemSearchParams { include_contents: true, item_type_only: Some("possession"), limit: 1, ..ItemSearchParams::base(&player_item, &what_name) }).await?; let target = if whom_name == "me" || whom_name == "self" { player_item.clone() } else { search_item_for_user(ctx, &ItemSearchParams { include_contents: true, include_loc_contents: true, limit: 1, ..ItemSearchParams::base(&player_item, &whom_name) }).await? }; let use_data = match item.possession_type.as_ref() .and_then(|poss_type| possession_data().get(&poss_type)) .and_then(|poss_data| poss_data.use_data.as_ref()) { None => user_error("You can't use that!".to_owned())?, Some(d) => d }; if let Some(err) = (use_data.errorf)(&item, &target) { user_error(err)?; } queue_command(ctx, &QueueCommand::Use { possession_id: item.item_code.clone(), target_id: format!("{}/{}", target.item_type, target.item_code)}).await?; Ok(()) } } static VERB_INT: Verb = Verb; pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;