302 lines
14 KiB
Rust
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;
|