use super::{ get_player_item_or_fail, search_item_for_user, user_error, ItemSearchParams, UResult, UserVerb, UserVerbRef, VerbContext, }; use crate::{ models::item::{Item, LiquidDetails, LiquidType}, regular_tasks::queued_command::{ queue_command_and_save, QueueCommand, QueueCommandHandler, QueuedCommandContext, }, services::{capacity::recalculate_container_weight_mut, comms::broadcast_to_room}, }; use ansi::ansi; use async_trait::async_trait; use std::collections::{btree_map::Entry, BTreeMap}; use std::time; 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( "You try to fill it, but your ghostly hands slip through it uselessly".to_owned(), )?; } let (from_item_type, from_item_code, to_item_type, to_item_code) = match ctx.command { QueueCommand::Fill { from_item_type, from_item_code, to_item_type, to_item_code, } => (from_item_type, from_item_code, to_item_type, to_item_code), _ => user_error("Unexpected command".to_owned())?, }; let from_item = match ctx .trans .find_item_by_type_code(&from_item_type, &from_item_code) .await? { None => user_error("Item not found".to_owned())?, Some(it) => it, }; let to_item = match ctx .trans .find_item_by_type_code(&to_item_type, &to_item_code) .await? { None => user_error("Item not found".to_owned())?, Some(it) => it, }; if to_item.location != ctx.item.location && to_item.location != ctx.item.refstr() { user_error(format!( "You try to fill {} but realise you no longer have it", to_item.display_for_sentence(ctx.explicit().await?, 1, false) ))? } if from_item.location != ctx.item.location && from_item.location != ctx.item.refstr() { user_error(format!( "You try to fill from {} but realise you no longer have it", to_item.display_for_sentence(ctx.explicit().await?, 1, false) ))? } let msg_exp = format!( "{} prepares to fill {} from {}\n", &ctx.item.display_for_sentence(true, 1, true), &to_item.display_for_sentence(true, 1, false), &from_item.display_for_sentence(true, 1, false), ); let msg_nonexp = format!( "{} prepares to fill {} from {}\n", &ctx.item.display_for_sentence(false, 1, true), &to_item.display_for_sentence(false, 1, false), &from_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 fill it, but your ghostly hands slip through it uselessly".to_owned(), )?; } let (from_item_type, from_item_code, to_item_type, to_item_code) = match ctx.command { QueueCommand::Fill { from_item_type, from_item_code, to_item_type, to_item_code, } => (from_item_type, from_item_code, to_item_type, to_item_code), _ => user_error("Unexpected command".to_owned())?, }; let from_item = match ctx .trans .find_item_by_type_code(&from_item_type, &from_item_code) .await? { None => user_error("Item not found".to_owned())?, Some(it) => it, }; let to_item = match ctx .trans .find_item_by_type_code(&to_item_type, &to_item_code) .await? { None => user_error("Item not found".to_owned())?, Some(it) => it, }; if to_item.location != ctx.item.location && to_item.location != ctx.item.refstr() { user_error(format!( "You try to fill {} but realise you no longer have it", to_item.display_for_sentence(ctx.explicit().await?, 1, false) ))? } if from_item.location != ctx.item.location && from_item.location != ctx.item.refstr() { user_error(format!( "You try to fill from {} but realise you no longer have it", to_item.display_for_sentence(ctx.explicit().await?, 1, false) ))? } let from_liquid_details = match from_item.liquid_details.as_ref() { None => user_error(format!( "{} appears to be empty.", from_item.display_for_sentence(ctx.explicit().await?, 1, true) ))?, Some(v) => v, }; let available_vol = from_liquid_details .contents .iter() .map(|r| r.1.clone()) .sum::(); if available_vol == 0 { user_error(format!( "{} appears to be empty.", from_item.display_for_sentence(ctx.explicit().await?, 1, true) ))? } let into_liqdata = match to_item .static_data() .and_then(|pd| pd.liquid_container_data.as_ref()) { None => user_error(format!( "You can't find a way to fill {}.", to_item.display_for_sentence(ctx.explicit().await?, 1, false) ))?, Some(v) => v, }; if let Some(allowed) = &into_liqdata.allowed_contents { for (liq_type, _) in &from_liquid_details.contents { if !allowed.contains(&liq_type) { user_error(format!( "You don't think putting {} into {} is a good idea.", liq_type.display(), to_item.display_for_sentence(ctx.explicit().await?, 1, false) ))?; } } } let capacity_remaining = into_liqdata.capacity - to_item .liquid_details .as_ref() .map(|ld| ld.contents.iter().map(|c| c.1.clone()).sum::()) .unwrap_or(0); let actually_transferred = available_vol.min(capacity_remaining); if actually_transferred == 0 { user_error(format!( "You don't think you can get any more into {}.", to_item.display_for_sentence(ctx.explicit().await?, 1, false) ))?; } let transfer_frac = (actually_transferred as f64) / (available_vol as f64); let mut remaining_total = actually_transferred; let transfer_volumes: BTreeMap = from_liquid_details .contents .iter() .flat_map(|(liqtype, vol)| { let move_vol = (((*vol as f64) * transfer_frac).ceil() as u64).min(remaining_total); remaining_total -= move_vol; if move_vol > 0 { Some((liqtype.clone(), move_vol)) } else { None } }) .collect(); let mut to_item_mut: Item = (*to_item).clone(); match to_item_mut.liquid_details.as_mut() { None => { to_item_mut.liquid_details = Some(LiquidDetails { contents: transfer_volumes.clone(), }) } Some(ld) => { for (liq, vol) in &transfer_volumes { ld.contents .entry(liq.clone()) .and_modify(|v| { *v += *vol; }) .or_insert(vol.clone()); } } } let mut from_item_mut: Item = (*from_item).clone(); if let Some(ld) = from_item_mut.liquid_details.as_mut() { for (liq, vol) in &transfer_volumes { match ld.contents.entry(liq.clone()) { Entry::Vacant(_) => {} Entry::Occupied(mut ent) => { if ent.get() <= vol { ent.remove(); } else { *(ent.get_mut()) -= *vol; } } } } } recalculate_container_weight_mut(&ctx.trans, &mut from_item_mut).await?; recalculate_container_weight_mut(&ctx.trans, &mut to_item_mut).await?; ctx.trans.save_item_model(&from_item_mut).await?; ctx.trans.save_item_model(&to_item_mut).await?; let msg_exp = format!( "{} fills {} from {}\n", &ctx.item.display_for_sentence(true, 1, true), &to_item.display_for_sentence(true, 1, false), &from_item.display_for_sentence(true, 1, false), ); let msg_nonexp = format!( "{} fills {} from {}\n", &ctx.item.display_for_sentence(false, 1, true), &to_item.display_for_sentence(false, 1, false), &from_item.display_for_sentence(false, 1, false) ); broadcast_to_room( ctx.trans, &ctx.item.location, None, &msg_exp, Some(&msg_nonexp), ) .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?; let (to_str, from_str) = match remaining.split_once(" from ") { None => user_error( ansi!("Try fill container from container.").to_owned(), )?, Some((to_str, from_str)) => (to_str.trim(), from_str.trim()), }; let to_target = search_item_for_user( ctx, &ItemSearchParams { include_contents: true, include_loc_contents: true, ..ItemSearchParams::base(&player_item, to_str) }, ) .await?; let from_target = search_item_for_user( ctx, &ItemSearchParams { include_contents: true, include_loc_contents: true, ..ItemSearchParams::base(&player_item, from_str) }, ) .await?; if player_item.death_data.is_some() { user_error( "You try to fill it, but your ghostly hands slip through it uselessly".to_owned(), )?; } if from_target.item_type != "possession" && from_target.item_type != "fixed_item" { user_error("You can't fill from that!".to_owned())?; } if to_target.item_type != "possession" && to_target.item_type != "fixed_item" { user_error("You can't fill that!".to_owned())?; } if to_target.item_type == from_target.item_type && to_target.item_code == from_target.item_code { user_error( "You can't figure out how to fill something from itself - a shame!".to_owned(), )?; } queue_command_and_save( ctx, &player_item, &QueueCommand::Fill { from_item_type: from_target.item_type.clone(), from_item_code: from_target.item_code.clone(), to_item_type: to_target.item_type.clone(), to_item_code: to_target.item_code.clone(), }, ) .await?; Ok(()) } } static VERB_INT: Verb = Verb; pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;