blastmud/blastmud_game/src/message_handler/user_commands/wear.rs
Condorra 261151881d Refactor to move command queue to item.
This is the start of being able to implement following in a way that
works for NPCs, but it isn't finished yet. It does mean NPCs can do
things like climb immediately, and will make it far simpler for NPCs
to do other player-like actions in the future.
2023-06-20 22:53:46 +10:00

263 lines
9.0 KiB
Rust

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::{Buff, BuffCause, BuffImpact, LocationActionType, SkillType},
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,
};
use async_trait::async_trait;
use chrono::Utc;
use std::time;
pub struct QueueHandler;
#[async_trait]
impl QueueCommandHandler for QueueHandler {
async fn start_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<time::Duration> {
if ctx.item.death_data.is_some() {
user_error(
"You try to wear it, but your ghostly hands slip through it uselessly".to_owned(),
)?;
}
let item_id = match ctx.command {
QueueCommand::Wear { 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,
};
let explicit = ctx.explicit().await?;
if item.location != ctx.item.refstr() {
user_error(format!(
"You try to wear {} but realise you no longer have it",
item.display_for_sentence(explicit, 1, false)
))?
}
if item.action_type == LocationActionType::Worn {
user_error("You realise you're already 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".to_owned())
})?;
poss_data
.wear_data
.as_ref()
.ok_or_else(|| UserError("You can't wear that!".to_owned()))?;
let msg_exp = format!(
"{} fumbles around trying to put on {}\n",
&ctx.item.display_for_sentence(true, 1, true),
&item.display_for_sentence(true, 1, false)
);
let msg_nonexp = format!(
"{} fumbles around trying to put on {}\n",
&ctx.item.display_for_sentence(false, 1, true),
&item.display_for_sentence(false, 1, false)
);
broadcast_to_room(
ctx.trans,
&ctx.item.location,
None,
&msg_exp,
Some(&msg_nonexp),
)
.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 wear it, but your ghostly hands slip through it uselessly".to_owned(),
)?;
}
let item_id = match ctx.command {
QueueCommand::Wear { 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,
};
let explicit = ctx.explicit().await?;
if item.location != ctx.item.refstr() {
user_error(format!(
"You try to wear {} but realise it is no longer there.",
&item.display_for_sentence(explicit, 1, false)
))?
}
if item.action_type == LocationActionType::Worn {
user_error("You realise you're already 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".to_owned())
})?;
let wear_data = poss_data
.wear_data
.as_ref()
.ok_or_else(|| UserError("You can't wear that!".to_owned()))?;
let other_clothes = ctx
.trans
.find_by_action_and_location(&ctx.item.refstr(), &LocationActionType::Worn)
.await?;
for part in &wear_data.covers_parts {
let thickness: f64 =
other_clothes
.iter()
.fold(wear_data.thickness, |tot, other_item| {
match other_item
.possession_type
.as_ref()
.and_then(|pt| possession_data().get(&pt))
.and_then(|pd| pd.wear_data.as_ref())
{
Some(wd) if wd.covers_parts.contains(&part) => tot + wd.thickness,
_ => tot,
}
});
if thickness > 12.0 {
user_error(format!(
"You're wearing too much on your {} already.",
part.display(ctx.item.sex.clone())
))?;
}
}
let msg_exp = format!(
"{} wears {}\n",
&ctx.item.display_for_sentence(true, 1, true),
&item.display_for_sentence(true, 1, false)
);
let msg_nonexp = format!(
"{} wears {}\n",
&ctx.item.display_for_sentence(false, 1, true),
&item.display_for_sentence(false, 1, false)
);
broadcast_to_room(
ctx.trans,
&ctx.item.location,
None,
&msg_exp,
Some(&msg_nonexp),
)
.await?;
let mut item_mut = (*item).clone();
item_mut.action_type = LocationActionType::Worn;
item_mut.action_type_started = Some(Utc::now());
if wear_data.dodge_penalty != 0.0 {
ctx.item.temporary_buffs.push(Buff {
description: "Dodge penalty".to_owned(),
cause: BuffCause::ByItem {
item_type: item_mut.item_type.clone(),
item_code: item_mut.item_code.clone(),
},
impacts: vec![BuffImpact::ChangeSkill {
skill: SkillType::Dodge,
magnitude: -wear_data.dodge_penalty,
}],
});
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);
}
}
}
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,
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"),
limit: get_limit.unwrap_or(100),
item_action_type_only: Some(&LocationActionType::Normal),
..ItemSearchParams::base(&player_item, &remaining)
},
)
.await?;
if player_item.death_data.is_some() {
user_error("The dead don't dress 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 wear that!".to_owned())?;
}
did_anything = true;
queue_command(
ctx,
&mut player_item_mut,
&QueueCommand::Wear {
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;