diff --git a/blastmud_game/src/message_handler/user_commands.rs b/blastmud_game/src/message_handler/user_commands.rs index a3b66460..2d1b41f4 100644 --- a/blastmud_game/src/message_handler/user_commands.rs +++ b/blastmud_game/src/message_handler/user_commands.rs @@ -15,6 +15,7 @@ mod describe; mod help; mod ignore; mod less_explicit_mode; +mod list; mod login; mod look; mod map; @@ -106,6 +107,8 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! { "l" => look::VERB, "look" => look::VERB, "read" => look::VERB, + + "list" => list::VERB, "lmap" => map::VERB, diff --git a/blastmud_game/src/message_handler/user_commands/list.rs b/blastmud_game/src/message_handler/user_commands/list.rs new file mode 100644 index 00000000..da35a6ee --- /dev/null +++ b/blastmud_game/src/message_handler/user_commands/list.rs @@ -0,0 +1,47 @@ +use super::{VerbContext, UserVerb, UserVerbRef, UResult, user_error, + get_player_item_or_fail}; +use crate::{ + static_content::room, + static_content::possession_type::possession_data, +}; +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 offer you any prices... 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 list stock 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 list stock because you're not in a shop.".to_owned())? + } + + let mut msg = String::new(); + msg.push_str(&format!(ansi!("| {:20} | {:15} |\n"), + ansi!("Item"), + ansi!("Price"))); + + 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)) + } + } + msg.push('\n'); + ctx.trans.queue_for_session(&ctx.session, Some(&msg)).await?; + Ok(()) + } +} +static VERB_INT: Verb = Verb; +pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef; diff --git a/blastmud_game/src/static_content/npc.rs b/blastmud_game/src/static_content/npc.rs index 2c2beef2..c33fcd70 100644 --- a/blastmud_game/src/static_content/npc.rs +++ b/blastmud_game/src/static_content/npc.rs @@ -92,7 +92,8 @@ impl Default for NPC { aliases: vec!(), says: vec!(), total_xp: 1000, - total_skills: SkillType::values().into_iter().map(|sk| (sk, 10.0)).collect(), + total_skills: SkillType::values().into_iter() + .map(|sk| (sk.clone(), if &sk == &SkillType::Dodge { 8.0 } else { 10.0 })).collect(), attackable: false, aggression: 0, intrinsic_weapon: None, @@ -155,6 +156,7 @@ pub fn npc_static_items() -> Box> { is_static: true, pronouns: c.pronouns.clone(), is_challenge_attack_only: !c.attackable, + total_xp: c.total_xp.clone(), total_skills: c.total_skills.clone(), species: c.species.clone(), aliases: c.aliases.iter().map(|a| (*a).to_owned()).collect::>(), diff --git a/blastmud_game/src/static_content/possession_type.rs b/blastmud_game/src/static_content/possession_type.rs index fdaa5b51..05149c01 100644 --- a/blastmud_game/src/static_content/possession_type.rs +++ b/blastmud_game/src/static_content/possession_type.rs @@ -54,13 +54,23 @@ impl Default for WeaponData { } pub struct PossessionData { - pub weapon_data: Option + 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>, } impl Default for PossessionData { fn default() -> Self { Self { - weapon_data: None + weapon_data: None, + name: "Thingy", + name_nonexp: None, + display: "A generic looking thing", + display_nonexp: None, } } } @@ -93,6 +103,7 @@ pub enum PossessionType { // Special values that substitute for possessions. Fangs, // Default weapon for certain animals // Real possessions from here on: + AntennaWhip, } pub fn fist() -> &'static WeaponData { @@ -156,6 +167,38 @@ pub fn possession_data() -> &'static BTreeMap { ..Default::default() }), ..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 \ + might do you until you get a better weapon!", + weapon_data: Some(WeaponData { + uses_skill: SkillType::Whips, + raw_min_to_learn: 0.0, + raw_max_to_learn: 2.0, + normal_attack_start_messages: vec!( + Box::new(|attacker, victim, exp| + format!("{} lines up {} antenna whip for a strike on {}", + &attacker.display_for_sentence(exp, 1, true), + &attacker.pronouns.possessive, + &victim.display_for_sentence(exp, 1, false), + ) + ) + ), + normal_attack_success_messages: vec!( + Box::new(|attacker, victim, part, exp| + format!("{}'s antenna whip scores a painful red line across {}'s {}", + &attacker.display_for_sentence(exp, 1, true), + &victim.display_for_sentence(exp, 1, false), + &part.display(victim.sex.clone()) + ) + ) + ), + normal_attack_mean_damage: 3.0, + normal_attack_stdev_damage: 3.0, + ..Default::default() + }), + ..Default::default() }) ).into_iter().collect() }) diff --git a/blastmud_game/src/static_content/room.rs b/blastmud_game/src/static_content/room.rs index 4d4063e4..8cec6c99 100644 --- a/blastmud_game/src/static_content/room.rs +++ b/blastmud_game/src/static_content/room.rs @@ -1,4 +1,7 @@ -use super::StaticItem; +use super::{ + StaticItem, + possession_type::PossessionType, +}; use once_cell::sync::OnceCell; use std::collections::BTreeMap; use async_trait::async_trait; @@ -6,7 +9,9 @@ use serde::{Serialize, Deserialize}; use crate::message_handler::user_commands::{ UResult, VerbContext }; -use crate::models::item::{Item, ItemFlag}; +use crate::{ + models::item::{Item, ItemFlag} +}; mod repro_xv; mod melbs; @@ -145,6 +150,20 @@ pub struct SecondaryZoneRecord { pub caption: Option<&'static str> } +pub struct RoomStock { + pub possession_type: PossessionType, + pub list_price: f64 +} + +impl Default for RoomStock { + fn default() -> Self { + Self { + possession_type: PossessionType::AntennaWhip, + list_price: 1000000000.0 + } + } +} + pub struct Room { pub zone: &'static str, // Other zones where it can be seen on the map and accessed. @@ -158,7 +177,9 @@ pub struct Room { pub exits: Vec, pub should_caption: bool, pub repel_npc: bool, - pub item_flags: Vec + pub item_flags: Vec, + // Empty means not a shop. + pub stock_list: Vec, } impl Default for Room { @@ -176,6 +197,7 @@ impl Default for Room { should_caption: true, repel_npc: false, item_flags: vec!(), + stock_list: vec!(), } } diff --git a/blastmud_game/src/static_content/room/melbs.rs b/blastmud_game/src/static_content/room/melbs.rs index 8e8138b2..d2891491 100644 --- a/blastmud_game/src/static_content/room/melbs.rs +++ b/blastmud_game/src/static_content/room/melbs.rs @@ -1,8 +1,11 @@ use super::{ - Room, GridCoords, Exit, Direction, ExitTarget, ExitType, + Room, RoomStock, GridCoords, Exit, Direction, ExitTarget, ExitType, SecondaryZoneRecord }; -use crate::models::item::ItemFlag; +use crate::{ + models::item::ItemFlag, + static_content::possession_type::PossessionType +}; use ansi::ansi; pub fn room_list() -> Vec { @@ -1707,10 +1710,41 @@ pub fn room_list() -> Vec { target: ExitTarget::UseGPS, exit_type: ExitType::Free }, + Exit { + direction: Direction::SOUTH, + target: ExitTarget::UseGPS, + exit_type: ExitType::Free + }, ), should_caption: false, ..Default::default() }, + Room { + zone: "melbs", + secondary_zones: vec!(), + code: "melbs_mckillocks_self_defence", + name: "McKillock's Self Defence", + short: ansi!("MS"), + description: ansi!("A neatly painted shop with bars covering the window, and a mean looking shop-keeper sitting behind a desk. [Use list to see stock for sale here]"), + description_less_explicit: None, + grid_coords: GridCoords { x: 3, y: 0, z: 0 }, + exits: vec!( + Exit { + direction: Direction::NORTH, + target: ExitTarget::UseGPS, + exit_type: ExitType::Free + }, + ), + should_caption: true, + stock_list: vec!( + RoomStock { + possession_type: PossessionType::AntennaWhip, + list_price: 100.0, + ..Default::default() + } + ), + ..Default::default() + }, Room { zone: "melbs", secondary_zones: vec!(),