forked from blasthavers/blastmud
Allow buying, wearing and removing clothes.
Step 2 will be to make clothes serve a functional purpose as armour.
This commit is contained in:
parent
79b0ed8540
commit
2747dddd90
@ -243,6 +243,7 @@ pub struct ItemSearchParams<'l> {
|
|||||||
pub include_active_players: bool,
|
pub include_active_players: bool,
|
||||||
pub include_all_players: bool,
|
pub include_all_players: bool,
|
||||||
pub item_type_only: Option<&'l str>,
|
pub item_type_only: Option<&'l str>,
|
||||||
|
pub item_action_type_only: Option<&'l LocationActionType>,
|
||||||
pub limit: u8,
|
pub limit: u8,
|
||||||
pub dead_first: bool,
|
pub dead_first: bool,
|
||||||
}
|
}
|
||||||
@ -258,6 +259,7 @@ impl ItemSearchParams<'_> {
|
|||||||
dead_first: false,
|
dead_first: false,
|
||||||
limit: 100,
|
limit: 100,
|
||||||
item_type_only: None,
|
item_type_only: None,
|
||||||
|
item_action_type_only: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -602,6 +604,19 @@ impl DBTrans {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let item_action_type_value: Option<serde_json::Value> = match search.item_action_type_only {
|
||||||
|
None => None,
|
||||||
|
Some(v) => Some(serde_json::to_value(v)?)
|
||||||
|
};
|
||||||
|
match item_action_type_value {
|
||||||
|
None => {}
|
||||||
|
Some(ref item_action_type) => {
|
||||||
|
extra_where.push_str(&format!(" AND (details->'action_type')::TEXT = ${}::JSONB::TEXT", param_no));
|
||||||
|
param_no += 1;
|
||||||
|
params.push(item_action_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if search.include_contents {
|
if search.include_contents {
|
||||||
ctes.push(format!("contents AS (\
|
ctes.push(format!("contents AS (\
|
||||||
SELECT details, details->'aliases' AS aliases FROM items WHERE details->>'location' = ${}\
|
SELECT details, details->'aliases' AS aliases FROM items WHERE details->>'location' = ${}\
|
||||||
@ -758,16 +773,20 @@ impl DBTrans {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find_by_action_and_location(&self, location: &str, action_type: &LocationActionType) -> DResult<Option<Arc<Item>>> {
|
pub async fn find_by_action_and_location(&self, location: &str, action_type: &LocationActionType) -> DResult<Vec<Arc<Item>>> {
|
||||||
if let Some(item) = self.pg_trans()?.query_opt(
|
Ok(self.pg_trans()?.query(
|
||||||
"SELECT details FROM items WHERE \
|
"SELECT details FROM items WHERE \
|
||||||
details->>'location' = $1 AND \
|
details->>'location' = $1 AND \
|
||||||
((details->'action_type')::TEXT = $2::JSONB::TEXT)",
|
((details->'action_type')::TEXT = $2::JSONB::TEXT) LIMIT 100",
|
||||||
&[&location,
|
&[&location,
|
||||||
&serde_json::to_value(action_type)?]).await? {
|
&serde_json::to_value(action_type)?]).await?
|
||||||
return Ok(Some(Arc::new(serde_json::from_value::<Item>(item.get("details"))?)));
|
.into_iter()
|
||||||
}
|
.filter_map(
|
||||||
Ok(None)
|
|row| match serde_json::from_value::<Item>(row.get("details")) {
|
||||||
|
Err(_) => None,
|
||||||
|
Ok(item) => Some(Arc::new(item))
|
||||||
|
}
|
||||||
|
).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list_consents(&self, consenting: &str) -> DResult<Vec<(String, ConsentType, Consent)>> {
|
pub async fn list_consents(&self, consenting: &str) -> DResult<Vec<(String, ConsentType, Consent)>> {
|
||||||
|
@ -38,6 +38,7 @@ mod page;
|
|||||||
pub mod parsing;
|
pub mod parsing;
|
||||||
mod quit;
|
mod quit;
|
||||||
pub mod register;
|
pub mod register;
|
||||||
|
pub mod remove;
|
||||||
pub mod rent;
|
pub mod rent;
|
||||||
pub mod say;
|
pub mod say;
|
||||||
mod score;
|
mod score;
|
||||||
@ -46,6 +47,7 @@ mod status;
|
|||||||
mod uninstall;
|
mod uninstall;
|
||||||
pub mod use_cmd;
|
pub mod use_cmd;
|
||||||
mod vacate;
|
mod vacate;
|
||||||
|
pub mod wear;
|
||||||
mod whisper;
|
mod whisper;
|
||||||
mod who;
|
mod who;
|
||||||
pub mod wield;
|
pub mod wield;
|
||||||
@ -164,6 +166,7 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! {
|
|||||||
"repl" => page::VERB,
|
"repl" => page::VERB,
|
||||||
"reply" => page::VERB,
|
"reply" => page::VERB,
|
||||||
|
|
||||||
|
"remove" => remove::VERB,
|
||||||
"rent" => rent::VERB,
|
"rent" => rent::VERB,
|
||||||
|
|
||||||
"\'" => say::VERB,
|
"\'" => say::VERB,
|
||||||
@ -186,6 +189,7 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! {
|
|||||||
"whisper" => whisper::VERB,
|
"whisper" => whisper::VERB,
|
||||||
"tell" => whisper::VERB,
|
"tell" => whisper::VERB,
|
||||||
|
|
||||||
|
"wear" => wear::VERB,
|
||||||
"wield" => wield::VERB,
|
"wield" => wield::VERB,
|
||||||
"who" => who::VERB,
|
"who" => who::VERB,
|
||||||
"write" => write::VERB,
|
"write" => write::VERB,
|
||||||
|
@ -35,6 +35,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use std::time;
|
use std::time;
|
||||||
|
use ansi::ansi;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use mockall_double::double;
|
use mockall_double::double;
|
||||||
#[double] use crate::db::DBTrans;
|
#[double] use crate::db::DBTrans;
|
||||||
@ -137,6 +138,10 @@ impl QueueCommandHandler for QueueHandler {
|
|||||||
)
|
)
|
||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
|
if item.action_type == LocationActionType::Worn {
|
||||||
|
user_error(
|
||||||
|
ansi!("You're wearing it - try using <bold>remove<reset> first").to_owned())?;
|
||||||
|
}
|
||||||
let msg_exp = format!("{} prepares to drop {}\n",
|
let msg_exp = format!("{} prepares to drop {}\n",
|
||||||
&player_item.display_for_sentence(true, 1, true),
|
&player_item.display_for_sentence(true, 1, true),
|
||||||
&item.display_for_sentence(true, 1, false));
|
&item.display_for_sentence(true, 1, false));
|
||||||
@ -167,6 +172,10 @@ impl QueueCommandHandler for QueueHandler {
|
|||||||
user_error(format!("You try to drop {} but realise you no longer have it!",
|
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)))?
|
&item.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false)))?
|
||||||
}
|
}
|
||||||
|
if item.action_type == LocationActionType::Worn {
|
||||||
|
user_error(
|
||||||
|
ansi!("You're wearing it - try using <bold>remove<reset> first").to_owned())?;
|
||||||
|
}
|
||||||
|
|
||||||
let possession_data = match item.possession_type.as_ref().and_then(|pt| possession_data().get(&pt)) {
|
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())?,
|
None => user_error("That item no longer exists in the game so can't be handled".to_owned())?,
|
||||||
|
@ -16,12 +16,14 @@ use crate::{
|
|||||||
room::{self, Direction},
|
room::{self, Direction},
|
||||||
dynzone::self,
|
dynzone::self,
|
||||||
possession_type::possession_data,
|
possession_type::possession_data,
|
||||||
|
species::{SpeciesType, species_info_map},
|
||||||
},
|
},
|
||||||
language,
|
language,
|
||||||
services::combat::max_health,
|
services::combat::max_health,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::collections::BTreeSet;
|
||||||
use mockall_double::double;
|
use mockall_double::double;
|
||||||
#[double] use crate::db::DBTrans;
|
#[double] use crate::db::DBTrans;
|
||||||
|
|
||||||
@ -31,7 +33,8 @@ pub async fn describe_normal_item(ctx: &VerbContext<'_>, item: &Item) -> UResult
|
|||||||
|
|
||||||
let mut items = ctx.trans.find_items_by_location(&format!("{}/{}",
|
let mut items = ctx.trans.find_items_by_location(&format!("{}/{}",
|
||||||
item.item_type, item.item_code)).await?;
|
item.item_type, item.item_code)).await?;
|
||||||
items.sort_unstable_by(|it1, it2| (&it1.display).cmp(&it2.display));
|
items.sort_unstable_by(|it1, it2| (&it1.action_type).cmp(&it2.action_type)
|
||||||
|
.then((&it1.display).cmp(&it2.display)));
|
||||||
|
|
||||||
let all_groups: Vec<Vec<&Arc<Item>>> = items
|
let all_groups: Vec<Vec<&Arc<Item>>> = items
|
||||||
.iter()
|
.iter()
|
||||||
@ -52,14 +55,73 @@ pub async fn describe_normal_item(ctx: &VerbContext<'_>, item: &Item) -> UResult
|
|||||||
let head = &group_items[0];
|
let head = &group_items[0];
|
||||||
let mut details = head.display_for_sentence(!ctx.session_dat.less_explicit_mode,
|
let mut details = head.display_for_sentence(!ctx.session_dat.less_explicit_mode,
|
||||||
group_items.len(), false);
|
group_items.len(), false);
|
||||||
if head.action_type == LocationActionType::Wielded {
|
match head.action_type {
|
||||||
details.push_str(" (wielded)");
|
LocationActionType::Wielded => details.push_str(" (wielded)"),
|
||||||
|
LocationActionType::Worn => continue,
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
phrases.push(details);
|
phrases.push(details);
|
||||||
}
|
}
|
||||||
let phrases_str: Vec<&str> = phrases.iter().map(|p| p.as_str()).collect();
|
let phrases_str: Vec<&str> = phrases.iter().map(|p| p.as_str()).collect();
|
||||||
contents_desc.push_str(&(language::join_words(&phrases_str) + ".\n"));
|
contents_desc.push_str(&(language::join_words(&phrases_str) + ".\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let anything_worn = items.iter().any(|it| it.action_type == LocationActionType::Worn);
|
||||||
|
if anything_worn {
|
||||||
|
let mut any_part_text = false;
|
||||||
|
let mut seen_clothes: BTreeSet<String> = BTreeSet::new();
|
||||||
|
for part in species_info_map().get(&item.species).map(|s| s.body_parts.clone())
|
||||||
|
.unwrap_or_else(|| vec!()) {
|
||||||
|
if let Some((top_item, covering_parts)) = items.iter()
|
||||||
|
.filter_map(
|
||||||
|
|it|
|
||||||
|
if it.action_type != LocationActionType::Worn {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
it.possession_type.as_ref()
|
||||||
|
.and_then(|pt| possession_data().get(&pt))
|
||||||
|
.and_then(|pd| pd.wear_data.as_ref())
|
||||||
|
.and_then(|wd| if wd.covers_parts.contains(&part) {
|
||||||
|
Some((it, wd.covers_parts.clone()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.filter_map(|(it, parts)| it.action_type_started.map(|st| ((it, parts), st)))
|
||||||
|
.max_by_key(|(_it, st)| st.clone()).map(|(it, _)| it)
|
||||||
|
{
|
||||||
|
any_part_text = true;
|
||||||
|
let display = top_item.display_for_session(&ctx.session_dat);
|
||||||
|
if !seen_clothes.contains(&display) {
|
||||||
|
seen_clothes.insert(display.clone());
|
||||||
|
contents_desc.push_str(&format!(
|
||||||
|
"On {} {}, you see {}. ",
|
||||||
|
&item.pronouns.possessive,
|
||||||
|
&language::join_words(
|
||||||
|
&covering_parts.iter().map(|p| p.display(None))
|
||||||
|
.collect::<Vec<&'static str>>()),
|
||||||
|
&display
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !ctx.session_dat.less_explicit_mode {
|
||||||
|
any_part_text = true;
|
||||||
|
contents_desc.push_str(&format!("{} {} {} completely bare. ",
|
||||||
|
&language::caps_first(&item.pronouns.possessive),
|
||||||
|
part.display(item.sex.clone()),
|
||||||
|
part.copula(item.sex.clone())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if any_part_text {
|
||||||
|
contents_desc.push_str("\n");
|
||||||
|
}
|
||||||
|
} else if item.species == SpeciesType::Human && !ctx.session_dat.less_explicit_mode {
|
||||||
|
contents_desc.push_str(&format!("{} is completely naked.\n",
|
||||||
|
&language::caps_first(&item.pronouns.possessive)));
|
||||||
|
}
|
||||||
|
|
||||||
let health_max = max_health(&item);
|
let health_max = max_health(&item);
|
||||||
if health_max > 0 {
|
if health_max > 0 {
|
||||||
let health_ratio = (item.health as f64) / (health_max as f64);
|
let health_ratio = (item.health as f64) / (health_max as f64);
|
||||||
@ -195,7 +257,7 @@ async fn describe_door(
|
|||||||
&state.description);
|
&state.description);
|
||||||
if let Some(lock) = ctx.trans.find_by_action_and_location(
|
if let Some(lock) = ctx.trans.find_by_action_and_location(
|
||||||
&room_item.refstr(),
|
&room_item.refstr(),
|
||||||
&LocationActionType::InstalledOnDoorAsLock((*direction).clone())).await?
|
&LocationActionType::InstalledOnDoorAsLock((*direction).clone())).await?.first()
|
||||||
{
|
{
|
||||||
let lock_desc = lock.display_for_session(&ctx.session_dat);
|
let lock_desc = lock.display_for_session(&ctx.session_dat);
|
||||||
msg.push_str(&format!(" The door is locked with {}",
|
msg.push_str(&format!(" The door is locked with {}",
|
||||||
|
@ -90,7 +90,7 @@ pub async fn attempt_open_immediate(trans: &DBTrans, ctx_opt: &mut Option<&mut V
|
|||||||
if let Some(revdir) = direction.reverse() {
|
if let Some(revdir) = direction.reverse() {
|
||||||
if let Some(lock) = trans.find_by_action_and_location(
|
if let Some(lock) = trans.find_by_action_and_location(
|
||||||
&entering_room_loc,
|
&entering_room_loc,
|
||||||
&LocationActionType::InstalledOnDoorAsLock(revdir.clone())).await?
|
&LocationActionType::InstalledOnDoorAsLock(revdir.clone())).await?.first()
|
||||||
{
|
{
|
||||||
if let Some(ctx) = ctx_opt {
|
if let Some(ctx) = ctx_opt {
|
||||||
if let Some(lockcheck) = lock.possession_type.as_ref()
|
if let Some(lockcheck) = lock.possession_type.as_ref()
|
||||||
@ -188,7 +188,7 @@ impl QueueCommandHandler for QueueHandler {
|
|||||||
if let Some(revdir) = direction.reverse() {
|
if let Some(revdir) = direction.reverse() {
|
||||||
if let Some(lock) = ctx.trans.find_by_action_and_location(
|
if let Some(lock) = ctx.trans.find_by_action_and_location(
|
||||||
&entering_room_loc,
|
&entering_room_loc,
|
||||||
&LocationActionType::InstalledOnDoorAsLock(revdir)).await?
|
&LocationActionType::InstalledOnDoorAsLock(revdir)).await?.first()
|
||||||
{
|
{
|
||||||
if let Some(lockcheck) = lock.possession_type.as_ref()
|
if let Some(lockcheck) = lock.possession_type.as_ref()
|
||||||
.and_then(|pt| possession_data().get(pt))
|
.and_then(|pt| possession_data().get(pt))
|
||||||
|
179
blastmud_game/src/message_handler/user_commands/remove.rs
Normal file
179
blastmud_game/src/message_handler/user_commands/remove.rs
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
use super::{
|
||||||
|
VerbContext,
|
||||||
|
UserVerb,
|
||||||
|
UserVerbRef,
|
||||||
|
UResult,
|
||||||
|
ItemSearchParams,
|
||||||
|
UserError,
|
||||||
|
user_error,
|
||||||
|
get_player_item_or_fail,
|
||||||
|
search_items_for_user,
|
||||||
|
parsing::parse_count
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
static_content::possession_type::{possession_data, WearData},
|
||||||
|
regular_tasks::queued_command::{
|
||||||
|
QueueCommandHandler,
|
||||||
|
QueueCommand,
|
||||||
|
queue_command
|
||||||
|
},
|
||||||
|
services::{
|
||||||
|
comms::broadcast_to_room,
|
||||||
|
},
|
||||||
|
models::item::{Item, LocationActionType},
|
||||||
|
};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use std::time;
|
||||||
|
|
||||||
|
async fn check_removeable(ctx: &mut VerbContext<'_>,
|
||||||
|
item: &Item, player_item: &Item) -> UResult<()> {
|
||||||
|
if item.location != player_item.refstr() {
|
||||||
|
user_error(format!("You try to remove {} but realise you no longer have it.",
|
||||||
|
&item.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false)))?
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.action_type != LocationActionType::Worn {
|
||||||
|
user_error("You realise you're not wearing it!".to_owned())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let poss_data = item.possession_type.as_ref()
|
||||||
|
.and_then(|pt| possession_data().get(&pt))
|
||||||
|
.ok_or_else(|| UserError(
|
||||||
|
"That item no longer exists in the game so can't be handled. Ask staff for help.".to_owned()))?;
|
||||||
|
|
||||||
|
let wear_data = poss_data.wear_data.as_ref().ok_or_else(
|
||||||
|
|| UserError("You seem to be wearing something that isn't clothes! Ask staff for help.".to_owned()))?;
|
||||||
|
|
||||||
|
let other_clothes =
|
||||||
|
ctx.trans.find_by_action_and_location(
|
||||||
|
&player_item.refstr(), &LocationActionType::Worn).await?;
|
||||||
|
|
||||||
|
if let Some(my_worn_since) = item.action_type_started {
|
||||||
|
for part in &wear_data.covers_parts {
|
||||||
|
if let Some(other_item) = other_clothes.iter().find(
|
||||||
|
|other_item|
|
||||||
|
match other_item.possession_type.as_ref()
|
||||||
|
.and_then(|pt| possession_data().get(&pt))
|
||||||
|
.and_then(|pd| pd.wear_data.as_ref()) {
|
||||||
|
None => false,
|
||||||
|
Some(WearData { covers_parts, .. }) =>
|
||||||
|
covers_parts.contains(&part) &&
|
||||||
|
other_item.action_type_started
|
||||||
|
.map(|other_worn_since| other_worn_since < my_worn_since)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
user_error(format!(
|
||||||
|
"You can't do that without first removing your {} from your {}.",
|
||||||
|
&other_item.display_for_session(&ctx.session_dat),
|
||||||
|
part.display(player_item.sex.clone())
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct QueueHandler;
|
||||||
|
#[async_trait]
|
||||||
|
impl QueueCommandHandler for QueueHandler {
|
||||||
|
async fn start_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand)
|
||||||
|
-> UResult<time::Duration> {
|
||||||
|
let player_item = get_player_item_or_fail(ctx).await?;
|
||||||
|
if player_item.death_data.is_some() {
|
||||||
|
user_error("You try to remove it, but your ghostly hands slip through it uselessly".to_owned())?;
|
||||||
|
}
|
||||||
|
let item_id = match command {
|
||||||
|
QueueCommand::Remove { 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
|
||||||
|
};
|
||||||
|
|
||||||
|
check_removeable(ctx, &item, &player_item).await?;
|
||||||
|
|
||||||
|
let msg_exp = format!("{} fumbles around trying to take off {}\n",
|
||||||
|
&player_item.display_for_sentence(true, 1, true),
|
||||||
|
&item.display_for_sentence(true, 1, false));
|
||||||
|
let msg_nonexp = format!("{} fumbles around trying to take off {}\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.death_data.is_some() {
|
||||||
|
user_error("You try to remove it, but your ghostly hands slip through it uselessly".to_owned())?;
|
||||||
|
}
|
||||||
|
let item_id = match command {
|
||||||
|
QueueCommand::Remove { 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
|
||||||
|
};
|
||||||
|
|
||||||
|
check_removeable(ctx, &item, &player_item).await?;
|
||||||
|
|
||||||
|
let msg_exp = format!("{} removes {}\n",
|
||||||
|
&player_item.display_for_sentence(true, 1, true),
|
||||||
|
&item.display_for_sentence(true, 1, false));
|
||||||
|
let msg_nonexp = format!("{} removes {}\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.action_type = LocationActionType::Normal;
|
||||||
|
item_mut.action_type_started = None;
|
||||||
|
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, mut remaining: &str) -> UResult<()> {
|
||||||
|
let player_item = get_player_item_or_fail(ctx).await?;
|
||||||
|
let mut get_limit = Some(1);
|
||||||
|
if remaining == "all" || remaining.starts_with("all ") {
|
||||||
|
remaining = remaining[3..].trim();
|
||||||
|
get_limit = None;
|
||||||
|
} else if let (Some(n), remaining2) = parse_count(remaining) {
|
||||||
|
get_limit = Some(n);
|
||||||
|
remaining = remaining2;
|
||||||
|
}
|
||||||
|
let targets = search_items_for_user(ctx, &ItemSearchParams {
|
||||||
|
include_contents: true,
|
||||||
|
item_type_only: Some("possession"),
|
||||||
|
item_action_type_only: Some(&LocationActionType::Worn),
|
||||||
|
limit: get_limit.unwrap_or(100),
|
||||||
|
..ItemSearchParams::base(&player_item, &remaining)
|
||||||
|
}).await?;
|
||||||
|
if player_item.death_data.is_some() {
|
||||||
|
user_error("The dead don't undress themselves".to_owned())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut did_anything: bool = false;
|
||||||
|
for target in targets.iter().filter(|t| t.action_type.is_visible_in_look()) {
|
||||||
|
if target.item_type != "possession" {
|
||||||
|
user_error("You can't remove that!".to_owned())?;
|
||||||
|
}
|
||||||
|
did_anything = true;
|
||||||
|
queue_command(ctx, &QueueCommand::Remove { possession_id: target.item_code.clone() }).await?;
|
||||||
|
}
|
||||||
|
if !did_anything {
|
||||||
|
user_error("I didn't find anything matching.".to_owned())?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static VERB_INT: Verb = Verb;
|
||||||
|
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;
|
189
blastmud_game/src/message_handler/user_commands/wear.rs
Normal file
189
blastmud_game/src/message_handler/user_commands/wear.rs
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
use super::{
|
||||||
|
VerbContext,
|
||||||
|
UserVerb,
|
||||||
|
UserVerbRef,
|
||||||
|
UResult,
|
||||||
|
ItemSearchParams,
|
||||||
|
UserError,
|
||||||
|
user_error,
|
||||||
|
get_player_item_or_fail,
|
||||||
|
search_items_for_user,
|
||||||
|
parsing::parse_count
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
static_content::possession_type::possession_data,
|
||||||
|
regular_tasks::queued_command::{
|
||||||
|
QueueCommandHandler,
|
||||||
|
QueueCommand,
|
||||||
|
queue_command
|
||||||
|
},
|
||||||
|
services::{
|
||||||
|
comms::broadcast_to_room,
|
||||||
|
},
|
||||||
|
models::item::LocationActionType,
|
||||||
|
};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use chrono::Utc;
|
||||||
|
use log::info;
|
||||||
|
use std::time;
|
||||||
|
|
||||||
|
pub struct QueueHandler;
|
||||||
|
#[async_trait]
|
||||||
|
impl QueueCommandHandler for QueueHandler {
|
||||||
|
async fn start_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand)
|
||||||
|
-> UResult<time::Duration> {
|
||||||
|
let player_item = get_player_item_or_fail(ctx).await?;
|
||||||
|
if player_item.death_data.is_some() {
|
||||||
|
user_error("You try to wear it, but your ghostly hands slip through it uselessly".to_owned())?;
|
||||||
|
}
|
||||||
|
let item_id = match command {
|
||||||
|
QueueCommand::Wear { 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 != player_item.refstr() {
|
||||||
|
user_error(
|
||||||
|
format!("You try to wear {} but realise you no longer have it",
|
||||||
|
item.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false)
|
||||||
|
)
|
||||||
|
)?
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.action_type == LocationActionType::Worn {
|
||||||
|
user_error("You realise you're already wearing it!".to_owned())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let poss_data = item.possession_type.as_ref()
|
||||||
|
.and_then(|pt| possession_data().get(&pt))
|
||||||
|
.ok_or_else(|| UserError(
|
||||||
|
"That item no longer exists in the game so can't be handled".to_owned()))?;
|
||||||
|
|
||||||
|
poss_data.wear_data.as_ref().ok_or_else(
|
||||||
|
|| UserError("You can't wear that!".to_owned()))?;
|
||||||
|
|
||||||
|
let msg_exp = format!("{} fumbles around trying to put on {}\n",
|
||||||
|
&player_item.display_for_sentence(true, 1, true),
|
||||||
|
&item.display_for_sentence(true, 1, false));
|
||||||
|
let msg_nonexp = format!("{} fumbles around trying to put on {}\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.death_data.is_some() {
|
||||||
|
user_error("You try to wear it, but your ghostly hands slip through it uselessly".to_owned())?;
|
||||||
|
}
|
||||||
|
let item_id = match command {
|
||||||
|
QueueCommand::Wear { 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 != player_item.refstr() {
|
||||||
|
user_error(format!("You try to wear {} but realise it is no longer there.",
|
||||||
|
&item.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false)))?
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.action_type == LocationActionType::Worn {
|
||||||
|
user_error("You realise you're already wearing it!".to_owned())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let poss_data = item.possession_type.as_ref()
|
||||||
|
.and_then(|pt| possession_data().get(&pt))
|
||||||
|
.ok_or_else(|| UserError(
|
||||||
|
"That item no longer exists in the game so can't be handled".to_owned()))?;
|
||||||
|
|
||||||
|
let wear_data = poss_data.wear_data.as_ref().ok_or_else(
|
||||||
|
|| UserError("You can't wear that!".to_owned()))?;
|
||||||
|
|
||||||
|
let other_clothes =
|
||||||
|
ctx.trans.find_by_action_and_location(
|
||||||
|
&player_item.refstr(), &LocationActionType::Worn).await?;
|
||||||
|
for part in &wear_data.covers_parts {
|
||||||
|
let thickness: f64 =
|
||||||
|
other_clothes.iter().fold(
|
||||||
|
wear_data.thickness,
|
||||||
|
|tot, other_item|
|
||||||
|
match other_item.possession_type.as_ref()
|
||||||
|
.and_then(|pt| possession_data().get(&pt))
|
||||||
|
.and_then(|pd| pd.wear_data.as_ref())
|
||||||
|
{
|
||||||
|
Some(wd) if wd.covers_parts.contains(&part) =>
|
||||||
|
tot + wd.thickness,
|
||||||
|
_ => tot,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
info!("Thickness with item: {}", thickness);
|
||||||
|
if thickness > 12.0 {
|
||||||
|
user_error(format!(
|
||||||
|
"You're wearing too much on your {} already.",
|
||||||
|
part.display(player_item.sex.clone())
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let msg_exp = format!("{} wears {}\n",
|
||||||
|
&player_item.display_for_sentence(true, 1, true),
|
||||||
|
&item.display_for_sentence(true, 1, false));
|
||||||
|
let msg_nonexp = format!("{} wears {}\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.action_type = LocationActionType::Worn;
|
||||||
|
item_mut.action_type_started = Some(Utc::now());
|
||||||
|
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, mut remaining: &str) -> UResult<()> {
|
||||||
|
let player_item = get_player_item_or_fail(ctx).await?;
|
||||||
|
let mut get_limit = Some(1);
|
||||||
|
if remaining == "all" || remaining.starts_with("all ") {
|
||||||
|
remaining = remaining[3..].trim();
|
||||||
|
get_limit = None;
|
||||||
|
} else if let (Some(n), remaining2) = parse_count(remaining) {
|
||||||
|
get_limit = Some(n);
|
||||||
|
remaining = remaining2;
|
||||||
|
}
|
||||||
|
let targets = search_items_for_user(ctx, &ItemSearchParams {
|
||||||
|
include_contents: true,
|
||||||
|
item_type_only: Some("possession"),
|
||||||
|
limit: get_limit.unwrap_or(100),
|
||||||
|
item_action_type_only: Some(&LocationActionType::Normal),
|
||||||
|
..ItemSearchParams::base(&player_item, &remaining)
|
||||||
|
}).await?;
|
||||||
|
if player_item.death_data.is_some() {
|
||||||
|
user_error("The dead don't dress themselves".to_owned())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut did_anything: bool = false;
|
||||||
|
for target in targets.iter().filter(|t| t.action_type.is_visible_in_look()) {
|
||||||
|
if target.item_type != "possession" {
|
||||||
|
user_error("You can't wear that!".to_owned())?;
|
||||||
|
}
|
||||||
|
did_anything = true;
|
||||||
|
queue_command(ctx, &QueueCommand::Wear { possession_id: target.item_code.clone() }).await?;
|
||||||
|
}
|
||||||
|
if !did_anything {
|
||||||
|
user_error("I didn't find anything matching.".to_owned())?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static VERB_INT: Verb = Verb;
|
||||||
|
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;
|
@ -2,7 +2,7 @@ use serde::{Serialize, Deserialize};
|
|||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||||
pub enum ConsentType {
|
pub enum ConsentType {
|
||||||
Fight,
|
Fight,
|
||||||
Medicine,
|
Medicine,
|
||||||
|
@ -383,6 +383,7 @@ pub struct Item {
|
|||||||
pub aliases: Vec<String>,
|
pub aliases: Vec<String>,
|
||||||
pub location: String, // Item reference as item_type/item_code.
|
pub location: String, // Item reference as item_type/item_code.
|
||||||
pub action_type: LocationActionType,
|
pub action_type: LocationActionType,
|
||||||
|
pub action_type_started: Option<DateTime<Utc>>,
|
||||||
pub presence_target: Option<String>, // e.g. what are they sitting on.
|
pub presence_target: Option<String>, // e.g. what are they sitting on.
|
||||||
pub is_static: bool,
|
pub is_static: bool,
|
||||||
pub death_data: Option<DeathData>,
|
pub death_data: Option<DeathData>,
|
||||||
@ -468,6 +469,7 @@ impl Default for Item {
|
|||||||
aliases: vec!(),
|
aliases: vec!(),
|
||||||
location: "room/storage".to_owned(),
|
location: "room/storage".to_owned(),
|
||||||
action_type: LocationActionType::Normal,
|
action_type: LocationActionType::Normal,
|
||||||
|
action_type_started: None,
|
||||||
presence_target: None,
|
presence_target: None,
|
||||||
is_static: false,
|
is_static: false,
|
||||||
death_data: None,
|
death_data: None,
|
||||||
|
@ -14,16 +14,18 @@ use crate::message_handler::user_commands::{
|
|||||||
VerbContext,
|
VerbContext,
|
||||||
CommandHandlingError,
|
CommandHandlingError,
|
||||||
UResult,
|
UResult,
|
||||||
get,
|
|
||||||
drop,
|
|
||||||
movement,
|
|
||||||
use_cmd,
|
|
||||||
wield,
|
|
||||||
user_error,
|
|
||||||
get_user_or_fail,
|
|
||||||
open,
|
|
||||||
close,
|
close,
|
||||||
cut
|
cut,
|
||||||
|
drop,
|
||||||
|
get,
|
||||||
|
get_user_or_fail,
|
||||||
|
movement,
|
||||||
|
open,
|
||||||
|
remove,
|
||||||
|
use_cmd,
|
||||||
|
user_error,
|
||||||
|
wear,
|
||||||
|
wield,
|
||||||
};
|
};
|
||||||
use crate::static_content::room::Direction;
|
use crate::static_content::room::Direction;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
@ -36,7 +38,9 @@ pub enum QueueCommand {
|
|||||||
Get { possession_id: String },
|
Get { possession_id: String },
|
||||||
Movement { direction: Direction },
|
Movement { direction: Direction },
|
||||||
OpenDoor { direction: Direction },
|
OpenDoor { direction: Direction },
|
||||||
|
Remove { possession_id: String },
|
||||||
Use { possession_id: String, target_id: String },
|
Use { possession_id: String, target_id: String },
|
||||||
|
Wear { possession_id: String },
|
||||||
Wield { possession_id: String },
|
Wield { possession_id: String },
|
||||||
}
|
}
|
||||||
impl QueueCommand {
|
impl QueueCommand {
|
||||||
@ -49,7 +53,9 @@ impl QueueCommand {
|
|||||||
Get {..} => "Get",
|
Get {..} => "Get",
|
||||||
Movement {..} => "Movement",
|
Movement {..} => "Movement",
|
||||||
OpenDoor {..} => "OpenDoor",
|
OpenDoor {..} => "OpenDoor",
|
||||||
|
Remove {..} => "Remove",
|
||||||
Use {..} => "Use",
|
Use {..} => "Use",
|
||||||
|
Wear {..} => "Wear",
|
||||||
Wield {..} => "Wield",
|
Wield {..} => "Wield",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,7 +77,9 @@ fn queue_command_registry() -> &'static BTreeMap<&'static str, &'static (dyn Que
|
|||||||
("Get", &get::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
|
("Get", &get::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
|
||||||
("Movement", &movement::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
|
("Movement", &movement::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
|
||||||
("OpenDoor", &open::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
|
("OpenDoor", &open::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
|
||||||
|
("Remove", &remove::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
|
||||||
("Use", &use_cmd::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
|
("Use", &use_cmd::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
|
||||||
|
("Wear", &wear::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
|
||||||
("Wield", &wield::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
|
("Wield", &wield::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
|
||||||
).into_iter().collect())
|
).into_iter().collect())
|
||||||
}
|
}
|
||||||
|
@ -287,6 +287,7 @@ pub async fn handle_resurrect(trans: &DBTrans, player: &mut Item) -> DResult<boo
|
|||||||
player.total_xp -= lost_xp;
|
player.total_xp -= lost_xp;
|
||||||
user.experience.xp_change_for_this_reroll -= lost_xp as i64;
|
user.experience.xp_change_for_this_reroll -= lost_xp as i64;
|
||||||
player.health = max_health(&player);
|
player.health = max_health(&player);
|
||||||
|
player.active_climb = None;
|
||||||
|
|
||||||
trans.save_user_model(&user).await?;
|
trans.save_user_model(&user).await?;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
@ -354,7 +355,7 @@ pub async fn stop_attacking(trans: &DBTrans, by_whom: &Item, to_whom: &Item) ->
|
|||||||
|
|
||||||
async fn what_wielded(trans: &DBTrans, who: &Item) -> DResult<&'static WeaponData> {
|
async fn what_wielded(trans: &DBTrans, who: &Item) -> DResult<&'static WeaponData> {
|
||||||
if let Some(item) = trans.find_by_action_and_location(
|
if let Some(item) = trans.find_by_action_and_location(
|
||||||
&who.refstr(), &LocationActionType::Wielded).await? {
|
&who.refstr(), &LocationActionType::Wielded).await?.first() {
|
||||||
if let Some(dat) = item.possession_type.as_ref()
|
if let Some(dat) = item.possession_type.as_ref()
|
||||||
.and_then(|pt| possession_data().get(&pt))
|
.and_then(|pt| possession_data().get(&pt))
|
||||||
.and_then(|pd| pd.weapon_data.as_ref()) {
|
.and_then(|pd| pd.weapon_data.as_ref()) {
|
||||||
|
@ -3,21 +3,26 @@ use crate::{
|
|||||||
models::item::{SkillType, Item, Pronouns},
|
models::item::{SkillType, Item, Pronouns},
|
||||||
models::consent::ConsentType,
|
models::consent::ConsentType,
|
||||||
message_handler::user_commands::{UResult, VerbContext},
|
message_handler::user_commands::{UResult, VerbContext},
|
||||||
static_content::room::Direction,
|
static_content::{
|
||||||
|
room::Direction,
|
||||||
|
species::BodyPart,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
use super::species::BodyPart;
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
||||||
mod fangs;
|
mod fangs;
|
||||||
mod antenna_whip;
|
mod whip;
|
||||||
mod blade;
|
mod blade;
|
||||||
mod trauma_kit;
|
mod trauma_kit;
|
||||||
mod corp_licence;
|
mod corp_licence;
|
||||||
mod lock;
|
mod lock;
|
||||||
mod meat;
|
mod meat;
|
||||||
|
pub mod head_armour;
|
||||||
|
pub mod torso_armour;
|
||||||
|
pub mod lower_armour;
|
||||||
|
|
||||||
pub type AttackMessageChoice = Vec<Box<dyn Fn(&Item, &Item, bool) -> String + 'static + Sync + Send>>;
|
pub type AttackMessageChoice = Vec<Box<dyn Fn(&Item, &Item, bool) -> String + 'static + Sync + Send>>;
|
||||||
pub type AttackMessageChoicePart = Vec<Box<dyn Fn(&Item, &Item, &BodyPart, bool) -> String + 'static + Sync + Send>>;
|
pub type AttackMessageChoicePart = Vec<Box<dyn Fn(&Item, &Item, &BodyPart, bool) -> String + 'static + Sync + Send>>;
|
||||||
@ -115,6 +120,11 @@ impl Default for UseData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct WearData {
|
||||||
|
pub covers_parts: Vec<BodyPart>,
|
||||||
|
pub thickness: f64,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait WriteHandler {
|
pub trait WriteHandler {
|
||||||
async fn write_cmd(&self, ctx: &mut VerbContext, player: &Item, on_what: &Item, write_what: &str) -> UResult<()>;
|
async fn write_cmd(&self, ctx: &mut VerbContext, player: &Item, on_what: &Item, write_what: &str) -> UResult<()>;
|
||||||
@ -148,6 +158,7 @@ pub struct PossessionData {
|
|||||||
pub sign_handler: Option<&'static (dyn ArglessHandler + Sync + Send)>,
|
pub sign_handler: Option<&'static (dyn ArglessHandler + Sync + Send)>,
|
||||||
pub write_handler: Option<&'static (dyn WriteHandler + Sync + Send)>,
|
pub write_handler: Option<&'static (dyn WriteHandler + Sync + Send)>,
|
||||||
pub can_butcher: bool,
|
pub can_butcher: bool,
|
||||||
|
pub wear_data: Option<WearData>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PossessionData {
|
impl Default for PossessionData {
|
||||||
@ -169,6 +180,7 @@ impl Default for PossessionData {
|
|||||||
sign_handler: None,
|
sign_handler: None,
|
||||||
write_handler: None,
|
write_handler: None,
|
||||||
can_butcher: false,
|
can_butcher: false,
|
||||||
|
wear_data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,16 +213,28 @@ pub enum PossessionType {
|
|||||||
// Special values that substitute for possessions.
|
// Special values that substitute for possessions.
|
||||||
Fangs, // Default weapon for certain animals
|
Fangs, // Default weapon for certain animals
|
||||||
// Real possessions from here on:
|
// Real possessions from here on:
|
||||||
|
// Armour
|
||||||
|
RustyMetalPot,
|
||||||
|
HockeyMask,
|
||||||
|
LeatherJacket,
|
||||||
|
LeatherPants,
|
||||||
|
// Weapons: Whips
|
||||||
AntennaWhip,
|
AntennaWhip,
|
||||||
|
// Weapons: Blades
|
||||||
|
ButcherKnife,
|
||||||
|
// Medical
|
||||||
MediumTraumaKit,
|
MediumTraumaKit,
|
||||||
EmptyMedicalBox,
|
EmptyMedicalBox,
|
||||||
|
// Corporate
|
||||||
NewCorpLicence,
|
NewCorpLicence,
|
||||||
CertificateOfIncorporation,
|
CertificateOfIncorporation,
|
||||||
|
// Security
|
||||||
Scanlock,
|
Scanlock,
|
||||||
ButcherKnife,
|
// Crafting
|
||||||
Steak,
|
Steak,
|
||||||
AnimalSkin,
|
AnimalSkin,
|
||||||
SeveredHead,
|
SeveredHead,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<Item> for PossessionType {
|
impl Into<Item> for PossessionType {
|
||||||
@ -267,23 +291,23 @@ pub fn fist() -> &'static WeaponData {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn possession_data() -> &'static BTreeMap<PossessionType, PossessionData> {
|
pub fn possession_data() -> &'static BTreeMap<PossessionType, &'static PossessionData> {
|
||||||
static POSSESSION_DATA: OnceCell<BTreeMap<PossessionType, PossessionData>> = OnceCell::new();
|
static POSSESSION_DATA: OnceCell<BTreeMap<PossessionType, &'static PossessionData>> = OnceCell::new();
|
||||||
use PossessionType::*;
|
use PossessionType::*;
|
||||||
&POSSESSION_DATA.get_or_init(|| {
|
&POSSESSION_DATA.get_or_init(|| {
|
||||||
vec!(
|
vec!(
|
||||||
(Fangs, fangs::data()),
|
(Fangs, fangs::data())
|
||||||
(AntennaWhip, antenna_whip::data()),
|
).into_iter()
|
||||||
(ButcherKnife, blade::butcher_data()),
|
.chain(whip::data().iter().map(|v| ((*v).0.clone(), &(*v).1)))
|
||||||
(MediumTraumaKit, trauma_kit::medium_data()),
|
.chain(blade::data().iter().map(|v| ((*v).0.clone(), &(*v).1)))
|
||||||
(EmptyMedicalBox, trauma_kit::empty_data()),
|
.chain(trauma_kit::data().iter().map(|v| ((*v).0.clone(), &(*v).1)))
|
||||||
(NewCorpLicence, corp_licence::data()),
|
.chain(corp_licence::data().iter().map(|v| ((*v).0.clone(), &(*v).1)))
|
||||||
(CertificateOfIncorporation, corp_licence::cert_data()),
|
.chain(lock::data().iter().map(|v| ((*v).0.clone(), &(*v).1)))
|
||||||
(Scanlock, lock::scan()),
|
.chain(meat::data().iter().map(|v| ((*v).0.clone(), &(*v).1)))
|
||||||
(Steak, meat::steak_data()),
|
.chain(head_armour::data().iter().map(|v| ((*v).0.clone(), &(*v).1)))
|
||||||
(AnimalSkin, meat::skin_data()),
|
.chain(torso_armour::data().iter().map(|v| ((*v).0.clone(), &(*v).1)))
|
||||||
(SeveredHead, meat::severed_head_data()),
|
.chain(lower_armour::data().iter().map(|v| ((*v).0.clone(), &(*v).1)))
|
||||||
).into_iter().collect()
|
.collect()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
use super::{PossessionData, WeaponData};
|
|
||||||
use crate::models::item::SkillType;
|
|
||||||
|
|
||||||
pub fn data() -> PossessionData {
|
|
||||||
PossessionData {
|
|
||||||
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,
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,39 +1,45 @@
|
|||||||
use super::{PossessionData, WeaponData};
|
use super::{PossessionData, WeaponData, PossessionType};
|
||||||
use crate::models::item::SkillType;
|
use crate::models::item::SkillType;
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
pub fn butcher_data() -> PossessionData {
|
pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
|
||||||
PossessionData {
|
static D: OnceCell<Vec<(PossessionType, PossessionData)>> =
|
||||||
display: "butcher knife",
|
OnceCell::new();
|
||||||
details: "A 30 cm long stainless steel blade, sharp on one edge with a pointy tip. It looks perfect for butchering things, and in a pinch you could probably fight with it too.",
|
&D.get_or_init(|| vec!(
|
||||||
aliases: vec!("butcher", "knife"),
|
(PossessionType::ButcherKnife,
|
||||||
weight: 250,
|
PossessionData {
|
||||||
can_butcher: true,
|
display: "butcher knife",
|
||||||
weapon_data: Some(WeaponData {
|
details: "A 30 cm long stainless steel blade, sharp on one edge with a pointy tip. It looks perfect for butchering things, and in a pinch you could probably fight with it too.",
|
||||||
uses_skill: SkillType::Blades,
|
aliases: vec!("butcher", "knife"),
|
||||||
raw_min_to_learn: 0.0,
|
weight: 250,
|
||||||
raw_max_to_learn: 2.0,
|
can_butcher: true,
|
||||||
normal_attack_start_messages: vec!(
|
weapon_data: Some(WeaponData {
|
||||||
Box::new(|attacker, victim, exp|
|
uses_skill: SkillType::Blades,
|
||||||
format!("{} raises {} butcher knife menancingly, preparing to attack {}",
|
raw_min_to_learn: 0.0,
|
||||||
&attacker.display_for_sentence(exp, 1, true),
|
raw_max_to_learn: 2.0,
|
||||||
&attacker.pronouns.possessive,
|
normal_attack_start_messages: vec!(
|
||||||
&victim.display_for_sentence(exp, 1, false),
|
Box::new(|attacker, victim, exp|
|
||||||
)
|
format!("{} raises {} butcher knife menancingly, preparing to attack {}",
|
||||||
)
|
&attacker.display_for_sentence(exp, 1, true),
|
||||||
),
|
&attacker.pronouns.possessive,
|
||||||
normal_attack_success_messages: vec!(
|
&victim.display_for_sentence(exp, 1, false),
|
||||||
Box::new(|attacker, victim, part, exp|
|
)
|
||||||
format!("{}'s butcher knife cuts into {}'s {}",
|
)
|
||||||
&attacker.display_for_sentence(exp, 1, true),
|
),
|
||||||
&victim.display_for_sentence(exp, 1, false),
|
normal_attack_success_messages: vec!(
|
||||||
&part.display(victim.sex.clone())
|
Box::new(|attacker, victim, part, exp|
|
||||||
)
|
format!("{}'s butcher knife cuts into {}'s {}",
|
||||||
)
|
&attacker.display_for_sentence(exp, 1, true),
|
||||||
),
|
&victim.display_for_sentence(exp, 1, false),
|
||||||
normal_attack_mean_damage: 2.0,
|
&part.display(victim.sex.clone())
|
||||||
normal_attack_stdev_damage: 2.0,
|
)
|
||||||
..Default::default()
|
)
|
||||||
}),
|
),
|
||||||
..Default::default()
|
normal_attack_mean_damage: 2.0,
|
||||||
}
|
normal_attack_stdev_damage: 2.0,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::{PossessionData, WriteHandler, ArglessHandler};
|
use super::{PossessionData, PossessionType, WriteHandler, ArglessHandler, possession_data};
|
||||||
use crate::{
|
use crate::{
|
||||||
models::{
|
models::{
|
||||||
item::{Item, ItemSpecialData},
|
item::{Item, ItemSpecialData},
|
||||||
@ -9,6 +9,7 @@ use crate::{
|
|||||||
parsing::parse_username,
|
parsing::parse_username,
|
||||||
user_error,
|
user_error,
|
||||||
UResult,
|
UResult,
|
||||||
|
CommandHandlingError::UserError,
|
||||||
VerbContext,
|
VerbContext,
|
||||||
},
|
},
|
||||||
services::comms::broadcast_to_room,
|
services::comms::broadcast_to_room,
|
||||||
@ -16,6 +17,7 @@ use crate::{
|
|||||||
use ansi::ansi;
|
use ansi::ansi;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
use super::PossessionType::*;
|
use super::PossessionType::*;
|
||||||
|
|
||||||
@ -102,7 +104,8 @@ impl ArglessHandler for CorpLicenceHandler {
|
|||||||
|
|
||||||
let mut what_mut = what.clone();
|
let mut what_mut = what.clone();
|
||||||
what_mut.possession_type = Some(CertificateOfIncorporation);
|
what_mut.possession_type = Some(CertificateOfIncorporation);
|
||||||
let cp_data = cert_data();
|
let cp_data = possession_data().get(&CertificateOfIncorporation)
|
||||||
|
.ok_or_else(|| UserError("Certificate of Incorporation no longer exists as an item".to_owned()))?;
|
||||||
what_mut.display = cp_data.display.to_owned();
|
what_mut.display = cp_data.display.to_owned();
|
||||||
what_mut.details = Some(cp_data.details.to_owned());
|
what_mut.details = Some(cp_data.details.to_owned());
|
||||||
ctx.trans.save_item_model(&what_mut).await?;
|
ctx.trans.save_item_model(&what_mut).await?;
|
||||||
@ -113,24 +116,29 @@ impl ArglessHandler for CorpLicenceHandler {
|
|||||||
|
|
||||||
static CORP_LICENCE_HANDLER: CorpLicenceHandler = CorpLicenceHandler {};
|
static CORP_LICENCE_HANDLER: CorpLicenceHandler = CorpLicenceHandler {};
|
||||||
|
|
||||||
pub fn data() -> PossessionData {
|
pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
|
||||||
PossessionData {
|
static D: OnceCell<Vec<(PossessionType, PossessionData)>> =
|
||||||
display: "new corp licence",
|
OnceCell::new();
|
||||||
details: ansi!("A blank form that you can <bold>use<reset> to establish a new corp. It rests on a clipboard with a pencil attached by a chain. There is a space to <bold>write<reset> on it [try <bold>write Blah on licence<reset> followed by <bold>sign licence<reset> to create a corp named Blah]"),
|
&D.get_or_init(|| vec!(
|
||||||
aliases: vec!("form", "license", "licence", "new"),
|
(NewCorpLicence,
|
||||||
weight: 10,
|
PossessionData {
|
||||||
becomes_on_spent: Some(CertificateOfIncorporation),
|
display: "new corp licence",
|
||||||
write_handler: Some(&CORP_LICENCE_HANDLER),
|
details: ansi!("A blank form that you can <bold>use<reset> to establish a new corp. It rests on a clipboard with a pencil attached by a chain. There is a space to <bold>write<reset> on it [try <bold>write Blah on licence<reset> followed by <bold>sign licence<reset> to create a corp named Blah]"),
|
||||||
sign_handler: Some(&CORP_LICENCE_HANDLER),
|
aliases: vec!("form", "license", "licence", "new"),
|
||||||
..Default::default()
|
weight: 10,
|
||||||
}
|
becomes_on_spent: Some(CertificateOfIncorporation),
|
||||||
}
|
write_handler: Some(&CORP_LICENCE_HANDLER),
|
||||||
|
sign_handler: Some(&CORP_LICENCE_HANDLER),
|
||||||
pub fn cert_data() -> PossessionData {
|
..Default::default()
|
||||||
PossessionData {
|
}),
|
||||||
display: "certificate of incorporation",
|
(
|
||||||
details: "A certificate recording the formation of a corp.",
|
CertificateOfIncorporation,
|
||||||
weight: 10,
|
PossessionData {
|
||||||
..Default::default()
|
display: "certificate of incorporation",
|
||||||
}
|
details: "A certificate recording the formation of a corp.",
|
||||||
|
weight: 10,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
@ -1,32 +1,37 @@
|
|||||||
use super::{PossessionData, WeaponData};
|
use super::{PossessionData, WeaponData};
|
||||||
use crate::models::item::SkillType;
|
use crate::models::item::SkillType;
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
pub fn data() -> PossessionData {
|
pub fn data() -> &'static PossessionData {
|
||||||
PossessionData {
|
static D: OnceCell<PossessionData> = OnceCell::new();
|
||||||
weapon_data: Some(WeaponData {
|
D.get_or_init(
|
||||||
uses_skill: SkillType::Fists,
|
||
|
||||||
raw_min_to_learn: 0.0,
|
PossessionData {
|
||||||
raw_max_to_learn: 2.0,
|
weapon_data: Some(WeaponData {
|
||||||
normal_attack_start_messages: vec!(
|
uses_skill: SkillType::Fists,
|
||||||
Box::new(|attacker, victim, exp|
|
raw_min_to_learn: 0.0,
|
||||||
format!("{} bares {} teeth and lunges at {}",
|
raw_max_to_learn: 2.0,
|
||||||
&attacker.display_for_sentence(exp, 1, true),
|
normal_attack_start_messages: vec!(
|
||||||
&attacker.pronouns.possessive,
|
Box::new(|attacker, victim, exp|
|
||||||
&victim.display_for_sentence(exp, 1, false),
|
format!("{} bares {} teeth and lunges at {}",
|
||||||
|
&attacker.display_for_sentence(exp, 1, true),
|
||||||
|
&attacker.pronouns.possessive,
|
||||||
|
&victim.display_for_sentence(exp, 1, false),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
),
|
normal_attack_success_messages: vec!(
|
||||||
normal_attack_success_messages: vec!(
|
Box::new(|attacker, victim, part, exp|
|
||||||
Box::new(|attacker, victim, part, exp|
|
format!("{}'s teeth connect and tear at the flesh of {}'s {}",
|
||||||
format!("{}'s teeth connect and tear at the flesh of {}'s {}",
|
&attacker.display_for_sentence(exp, 1, true),
|
||||||
&attacker.display_for_sentence(exp, 1, true),
|
&victim.display_for_sentence(exp, 1, false),
|
||||||
&victim.display_for_sentence(exp, 1, false),
|
&part.display(victim.sex.clone())
|
||||||
&part.display(victim.sex.clone())
|
)
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
),
|
..Default::default()
|
||||||
..Default::default()
|
}),
|
||||||
}),
|
..Default::default()
|
||||||
..Default::default()
|
}
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
use super::{PossessionData, PossessionType, WearData};
|
||||||
|
use crate::static_content::species::BodyPart;
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
|
pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
|
||||||
|
static D: OnceCell<Vec<(PossessionType, PossessionData)>> =
|
||||||
|
OnceCell::new();
|
||||||
|
&D.get_or_init(|| vec!(
|
||||||
|
(
|
||||||
|
PossessionType::RustyMetalPot,
|
||||||
|
PossessionData {
|
||||||
|
display: "rusty metal pot",
|
||||||
|
details: "A metal pot that has rusted and is a bit dinged up - it looks like someone that way inclined could wear it as a serviceable helmet.",
|
||||||
|
aliases: vec!("pot", "rusted", "rusty"),
|
||||||
|
wear_data: Some(WearData {
|
||||||
|
covers_parts: vec!(BodyPart::Head),
|
||||||
|
thickness: 4.0,
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
(
|
||||||
|
PossessionType::HockeyMask,
|
||||||
|
PossessionData {
|
||||||
|
display: "hockey mask",
|
||||||
|
details: "A white face-hugging fibreglass hockey mask, with small air holes across the face, but no specific hole for the mouth. It looks like it would give a degree of protection to the face, but it might also make someone look like a serial killer!",
|
||||||
|
aliases: vec!("mask"),
|
||||||
|
wear_data: Some(WearData {
|
||||||
|
covers_parts: vec!(BodyPart::Face),
|
||||||
|
thickness: 4.0,
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
))
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
use super::{PossessionData, ArglessHandler};
|
use super::{PossessionData, ArglessHandler, PossessionType};
|
||||||
use crate::{
|
use crate::{
|
||||||
models::item::{Item, LocationActionType},
|
models::item::{Item, LocationActionType},
|
||||||
message_handler::user_commands::{user_error, VerbContext, UResult},
|
message_handler::user_commands::{user_error, VerbContext, UResult},
|
||||||
@ -10,6 +10,7 @@ use crate::{
|
|||||||
capacity::{check_item_capacity, CapacityLevel}}
|
capacity::{check_item_capacity, CapacityLevel}}
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
struct ScanLockLockcheck;
|
struct ScanLockLockcheck;
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@ -37,10 +38,10 @@ impl InstallHandler for ScanLockInstall {
|
|||||||
user_error("That scanlock is already in use.".to_owned())?;
|
user_error("That scanlock is already in use.".to_owned())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.trans.find_by_action_and_location(
|
if !ctx.trans.find_by_action_and_location(
|
||||||
&room.refstr(),
|
&room.refstr(),
|
||||||
&LocationActionType::InstalledOnDoorAsLock(direction.clone())
|
&LocationActionType::InstalledOnDoorAsLock(direction.clone())
|
||||||
).await?.is_some() {
|
).await?.is_empty() {
|
||||||
user_error("There's already a lock on that door - uninstall it first.".to_owned())?;
|
user_error("There's already a lock on that door - uninstall it first.".to_owned())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,14 +114,20 @@ impl InstallHandler for ScanLockInstall {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn scan() -> PossessionData {
|
pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
|
||||||
PossessionData {
|
static D: OnceCell<Vec<(PossessionType, PossessionData)>> =
|
||||||
display: "scanlock",
|
OnceCell::new();
|
||||||
details: "A relatively basic lock with a fingerprint scanner built into it, made to ensure only the owner of something can enter or use it.",
|
&D.get_or_init(|| vec!(
|
||||||
aliases: vec!("lock"),
|
(PossessionType::Scanlock,
|
||||||
weight: LOCK_WEIGHT,
|
PossessionData {
|
||||||
lockcheck_handler: Some(&ScanLockLockcheck),
|
display: "scanlock",
|
||||||
install_handler: Some(&ScanLockInstall),
|
details: "A relatively basic lock with a fingerprint scanner built into it, made to ensure only the owner of something can enter or use it.",
|
||||||
..Default::default()
|
aliases: vec!("lock"),
|
||||||
}
|
weight: LOCK_WEIGHT,
|
||||||
|
lockcheck_handler: Some(&ScanLockLockcheck),
|
||||||
|
install_handler: Some(&ScanLockInstall),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
use super::{PossessionData, PossessionType, WearData};
|
||||||
|
use crate::static_content::species::BodyPart;
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
|
pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
|
||||||
|
static D: OnceCell<Vec<(PossessionType, PossessionData)>> =
|
||||||
|
OnceCell::new();
|
||||||
|
&D.get_or_init(|| vec!(
|
||||||
|
(
|
||||||
|
PossessionType::LeatherPants,
|
||||||
|
PossessionData {
|
||||||
|
display: "pair of leather pants",
|
||||||
|
details: "Black leather pants that looks like they would protect you from falling off a motorbike, or maybe even offer some protection against certain weapons",
|
||||||
|
aliases: vec!("leather pants", "pants"),
|
||||||
|
wear_data: Some(WearData {
|
||||||
|
covers_parts: vec!(BodyPart::Groin, BodyPart::Legs),
|
||||||
|
thickness: 4.0,
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
@ -1,30 +1,37 @@
|
|||||||
use super::PossessionData;
|
use super::{PossessionData, PossessionType};
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
pub fn skin_data() -> PossessionData {
|
pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
|
||||||
PossessionData {
|
static D: OnceCell<Vec<(PossessionType, PossessionData)>> =
|
||||||
display: "animal skin",
|
OnceCell::new();
|
||||||
aliases: vec!("skin"),
|
&D.get_or_init(|| vec!(
|
||||||
details: "The skin of an animal of some kind. It looks like you could make something out of this",
|
(PossessionType::AnimalSkin,
|
||||||
weight: 100,
|
PossessionData {
|
||||||
..Default::default()
|
display: "animal skin",
|
||||||
}
|
aliases: vec!("skin"),
|
||||||
}
|
details: "The skin of an animal of some kind. It looks like you could make something out of this",
|
||||||
|
weight: 100,
|
||||||
pub fn steak_data() -> PossessionData {
|
..Default::default()
|
||||||
PossessionData {
|
}
|
||||||
display: "steak",
|
),
|
||||||
details: "A hunk of raw red meat, dripping with blood",
|
(
|
||||||
weight: 100,
|
PossessionType::Steak,
|
||||||
..Default::default()
|
PossessionData {
|
||||||
}
|
display: "steak",
|
||||||
}
|
details: "A hunk of raw red meat, dripping with blood",
|
||||||
|
weight: 100,
|
||||||
pub fn severed_head_data() -> PossessionData {
|
..Default::default()
|
||||||
PossessionData {
|
}
|
||||||
display: "severed head",
|
),
|
||||||
aliases: vec!("head"),
|
(
|
||||||
details: "A head that has been chopped clean from the body",
|
PossessionType::SeveredHead,
|
||||||
weight: 250,
|
PossessionData {
|
||||||
..Default::default()
|
display: "severed head",
|
||||||
}
|
aliases: vec!("head"),
|
||||||
|
details: "A head that has been chopped clean from the body",
|
||||||
|
weight: 250,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
use super::{PossessionData, PossessionType, WearData};
|
||||||
|
use crate::static_content::species::BodyPart;
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
|
pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
|
||||||
|
static D: OnceCell<Vec<(PossessionType, PossessionData)>> =
|
||||||
|
OnceCell::new();
|
||||||
|
&D.get_or_init(|| vec!(
|
||||||
|
(
|
||||||
|
PossessionType::LeatherJacket,
|
||||||
|
PossessionData {
|
||||||
|
display: "leather jacket",
|
||||||
|
details: "A black leather jacket that looks like it would protect you from falling off a motorbike, or maybe even offer some protection against certain weapons",
|
||||||
|
aliases: vec!("jacket"),
|
||||||
|
wear_data: Some(WearData {
|
||||||
|
covers_parts: vec!(BodyPart::Arms,
|
||||||
|
BodyPart::Chest,
|
||||||
|
BodyPart::Back),
|
||||||
|
thickness: 4.0,
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
@ -1,237 +1,244 @@
|
|||||||
use super::{PossessionData, UseData, UseEffect, ChargeData};
|
use super::{PossessionData, PossessionType, UseData, UseEffect, ChargeData};
|
||||||
use crate::models::{
|
use crate::models::{
|
||||||
item::SkillType,
|
item::SkillType,
|
||||||
consent::ConsentType,
|
consent::ConsentType,
|
||||||
};
|
};
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
use super::PossessionType::*;
|
use super::PossessionType::*;
|
||||||
|
|
||||||
pub fn medium_data() -> PossessionData {
|
pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
|
||||||
PossessionData {
|
static D: OnceCell<Vec<(PossessionType, PossessionData)>> =
|
||||||
display: "medium trauma kit",
|
OnceCell::new();
|
||||||
details: "A collection of bandages and and small gadgets that look like they could, in the right hands, make an unhealthy person healthy again. It looks like when brand new, it could be used 5 times.",
|
&D.get_or_init(|| vec!(
|
||||||
aliases: vec!("trauma"),
|
(MediumTraumaKit,
|
||||||
charge_data: Some(ChargeData {
|
PossessionData {
|
||||||
max_charges: 5,
|
display: "medium trauma kit",
|
||||||
charge_name_prefix: "treatment",
|
details: "A collection of bandages and and small gadgets that look like they could, in the right hands, make an unhealthy person healthy again. It looks like when brand new, it could be used 5 times.",
|
||||||
charge_name_suffix: "worth of supplies",
|
aliases: vec!("trauma"),
|
||||||
..Default::default()
|
charge_data: Some(ChargeData {
|
||||||
}),
|
max_charges: 5,
|
||||||
use_data: Some(UseData {
|
charge_name_prefix: "treatment",
|
||||||
uses_skill: SkillType::Medic,
|
charge_name_suffix: "worth of supplies",
|
||||||
diff_level: 10.0,
|
..Default::default()
|
||||||
crit_fail_effects: vec!(
|
}),
|
||||||
UseEffect::BroadcastMessage {
|
use_data: Some(UseData {
|
||||||
messagef: Box::new(|player, _item, target| (
|
uses_skill: SkillType::Medic,
|
||||||
format!(
|
diff_level: 10.0,
|
||||||
"{} attempts to heal {} with a trauma kit, but fucks it up badly\n",
|
crit_fail_effects: vec!(
|
||||||
&player.display_for_sentence(true, 1, true),
|
UseEffect::BroadcastMessage {
|
||||||
&if target.item_type == player.item_type && target.item_code == player.item_code {
|
messagef: Box::new(|player, _item, target| (
|
||||||
player.pronouns.intensive.clone()
|
format!(
|
||||||
} else {
|
"{} attempts to heal {} with a trauma kit, but fucks it up badly\n",
|
||||||
target.display_for_sentence(true, 1, false)
|
&player.display_for_sentence(true, 1, true),
|
||||||
}
|
&if target.item_type == player.item_type && target.item_code == player.item_code {
|
||||||
),
|
player.pronouns.intensive.clone()
|
||||||
format!("{} attempts to heal {} with a trauma kit, but messes it up badly\n",
|
} else {
|
||||||
&player.display_for_sentence(false, 1, true),
|
target.display_for_sentence(true, 1, false)
|
||||||
&if target.item_type == player.item_type && target.item_code == player.item_code {
|
}
|
||||||
player.pronouns.intensive.clone()
|
),
|
||||||
} else {
|
format!("{} attempts to heal {} with a trauma kit, but messes it up badly\n",
|
||||||
|
&player.display_for_sentence(false, 1, true),
|
||||||
|
&if target.item_type == player.item_type && target.item_code == player.item_code {
|
||||||
|
player.pronouns.intensive.clone()
|
||||||
|
} else {
|
||||||
|
target.display_for_sentence(false, 1, false)
|
||||||
|
}
|
||||||
|
)))
|
||||||
|
},
|
||||||
|
UseEffect::ChangeTargetHealth {
|
||||||
|
delay_secs: 0, base_effect: -2, skill_multiplier: -3.0,
|
||||||
|
max_effect: -5,
|
||||||
|
message: Box::new(
|
||||||
|
|target|
|
||||||
|
(format!(
|
||||||
|
"Fuck! The trauma kit makes {}'s condition worse",
|
||||||
|
target.display_for_sentence(true, 1, false)),
|
||||||
|
format!(
|
||||||
|
"The trauma kit makes {}'s condition worse",
|
||||||
target.display_for_sentence(false, 1, false)
|
target.display_for_sentence(false, 1, false)
|
||||||
}
|
)
|
||||||
)))
|
))
|
||||||
},
|
}
|
||||||
UseEffect::ChangeTargetHealth {
|
),
|
||||||
delay_secs: 0, base_effect: -2, skill_multiplier: -3.0,
|
fail_effects: vec!(
|
||||||
max_effect: -5,
|
UseEffect::BroadcastMessage {
|
||||||
message: Box::new(
|
messagef: Box::new(|player, _item, target| (
|
||||||
|target|
|
format!(
|
||||||
(format!(
|
"{} attempts unsuccessfully to heal {} with a trauma kit\n",
|
||||||
"Fuck! The trauma kit makes {}'s condition worse",
|
&player.display_for_sentence(true, 1, true),
|
||||||
target.display_for_sentence(true, 1, false)),
|
&if target.item_type == player.item_type && target.item_code == player.item_code {
|
||||||
format!(
|
player.pronouns.intensive.clone()
|
||||||
"The trauma kit makes {}'s condition worse",
|
} else {
|
||||||
target.display_for_sentence(false, 1, false)
|
target.display_for_sentence(true, 1, false)
|
||||||
)
|
}
|
||||||
))
|
),
|
||||||
}
|
format!("{} attempts unsuccessfully to heal {} with a trauma kit\n",
|
||||||
),
|
&player.display_for_sentence(false, 1, true),
|
||||||
fail_effects: vec!(
|
&if target.item_type == player.item_type && target.item_code == player.item_code {
|
||||||
UseEffect::BroadcastMessage {
|
player.pronouns.intensive.clone()
|
||||||
messagef: Box::new(|player, _item, target| (
|
} else {
|
||||||
format!(
|
target.display_for_sentence(false, 1, false)
|
||||||
"{} attempts unsuccessfully to heal {} with a trauma kit\n",
|
}
|
||||||
&player.display_for_sentence(true, 1, true),
|
)))
|
||||||
&if target.item_type == player.item_type && target.item_code == player.item_code {
|
},
|
||||||
player.pronouns.intensive.clone()
|
),
|
||||||
} else {
|
success_effects: vec!(
|
||||||
target.display_for_sentence(true, 1, false)
|
UseEffect::BroadcastMessage {
|
||||||
}
|
messagef: Box::new(|player, _item, target| (
|
||||||
),
|
format!(
|
||||||
format!("{} attempts unsuccessfully to heal {} with a trauma kit\n",
|
"{} expertly heals {} with a trauma kit\n",
|
||||||
&player.display_for_sentence(false, 1, true),
|
&player.display_for_sentence(true, 1, true),
|
||||||
&if target.item_type == player.item_type && target.item_code == player.item_code {
|
&if target.item_type == player.item_type && target.item_code == player.item_code {
|
||||||
player.pronouns.intensive.clone()
|
player.pronouns.intensive.clone()
|
||||||
} else {
|
} else {
|
||||||
target.display_for_sentence(false, 1, false)
|
target.display_for_sentence(true, 1, false)
|
||||||
}
|
}
|
||||||
)))
|
),
|
||||||
},
|
format!("{} expertly heals {} with a trauma kit\n",
|
||||||
),
|
&player.display_for_sentence(false, 1, true),
|
||||||
success_effects: vec!(
|
&if target.item_type == player.item_type && target.item_code == player.item_code {
|
||||||
UseEffect::BroadcastMessage {
|
player.pronouns.intensive.clone()
|
||||||
messagef: Box::new(|player, _item, target| (
|
} else {
|
||||||
format!(
|
target.display_for_sentence(false, 1, false)
|
||||||
"{} expertly heals {} with a trauma kit\n",
|
}
|
||||||
&player.display_for_sentence(true, 1, true),
|
)))
|
||||||
&if target.item_type == player.item_type && target.item_code == player.item_code {
|
},
|
||||||
player.pronouns.intensive.clone()
|
UseEffect::ChangeTargetHealth {
|
||||||
} else {
|
delay_secs: 0, base_effect: 2, skill_multiplier: 8.0,
|
||||||
target.display_for_sentence(true, 1, false)
|
max_effect: 10,
|
||||||
}
|
message: Box::new(
|
||||||
),
|
|target|
|
||||||
format!("{} expertly heals {} with a trauma kit\n",
|
(format!(
|
||||||
&player.display_for_sentence(false, 1, true),
|
"FUUUCK! It hurts {}, but also starts to soothe {}",
|
||||||
&if target.item_type == player.item_type && target.item_code == player.item_code {
|
target.display_for_sentence(true, 1, false),
|
||||||
player.pronouns.intensive.clone()
|
&target.pronouns.object
|
||||||
} else {
|
),
|
||||||
target.display_for_sentence(false, 1, false)
|
format!(
|
||||||
}
|
"It hurts {}, but also starts to soothe {}",
|
||||||
)))
|
target.display_for_sentence(true, 1, false),
|
||||||
},
|
&target.pronouns.object
|
||||||
UseEffect::ChangeTargetHealth {
|
))
|
||||||
delay_secs: 0, base_effect: 2, skill_multiplier: 8.0,
|
)
|
||||||
max_effect: 10,
|
},
|
||||||
message: Box::new(
|
UseEffect::ChangeTargetHealth {
|
||||||
|target|
|
delay_secs: 10, base_effect: 2, skill_multiplier: 7.0,
|
||||||
(format!(
|
max_effect: 9,
|
||||||
"FUUUCK! It hurts {}, but also starts to soothe {}",
|
message: Box::new(
|
||||||
target.display_for_sentence(true, 1, false),
|
|target|
|
||||||
&target.pronouns.object
|
(format!(
|
||||||
),
|
"FUUUCK! It hurts {}, but also starts to soothe {}",
|
||||||
format!(
|
target.display_for_sentence(true, 1, false),
|
||||||
"It hurts {}, but also starts to soothe {}",
|
&target.pronouns.object
|
||||||
target.display_for_sentence(true, 1, false),
|
),
|
||||||
&target.pronouns.object
|
format!(
|
||||||
))
|
"It hurts {}, but also starts to soothe {}",
|
||||||
)
|
target.display_for_sentence(true, 1, false),
|
||||||
},
|
&target.pronouns.object
|
||||||
UseEffect::ChangeTargetHealth {
|
))
|
||||||
delay_secs: 10, base_effect: 2, skill_multiplier: 7.0,
|
)
|
||||||
max_effect: 9,
|
},
|
||||||
message: Box::new(
|
UseEffect::ChangeTargetHealth {
|
||||||
|target|
|
delay_secs: 20, base_effect: 1, skill_multiplier: 6.0,
|
||||||
(format!(
|
max_effect: 7,
|
||||||
"FUUUCK! It hurts {}, but also starts to soothe {}",
|
message: Box::new(
|
||||||
target.display_for_sentence(true, 1, false),
|
|target|
|
||||||
&target.pronouns.object
|
(format!(
|
||||||
),
|
"The bandages soothe {}'s wounds",
|
||||||
format!(
|
target.display_for_sentence(true, 1, false),
|
||||||
"It hurts {}, but also starts to soothe {}",
|
),
|
||||||
target.display_for_sentence(true, 1, false),
|
format!(
|
||||||
&target.pronouns.object
|
"The bandages soothe {}'s wounds",
|
||||||
))
|
target.display_for_sentence(false, 1, false),
|
||||||
)
|
))
|
||||||
},
|
)
|
||||||
UseEffect::ChangeTargetHealth {
|
},
|
||||||
delay_secs: 20, base_effect: 1, skill_multiplier: 6.0,
|
UseEffect::ChangeTargetHealth {
|
||||||
max_effect: 7,
|
delay_secs: 30, base_effect: 1, skill_multiplier: 5.0,
|
||||||
message: Box::new(
|
max_effect: 6,
|
||||||
|target|
|
message: Box::new(
|
||||||
(format!(
|
|target|
|
||||||
"The bandages soothe {}'s wounds",
|
(format!(
|
||||||
target.display_for_sentence(true, 1, false),
|
"The bandages soothe {}'s wounds",
|
||||||
),
|
target.display_for_sentence(true, 1, false),
|
||||||
format!(
|
),
|
||||||
"The bandages soothe {}'s wounds",
|
format!(
|
||||||
target.display_for_sentence(false, 1, false),
|
"The bandages soothe {}'s wounds",
|
||||||
))
|
target.display_for_sentence(false, 1, false),
|
||||||
)
|
))
|
||||||
},
|
)
|
||||||
UseEffect::ChangeTargetHealth {
|
},
|
||||||
delay_secs: 30, base_effect: 1, skill_multiplier: 5.0,
|
UseEffect::ChangeTargetHealth {
|
||||||
max_effect: 6,
|
delay_secs: 40, base_effect: 0, skill_multiplier: 4.0,
|
||||||
message: Box::new(
|
max_effect: 4,
|
||||||
|target|
|
message: Box::new(
|
||||||
(format!(
|
|target|
|
||||||
"The bandages soothe {}'s wounds",
|
(format!(
|
||||||
target.display_for_sentence(true, 1, false),
|
"The bandages soothe {}'s wounds",
|
||||||
),
|
target.display_for_sentence(true, 1, false),
|
||||||
format!(
|
),
|
||||||
"The bandages soothe {}'s wounds",
|
format!(
|
||||||
target.display_for_sentence(false, 1, false),
|
"The bandages soothe {}'s wounds",
|
||||||
))
|
target.display_for_sentence(false, 1, false),
|
||||||
)
|
))
|
||||||
},
|
)
|
||||||
UseEffect::ChangeTargetHealth {
|
},
|
||||||
delay_secs: 40, base_effect: 0, skill_multiplier: 4.0,
|
UseEffect::ChangeTargetHealth {
|
||||||
max_effect: 4,
|
delay_secs: 50, base_effect: 0, skill_multiplier: 3.0,
|
||||||
message: Box::new(
|
max_effect: 3,
|
||||||
|target|
|
message: Box::new(
|
||||||
(format!(
|
|target|
|
||||||
"The bandages soothe {}'s wounds",
|
(format!(
|
||||||
target.display_for_sentence(true, 1, false),
|
"The bandages soothe {}'s wounds",
|
||||||
),
|
target.display_for_sentence(true, 1, false),
|
||||||
format!(
|
),
|
||||||
"The bandages soothe {}'s wounds",
|
format!(
|
||||||
target.display_for_sentence(false, 1, false),
|
"The bandages soothe {}'s wounds",
|
||||||
))
|
target.display_for_sentence(false, 1, false),
|
||||||
)
|
))
|
||||||
},
|
)
|
||||||
UseEffect::ChangeTargetHealth {
|
},
|
||||||
delay_secs: 50, base_effect: 0, skill_multiplier: 3.0,
|
UseEffect::ChangeTargetHealth {
|
||||||
max_effect: 3,
|
delay_secs: 60, base_effect: 0, skill_multiplier: 2.0,
|
||||||
message: Box::new(
|
max_effect: 2,
|
||||||
|target|
|
message: Box::new(
|
||||||
(format!(
|
|target|
|
||||||
"The bandages soothe {}'s wounds",
|
(format!(
|
||||||
target.display_for_sentence(true, 1, false),
|
"The bandages soothe {}'s wounds",
|
||||||
),
|
target.display_for_sentence(true, 1, false),
|
||||||
format!(
|
),
|
||||||
"The bandages soothe {}'s wounds",
|
format!(
|
||||||
target.display_for_sentence(false, 1, false),
|
"The bandages soothe {}'s wounds",
|
||||||
))
|
target.display_for_sentence(false, 1, false),
|
||||||
)
|
))
|
||||||
},
|
)
|
||||||
UseEffect::ChangeTargetHealth {
|
},
|
||||||
delay_secs: 60, base_effect: 0, skill_multiplier: 2.0,
|
),
|
||||||
max_effect: 2,
|
task_ref: "bandage",
|
||||||
message: Box::new(
|
errorf: Box::new(
|
||||||
|target|
|
|_item, target|
|
||||||
(format!(
|
if target.death_data.is_some() {
|
||||||
"The bandages soothe {}'s wounds",
|
Some(format!("It is too late, {}'s dead", target.pronouns.subject))
|
||||||
target.display_for_sentence(true, 1, false),
|
} else if target.item_type != "player" && target.item_type != "npc" {
|
||||||
),
|
Some("It only works on animals.".to_owned())
|
||||||
format!(
|
} else {
|
||||||
"The bandages soothe {}'s wounds",
|
None
|
||||||
target.display_for_sentence(false, 1, false),
|
}),
|
||||||
))
|
needs_consent_check: Some(ConsentType::Medicine),
|
||||||
)
|
..Default::default()
|
||||||
},
|
}),
|
||||||
),
|
becomes_on_spent: Some(EmptyMedicalBox),
|
||||||
task_ref: "bandage",
|
..Default::default()
|
||||||
errorf: Box::new(
|
}
|
||||||
|_item, target|
|
),
|
||||||
if target.death_data.is_some() {
|
(
|
||||||
Some(format!("It is too late, {}'s dead", target.pronouns.subject))
|
EmptyMedicalBox,
|
||||||
} else if target.item_type != "player" && target.item_type != "npc" {
|
PossessionData {
|
||||||
Some("It only works on animals.".to_owned())
|
display: "empty medical box",
|
||||||
} else {
|
details: "An empty box that looks like it once had something medical in it.",
|
||||||
None
|
aliases: vec!("box"),
|
||||||
}),
|
..Default::default()
|
||||||
needs_consent_check: Some(ConsentType::Medicine),
|
}
|
||||||
..Default::default()
|
)
|
||||||
}),
|
))
|
||||||
becomes_on_spent: Some(EmptyMedicalBox),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn empty_data() -> PossessionData {
|
|
||||||
PossessionData {
|
|
||||||
display: "empty medical box",
|
|
||||||
details: "An empty box that looks like it once had something medical in it.",
|
|
||||||
aliases: vec!("box"),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
45
blastmud_game/src/static_content/possession_type/whip.rs
Normal file
45
blastmud_game/src/static_content/possession_type/whip.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
use super::{PossessionData, PossessionType, WeaponData};
|
||||||
|
use crate::models::item::SkillType;
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
|
pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
|
||||||
|
static D: OnceCell<Vec<(PossessionType, PossessionData)>> =
|
||||||
|
OnceCell::new();
|
||||||
|
&D.get_or_init(|| vec!(
|
||||||
|
(PossessionType::AntennaWhip,
|
||||||
|
PossessionData {
|
||||||
|
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,
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
))
|
||||||
|
}
|
@ -2309,7 +2309,6 @@ pub fn room_list() -> Vec<Room> {
|
|||||||
should_caption: false,
|
should_caption: false,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
|
||||||
Room {
|
Room {
|
||||||
zone: "melbs",
|
zone: "melbs",
|
||||||
secondary_zones: vec!(),
|
secondary_zones: vec!(),
|
||||||
@ -2320,6 +2319,10 @@ pub fn room_list() -> Vec<Room> {
|
|||||||
description_less_explicit: None,
|
description_less_explicit: None,
|
||||||
grid_coords: GridCoords { x: 2, y: 3, z: 0 },
|
grid_coords: GridCoords { x: 2, y: 3, z: 0 },
|
||||||
exits: vec!(
|
exits: vec!(
|
||||||
|
Exit {
|
||||||
|
direction: Direction::NORTH,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
Exit {
|
Exit {
|
||||||
direction: Direction::WEST,
|
direction: Direction::WEST,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@ -2332,6 +2335,41 @@ pub fn room_list() -> Vec<Room> {
|
|||||||
should_caption: false,
|
should_caption: false,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
Room {
|
||||||
|
zone: "melbs",
|
||||||
|
secondary_zones: vec!(),
|
||||||
|
code: "melbs_riotready",
|
||||||
|
name: "Riot Ready",
|
||||||
|
short: ansi!("<bggreen><red>RR<reset>"),
|
||||||
|
description: ansi!("A small shop filled with shelves, racks, and hangers that seem to hold all kinds of safety and tactical defensive equipment that one might need to survive in a post-apocalyptic world. A weary looking middle aged lady stands on the display floor, herself clad in black tactical helmets and vests, making you wonder if it is to showcase the wares, or protect herself from looters. Across her right breast is a name tag reading \"Sharon\" with a logo featuring a smiley face below it. [Type <bold>list<reset> to see what's for sale]"),
|
||||||
|
description_less_explicit: None,
|
||||||
|
grid_coords: GridCoords { x: 2, y: 2, z: 0 },
|
||||||
|
exits: vec!(
|
||||||
|
Exit {
|
||||||
|
direction: Direction::SOUTH,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
should_caption: true,
|
||||||
|
stock_list: vec!(
|
||||||
|
RoomStock {
|
||||||
|
possession_type: PossessionType::HockeyMask,
|
||||||
|
list_price: 1000,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
RoomStock {
|
||||||
|
possession_type: PossessionType::LeatherJacket,
|
||||||
|
list_price: 500,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
RoomStock {
|
||||||
|
possession_type: PossessionType::LeatherPants,
|
||||||
|
list_price: 500,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
Room {
|
Room {
|
||||||
zone: "melbs",
|
zone: "melbs",
|
||||||
secondary_zones: vec!(),
|
secondary_zones: vec!(),
|
||||||
@ -2407,7 +2445,12 @@ pub fn room_list() -> Vec<Room> {
|
|||||||
possession_type: PossessionType::ButcherKnife,
|
possession_type: PossessionType::ButcherKnife,
|
||||||
list_price: 120,
|
list_price: 120,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
},
|
||||||
|
RoomStock {
|
||||||
|
possession_type: PossessionType::RustyMetalPot,
|
||||||
|
list_price: 400,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
),
|
),
|
||||||
should_caption: true,
|
should_caption: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -30,6 +30,7 @@ pub enum BodyPart {
|
|||||||
Back,
|
Back,
|
||||||
Groin,
|
Groin,
|
||||||
Arms,
|
Arms,
|
||||||
|
Hands,
|
||||||
Legs,
|
Legs,
|
||||||
Feet
|
Feet
|
||||||
}
|
}
|
||||||
@ -51,10 +52,29 @@ impl BodyPart {
|
|||||||
_ => "groin"
|
_ => "groin"
|
||||||
},
|
},
|
||||||
Arms => "arms",
|
Arms => "arms",
|
||||||
|
Hands => "hands",
|
||||||
Legs => "legs",
|
Legs => "legs",
|
||||||
Feet => "feet"
|
Feet => "feet"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn copula(&self, sex: Option<Sex>) -> &'static str {
|
||||||
|
use BodyPart::*;
|
||||||
|
match self {
|
||||||
|
Head => "is",
|
||||||
|
Face => "is",
|
||||||
|
Chest => match sex {
|
||||||
|
Some(Sex::Female) => "are",
|
||||||
|
_ => "is",
|
||||||
|
},
|
||||||
|
Back => "is",
|
||||||
|
Groin => "is",
|
||||||
|
Arms => "are",
|
||||||
|
Hands => "are",
|
||||||
|
Legs => "are",
|
||||||
|
Feet => "are"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SpeciesInfo {
|
pub struct SpeciesInfo {
|
||||||
@ -76,6 +96,7 @@ pub fn species_info_map() -> &'static BTreeMap<SpeciesType, SpeciesInfo> {
|
|||||||
BodyPart::Back,
|
BodyPart::Back,
|
||||||
BodyPart::Groin,
|
BodyPart::Groin,
|
||||||
BodyPart::Arms,
|
BodyPart::Arms,
|
||||||
|
BodyPart::Hands,
|
||||||
BodyPart::Legs,
|
BodyPart::Legs,
|
||||||
BodyPart::Feet
|
BodyPart::Feet
|
||||||
),
|
),
|
||||||
|
Loading…
Reference in New Issue
Block a user