453 lines
17 KiB
Rust
453 lines
17 KiB
Rust
use super::{
|
|
get_player_item_or_fail, search_item_for_user, user_error, ItemSearchParams, UResult,
|
|
UserError, UserVerb, UserVerbRef, VerbContext,
|
|
};
|
|
use crate::{
|
|
models::item::{Item, ItemFlag},
|
|
regular_tasks::queued_command::{
|
|
queue_command_and_save, QueueCommand, QueueCommandHandler, QueuedCommandContext,
|
|
},
|
|
services::{
|
|
comms::broadcast_to_room,
|
|
destroy_container,
|
|
skills::{crit_fail_penalty_for_skill, skill_check_and_grind},
|
|
},
|
|
static_content::possession_type::{
|
|
possession_data, recipe_craft_by_recipe, CraftData, PossessionType,
|
|
},
|
|
};
|
|
use async_trait::async_trait;
|
|
use std::time;
|
|
use std::{collections::BTreeSet, sync::Arc};
|
|
|
|
// This is written this way for future expansion to dynamic recipes.
|
|
async fn get_craft_data_for_instructions<'l>(instructions: &'l Item) -> UResult<Option<CraftData>> {
|
|
// For now, only static recipes, so we just fetch them...
|
|
Ok(instructions
|
|
.possession_type
|
|
.as_ref()
|
|
.and_then(|pt| recipe_craft_by_recipe().get(pt))
|
|
.map(|rcd| rcd.craft_data.clone()))
|
|
}
|
|
|
|
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("The dead aren't very good at making stuff.".to_owned())?;
|
|
}
|
|
let (bench_id_opt, instructions_id) = match ctx.command {
|
|
QueueCommand::Make {
|
|
ref bench_possession_id,
|
|
ref instructions_possession_id,
|
|
..
|
|
} => (
|
|
bench_possession_id.as_ref().map(|s| s.as_str()),
|
|
instructions_possession_id,
|
|
),
|
|
_ => user_error("Unexpected command".to_owned())?,
|
|
};
|
|
|
|
let (expected_location, bench_opt) = match bench_id_opt {
|
|
None => (ctx.item.location.clone(), None),
|
|
Some(bench_id) => {
|
|
let bench = ctx
|
|
.trans
|
|
.find_item_by_type_code("possession", bench_id)
|
|
.await?
|
|
.ok_or_else(|| {
|
|
UserError(
|
|
"Hmm, you can't find the equipment you were planning to use!"
|
|
.to_owned(),
|
|
)
|
|
})?;
|
|
if bench.location != ctx.item.location {
|
|
user_error(
|
|
"Hmm, you can't find the equipment you were planning to use!".to_owned(),
|
|
)?;
|
|
}
|
|
(bench.refstr(), Some(bench))
|
|
}
|
|
};
|
|
|
|
let instructions = ctx
|
|
.trans
|
|
.find_item_by_type_code("possession", instructions_id)
|
|
.await?
|
|
.ok_or_else(|| {
|
|
UserError(
|
|
"Hmm, you can't find the instructions you were planning to follow!".to_owned(),
|
|
)
|
|
})?;
|
|
if instructions.location != expected_location {
|
|
user_error(
|
|
"Hmm, you can't find the instructions you were planning to follow!".to_owned(),
|
|
)?;
|
|
}
|
|
|
|
let mut msg_exp = format!(
|
|
"{} starts fiddling around trying to make something",
|
|
&ctx.item.display_for_sentence(true, 1, true)
|
|
);
|
|
let mut msg_nonexp = format!(
|
|
"{} starts fiddling around trying to make something",
|
|
&ctx.item.display_for_sentence(false, 1, true)
|
|
);
|
|
|
|
match bench_opt {
|
|
None => {}
|
|
Some(bench) => {
|
|
msg_exp.push_str(&format!(
|
|
" on {}",
|
|
bench.display_for_sentence(true, 1, false)
|
|
));
|
|
msg_nonexp.push_str(&format!(
|
|
" on {}",
|
|
bench.display_for_sentence(false, 1, false)
|
|
));
|
|
}
|
|
}
|
|
msg_exp.push_str(".\n");
|
|
msg_nonexp.push_str(".\n");
|
|
|
|
broadcast_to_room(
|
|
&ctx.trans,
|
|
&ctx.item.location,
|
|
None,
|
|
&msg_exp,
|
|
Some(&msg_nonexp),
|
|
)
|
|
.await?;
|
|
|
|
Ok(time::Duration::from_secs(1))
|
|
}
|
|
|
|
async fn finish_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<()> {
|
|
let (bench_id_opt, instructions_id, already_used) = match ctx.command {
|
|
QueueCommand::Make {
|
|
ref bench_possession_id,
|
|
ref instructions_possession_id,
|
|
ref already_used,
|
|
} => (
|
|
bench_possession_id.as_ref().map(|s| s.as_str()),
|
|
instructions_possession_id,
|
|
already_used,
|
|
),
|
|
_ => user_error("Unexpected command".to_owned())?,
|
|
};
|
|
|
|
let (expected_location, bench_opt) = match bench_id_opt {
|
|
None => (ctx.item.location.clone(), None),
|
|
Some(bench_id) => {
|
|
let bench = ctx
|
|
.trans
|
|
.find_item_by_type_code("possession", bench_id)
|
|
.await?
|
|
.ok_or_else(|| {
|
|
UserError(
|
|
"Hmm, you can't find the equipment you were planning to use!"
|
|
.to_owned(),
|
|
)
|
|
})?;
|
|
if bench.location != ctx.item.location {
|
|
user_error(
|
|
"Hmm, you can't find the equipment you were planning to use!".to_owned(),
|
|
)?;
|
|
}
|
|
(bench.refstr(), Some(bench))
|
|
}
|
|
};
|
|
|
|
let instructions = ctx
|
|
.trans
|
|
.find_item_by_type_code("possession", instructions_id)
|
|
.await?
|
|
.ok_or_else(|| {
|
|
UserError(
|
|
"Hmm, you can't find the instructions you were planning to follow!".to_owned(),
|
|
)
|
|
})?;
|
|
if instructions.location != expected_location {
|
|
user_error(
|
|
"Hmm, you can't find the instructions you were planning to follow!".to_owned(),
|
|
)?;
|
|
}
|
|
|
|
if let Some(bench) = bench_opt.as_ref() {
|
|
if let Some(bench_data) = bench
|
|
.possession_type
|
|
.as_ref()
|
|
.and_then(|pt| possession_data().get(pt))
|
|
.and_then(|pd| pd.bench_data)
|
|
{
|
|
bench_data
|
|
.check_make(&ctx.trans, bench, &instructions)
|
|
.await?;
|
|
}
|
|
}
|
|
|
|
let (on_what_exp, on_what_nonexp) = match bench_opt {
|
|
None => ("".to_owned(), "".to_owned()),
|
|
Some(bench) => (
|
|
format!(" on {}", bench.display_for_sentence(true, 1, false)),
|
|
format!(" on {}", bench.display_for_sentence(false, 1, false)),
|
|
),
|
|
};
|
|
|
|
let craft_data = get_craft_data_for_instructions(&instructions)
|
|
.await?
|
|
.ok_or_else(|| UserError("Looks like you can't make that anymore.".to_owned()))?;
|
|
let mut ingredients_left: Vec<PossessionType> = craft_data.inputs.clone();
|
|
|
|
let mut to_destroy_if_success: Vec<Arc<Item>> = Vec::new();
|
|
for item_id in already_used.iter() {
|
|
let item = ctx
|
|
.trans
|
|
.find_item_by_type_code("possession", &item_id)
|
|
.await?
|
|
.ok_or_else(|| UserError("Item used in crafting not found.".to_owned()))?;
|
|
to_destroy_if_success.push(item.clone());
|
|
let possession_type = item
|
|
.possession_type
|
|
.as_ref()
|
|
.ok_or_else(|| UserError("Item used in crafting not a possession.".to_owned()))?;
|
|
if let Some(match_pos) = ingredients_left.iter().position(|pt| pt == possession_type) {
|
|
ingredients_left.remove(match_pos);
|
|
}
|
|
}
|
|
|
|
let session = if ctx.item.item_type == "player" {
|
|
ctx.trans
|
|
.find_session_for_player(&ctx.item.item_code)
|
|
.await?
|
|
} else {
|
|
None
|
|
};
|
|
let explicit = session
|
|
.as_ref()
|
|
.map(|s| !s.1.less_explicit_mode)
|
|
.unwrap_or(false);
|
|
|
|
match ingredients_left.iter().next() {
|
|
None => {
|
|
for item in to_destroy_if_success {
|
|
destroy_container(&ctx.trans, &item).await?;
|
|
}
|
|
let mut new_item: Item = craft_data.output.clone().into();
|
|
new_item.item_code = ctx.trans.alloc_item_code().await?.to_string();
|
|
new_item.location = expected_location.clone();
|
|
ctx.trans.create_item(&new_item).await?;
|
|
broadcast_to_room(
|
|
&ctx.trans,
|
|
&ctx.item.location,
|
|
None,
|
|
&format!(
|
|
"{} makes a {}{}.\n",
|
|
&ctx.item.display_for_sentence(true, 1, true),
|
|
&new_item.display_for_sentence(true, 1, false),
|
|
&on_what_exp
|
|
),
|
|
Some(&format!(
|
|
"{} makes a {}{}.\n",
|
|
&ctx.item.display_for_sentence(false, 1, true),
|
|
&new_item.display_for_sentence(false, 1, false),
|
|
&on_what_nonexp
|
|
)),
|
|
)
|
|
.await?;
|
|
}
|
|
Some(possession_type) => {
|
|
let addable = ctx
|
|
.trans
|
|
.find_items_by_location_possession_type_excluding(
|
|
expected_location.as_str(),
|
|
possession_type,
|
|
&already_used.iter().map(|v| v.as_str()).collect(),
|
|
)
|
|
.await?;
|
|
|
|
let pd = possession_data().get(&possession_type).ok_or_else(|| {
|
|
UserError(
|
|
"Looks like something needed to make that is something I know nothing about!".to_owned(),
|
|
)
|
|
})?;
|
|
|
|
match addable.iter().next() {
|
|
None => user_error(format!(
|
|
"You realise you'd need {}.",
|
|
if explicit {
|
|
pd.display
|
|
} else {
|
|
pd.display_less_explicit.unwrap_or(pd.display)
|
|
}
|
|
))?,
|
|
Some(item) => {
|
|
let skill_result = skill_check_and_grind(
|
|
&ctx.trans,
|
|
ctx.item,
|
|
&craft_data.skill,
|
|
craft_data.difficulty,
|
|
)
|
|
.await?;
|
|
if skill_result <= -0.5 {
|
|
crit_fail_penalty_for_skill(&ctx.trans, ctx.item, &craft_data.skill)
|
|
.await?;
|
|
ctx.trans
|
|
.delete_item(&item.item_type, &item.item_code)
|
|
.await?;
|
|
if let Some((sess, _)) = session {
|
|
ctx.trans
|
|
.queue_for_session(
|
|
&sess,
|
|
Some(&format!(
|
|
"You try adding {}, but it goes badly and you waste it.\n",
|
|
&item.display_for_sentence(explicit, 1, false)
|
|
)),
|
|
)
|
|
.await?;
|
|
}
|
|
} else if skill_result <= 0.0 {
|
|
if let Some((sess, _)) = session {
|
|
ctx.trans
|
|
.queue_for_session(
|
|
&sess,
|
|
Some(&format!(
|
|
"You try and fail at adding {}.\n",
|
|
&item.display_for_sentence(explicit, 1, false)
|
|
)),
|
|
)
|
|
.await?;
|
|
}
|
|
} else {
|
|
if let Some((sess, _)) = session {
|
|
ctx.trans
|
|
.queue_for_session(
|
|
&sess,
|
|
Some(&format!(
|
|
"You try adding {}.\n",
|
|
&item.display_for_sentence(explicit, 1, false),
|
|
)),
|
|
)
|
|
.await?;
|
|
}
|
|
let mut new_already_used = (*already_used).clone();
|
|
new_already_used.insert(item.item_code.clone());
|
|
|
|
ctx.item.queue.push_front(QueueCommand::Make {
|
|
bench_possession_id: bench_id_opt.map(|id| id.to_owned()),
|
|
instructions_possession_id: instructions_id.to_string(),
|
|
already_used: new_already_used,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub struct Verb;
|
|
#[async_trait]
|
|
impl UserVerb for Verb {
|
|
async fn handle(
|
|
self: &Self,
|
|
ctx: &mut VerbContext,
|
|
_verb: &str,
|
|
remaining: &str,
|
|
) -> UResult<()> {
|
|
let rtrim = remaining.trim();
|
|
let player_item = get_player_item_or_fail(ctx).await?;
|
|
|
|
if player_item.death_data.is_some() {
|
|
user_error("The dead aren't very good at making stuff.".to_owned())?;
|
|
}
|
|
|
|
let (bench, output) = match rtrim.split_once(" on ") {
|
|
None => (None, rtrim),
|
|
Some((output_str, bench_str)) => {
|
|
let bench = search_item_for_user(
|
|
ctx,
|
|
&ItemSearchParams {
|
|
item_type_only: Some("possession"),
|
|
include_loc_contents: true,
|
|
..ItemSearchParams::base(&player_item, bench_str.trim())
|
|
},
|
|
)
|
|
.await?;
|
|
(Some(bench), output_str.trim())
|
|
}
|
|
};
|
|
|
|
let instructions = search_item_for_user(
|
|
ctx,
|
|
&ItemSearchParams {
|
|
item_type_only: Some("possession"),
|
|
include_contents: true,
|
|
flagged_only: Some(ItemFlag::Instructions),
|
|
..ItemSearchParams::base(bench.as_ref().unwrap_or(&player_item), output.trim())
|
|
},
|
|
)
|
|
.await?;
|
|
|
|
let recipe_craft = instructions
|
|
.possession_type
|
|
.as_ref()
|
|
.and_then(|pt| recipe_craft_by_recipe().get(&pt))
|
|
.ok_or_else(|| {
|
|
UserError(
|
|
"Sorry, those instructions no longer seem to form part of the game!".to_owned(),
|
|
)
|
|
})?;
|
|
|
|
match (recipe_craft.bench.as_ref(), bench.as_ref()) {
|
|
(Some(bench_type), None) => user_error(format!(
|
|
"The {} can only be made on the {}.",
|
|
&instructions.display_for_session(&ctx.session_dat),
|
|
possession_data()
|
|
.get(bench_type)
|
|
.map(|pd| if ctx.session_dat.less_explicit_mode {
|
|
pd.display_less_explicit.unwrap_or(pd.display)
|
|
} else {
|
|
pd.display
|
|
})
|
|
.unwrap_or("bench")
|
|
))?,
|
|
(Some(bench_type), Some(bench))
|
|
if bench.possession_type.as_ref() != Some(bench_type) =>
|
|
{
|
|
user_error(format!(
|
|
"The {} can only be made on the {}.",
|
|
&instructions.display_for_session(&ctx.session_dat),
|
|
possession_data()
|
|
.get(bench_type)
|
|
.map(|pd| {
|
|
if ctx.session_dat.less_explicit_mode {
|
|
pd.display_less_explicit.unwrap_or(pd.display)
|
|
} else {
|
|
pd.display
|
|
}
|
|
})
|
|
.unwrap_or("bench")
|
|
))?
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
queue_command_and_save(
|
|
ctx,
|
|
&player_item,
|
|
&QueueCommand::Make {
|
|
bench_possession_id: bench.as_ref().map(|b| b.item_code.clone()),
|
|
instructions_possession_id: instructions.item_code.clone(),
|
|
already_used: BTreeSet::<String>::new(),
|
|
},
|
|
)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
}
|
|
static VERB_INT: Verb = Verb;
|
|
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;
|