blastmud/blastmud_game/src/message_handler/user_commands/use_cmd.rs

302 lines
14 KiB
Rust

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<time::Duration> {
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;