From 985f93ca08f8763be53b3840181e087dbbc2633a Mon Sep 17 00:00:00 2001 From: Shagnor Date: Mon, 30 Jan 2023 22:28:43 +1100 Subject: [PATCH] Implement buying possessions. --- .../src/message_handler/user_commands.rs | 2 + .../src/message_handler/user_commands/buy.rs | 86 +++++++++++++++++++ .../src/message_handler/user_commands/list.rs | 7 +- blastmud_game/src/models/item.rs | 3 + blastmud_game/src/models/user.rs | 5 +- .../src/static_content/possession_type.rs | 27 +++--- blastmud_game/src/static_content/room.rs | 4 +- .../src/static_content/room/melbs.rs | 2 +- 8 files changed, 117 insertions(+), 19 deletions(-) create mode 100644 blastmud_game/src/message_handler/user_commands/buy.rs diff --git a/blastmud_game/src/message_handler/user_commands.rs b/blastmud_game/src/message_handler/user_commands.rs index 2d1b41f4..01b74f4b 100644 --- a/blastmud_game/src/message_handler/user_commands.rs +++ b/blastmud_game/src/message_handler/user_commands.rs @@ -11,6 +11,7 @@ use std::sync::Arc; mod agree; pub mod attack; +mod buy; mod describe; mod help; mod ignore; @@ -100,6 +101,7 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! { // Other commands (alphabetical except aliases grouped): "attack" => attack::VERB, + "buy" => buy::VERB, "kill" => attack::VERB, "k" => attack::VERB, diff --git a/blastmud_game/src/message_handler/user_commands/buy.rs b/blastmud_game/src/message_handler/user_commands/buy.rs new file mode 100644 index 00000000..bf1ca0f1 --- /dev/null +++ b/blastmud_game/src/message_handler/user_commands/buy.rs @@ -0,0 +1,86 @@ +use super::{ + VerbContext, UserVerb, UserVerbRef, UResult, user_error, + get_player_item_or_fail, + parsing::parse_offset, +}; +use crate::{ + static_content::room, + static_content::possession_type::possession_data, + models::item::Item, +}; +use async_trait::async_trait; +use ansi::ansi; + +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?; + + if player_item.is_dead { + user_error("Nobody seems to listen when you try to buy... possibly because you're dead.".to_owned())? + } + let (heretype, herecode) = player_item.location.split_once("/").unwrap_or(("room", "repro_xv_chargen")); + if heretype != "room" { + user_error("Can't buy anything because you're not in a shop.".to_owned())?; + } + let room = match room::room_map_by_code().get(herecode) { + None => user_error("Can't find that shop.".to_owned())?, + Some(r) => r + }; + if room.stock_list.is_empty() { + user_error("Can't buy anything because you're not in a shop.".to_owned())? + } + + let (offset_m, remaining) = parse_offset(remaining); + let mut offset_remaining = offset_m.unwrap_or(1); + let match_item = remaining.trim().to_lowercase(); + + if match_item == "" { + user_error("You need to specify what to buy.".to_owned())? + } + + for stock in &room.stock_list { + if let Some(possession_type) = possession_data().get(&stock.possession_type) { + if possession_type.display.to_lowercase().starts_with(&match_item) || + possession_type.display_less_explicit.map(|d| d.to_lowercase().starts_with(&match_item)).unwrap_or(false) || + possession_type.aliases.iter().any(|al| al.starts_with(&match_item)) { + if offset_remaining <= 1 { + if let Some(mut user) = ctx.user_dat.as_mut() { + if user.credits < stock.list_price { + user_error("You don't have enough credits to buy that!".to_owned())?; + } + user.credits -= stock.list_price; + let item_code = ctx.trans.alloc_item_code().await?; + let new_item = Item { + item_type: "possession".to_owned(), + item_code: format!("{}", item_code), + possession_type: Some(stock.possession_type.clone()), + display: possession_type.display.to_owned(), + display_less_explicit: possession_type.display_less_explicit.map(|d| d.to_owned()), + details: Some(possession_type.details.to_owned()), + details_less_explicit: possession_type.details_less_explicit.map(|d| d.to_owned()), + aliases: possession_type.aliases.iter().map(|al| (*al).to_owned()).collect(), + location: format!("player/{}", &player_item.item_code), + health: possession_type.max_health, + ..Default::default() + }; + ctx.trans.create_item(&new_item).await?; + ctx.trans.queue_for_session( + &ctx.session, + Some(&format!("Your wristpad beeps for a deduction of {} credits.\n", stock.list_price)) + ).await?; + } + return Ok(()); + } else { + offset_remaining -= 1; + } + } + } + + } + user_error(ansi!("That doesn't seem to be for sale. Try list").to_owned()) + } +} +static VERB_INT: Verb = Verb; +pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef; diff --git a/blastmud_game/src/message_handler/user_commands/list.rs b/blastmud_game/src/message_handler/user_commands/list.rs index da35a6ee..493413fd 100644 --- a/blastmud_game/src/message_handler/user_commands/list.rs +++ b/blastmud_game/src/message_handler/user_commands/list.rs @@ -35,10 +35,13 @@ impl UserVerb for Verb { for stock in &room.stock_list { if let Some(possession_type) = possession_data().get(&stock.possession_type) { - msg.push_str(&format!("| {:20} | {:15.2} |\n", &possession_type.name, &stock.list_price)) + let display = if ctx.session_dat.less_explicit_mode { + possession_type.display_less_explicit.as_ref().unwrap_or(&possession_type.display) + } else { &possession_type.display }; + msg.push_str(&format!("| {:20} | {:15.2} |\n", display, &stock.list_price)) } } - msg.push('\n'); + msg.push_str(ansi!("\nUse buy item to purchase something.\n")); ctx.trans.queue_for_session(&ctx.session, Some(&msg)).await?; Ok(()) } diff --git a/blastmud_game/src/models/item.rs b/blastmud_game/src/models/item.rs index 9823eb79..9b4ce238 100644 --- a/blastmud_game/src/models/item.rs +++ b/blastmud_game/src/models/item.rs @@ -3,6 +3,7 @@ use std::collections::BTreeMap; use crate::{ language, static_content::species::SpeciesType, + static_content::possession_type::PossessionType, }; use super::session::Session; @@ -278,6 +279,7 @@ impl Default for ActiveCombat { pub struct Item { pub item_code: String, pub item_type: String, + pub possession_type: Option, pub display: String, pub display_less_explicit: Option, pub details: Option, @@ -351,6 +353,7 @@ impl Default for Item { Self { item_code: "unset".to_owned(), item_type: "unset".to_owned(), + possession_type: None, display: "Item".to_owned(), display_less_explicit: None, details: None, diff --git a/blastmud_game/src/models/user.rs b/blastmud_game/src/models/user.rs index c9f89884..70eabaf9 100644 --- a/blastmud_game/src/models/user.rs +++ b/blastmud_game/src/models/user.rs @@ -35,8 +35,8 @@ pub struct User { pub raw_skills: BTreeMap, pub raw_stats: BTreeMap, pub last_skill_improve: BTreeMap>, - // Reminder: Consider backwards compatibility when updating this. New fields should generally - // be an Option, or things will crash out for existing sessions. + pub credits: u64, + // Reminder: Consider backwards compatibility when updating this. } impl Default for UserTermData { @@ -77,6 +77,7 @@ impl Default for User { raw_skills: BTreeMap::new(), raw_stats: BTreeMap::new(), last_skill_improve: BTreeMap::new(), + credits: 500 } } } diff --git a/blastmud_game/src/static_content/possession_type.rs b/blastmud_game/src/static_content/possession_type.rs index 05149c01..f155a357 100644 --- a/blastmud_game/src/static_content/possession_type.rs +++ b/blastmud_game/src/static_content/possession_type.rs @@ -55,22 +55,24 @@ impl Default for WeaponData { pub struct PossessionData { pub weapon_data: Option, - pub name: &'static str, - pub name_nonexp: Option<&'static str>, - #[allow(unused)] - pub display: &'static str, - #[allow(unused)] - pub display_nonexp: Option<&'static str>, + pub display: &'static str, + pub display_less_explicit: Option<&'static str>, + pub details: &'static str, + pub details_less_explicit: Option<&'static str>, + pub aliases: Vec<&'static str>, + pub max_health: u64, } impl Default for PossessionData { fn default() -> Self { Self { weapon_data: None, - name: "Thingy", - name_nonexp: None, - display: "A generic looking thing", - display_nonexp: None, + display: "Thingy", + display_less_explicit: None, + details: "A generic looking thing", + details_less_explicit: None, + aliases: vec!(), + max_health: 10, } } } @@ -169,9 +171,10 @@ pub fn possession_data() -> &'static BTreeMap { ..Default::default() }), (AntennaWhip, PossessionData { - name: "Antenna whip", - display: "A crudely fashioned whip made from a broken metal antenna. It looks a bit flimsy, but it \ + display: "Antenna whip", + details: "A crudely fashioned whip made from a broken metal antenna. It looks a bit flimsy, but it \ might do you until you get a better weapon!", + aliases: vec!("whip"), weapon_data: Some(WeaponData { uses_skill: SkillType::Whips, raw_min_to_learn: 0.0, diff --git a/blastmud_game/src/static_content/room.rs b/blastmud_game/src/static_content/room.rs index 8cec6c99..e307fc2c 100644 --- a/blastmud_game/src/static_content/room.rs +++ b/blastmud_game/src/static_content/room.rs @@ -152,14 +152,14 @@ pub struct SecondaryZoneRecord { pub struct RoomStock { pub possession_type: PossessionType, - pub list_price: f64 + pub list_price: u64 } impl Default for RoomStock { fn default() -> Self { Self { possession_type: PossessionType::AntennaWhip, - list_price: 1000000000.0 + list_price: 1000000000 } } } diff --git a/blastmud_game/src/static_content/room/melbs.rs b/blastmud_game/src/static_content/room/melbs.rs index d2891491..7f629274 100644 --- a/blastmud_game/src/static_content/room/melbs.rs +++ b/blastmud_game/src/static_content/room/melbs.rs @@ -1739,7 +1739,7 @@ pub fn room_list() -> Vec { stock_list: vec!( RoomStock { possession_type: PossessionType::AntennaWhip, - list_price: 100.0, + list_price: 100, ..Default::default() } ),