From b3cbc9f5444145143171a735a6d62f496228ffe5 Mon Sep 17 00:00:00 2001 From: Condorra Date: Fri, 14 Jul 2023 23:03:06 +1000 Subject: [PATCH] Add a bookshop, and add getting things from containers The book doesn't yet contain recipes, but when it does, we will be able to get things out of it to use in the non-improv crafting system (to be implemented). --- .../src/message_handler/user_commands/get.rs | 303 +++++++++++++----- .../src/regular_tasks/queued_command.rs | 9 + blastmud_game/src/services/capacity.rs | 45 ++- .../src/static_content/possession_type.rs | 4 + .../static_content/possession_type/books.rs | 16 + .../src/static_content/room/melbs.rs | 29 ++ 6 files changed, 323 insertions(+), 83 deletions(-) create mode 100644 blastmud_game/src/static_content/possession_type/books.rs diff --git a/blastmud_game/src/message_handler/user_commands/get.rs b/blastmud_game/src/message_handler/user_commands/get.rs index 32e22d93..c37ba332 100644 --- a/blastmud_game/src/message_handler/user_commands/get.rs +++ b/blastmud_game/src/message_handler/user_commands/get.rs @@ -1,6 +1,6 @@ use super::{ - get_player_item_or_fail, parsing::parse_count, search_items_for_user, user_error, - ItemSearchParams, UResult, UserVerb, UserVerbRef, VerbContext, + get_player_item_or_fail, parsing::parse_count, search_item_for_user, search_items_for_user, + user_error, ItemSearchParams, UResult, UserError, UserVerb, UserVerbRef, VerbContext, }; use crate::{ models::item::LocationActionType, @@ -25,42 +25,94 @@ impl QueueCommandHandler for QueueHandler { "You try to get it, but your ghostly hands slip through it uselessly".to_owned(), )?; } - let item_id = match ctx.command { - QueueCommand::Get { possession_id } => possession_id, + match ctx.command { + QueueCommand::Get { possession_id } => { + let item = match ctx + .trans + .find_item_by_type_code("possession", &possession_id) + .await? + { + None => user_error("Item not found".to_owned())?, + Some(it) => it, + }; + if item.location != ctx.item.location { + user_error(format!( + "You try to get {} but realise it is no longer there", + item.display_for_sentence(ctx.explicit().await?, 1, false) + ))? + } + let msg_exp = format!( + "{} fumbles around trying to pick up {}\n", + &ctx.item.display_for_sentence(true, 1, true), + &item.display_for_sentence(true, 1, false) + ); + let msg_nonexp = format!( + "{} fumbles around trying to pick up {}\n", + &ctx.item.display_for_sentence(false, 1, true), + &item.display_for_sentence(false, 1, false) + ); + broadcast_to_room( + ctx.trans, + &ctx.item.location, + None, + &msg_exp, + Some(&msg_nonexp), + ) + .await?; + } + QueueCommand::GetFromContainer { + from_possession_id, + get_possession_id, + } => { + let container = ctx + .trans + .find_item_by_type_code("possession", &from_possession_id) + .await? + .ok_or_else(|| UserError("Item to get from not found".to_owned()))?; + if container.location != ctx.item.location + && container.location != ctx.item.refstr() + { + user_error(format!( + "You try to get something from {} but realise {} is no longer there", + container.display_for_sentence(ctx.explicit().await?, 1, false), + container.display_for_sentence(ctx.explicit().await?, 1, false), + ))? + } + let item = ctx + .trans + .find_item_by_type_code("possession", &get_possession_id) + .await? + .ok_or_else(|| UserError("Item to get not found".to_owned()))?; + if item.location != container.refstr() { + user_error(format!( + "You try to get {} but realise it is no longer in {}", + item.display_for_sentence(ctx.explicit().await?, 1, false), + container.display_for_sentence(ctx.explicit().await?, 1, false), + ))? + } + let msg_exp = format!( + "{} fumbles around trying to get {} from {}.\n", + &ctx.item.display_for_sentence(true, 1, true), + &item.display_for_sentence(true, 1, false), + &container.display_for_sentence(true, 1, false) + ); + let msg_nonexp = format!( + "{} fumbles around trying to get {} from {}.\n", + &ctx.item.display_for_sentence(false, 1, true), + &item.display_for_sentence(false, 1, false), + &container.display_for_sentence(false, 1, false) + ); + broadcast_to_room( + ctx.trans, + &ctx.item.location, + None, + &msg_exp, + Some(&msg_nonexp), + ) + .await?; + } _ => 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 != ctx.item.location { - user_error(format!( - "You try to get {} but realise it is no longer there", - item.display_for_sentence(ctx.explicit().await?, 1, false) - ))? - } - let msg_exp = format!( - "{} fumbles around trying to pick up {}\n", - &ctx.item.display_for_sentence(true, 1, true), - &item.display_for_sentence(true, 1, false) - ); - let msg_nonexp = format!( - "{} fumbles around trying to pick up {}\n", - &ctx.item.display_for_sentence(false, 1, true), - &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)) } @@ -71,24 +123,97 @@ impl QueueCommandHandler for QueueHandler { "You try to get it, but your ghostly hands slip through it uselessly".to_owned(), )?; } - let item_id = match ctx.command { - QueueCommand::Get { possession_id } => possession_id, + let item = match ctx.command { + QueueCommand::Get { possession_id } => { + let item = match ctx + .trans + .find_item_by_type_code("possession", &possession_id) + .await? + { + None => user_error("Item not found".to_owned())?, + Some(it) => it, + }; + if item.location != ctx.item.location { + user_error(format!( + "You try to get {} but realise it is no longer there", + &item.display_for_sentence(ctx.explicit().await?, 1, false) + ))? + } + + let msg_exp = format!( + "{} picks up {}\n", + &ctx.item.display_for_sentence(true, 1, true), + &item.display_for_sentence(true, 1, false) + ); + let msg_nonexp = format!( + "{} picks up {}\n", + &ctx.item.display_for_sentence(false, 1, true), + &item.display_for_sentence(false, 1, false) + ); + broadcast_to_room( + ctx.trans, + &ctx.item.location, + None, + &msg_exp, + Some(&msg_nonexp), + ) + .await?; + item + } + QueueCommand::GetFromContainer { + from_possession_id, + get_possession_id, + } => { + let container = ctx + .trans + .find_item_by_type_code("possession", &from_possession_id) + .await? + .ok_or_else(|| UserError("Item to get from not found".to_owned()))?; + if container.location != ctx.item.location + && container.location != ctx.item.refstr() + { + user_error(format!( + "You try to get something from {} but realise {} is no longer there", + container.display_for_sentence(ctx.explicit().await?, 1, false), + container.display_for_sentence(ctx.explicit().await?, 1, false), + ))? + } + let item = ctx + .trans + .find_item_by_type_code("possession", &get_possession_id) + .await? + .ok_or_else(|| UserError("Item to get not found".to_owned()))?; + if item.location != container.refstr() { + user_error(format!( + "You try to get {} but realise it is no longer in {}", + item.display_for_sentence(ctx.explicit().await?, 1, false), + container.display_for_sentence(ctx.explicit().await?, 1, false), + ))? + } + let msg_exp = format!( + "{} gets {} from {}.\n", + &ctx.item.display_for_sentence(true, 1, true), + &item.display_for_sentence(true, 1, false), + &container.display_for_sentence(true, 1, false) + ); + let msg_nonexp = format!( + "{} gets {} from {}.\n", + &ctx.item.display_for_sentence(false, 1, true), + &item.display_for_sentence(false, 1, false), + &container.display_for_sentence(false, 1, false) + ); + broadcast_to_room( + ctx.trans, + &ctx.item.location, + None, + &msg_exp, + Some(&msg_nonexp), + ) + .await?; + item + } _ => 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 != ctx.item.location { - user_error(format!( - "You try to get {} but realise it is no longer there", - &item.display_for_sentence(ctx.explicit().await?, 1, false) - ))? - } let possession_data = match item .possession_type @@ -108,7 +233,7 @@ impl QueueCommandHandler for QueueHandler { CapacityLevel::OverBurdened => { let explicit = ctx.explicit().await?; user_error(format!( - "{} You drop {} because it is too heavy!", + "{} You can't get {} because it is too heavy!", if explicit { "Fuck!" } else { "Rats!" }, &ctx.item.display_for_sentence(explicit, 1, false) ))? @@ -116,24 +241,6 @@ impl QueueCommandHandler for QueueHandler { _ => (), } - let msg_exp = format!( - "{} picks up {}\n", - &ctx.item.display_for_sentence(true, 1, true), - &item.display_for_sentence(true, 1, false) - ); - let msg_nonexp = format!( - "{} picks up {}\n", - &ctx.item.display_for_sentence(false, 1, true), - &item.display_for_sentence(false, 1, false) - ); - broadcast_to_room( - ctx.trans, - &ctx.item.location, - None, - &msg_exp, - Some(&msg_nonexp), - ) - .await?; let mut item_mut = (*item).clone(); item_mut.location = ctx.item.refstr(); item_mut.action_type = LocationActionType::Normal; @@ -152,7 +259,7 @@ impl UserVerb for Verb { mut remaining: &str, ) -> UResult<()> { let player_item = get_player_item_or_fail(ctx).await?; - // TODO: Parse "get target from container" variant + let mut get_limit = Some(1); if remaining == "all" || remaining.starts_with("all ") { remaining = remaining[3..].trim(); @@ -161,13 +268,33 @@ impl UserVerb for Verb { get_limit = Some(n); remaining = remaining2; } + + let (search_what, for_what, include_contents, include_loc_contents) = + match remaining.split_once(" from ") { + None => (player_item.clone(), remaining, false, true), + Some((item_str_raw, container_str_raw)) => { + let container = search_item_for_user( + ctx, + &ItemSearchParams { + include_loc_contents: true, + include_contents: true, + item_type_only: Some("possession"), + ..ItemSearchParams::base(&player_item, container_str_raw.trim()) + }, + ) + .await?; + (container, item_str_raw.trim(), true, false) + } + }; + let targets = search_items_for_user( ctx, &ItemSearchParams { - include_loc_contents: true, + include_loc_contents, + include_contents, item_type_only: Some("possession"), limit: get_limit.unwrap_or(100), - ..ItemSearchParams::base(&player_item, &remaining) + ..ItemSearchParams::base(&search_what, for_what) }, ) .await?; @@ -187,14 +314,26 @@ impl UserVerb for Verb { user_error("You can't get that!".to_owned())?; } did_anything = true; - queue_command( - ctx, - &mut player_item_mut, - &QueueCommand::Get { - possession_id: target.item_code.clone(), - }, - ) - .await?; + if include_loc_contents { + queue_command( + ctx, + &mut player_item_mut, + &QueueCommand::Get { + possession_id: target.item_code.clone(), + }, + ) + .await?; + } else { + queue_command( + ctx, + &mut player_item_mut, + &QueueCommand::GetFromContainer { + from_possession_id: search_what.item_code.clone(), + get_possession_id: target.item_code.clone(), + }, + ) + .await?; + } } if !did_anything { user_error("I didn't find anything matching.".to_owned())?; diff --git a/blastmud_game/src/regular_tasks/queued_command.rs b/blastmud_game/src/regular_tasks/queued_command.rs index 9a3456bb..68804602 100644 --- a/blastmud_game/src/regular_tasks/queued_command.rs +++ b/blastmud_game/src/regular_tasks/queued_command.rs @@ -63,6 +63,10 @@ pub enum QueueCommand { Get { possession_id: String, }, + GetFromContainer { + from_possession_id: String, + get_possession_id: String, + }, Movement { direction: Direction, source: MovementSource, @@ -100,6 +104,7 @@ impl QueueCommand { Cut { .. } => "Cut", Drop { .. } => "Drop", Get { .. } => "Get", + GetFromContainer { .. } => "GetFromContainer", Movement { .. } => "Movement", OpenDoor { .. } => "OpenDoor", Remove { .. } => "Remove", @@ -167,6 +172,10 @@ fn queue_command_registry( "Get", &get::QueueHandler as &(dyn QueueCommandHandler + Sync + Send), ), + ( + "GetFromContainer", + &get::QueueHandler as &(dyn QueueCommandHandler + Sync + Send), + ), ( "Movement", &movement::QueueHandler as &(dyn QueueCommandHandler + Sync + Send), diff --git a/blastmud_game/src/services/capacity.rs b/blastmud_game/src/services/capacity.rs index 8c690936..1f4f2a7c 100644 --- a/blastmud_game/src/services/capacity.rs +++ b/blastmud_game/src/services/capacity.rs @@ -64,11 +64,27 @@ pub async fn check_item_capacity( #[cfg(test)] mod test { use super::*; - use crate::db::{LocationStats, MockDBTrans}; + use crate::{ + db::{LocationStats, MockDBTrans}, + models::item::StatType, + }; #[tokio::test] async fn check_item_capacity_should_say_above_item_limit_if_over() { let mut mock_db = MockDBTrans::new(); + mock_db + .expect_find_item_by_type_code() + .withf(|t, c| t == "player" && c == "foo") + .returning(|_, _| { + Ok(Some( + Item { + item_type: "player".to_owned(), + item_code: "foo".to_owned(), + ..Default::default() + } + .into(), + )) + }); mock_db .expect_get_location_stats() .withf(|s| s == "player/foo") @@ -89,6 +105,19 @@ mod test { #[tokio::test] async fn check_item_capacity_should_say_overburdened_if_over() { let mut mock_db = MockDBTrans::new(); + mock_db + .expect_find_item_by_type_code() + .withf(|t, c| t == "player" && c == "foo") + .returning(|_, _| { + Ok(Some( + Item { + item_type: "player".to_owned(), + item_code: "foo".to_owned(), + ..Default::default() + } + .into(), + )) + }); mock_db .expect_get_location_stats() .withf(|s| s == "player/foo") @@ -109,6 +138,20 @@ mod test { #[tokio::test] async fn check_item_capacity_should_say_unburdened_when_low_weight() { let mut mock_db = MockDBTrans::new(); + mock_db + .expect_find_item_by_type_code() + .withf(|t, c| t == "player" && c == "foo") + .returning(|_, _| { + Ok(Some( + Item { + item_type: "player".to_owned(), + item_code: "foo".to_owned(), + total_stats: vec![(StatType::Brawn, 8.0)].into_iter().collect(), + ..Default::default() + } + .into(), + )) + }); mock_db .expect_get_location_stats() .withf(|s| s == "player/foo") diff --git a/blastmud_game/src/static_content/possession_type.rs b/blastmud_game/src/static_content/possession_type.rs index a31c589d..87093c31 100644 --- a/blastmud_game/src/static_content/possession_type.rs +++ b/blastmud_game/src/static_content/possession_type.rs @@ -12,6 +12,7 @@ use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, BTreeSet}; mod blade; +mod books; mod corp_licence; mod fangs; pub mod head_armour; @@ -338,6 +339,8 @@ pub enum PossessionType { Steak, AnimalSkin, SeveredHead, + // Recipes + CulinaryEssentials, } impl Into for PossessionType { @@ -432,6 +435,7 @@ pub fn possession_data() -> &'static BTreeMap &'static Vec<(PossessionType, PossessionData)> { + static D: OnceCell> = OnceCell::new(); + &D.get_or_init(|| vec!( + (PossessionType::CulinaryEssentials, + PossessionData { + display: "Culinary Essentials cookbook", + aliases: vec!["book", "cookbook"], + details: "A weathered cookbook filled with essential recipes for survival in the post-apocalyptic world. Its pages are yellowed and marked with stains, a testament to its extensive use by those seeking sustenance and comfort amidst scarcity.", + weight: 300, + ..Default::default() + }), + )) +} diff --git a/blastmud_game/src/static_content/room/melbs.rs b/blastmud_game/src/static_content/room/melbs.rs index 451e4da0..6b3b7e6c 100644 --- a/blastmud_game/src/static_content/room/melbs.rs +++ b/blastmud_game/src/static_content/room/melbs.rs @@ -2155,10 +2155,39 @@ pub fn room_list() -> Vec { direction: Direction::SOUTH, ..Default::default() }, + Exit { + direction: Direction::EAST, + ..Default::default() + }, ), should_caption: false, ..Default::default() }, + Room { + zone: "melbs", + secondary_zones: vec!(), + code: "melbs_dustypages", + name: "The Dusty Pages", + short: ansi!("DP"), + description: "Beneath a large hand-carved wooden sign reading \"The Dusty Pages\" lies a small oasis of knowledge. The room is dimly lit, with flickering candles and shafts of sunlight piercing through cracked windows. The air is heavy with the scent of decaying books and the lingering memories of a bygone era.\n\nShelves made of salvaged wood stand defiantly against the crumbling walls, bearing the weight of books that have miraculously survived the ravages of time and nuclear fallout. The covers are worn and the pages yellowed, but the knowledge contained within remains invaluable.\n\nThe inhabitants of this forsaken land gather here, seeking solace and hope within the forgotten stories and practical guides that line the shelves.\n\nThe Dusty Pages stands as a beacon of intellectual survival, a sanctuary where survivors can momentarily escape the harsh realities of their existence.", + description_less_explicit: None, + grid_coords: GridCoords { x: 6, y: 4, z: 0 }, + exits: vec!( + Exit { + direction: Direction::WEST, + ..Default::default() + }, + ), + should_caption: true, + stock_list: vec!( + RoomStock { + possession_type: PossessionType::CulinaryEssentials, + list_price: 200, + ..Default::default() + } + ), + ..Default::default() + }, Room { zone: "melbs", secondary_zones: vec!(),