Allow (un)installation of scanlocks.

This commit is contained in:
Condorra 2023-04-21 23:33:23 +10:00
parent 3d3f792fdc
commit ed3d77dcbe
11 changed files with 300 additions and 13 deletions

View File

@ -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"))?)));

View File

@ -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,
@ -171,6 +174,7 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! {
"stat" => status::VERB,
"status" => status::VERB,
"uninstall" => uninstall::VERB,
"use" => use_cmd::VERB,
"vacate" => vacate::VERB,

View File

@ -102,6 +102,9 @@ impl QueueCommandHandler for QueueHandler {
).await?;
}
ctx.trans.delete_task("SwingShut",
&format!("{}/{}", &room_1.refstr(), &dir_in_room.describe())).await?;
Ok(())
}
}

View File

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

View 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;

View File

@ -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>>>())

View 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;

View File

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

View File

@ -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()) {

View File

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

View File

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