use super::{ VerbContext, UserVerb, UserVerbRef, UResult, ItemSearchParams, user_error, get_player_item_or_fail, search_items_for_user, parsing::parse_count, }; use crate::{ static_content::possession_type::possession_data, regular_tasks::{ queued_command::{ QueueCommandHandler, QueueCommand, queue_command }, TaskHandler, TaskRunContext, }, services::{ broadcast_to_room, capacity::{ check_item_capacity, CapacityLevel, } }, DResult, models::{ item::{LocationActionType, Item, ItemFlag}, task::{Task, TaskMeta, TaskDetails}, }, }; use async_trait::async_trait; use std::time; use chrono::Utc; use mockall_double::double; #[double] use crate::db::DBTrans; pub struct ExpireItemTaskHandler; #[async_trait] impl TaskHandler for ExpireItemTaskHandler { async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult> { let item_code = match &mut ctx.task.details { TaskDetails::ExpireItem { item_code } => item_code, _ => Err("Expected ExpireItem type")? }; let item = match ctx.trans.find_item_by_type_code("possession", item_code).await? { None => { return Ok(None); } Some(it) => it }; let (loc_type, loc_code) = match item.location.split_once("/") { None => return Ok(None), Some(p) => p }; if loc_type != "room" { return Ok(None); } let loc_item = match ctx.trans.find_item_by_type_code(loc_type, loc_code).await? { None => return Ok(None), Some(i) => i }; if loc_item.flags.contains(&ItemFlag::DroppedItemsDontExpire) { return Ok(None); } ctx.trans.delete_item("possession", item_code).await?; Ok(None) } } pub static EXPIRE_ITEM_HANDLER: &'static (dyn TaskHandler + Sync + Send) = &ExpireItemTaskHandler; pub async fn consider_expire_job_for_item(trans: &DBTrans, item: &Item) -> DResult<()> { let (loc_type, loc_code) = match item.location.split_once("/") { None => return Ok(()), Some(p) => p }; if loc_type != "room" { return Ok(()) } let loc_item = match trans.find_item_by_type_code(loc_type, loc_code).await? { None => return Ok(()), Some(i) => i }; if loc_item.flags.contains(&ItemFlag::DroppedItemsDontExpire) { return Ok(()); } trans.upsert_task( &Task { meta: TaskMeta { task_code: format!("{}/{}", item.item_type, item.item_code), next_scheduled: Utc::now() + chrono::Duration::hours(1), ..Default::default() }, details: TaskDetails::ExpireItem { item_code: item.item_code.clone() } } ).await?; Ok(()) } pub struct QueueHandler; #[async_trait] impl QueueCommandHandler for QueueHandler { async fn start_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand) -> UResult { let player_item = get_player_item_or_fail(ctx).await?; if player_item.is_dead { user_error("You try to drop it, but your ghostly hands slip through it uselessly".to_owned())?; } let item_id = match command { QueueCommand::Drop { 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 }; if item.location != format!("{}/{}", &player_item.item_type, &player_item.item_code) { user_error( format!("You try to drop {} but realise you no longer have it", item.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false) ) )? } let msg_exp = format!("{} prepares to drop {}\n", &player_item.display_for_sentence(true, 1, true), &item.display_for_sentence(true, 1, false)); let msg_nonexp = format!("{} prepares to drop {}\n", &player_item.display_for_sentence(false, 1, true), &item.display_for_sentence(false, 1, false)); broadcast_to_room(ctx.trans, &player_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 VerbContext<'_>, command: &QueueCommand) -> UResult<()> { let player_item = get_player_item_or_fail(ctx).await?; if player_item.is_dead { user_error("You try to get it, but your ghostly hands slip through it uselessly".to_owned())?; } let item_id = match command { QueueCommand::Drop { 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 }; if item.location != format!("{}/{}", &player_item.item_type, &player_item.item_code) { user_error(format!("You try to drop {} but realise you no longer have it!", &item.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false)))? } let possession_data = match item.possession_type.as_ref().and_then(|pt| possession_data().get(&pt)) { None => user_error("That item no longer exists in the game so can't be handled".to_owned())?, Some(pd) => pd }; match check_item_capacity(ctx.trans, &player_item.location, possession_data.weight).await? { CapacityLevel::AboveItemLimit => user_error( format!("You can't drop {}, because it is so cluttered here there is no where to put it!", &item.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false) ), )?, _ => () } let msg_exp = format!("{} drops {}\n", &player_item.display_for_sentence(true, 1, true), &item.display_for_sentence(true, 1, false)); let msg_nonexp = format!("{} drops {}\n", &player_item.display_for_sentence(false, 1, true), &item.display_for_sentence(false, 1, false)); broadcast_to_room(ctx.trans, &player_item.location, None, &msg_exp, Some(&msg_nonexp)).await?; let mut item_mut = (*item).clone(); item_mut.location = player_item.location.clone(); consider_expire_job_for_item(ctx.trans, &item_mut).await?; item_mut.action_type = LocationActionType::Normal; 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), ..ItemSearchParams::base(&player_item, &remaining) }).await?; if player_item.is_dead { user_error("You try to drop it, but your ghostly hands slip through it uselessly".to_owned())?; } for target in targets { if target.item_type != "possession" { user_error("You can't drop that!".to_owned())?; } queue_command(ctx, &QueueCommand::Drop { possession_id: target.item_code.clone() }).await?; } Ok(()) } } static VERB_INT: Verb = Verb; pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;