Implement buying possessions.

This commit is contained in:
Condorra 2023-01-30 22:28:43 +11:00
parent 722757468f
commit 985f93ca08
8 changed files with 117 additions and 19 deletions

View File

@ -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,

View File

@ -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 <bold>list<reset>").to_owned())
}
}
static VERB_INT: Verb = Verb;
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;

View File

@ -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 <bold>buy<reset> item to purchase something.\n"));
ctx.trans.queue_for_session(&ctx.session, Some(&msg)).await?;
Ok(())
}

View File

@ -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<PossessionType>,
pub display: String,
pub display_less_explicit: Option<String>,
pub details: Option<String>,
@ -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,

View File

@ -35,8 +35,8 @@ pub struct User {
pub raw_skills: BTreeMap<SkillType, f64>,
pub raw_stats: BTreeMap<StatType, f64>,
pub last_skill_improve: BTreeMap<SkillType, DateTime<Utc>>,
// 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
}
}
}

View File

@ -55,22 +55,24 @@ impl Default for WeaponData {
pub struct PossessionData {
pub weapon_data: Option<WeaponData>,
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_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<PossessionType, PossessionData> {
..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,

View File

@ -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
}
}
}

View File

@ -1739,7 +1739,7 @@ pub fn room_list() -> Vec<Room> {
stock_list: vec!(
RoomStock {
possession_type: PossessionType::AntennaWhip,
list_price: 100.0,
list_price: 100,
..Default::default()
}
),