Allow (un)installation of scanlocks.
This commit is contained in:
parent
3d3f792fdc
commit
ed3d77dcbe
@ -739,7 +739,7 @@ impl DBTrans {
|
||||
self.pg_trans()?.execute("UPDATE items SET details=\
|
||||
JSONB_SET(details, '{action_type}', $1) \
|
||||
WHERE details->>'location' = $2 AND \
|
||||
details->>'action_type' = $3::JSONB::TEXT",
|
||||
(details->'action_type')::TEXT = $3::JSONB::TEXT",
|
||||
&[&serde_json::to_value(other_item_action_type)?,
|
||||
&item.location,
|
||||
&serde_json::to_value(new_action_type)?
|
||||
@ -759,7 +759,7 @@ impl DBTrans {
|
||||
if let Some(item) = self.pg_trans()?.query_opt(
|
||||
"SELECT details FROM items WHERE \
|
||||
details->>'location' = $1 AND \
|
||||
details->>'action_type' = $2::JSONB::TEXT",
|
||||
((details->'action_type')::TEXT = $2::JSONB::TEXT)",
|
||||
&[&location,
|
||||
&serde_json::to_value(action_type)?]).await? {
|
||||
return Ok(Some(Arc::new(serde_json::from_value::<Item>(item.get("details"))?)));
|
||||
|
@ -23,6 +23,7 @@ pub mod get;
|
||||
mod describe;
|
||||
mod help;
|
||||
mod ignore;
|
||||
mod install;
|
||||
mod inventory;
|
||||
mod less_explicit_mode;
|
||||
mod list;
|
||||
@ -40,6 +41,7 @@ pub mod say;
|
||||
mod score;
|
||||
mod sign;
|
||||
mod status;
|
||||
mod uninstall;
|
||||
pub mod use_cmd;
|
||||
mod vacate;
|
||||
mod whisper;
|
||||
@ -130,6 +132,7 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! {
|
||||
"corp" => corp::VERB,
|
||||
"drop" => drop::VERB,
|
||||
"get" => get::VERB,
|
||||
"install" => install::VERB,
|
||||
"inventory" => inventory::VERB,
|
||||
"inv" => inventory::VERB,
|
||||
"i" => inventory::VERB,
|
||||
@ -170,7 +173,8 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! {
|
||||
"st" => status::VERB,
|
||||
"stat" => status::VERB,
|
||||
"status" => status::VERB,
|
||||
|
||||
|
||||
"uninstall" => uninstall::VERB,
|
||||
"use" => use_cmd::VERB,
|
||||
"vacate" => vacate::VERB,
|
||||
|
||||
|
@ -101,6 +101,9 @@ impl QueueCommandHandler for QueueHandler {
|
||||
)
|
||||
).await?;
|
||||
}
|
||||
|
||||
ctx.trans.delete_task("SwingShut",
|
||||
&format!("{}/{}", &room_1.refstr(), &dir_in_room.describe())).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -136,12 +136,18 @@ impl UserVerb for Verb {
|
||||
if player_item.is_dead {
|
||||
user_error("You try to get it, but your ghostly hands slip through it uselessly".to_owned())?;
|
||||
}
|
||||
for target in targets {
|
||||
|
||||
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 get that!".to_owned())?;
|
||||
}
|
||||
did_anything = true;
|
||||
queue_command(ctx, &QueueCommand::Get { possession_id: target.item_code.clone() }).await?;
|
||||
}
|
||||
if !did_anything {
|
||||
user_error("I didn't find anything matching.".to_owned())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
66
blastmud_game/src/message_handler/user_commands/install.rs
Normal file
66
blastmud_game/src/message_handler/user_commands/install.rs
Normal file
@ -0,0 +1,66 @@
|
||||
use super::{VerbContext, UserVerb, UserVerbRef, UResult,
|
||||
UserError,
|
||||
user_error, get_player_item_or_fail, search_item_for_user};
|
||||
use crate::{
|
||||
db::ItemSearchParams,
|
||||
static_content::{
|
||||
possession_type::possession_data,
|
||||
room::Direction,
|
||||
},
|
||||
models::item::ItemFlag,
|
||||
};
|
||||
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 (install_what_raw, what_dir_raw) = match remaining.rsplit_once(" on door to ") {
|
||||
None => user_error(ansi!("Install where? Try <bold>install<reset> <lt>lock> <bold>on door to<reset> <lt>direction>").to_owned())?,
|
||||
Some(v) => v
|
||||
};
|
||||
let player_item = get_player_item_or_fail(ctx).await?;
|
||||
if player_item.is_dead {
|
||||
user_error("Apparently, you have to be alive to work as an installer.\
|
||||
So discriminatory!".to_owned())?;
|
||||
}
|
||||
let item = search_item_for_user(ctx, &ItemSearchParams {
|
||||
include_contents: true,
|
||||
..ItemSearchParams::base(&player_item, install_what_raw.trim())
|
||||
}).await?;
|
||||
if item.item_type != "possession" {
|
||||
user_error("You can't install that!".to_owned())?;
|
||||
}
|
||||
|
||||
let handler = match item.possession_type.as_ref()
|
||||
.and_then(|pt| possession_data().get(pt))
|
||||
.and_then(|pd| pd.install_handler) {
|
||||
None => user_error("You can't install that!".to_owned())?,
|
||||
Some(h) => h
|
||||
};
|
||||
|
||||
let (loc_t, loc_c) = player_item.location.split_once("/")
|
||||
.ok_or_else(|| UserError("Invalid current location".to_owned()))?;
|
||||
let loc_item = ctx.trans.find_item_by_type_code(loc_t, loc_c).await?
|
||||
.ok_or_else(|| UserError("Can't find your location".to_owned()))?;
|
||||
if loc_item.owner.as_ref() != Some(&player_item.refstr()) || !loc_item.flags.contains(&ItemFlag::PrivatePlace) {
|
||||
user_error("You can only install things while standing in a private room you own. \
|
||||
If you are outside, try installing from the inside."
|
||||
.to_owned())?;
|
||||
}
|
||||
|
||||
let dir = Direction::parse(what_dir_raw.trim()).ok_or_else(
|
||||
|| UserError("Invalid direction.".to_owned()))?;
|
||||
|
||||
loc_item.door_states.as_ref().and_then(|ds| ds.get(&dir)).ok_or_else(
|
||||
|| UserError("No door to that direction in this room - are you on the wrong side?".to_owned())
|
||||
)?;
|
||||
|
||||
handler.install_cmd(ctx, &player_item, &item, &loc_item, &dir).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
static VERB_INT: Verb = Verb;
|
||||
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;
|
@ -193,8 +193,7 @@ async fn describe_door(
|
||||
&LocationActionType::InstalledOnDoorAsLock((*direction).clone())).await?
|
||||
{
|
||||
let lock_desc = lock.display_for_session(&ctx.session_dat);
|
||||
msg.push_str(&format!(" The door is locked with {} {}",
|
||||
&language::indefinite_article(&lock_desc),
|
||||
msg.push_str(&format!(" The door is locked with {}",
|
||||
&lock_desc
|
||||
));
|
||||
}
|
||||
@ -217,6 +216,7 @@ async fn list_room_contents<'l>(ctx: &'l VerbContext<'_>, item: &'l Item) -> URe
|
||||
|
||||
let all_groups: Vec<Vec<&Arc<Item>>> = items
|
||||
.iter()
|
||||
.filter(|i| i.action_type.is_visible_in_look())
|
||||
.group_by(|i| i.display_for_sentence(true, 1, false))
|
||||
.into_iter()
|
||||
.map(|(_, g)|g.collect::<Vec<&Arc<Item>>>())
|
||||
|
67
blastmud_game/src/message_handler/user_commands/uninstall.rs
Normal file
67
blastmud_game/src/message_handler/user_commands/uninstall.rs
Normal file
@ -0,0 +1,67 @@
|
||||
use super::{VerbContext, UserVerb, UserVerbRef, UResult,
|
||||
UserError,
|
||||
user_error, get_player_item_or_fail, search_items_for_user};
|
||||
use crate::{
|
||||
db::ItemSearchParams,
|
||||
static_content::{
|
||||
possession_type::possession_data,
|
||||
room::Direction,
|
||||
},
|
||||
models::item::ItemFlag,
|
||||
};
|
||||
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 (uninstall_what_raw, what_dir_raw) = match remaining.rsplit_once(" from door to ") {
|
||||
None => user_error(ansi!("Uninstall from where? Try <bold>uninstall<reset> <lt>lock> <bold>from door to<reset> <lt>direction>").to_owned())?,
|
||||
Some(v) => v
|
||||
};
|
||||
let player_item = get_player_item_or_fail(ctx).await?;
|
||||
if player_item.is_dead {
|
||||
user_error("Apparently, you have to be alive to work as an uninstaller. \
|
||||
So discriminatory!".to_owned())?;
|
||||
}
|
||||
|
||||
let (loc_t, loc_c) = player_item.location.split_once("/")
|
||||
.ok_or_else(|| UserError("Invalid current location".to_owned()))?;
|
||||
let loc_item = ctx.trans.find_item_by_type_code(loc_t, loc_c).await?
|
||||
.ok_or_else(|| UserError("Can't find your location".to_owned()))?;
|
||||
if loc_item.owner.as_ref() != Some(&player_item.refstr()) || !loc_item.flags.contains(&ItemFlag::PrivatePlace) {
|
||||
user_error("You can only uninstall things while standing in a private room you own. \
|
||||
If you are outside, try uninstalling from the inside."
|
||||
.to_owned())?;
|
||||
}
|
||||
|
||||
let dir = Direction::parse(what_dir_raw.trim()).ok_or_else(
|
||||
|| UserError("Invalid direction.".to_owned()))?;
|
||||
|
||||
let cand_items = search_items_for_user(ctx, &ItemSearchParams {
|
||||
include_loc_contents: true,
|
||||
..ItemSearchParams::base(&player_item, uninstall_what_raw.trim())
|
||||
}).await?;
|
||||
let item = cand_items.iter().find(|it| it.action_type.is_in_direction(&dir))
|
||||
.ok_or_else(
|
||||
|| UserError(
|
||||
"Sorry, I couldn't find anything matching installed on that door.".to_owned()))?;
|
||||
if item.item_type != "possession" {
|
||||
user_error("You can't uninstall that!".to_owned())?;
|
||||
}
|
||||
|
||||
let handler = match item.possession_type.as_ref()
|
||||
.and_then(|pt| possession_data().get(pt))
|
||||
.and_then(|pd| pd.install_handler) {
|
||||
None => user_error("You can't uninstall that!".to_owned())?,
|
||||
Some(h) => h
|
||||
};
|
||||
|
||||
handler.uninstall_cmd(ctx, &player_item, &item, &loc_item, &dir).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
static VERB_INT: Verb = Verb;
|
||||
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;
|
@ -260,6 +260,23 @@ pub enum LocationActionType {
|
||||
InstalledOnDoorAsLock(Direction),
|
||||
}
|
||||
|
||||
impl LocationActionType {
|
||||
pub fn is_visible_in_look(&self) -> bool {
|
||||
use LocationActionType::*;
|
||||
match self {
|
||||
InstalledOnDoorAsLock(_) => false,
|
||||
_ => true
|
||||
}
|
||||
}
|
||||
pub fn is_in_direction(&self, dir: &Direction) -> bool {
|
||||
use LocationActionType::*;
|
||||
match self {
|
||||
InstalledOnDoorAsLock(d) if d == dir => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub enum Sex {
|
||||
Male,
|
||||
|
@ -332,7 +332,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> {
|
||||
if let Some(item) = trans.find_by_action_and_location(
|
||||
&format!("{}/{}", &who.item_type, &who.item_code), &LocationActionType::Wielded).await? {
|
||||
&who.refstr(), &LocationActionType::Wielded).await? {
|
||||
if let Some(dat) = item.possession_type.as_ref()
|
||||
.and_then(|pt| possession_data().get(&pt))
|
||||
.and_then(|pd| pd.weapon_data.as_ref()) {
|
||||
|
@ -3,6 +3,7 @@ use crate::{
|
||||
models::item::{SkillType, Item, Pronouns},
|
||||
models::consent::ConsentType,
|
||||
message_handler::user_commands::{UResult, VerbContext},
|
||||
static_content::room::Direction,
|
||||
};
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::collections::BTreeMap;
|
||||
@ -122,6 +123,12 @@ pub trait ArglessHandler {
|
||||
async fn cmd(&self, ctx: &mut VerbContext, player: &Item, what: &Item) -> UResult<()>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait InstallHandler {
|
||||
async fn install_cmd(&self, ctx: &mut VerbContext, player: &Item, what: &Item, room: &Item, direction: &Direction) -> UResult<()>;
|
||||
async fn uninstall_cmd(&self, ctx: &mut VerbContext, player: &Item, what: &Item, room: &Item, direction: &Direction) -> UResult<()>;
|
||||
}
|
||||
|
||||
pub struct PossessionData {
|
||||
pub weapon_data: Option<WeaponData>,
|
||||
pub display: &'static str,
|
||||
@ -134,9 +141,10 @@ pub struct PossessionData {
|
||||
pub use_data: Option<UseData>,
|
||||
pub becomes_on_spent: Option<PossessionType>,
|
||||
pub weight: u64,
|
||||
pub write_handler: Option<&'static (dyn WriteHandler + Sync + Send)>,
|
||||
pub sign_handler: Option<&'static (dyn ArglessHandler + Sync + Send)>,
|
||||
pub install_handler: Option<&'static (dyn InstallHandler + Sync + Send)>,
|
||||
pub lockcheck_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)>,
|
||||
}
|
||||
|
||||
impl Default for PossessionData {
|
||||
@ -153,9 +161,10 @@ impl Default for PossessionData {
|
||||
charge_data: None,
|
||||
becomes_on_spent: None,
|
||||
use_data: None,
|
||||
write_handler: None,
|
||||
sign_handler: None,
|
||||
install_handler: None,
|
||||
lockcheck_handler: None,
|
||||
sign_handler: None,
|
||||
write_handler: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,126 @@
|
||||
use super::PossessionData;
|
||||
use super::{PossessionData, ArglessHandler};
|
||||
use crate::{
|
||||
models::item::{Item, LocationActionType},
|
||||
message_handler::user_commands::{user_error, VerbContext, UResult},
|
||||
static_content::{
|
||||
possession_type::InstallHandler,
|
||||
room::Direction,
|
||||
},
|
||||
services::{comms::broadcast_to_room,
|
||||
capacity::{check_item_capacity, CapacityLevel}}
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
|
||||
struct ScanLockLockcheck;
|
||||
#[async_trait]
|
||||
impl ArglessHandler for ScanLockLockcheck {
|
||||
async fn cmd(&self, ctx: &mut VerbContext, player: &Item, what: &Item) -> UResult<()> {
|
||||
if what.owner == Some(player.refstr()) {
|
||||
ctx.trans.queue_for_session(&ctx.session, Some(
|
||||
"The scanlock in the door emits a single high-pitched beep as it unlocks.\n")).await?;
|
||||
} else {
|
||||
user_error(
|
||||
"The scanlock in the door emits a medium-pitched tone followed by a low tone, and remains locked.".to_owned())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
static LOCK_WEIGHT: u64 = 500;
|
||||
|
||||
struct ScanLockInstall;
|
||||
#[async_trait]
|
||||
impl InstallHandler for ScanLockInstall {
|
||||
async fn install_cmd(&self, ctx: &mut VerbContext, player: &Item, what: &Item, room: &Item,
|
||||
direction: &Direction) -> UResult<()> {
|
||||
if what.action_type != LocationActionType::Normal {
|
||||
user_error("That scanlock is already in use.".to_owned())?;
|
||||
}
|
||||
|
||||
if ctx.trans.find_by_action_and_location(
|
||||
&room.refstr(),
|
||||
&LocationActionType::InstalledOnDoorAsLock(direction.clone())
|
||||
).await?.is_some() {
|
||||
user_error("There's already a lock on that door - uninstall it first.".to_owned())?;
|
||||
}
|
||||
|
||||
match check_item_capacity(&ctx.trans, &room.refstr(), LOCK_WEIGHT).await? {
|
||||
CapacityLevel::OverBurdened | CapacityLevel::AboveItemLimit =>
|
||||
user_error("That room has so much stuff, you can't install anything new."
|
||||
.to_owned())?,
|
||||
_ => {}
|
||||
};
|
||||
let mut what_mut = (*what).clone();
|
||||
what_mut.location = room.refstr();
|
||||
what_mut.action_type = LocationActionType::InstalledOnDoorAsLock(direction.clone());
|
||||
what_mut.owner = room.owner.clone();
|
||||
ctx.trans.save_item_model(&what_mut).await?;
|
||||
|
||||
broadcast_to_room(
|
||||
&ctx.trans, &room.refstr(), None,
|
||||
&format!("{} bangs the door to the {} as he installs {} on it.\n",
|
||||
&player.display_for_sentence(true, 1, true),
|
||||
&direction.describe(),
|
||||
&what.display_for_sentence(true, 1, false)),
|
||||
Some(
|
||||
&format!("{} bangs the door to the {} as he installs {} on it.\n",
|
||||
&player.display_for_sentence(false, 1, true),
|
||||
&direction.describe(),
|
||||
&what.display_for_sentence(false, 1, false)),
|
||||
)).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn uninstall_cmd(&self, ctx: &mut VerbContext, player: &Item, what: &Item, room: &Item,
|
||||
direction: &Direction) -> UResult<()> {
|
||||
if what.action_type != LocationActionType::InstalledOnDoorAsLock(direction.clone()) {
|
||||
user_error("That scanlock is not installed as a lock.".to_owned())?;
|
||||
}
|
||||
|
||||
let mut what_mut = (*what).clone();
|
||||
|
||||
let extra_text = match check_item_capacity(&ctx.trans, &player.refstr(), LOCK_WEIGHT).await? {
|
||||
CapacityLevel::OverBurdened | CapacityLevel::AboveItemLimit => {
|
||||
", dropping it on the floor since he can't hold it."
|
||||
},
|
||||
_ => {
|
||||
what_mut.location = player.refstr();
|
||||
""
|
||||
}
|
||||
};
|
||||
what_mut.action_type = LocationActionType::Normal;
|
||||
what_mut.owner = None;
|
||||
ctx.trans.save_item_model(&what_mut).await?;
|
||||
|
||||
broadcast_to_room(
|
||||
&ctx.trans, &room.refstr(), None,
|
||||
&format!("{} bangs the door to the {} as he uninstalls {} from it{}.\n",
|
||||
&player.display_for_sentence(true, 1, true),
|
||||
&direction.describe(),
|
||||
&what.display_for_sentence(true, 1, false),
|
||||
extra_text
|
||||
),
|
||||
Some(
|
||||
&format!("{} bangs the door to the {} as he uninstalls {} from it{}.\n",
|
||||
&player.display_for_sentence(false, 1, true),
|
||||
&direction.describe(),
|
||||
&what.display_for_sentence(false, 1, false),
|
||||
extra_text
|
||||
),
|
||||
)).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn scan() -> PossessionData {
|
||||
PossessionData {
|
||||
display: "scanlock",
|
||||
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.",
|
||||
aliases: vec!("lock"),
|
||||
weight: 500,
|
||||
weight: LOCK_WEIGHT,
|
||||
lockcheck_handler: Some(&ScanLockLockcheck),
|
||||
install_handler: Some(&ScanLockInstall),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user