From d5314c981e4a9475b6709535757bdb394474846a Mon Sep 17 00:00:00 2001 From: Condorra Date: Mon, 20 Feb 2023 17:38:35 +1100 Subject: [PATCH] Add drop command, and show inventory on look. --- .../src/message_handler/user_commands.rs | 2 + .../src/message_handler/user_commands/drop.rs | 133 ++++++++++++++++++ .../src/message_handler/user_commands/look.rs | 51 ++++++- .../src/regular_tasks/queued_command.rs | 8 +- 4 files changed, 185 insertions(+), 9 deletions(-) create mode 100644 blastmud_game/src/message_handler/user_commands/drop.rs diff --git a/blastmud_game/src/message_handler/user_commands.rs b/blastmud_game/src/message_handler/user_commands.rs index ab64d7fd..af7cc6d1 100644 --- a/blastmud_game/src/message_handler/user_commands.rs +++ b/blastmud_game/src/message_handler/user_commands.rs @@ -14,6 +14,7 @@ use std::sync::Arc; mod agree; pub mod attack; mod buy; +pub mod drop; pub mod get; mod describe; mod help; @@ -107,6 +108,7 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! { // Other commands (alphabetical except aliases grouped): "attack" => attack::VERB, "buy" => buy::VERB, + "drop" => drop::VERB, "get" => get::VERB, "inventory" => inventory::VERB, "inv" => inventory::VERB, diff --git a/blastmud_game/src/message_handler/user_commands/drop.rs b/blastmud_game/src/message_handler/user_commands/drop.rs new file mode 100644 index 00000000..9331e8a5 --- /dev/null +++ b/blastmud_game/src/message_handler/user_commands/drop.rs @@ -0,0 +1,133 @@ +use super::{ + VerbContext, + UserVerb, + UserVerbRef, + UResult, + ItemSearchParams, + user_error, + get_player_item_or_fail, + search_item_for_user, +}; +use crate::{ + static_content::possession_type::possession_data, + regular_tasks::queued_command::{ + QueueCommandHandler, + QueueCommand, + queue_command + }, + services::{ + broadcast_to_room, + capacity::{ + check_item_capacity, + CapacityLevel, + } + }, + models::item::LocationActionType, +}; +use async_trait::async_trait; +use std::time; + +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(); + 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, remaining: &str) -> UResult<()> { + let player_item = get_player_item_or_fail(ctx).await?; + let target = search_item_for_user(ctx, &ItemSearchParams { + include_contents: true, + ..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())?; + } + 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; diff --git a/blastmud_game/src/message_handler/user_commands/look.rs b/blastmud_game/src/message_handler/user_commands/look.rs index 4f894ff3..fc1c57ca 100644 --- a/blastmud_game/src/message_handler/user_commands/look.rs +++ b/blastmud_game/src/message_handler/user_commands/look.rs @@ -2,9 +2,12 @@ use super::{VerbContext, UserVerb, UserVerbRef, UResult, UserError, user_error, get_player_item_or_fail, search_item_for_user}; use async_trait::async_trait; use ansi::{ansi, flow_around, word_wrap}; -use crate::db::ItemSearchParams; -use crate::models::{item::{Item, LocationActionType, Subattack, ItemFlag}}; -use crate::static_content::room::{self, Direction}; +use crate::{ + db::ItemSearchParams, + models::{item::{Item, LocationActionType, Subattack, ItemFlag}}, + static_content::room::{self, Direction}, + language, +}; use itertools::Itertools; use std::sync::Arc; @@ -39,11 +42,45 @@ pub fn render_map(room: &room::Room, width: usize, height: usize) -> String { } pub async fn describe_normal_item(ctx: &VerbContext<'_>, item: &Item) -> UResult<()> { + let mut contents_desc = String::new(); + + let mut items = ctx.trans.find_items_by_location(&format!("{}/{}", + item.item_type, item.item_code)).await?; + items.sort_unstable_by(|it1, it2| (&it1.display).cmp(&it2.display)); + + let all_groups: Vec>> = items + .iter() + .group_by(|i| i.display_for_sentence(true, 1, false)) + .into_iter() + .map(|(_, g)|g.collect::>>()) + .collect::>>>(); + + if all_groups.len() > 0 { + contents_desc.push_str(&(language::caps_first(&item.pronouns.subject))); + if item.item_type == "player" || item.item_type == "npc" { + contents_desc.push_str("'s carrying "); + } else { + contents_desc.push_str(" contains "); + } + let mut phrases = Vec::::new(); + for group_items in all_groups { + let head = &group_items[0]; + let mut details = head.display_for_sentence(!ctx.session_dat.less_explicit_mode, + group_items.len(), false); + if head.action_type == LocationActionType::Wielded { + details.push_str(" (wielded)"); + } + phrases.push(details); + } + let phrases_str: Vec<&str> = phrases.iter().map(|p| p.as_str()).collect(); + contents_desc.push_str(&(language::join_words(&phrases_str) + ".\n")); + } ctx.trans.queue_for_session( ctx.session, - Some(&format!("{}\n{}\n", + Some(&format!("{}\n{}\n{}", &item.display_for_session(&ctx.session_dat), - item.details_for_session(&ctx.session_dat).unwrap_or("") + item.details_for_session(&ctx.session_dat).unwrap_or(""), + contents_desc, )) ).await?; Ok(()) @@ -73,7 +110,7 @@ pub async fn describe_room(ctx: &VerbContext<'_>, item: &Item, Ok(()) } -async fn list_item_contents<'l>(ctx: &'l VerbContext<'_>, item: &'l Item) -> UResult { +async fn list_room_contents<'l>(ctx: &'l VerbContext<'_>, item: &'l Item) -> UResult { if item.flags.contains(&ItemFlag::NoSeeContents) { return Ok(" It is too foggy to see who or what else is here.".to_owned()); } @@ -192,7 +229,7 @@ impl UserVerb for Verb { let room = room::room_map_by_code().get(item.item_code.as_str()) .ok_or_else(|| UserError("Sorry, that room no longer exists".to_owned()))?; - describe_room(ctx, &item, &room, &list_item_contents(ctx, &item).await?).await?; + describe_room(ctx, &item, &room, &list_room_contents(ctx, &item).await?).await?; } Ok(()) } diff --git a/blastmud_game/src/regular_tasks/queued_command.rs b/blastmud_game/src/regular_tasks/queued_command.rs index 3667b356..a91ee086 100644 --- a/blastmud_game/src/regular_tasks/queued_command.rs +++ b/blastmud_game/src/regular_tasks/queued_command.rs @@ -14,9 +14,10 @@ use crate::message_handler::user_commands::{ VerbContext, CommandHandlingError, UResult, + get, + drop, movement, wield, - get, user_error, get_user_or_fail }; @@ -28,6 +29,7 @@ pub enum QueueCommand { Movement { direction: Direction }, Wield { possession_id: String }, Get { possession_id: String }, + Drop { possession_id: String }, } impl QueueCommand { pub fn name(&self) -> &'static str { @@ -36,6 +38,7 @@ impl QueueCommand { Movement {..} => "Movement", Wield {..} => "Wield", Get {..} => "Get", + Drop {..} => "Drop", } } } @@ -50,9 +53,10 @@ fn queue_command_registry() -> &'static BTreeMap<&'static str, &'static (dyn Que static REGISTRY: OnceCell> = OnceCell::new(); REGISTRY.get_or_init(|| vec!( + ("Drop", &drop::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)), + ("Get", &get::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)), ("Movement", &movement::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)), ("Wield", &wield::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)), - ("Get", &get::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)), ).into_iter().collect()) }