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}, urges::change_stress_considering_cool, }, static_content::possession_type::{ possession_data, recipe_craft_by_recipe, CraftData, PossessionType, }, }; use ansi::ansi; 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> { // 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 { if ctx.item.death_data.is_some() { user_error("The dead aren't very good at making stuff.".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 \ sit or recline for a bit!" ) .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 = format!( "{} starts fiddling around trying to make something", &ctx.item.display_for_sentence(true, 1, true) ); match bench_opt { None => {} Some(bench) => { msg.push_str(&format!( " on {}", bench.display_for_sentence(true, 1, false) )); } } msg.push_str(".\n"); broadcast_to_room(&ctx.trans, &ctx.item.location, None, &msg).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 = match bench_opt { None => "".to_owned(), Some(bench) => format!(" on {}", bench.display_for_sentence(true, 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 = craft_data.inputs.clone(); let mut to_destroy_if_success: Vec> = 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 ), ) .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?; change_stress_considering_cool(&ctx.trans, &mut ctx.item, 1000).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 { change_stress_considering_cool(&ctx.trans, &mut ctx.item, 500).await?; 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::::new(), }, ) .await?; Ok(()) } } static VERB_INT: Verb = Verb; pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;