386 lines
14 KiB
Rust
386 lines
14 KiB
Rust
use super::{
|
|
get_player_item_or_fail, search_item_for_user, user_error, UResult, UserError, UserVerb,
|
|
UserVerbRef, VerbContext,
|
|
};
|
|
#[double]
|
|
use crate::db::DBTrans;
|
|
use crate::{
|
|
db::ItemSearchParams,
|
|
language::join_words,
|
|
models::item::{DeathData, Item, SkillType},
|
|
regular_tasks::queued_command::{
|
|
queue_command_and_save, QueueCommand, QueueCommandHandler, QueuedCommandContext,
|
|
},
|
|
services::{
|
|
capacity::{check_item_capacity, CapacityLevel},
|
|
combat::corpsify_item,
|
|
comms::broadcast_to_room,
|
|
destroy_container,
|
|
skills::skill_check_and_grind,
|
|
urges::change_stress_considering_cool,
|
|
},
|
|
static_content::possession_type::{can_butcher_possessions, possession_data},
|
|
};
|
|
use ansi::ansi;
|
|
use async_trait::async_trait;
|
|
use mockall_double::double;
|
|
use std::{sync::Arc, 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 butcher things while they are dead, not while YOU are dead!".to_owned(),
|
|
)?;
|
|
}
|
|
if ctx.item.urges.as_ref().map(|u| u.stress.value).unwrap_or(0) > 7000 {
|
|
user_error(
|
|
ansi!(
|
|
"You are too tired and stressed to consider crafts. Maybe try to \
|
|
<bold>sit<reset> or <bold>recline<reset> for a bit!"
|
|
)
|
|
.to_owned(),
|
|
)?;
|
|
}
|
|
let (from_corpse_id, what_part) = match ctx.command {
|
|
QueueCommand::Cut {
|
|
from_corpse,
|
|
what_part,
|
|
} => (from_corpse, what_part),
|
|
_ => user_error("Unexpected command".to_owned())?,
|
|
};
|
|
let corpse = match ctx
|
|
.trans
|
|
.find_item_by_type_code("corpse", &from_corpse_id)
|
|
.await?
|
|
{
|
|
None => user_error("The corpse seems to be gone".to_owned())?,
|
|
Some(it) => it,
|
|
};
|
|
|
|
let explicit = ctx.explicit().await?;
|
|
if corpse.location != ctx.item.location {
|
|
user_error(format!(
|
|
"You try to cut {} but realise it is no longer there.",
|
|
corpse.display_for_sentence(explicit, 1, false)
|
|
))?
|
|
}
|
|
ensure_has_butcher_tool(&ctx.trans, &ctx.item).await?;
|
|
match corpse.death_data.as_ref() {
|
|
None => user_error(format!(
|
|
"You can't do that while {} is still alive!",
|
|
corpse.pronouns.subject
|
|
))?,
|
|
Some(DeathData {
|
|
parts_remaining, ..
|
|
}) => {
|
|
if !parts_remaining.iter().any(|pt| {
|
|
possession_data()
|
|
.get(pt)
|
|
.map(|pd| &pd.display == &what_part)
|
|
== Some(true)
|
|
}) {
|
|
user_error(format!(
|
|
"That part ({}) is now gone. Parts you can cut: {}",
|
|
&what_part,
|
|
&join_words(
|
|
&parts_remaining
|
|
.iter()
|
|
.filter_map(|pt| possession_data().get(pt))
|
|
.map(|pd| pd.display)
|
|
.collect::<Vec<&'static str>>()
|
|
)
|
|
))?;
|
|
}
|
|
}
|
|
};
|
|
|
|
let msg_exp = format!(
|
|
"{} prepares to cut {} from {}\n",
|
|
&ctx.item.display_for_sentence(true, 1, true),
|
|
&what_part,
|
|
&corpse.display_for_sentence(true, 1, false)
|
|
);
|
|
let msg_nonexp = format!(
|
|
"{} prepares to cut {} from {}\n",
|
|
&ctx.item.display_for_sentence(false, 1, true),
|
|
&what_part,
|
|
&corpse.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 butcher things while they are dead, not while YOU are dead!".to_owned(),
|
|
)?;
|
|
}
|
|
let (from_corpse_id, what_part) = match ctx.command {
|
|
QueueCommand::Cut {
|
|
from_corpse,
|
|
what_part,
|
|
} => (from_corpse, what_part),
|
|
_ => user_error("Unexpected command".to_owned())?,
|
|
};
|
|
ensure_has_butcher_tool(&ctx.trans, &ctx.item).await?;
|
|
let corpse = match ctx
|
|
.trans
|
|
.find_item_by_type_code("corpse", &from_corpse_id)
|
|
.await?
|
|
{
|
|
None => user_error("The corpse seems to be gone".to_owned())?,
|
|
Some(it) => it,
|
|
};
|
|
|
|
let explicit = ctx.explicit().await?;
|
|
if corpse.location != ctx.item.location {
|
|
user_error(format!(
|
|
"You try to cut {} but realise it is no longer there.",
|
|
corpse.display_for_sentence(explicit, 1, false)
|
|
))?
|
|
}
|
|
|
|
let possession_type = match corpse.death_data.as_ref() {
|
|
None => user_error(format!(
|
|
"You can't do that while {} is still alive!",
|
|
corpse.pronouns.subject
|
|
))?,
|
|
Some(DeathData {
|
|
parts_remaining, ..
|
|
}) => parts_remaining
|
|
.iter()
|
|
.find(|pt| {
|
|
possession_data()
|
|
.get(pt)
|
|
.map(|pd| &pd.display == &what_part)
|
|
== Some(true)
|
|
})
|
|
.ok_or_else(|| {
|
|
UserError(format!(
|
|
"Parts you can cut: {}",
|
|
&join_words(
|
|
&parts_remaining
|
|
.iter()
|
|
.filter_map(|pt| possession_data().get(pt))
|
|
.map(|pd| pd.display)
|
|
.collect::<Vec<&'static str>>()
|
|
)
|
|
))
|
|
})?,
|
|
};
|
|
|
|
let possession_data = possession_data()
|
|
.get(possession_type)
|
|
.ok_or_else(|| UserError("That part doesn't exist anymore".to_owned()))?;
|
|
|
|
let mut corpse_mut = (*corpse).clone();
|
|
match corpse_mut.death_data.as_mut() {
|
|
None => {}
|
|
Some(dd) => {
|
|
dd.parts_remaining = dd
|
|
.parts_remaining
|
|
.iter()
|
|
.take_while(|pt| pt != &possession_type)
|
|
.chain(
|
|
dd.parts_remaining
|
|
.iter()
|
|
.skip_while(|pt| pt != &possession_type)
|
|
.skip(1),
|
|
)
|
|
.map(|pt| (*pt).clone())
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
match check_item_capacity(&ctx.trans, &ctx.item, possession_data.weight).await? {
|
|
CapacityLevel::AboveItemLimit | CapacityLevel::OverBurdened => {
|
|
user_error("You have too much stuff to take that on!".to_owned())?
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
if corpse_mut
|
|
.death_data
|
|
.as_ref()
|
|
.map(|dd| dd.parts_remaining.is_empty())
|
|
== Some(true)
|
|
{
|
|
destroy_container(&ctx.trans, &corpse_mut).await?;
|
|
} else {
|
|
ctx.trans.save_item_model(&corpse_mut).await?;
|
|
}
|
|
|
|
if skill_check_and_grind(&ctx.trans, ctx.item, &SkillType::Craft, 10.0).await? < 0.0 {
|
|
change_stress_considering_cool(&ctx.trans, &mut ctx.item, 500).await?;
|
|
broadcast_to_room(
|
|
&ctx.trans,
|
|
&ctx.item.location,
|
|
None,
|
|
&format!(
|
|
"{} tries to cut the {} from {}, but only leaves a mutilated mess.\n",
|
|
&ctx.item.display_for_sentence(true, 1, true),
|
|
possession_data.display,
|
|
corpse.display_for_sentence(true, 1, false)
|
|
),
|
|
Some(&format!(
|
|
"{} tries to cut the {} from {}, but only leaves a mutilated mess.\n",
|
|
&ctx.item.display_for_sentence(true, 1, true),
|
|
possession_data.display,
|
|
corpse.display_for_sentence(true, 1, false)
|
|
)),
|
|
)
|
|
.await?;
|
|
} else {
|
|
let mut new_item: Item = (*possession_type).clone().into();
|
|
new_item.item_code = format!("{}", ctx.trans.alloc_item_code().await?);
|
|
new_item.location = ctx.item.refstr();
|
|
ctx.trans.save_item_model(&new_item).await?;
|
|
|
|
broadcast_to_room(
|
|
&ctx.trans,
|
|
&ctx.item.location,
|
|
None,
|
|
&format!(
|
|
"{} expertly cuts the {} from {}.\n",
|
|
&ctx.item.display_for_sentence(true, 1, true),
|
|
possession_data.display,
|
|
corpse.display_for_sentence(true, 1, false)
|
|
),
|
|
Some(&format!(
|
|
"{} expertly cuts the {} from {}.\n",
|
|
&ctx.item.display_for_sentence(true, 1, true),
|
|
possession_data.display,
|
|
corpse.display_for_sentence(true, 1, false)
|
|
)),
|
|
)
|
|
.await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub async fn ensure_has_butcher_tool(trans: &DBTrans, player_item: &Item) -> UResult<()> {
|
|
if trans
|
|
.count_matching_possessions(&player_item.refstr(), &can_butcher_possessions())
|
|
.await?
|
|
< 1
|
|
{
|
|
user_error("You have nothing sharp on you suitable for butchery!".to_owned())?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub struct Verb;
|
|
#[async_trait]
|
|
impl UserVerb for Verb {
|
|
async fn handle(
|
|
self: &Self,
|
|
ctx: &mut VerbContext,
|
|
_verb: &str,
|
|
remaining: &str,
|
|
) -> UResult<()> {
|
|
let (what_raw, corpse_raw) = match remaining.split_once(" from ") {
|
|
None => user_error(
|
|
ansi!("Usage: <bold>cut<reset> thing <bold>from<reset> corpse").to_owned(),
|
|
)?,
|
|
Some(v) => v,
|
|
};
|
|
|
|
let player_item = get_player_item_or_fail(ctx).await?;
|
|
|
|
if player_item.death_data.is_some() {
|
|
user_error(
|
|
"You butcher things while they are dead, not while YOU are dead!".to_owned(),
|
|
)?
|
|
}
|
|
|
|
let possible_corpse = search_item_for_user(
|
|
ctx,
|
|
&ItemSearchParams {
|
|
include_loc_contents: true,
|
|
dead_first: true,
|
|
..ItemSearchParams::base(&player_item, corpse_raw.trim())
|
|
},
|
|
)
|
|
.await?;
|
|
|
|
let what_norm = what_raw.trim().to_lowercase();
|
|
let possession_type = match possible_corpse.death_data.as_ref() {
|
|
None => user_error(format!(
|
|
"You can't do that while {} is still alive!",
|
|
possible_corpse.pronouns.subject
|
|
))?,
|
|
Some(DeathData {
|
|
parts_remaining, ..
|
|
}) => parts_remaining
|
|
.iter()
|
|
.find(|pt| {
|
|
possession_data().get(pt).map(|pd| {
|
|
pd.display.to_lowercase() == what_norm
|
|
|| pd.aliases.iter().any(|a| a.to_lowercase() == what_norm)
|
|
}) == Some(true)
|
|
})
|
|
.ok_or_else(|| {
|
|
UserError(format!(
|
|
"Parts you can cut: {}",
|
|
&join_words(
|
|
&parts_remaining
|
|
.iter()
|
|
.filter_map(|pt| possession_data().get(pt))
|
|
.map(|pd| pd.display)
|
|
.collect::<Vec<&'static str>>()
|
|
)
|
|
))
|
|
})?,
|
|
}
|
|
.clone();
|
|
|
|
let corpse = if possible_corpse.item_type == "corpse" {
|
|
possible_corpse
|
|
} else if possible_corpse.item_type == "npc" || possible_corpse.item_type == "player" {
|
|
let mut possible_corpse_mut = (*possible_corpse).clone();
|
|
possible_corpse_mut.location = if possible_corpse.item_type == "npc" {
|
|
"room/valhalla"
|
|
} else {
|
|
"room/repro_xv_respawn"
|
|
}
|
|
.to_owned();
|
|
Arc::new(corpsify_item(&ctx.trans, &possible_corpse).await?)
|
|
} else {
|
|
user_error("You can't butcher that!".to_owned())?
|
|
};
|
|
|
|
let possession_data = possession_data()
|
|
.get(&possession_type)
|
|
.ok_or_else(|| UserError("That part doesn't exist anymore".to_owned()))?;
|
|
|
|
ensure_has_butcher_tool(&ctx.trans, &player_item).await?;
|
|
|
|
queue_command_and_save(
|
|
ctx,
|
|
&player_item,
|
|
&QueueCommand::Cut {
|
|
from_corpse: corpse.item_code.clone(),
|
|
what_part: possession_data.display.to_owned(),
|
|
},
|
|
)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
}
|
|
static VERB_INT: Verb = Verb;
|
|
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;
|