forked from blasthavers/blastmud
Allow doors to open.
This commit is contained in:
parent
284c49b4a1
commit
131512fbf6
@ -739,11 +739,10 @@ impl DBTrans {
|
|||||||
self.pg_trans()?.execute("UPDATE items SET details=\
|
self.pg_trans()?.execute("UPDATE items SET details=\
|
||||||
JSONB_SET(details, '{action_type}', $1) \
|
JSONB_SET(details, '{action_type}', $1) \
|
||||||
WHERE details->>'location' = $2 AND \
|
WHERE details->>'location' = $2 AND \
|
||||||
details->>'action_type' = $3",
|
details->>'action_type' = $3::JSONB::TEXT",
|
||||||
&[&serde_json::to_value(other_item_action_type)?,
|
&[&serde_json::to_value(other_item_action_type)?,
|
||||||
&item.location,
|
&item.location,
|
||||||
&serde_json::to_value(new_action_type)?
|
&serde_json::to_value(new_action_type)?
|
||||||
.as_str().unwrap()
|
|
||||||
]).await?;
|
]).await?;
|
||||||
self.pg_trans()?.execute("UPDATE items SET details=\
|
self.pg_trans()?.execute("UPDATE items SET details=\
|
||||||
JSONB_SET(details, '{action_type}', $1) \
|
JSONB_SET(details, '{action_type}', $1) \
|
||||||
@ -760,9 +759,9 @@ impl DBTrans {
|
|||||||
if let Some(item) = self.pg_trans()?.query_opt(
|
if let Some(item) = self.pg_trans()?.query_opt(
|
||||||
"SELECT details FROM items WHERE \
|
"SELECT details FROM items WHERE \
|
||||||
details->>'location' = $1 AND \
|
details->>'location' = $1 AND \
|
||||||
details->>'action_type' = $2",
|
details->>'action_type' = $2::JSONB::TEXT",
|
||||||
&[&location,
|
&[&location,
|
||||||
&serde_json::to_value(action_type)?.as_str().unwrap()]).await? {
|
&serde_json::to_value(action_type)?]).await? {
|
||||||
return Ok(Some(Arc::new(serde_json::from_value::<Item>(item.get("details"))?)));
|
return Ok(Some(Arc::new(serde_json::from_value::<Item>(item.get("details"))?)));
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
@ -29,6 +29,7 @@ mod login;
|
|||||||
mod look;
|
mod look;
|
||||||
mod map;
|
mod map;
|
||||||
pub mod movement;
|
pub mod movement;
|
||||||
|
pub mod open;
|
||||||
mod page;
|
mod page;
|
||||||
pub mod parsing;
|
pub mod parsing;
|
||||||
mod quit;
|
mod quit;
|
||||||
@ -145,6 +146,8 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! {
|
|||||||
"gm" => map::VERB,
|
"gm" => map::VERB,
|
||||||
"gmap" => map::VERB,
|
"gmap" => map::VERB,
|
||||||
|
|
||||||
|
"open" => open::VERB,
|
||||||
|
|
||||||
"p" => page::VERB,
|
"p" => page::VERB,
|
||||||
"page" => page::VERB,
|
"page" => page::VERB,
|
||||||
"pg" => page::VERB,
|
"pg" => page::VERB,
|
||||||
|
@ -45,8 +45,8 @@ static REGISTERED_HELP_PAGES: phf::Map<&'static str, &'static str> = phf_map! {
|
|||||||
ansi!("So you've just landed in BlastMud, and want to know how to get started?\n\
|
ansi!("So you've just landed in BlastMud, and want to know how to get started?\n\
|
||||||
You control your character, and can tell your character to move around the\n\
|
You control your character, and can tell your character to move around the\n\
|
||||||
world, and see things through their eyes.\n\
|
world, and see things through their eyes.\n\
|
||||||
The world (yes, even outside!) is divided up into rooms, and each room has\n
|
The world (yes, even outside!) is divided up into rooms, and each room has\n\
|
||||||
exits that you are allowed to take, normally called north, south, east, west,\n
|
exits that you are allowed to take, normally called north, south, east, west,\n\
|
||||||
northeast, northwest, southeast, southwest, up and down (sometimes you can also go in).\n\
|
northeast, northwest, southeast, southwest, up and down (sometimes you can also go in).\n\
|
||||||
\n\
|
\n\
|
||||||
Try <bold>look<reset> (or <bold>l<reset>) to look at the current room. It will\n\
|
Try <bold>look<reset> (or <bold>l<reset>) to look at the current room. It will\n\
|
||||||
@ -56,7 +56,9 @@ static REGISTERED_HELP_PAGES: phf::Map<&'static str, &'static str> = phf_map! {
|
|||||||
<bold>l<reset> followed by the name of the object (shortening is okay).\n\
|
<bold>l<reset> followed by the name of the object (shortening is okay).\n\
|
||||||
Once you know what direction to move, you can type the direction, using either\n\
|
Once you know what direction to move, you can type the direction, using either\n\
|
||||||
the full name of the direction (e.g. <bold>northeast<reset> or <bold>south<reset>,\n\
|
the full name of the direction (e.g. <bold>northeast<reset> or <bold>south<reset>,\n\
|
||||||
or a short form (<bold>n<reset>, <bold>e<reset>, <bold>s<reset>, <bold>w<reset>, <bold>ne<reset>, <bold>nw<reset>, <bold>se<reset>, <bold>sw<reset>, <bold>up<reset>, <bold>down<reset>)."),
|
or a short form (<bold>n<reset>, <bold>e<reset>, <bold>s<reset>, <bold>w<reset>, <bold>ne<reset>, <bold>nw<reset>, <bold>se<reset>, <bold>sw<reset>, <bold>up<reset>, <bold>down<reset>).\n\n\
|
||||||
|
Also try the map commands - <bold>lmap<reset> for a local map including exits, and <bold>gmap<reset> for a giant \
|
||||||
|
map to let you see the broader context."),
|
||||||
"movement" =>
|
"movement" =>
|
||||||
ansi!("Once you know what direction to move, you can type the direction, using either\n\
|
ansi!("Once you know what direction to move, you can type the direction, using either\n\
|
||||||
the full name of the direction (e.g. <bold>northeast<reset> or <bold>south<reset>,\n\
|
the full name of the direction (e.g. <bold>northeast<reset> or <bold>south<reset>,\n\
|
||||||
|
@ -241,7 +241,7 @@ async fn list_room_contents<'l>(ctx: &'l VerbContext<'_>, item: &'l Item) -> URe
|
|||||||
Ok(buf)
|
Ok(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn direction_to_item(
|
pub async fn direction_to_item(
|
||||||
trans: &DBTrans,
|
trans: &DBTrans,
|
||||||
use_location: &str,
|
use_location: &str,
|
||||||
direction: &Direction,
|
direction: &Direction,
|
||||||
|
255
blastmud_game/src/message_handler/user_commands/open.rs
Normal file
255
blastmud_game/src/message_handler/user_commands/open.rs
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
use super::{
|
||||||
|
VerbContext, UserVerb, UserVerbRef, UResult, UserError, user_error,
|
||||||
|
get_player_item_or_fail,
|
||||||
|
look::direction_to_item,
|
||||||
|
};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use crate::{
|
||||||
|
DResult,
|
||||||
|
regular_tasks::{
|
||||||
|
TaskRunContext,
|
||||||
|
TaskHandler,
|
||||||
|
queued_command::{
|
||||||
|
QueueCommandHandler,
|
||||||
|
QueueCommand,
|
||||||
|
queue_command
|
||||||
|
},
|
||||||
|
},
|
||||||
|
static_content::{
|
||||||
|
room::Direction,
|
||||||
|
possession_type::possession_data,
|
||||||
|
},
|
||||||
|
models::{
|
||||||
|
item::{Item, LocationActionType},
|
||||||
|
task::{Task, TaskMeta, TaskDetails}
|
||||||
|
},
|
||||||
|
services::comms::broadcast_to_room,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time;
|
||||||
|
use chrono::{self, Utc};
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SwingShutHandler;
|
||||||
|
pub static SWING_SHUT_HANDLER: &'static (dyn TaskHandler + Sync + Send) = &SwingShutHandler;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl TaskHandler for SwingShutHandler {
|
||||||
|
async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
|
||||||
|
info!("Starting swing shut");
|
||||||
|
let (room_str, direction) = match &ctx.task.details {
|
||||||
|
TaskDetails::SwingShut { room_item, direction } => (room_item, direction),
|
||||||
|
_ => { return Ok(None); }
|
||||||
|
};
|
||||||
|
let (room_item_type, room_item_code) = match room_str.split_once("/") {
|
||||||
|
None => { return Ok(None); },
|
||||||
|
Some(v) => v
|
||||||
|
};
|
||||||
|
let room_item = match ctx.trans.find_item_by_type_code(room_item_type, room_item_code).await? {
|
||||||
|
None => { return Ok(None); },
|
||||||
|
Some(v) => v
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut room_item_mut = (*room_item).clone();
|
||||||
|
let mut door_state = match room_item_mut.door_states.as_mut().and_then(|ds| ds.get_mut(&direction)) {
|
||||||
|
None => { return Ok(None); },
|
||||||
|
Some(v) => v
|
||||||
|
};
|
||||||
|
(*door_state).open = false;
|
||||||
|
ctx.trans.save_item_model(&room_item_mut).await?;
|
||||||
|
|
||||||
|
let msg = format!("The door to the {} swings shut with a click.\n",
|
||||||
|
&direction.describe());
|
||||||
|
broadcast_to_room(&ctx.trans, &room_str, None, &msg, Some(&msg)).await?;
|
||||||
|
|
||||||
|
if let Ok(Some(other_room)) = direction_to_item(&ctx.trans, &room_str, &direction).await {
|
||||||
|
let msg = format!("The door to the {} swings shut with a click.\n",
|
||||||
|
&direction.reverse().map(|d| d.describe()).unwrap_or_else(|| "outside".to_owned()));
|
||||||
|
broadcast_to_room(&ctx.trans, &other_room.refstr(), None, &msg, Some(&msg)).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Finishing swing shut");
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct QueueHandler;
|
||||||
|
#[async_trait]
|
||||||
|
impl QueueCommandHandler for QueueHandler {
|
||||||
|
async fn start_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand)
|
||||||
|
-> UResult<time::Duration> {
|
||||||
|
info!("Starting open start_command");
|
||||||
|
let direction = match command {
|
||||||
|
QueueCommand::OpenDoor { direction } => direction,
|
||||||
|
_ => user_error("Unexpected command".to_owned())?
|
||||||
|
};
|
||||||
|
info!("Direction good");
|
||||||
|
let player_item = get_player_item_or_fail(ctx).await?;
|
||||||
|
info!("Got player");
|
||||||
|
match is_door_in_direction(ctx, &direction, &player_item).await? {
|
||||||
|
DoorSituation::NoDoor => user_error("There is no door to open.".to_owned())?,
|
||||||
|
DoorSituation::DoorIntoRoom { is_open: true, .. } | DoorSituation::DoorOutOfRoom { is_open: true, .. } =>
|
||||||
|
user_error("The door is already open.".to_owned())?,
|
||||||
|
DoorSituation::DoorIntoRoom { room_with_door: entering_room, .. } => {
|
||||||
|
let entering_room_loc = entering_room.refstr();
|
||||||
|
if let Some(revdir) = direction.reverse() {
|
||||||
|
if let Some(lock) = ctx.trans.find_by_action_and_location(
|
||||||
|
&entering_room_loc,
|
||||||
|
&LocationActionType::InstalledOnDoorAsLock(revdir)).await?
|
||||||
|
{
|
||||||
|
if let Some(lockcheck) = lock.possession_type.as_ref()
|
||||||
|
.and_then(|pt| possession_data().get(pt))
|
||||||
|
.and_then(|pd| pd.lockcheck_handler) {
|
||||||
|
lockcheck.cmd(ctx, &player_item, &lock).await?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Clean exit open start_command");
|
||||||
|
Ok(time::Duration::from_secs(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unreachable_patterns)]
|
||||||
|
async fn finish_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand)
|
||||||
|
-> UResult<()> {
|
||||||
|
info!("Starting open finish_command");
|
||||||
|
let direction = match command {
|
||||||
|
QueueCommand::OpenDoor { direction } => direction,
|
||||||
|
_ => user_error("Unexpected command".to_owned())?
|
||||||
|
};
|
||||||
|
let player_item = get_player_item_or_fail(ctx).await?;
|
||||||
|
let (room_1, dir_in_room, room_2) = match is_door_in_direction(ctx, &direction, &player_item).await? {
|
||||||
|
DoorSituation::NoDoor => user_error("There is no door to open.".to_owned())?,
|
||||||
|
DoorSituation::DoorIntoRoom { is_open: true, .. } |
|
||||||
|
DoorSituation::DoorOutOfRoom { is_open: true, .. } =>
|
||||||
|
user_error("The door is already open.".to_owned())?,
|
||||||
|
DoorSituation::DoorIntoRoom { room_with_door, current_room, .. } => {
|
||||||
|
let entering_room_loc = room_with_door.refstr();
|
||||||
|
if let Some(revdir) = direction.reverse() {
|
||||||
|
if let Some(lock) = ctx.trans.find_by_action_and_location(
|
||||||
|
&entering_room_loc,
|
||||||
|
&LocationActionType::InstalledOnDoorAsLock(revdir.clone())).await?
|
||||||
|
{
|
||||||
|
if let Some(lockcheck) = lock.possession_type.as_ref()
|
||||||
|
.and_then(|pt| possession_data().get(pt))
|
||||||
|
.and_then(|pd| pd.lockcheck_handler) {
|
||||||
|
lockcheck.cmd(ctx, &player_item, &lock).await?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut entering_room_mut = (*room_with_door).clone();
|
||||||
|
if let Some(door_map) = entering_room_mut.door_states.as_mut() {
|
||||||
|
if let Some(door) = door_map.get_mut(&direction) {
|
||||||
|
(*door).open = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.trans.save_item_model(&entering_room_mut).await?;
|
||||||
|
(room_with_door, revdir, current_room)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
user_error("There's no door possible there.".to_owned())?
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DoorSituation::DoorOutOfRoom { room_with_door, new_room, .. } => {
|
||||||
|
let mut entering_room_mut = (*room_with_door).clone();
|
||||||
|
if let Some(door_map) = entering_room_mut.door_states.as_mut() {
|
||||||
|
if let Some(door) = door_map.get_mut(&direction) {
|
||||||
|
(*door).open = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.trans.save_item_model(&entering_room_mut).await?;
|
||||||
|
(room_with_door, direction.clone(), new_room)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (loc, dir) in [(&room_1.refstr(), &dir_in_room.describe()),
|
||||||
|
(&room_2.refstr(), &dir_in_room.reverse().map(|d| d.describe())
|
||||||
|
.unwrap_or_else(|| "outside".to_owned()))] {
|
||||||
|
broadcast_to_room(
|
||||||
|
&ctx.trans,
|
||||||
|
loc,
|
||||||
|
None,
|
||||||
|
&format!("{} opens the door to the {}.\n",
|
||||||
|
&player_item.display_for_sentence(true, 1, true),
|
||||||
|
dir
|
||||||
|
),
|
||||||
|
Some(
|
||||||
|
&format!("{} opens the door to the {}.\n",
|
||||||
|
&player_item.display_for_sentence(false, 1, true),
|
||||||
|
dir
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.trans.upsert_task(&Task {
|
||||||
|
meta: TaskMeta {
|
||||||
|
task_code: format!("{}/{}", &room_1.refstr(), &direction.describe()),
|
||||||
|
next_scheduled: Utc::now() + chrono::Duration::seconds(120),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
details: TaskDetails::SwingShut {
|
||||||
|
room_item: room_1.refstr(),
|
||||||
|
direction: dir_in_room.clone()
|
||||||
|
}
|
||||||
|
}).await?;
|
||||||
|
|
||||||
|
info!("Clean exit open finish_command");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum DoorSituation {
|
||||||
|
NoDoor,
|
||||||
|
DoorIntoRoom { is_open: bool, room_with_door: Arc<Item>, current_room: Arc<Item> }, // Can be locked etc...
|
||||||
|
DoorOutOfRoom { is_open: bool, room_with_door: Arc<Item>, new_room: Arc<Item> } // No lockable.
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn is_door_in_direction(ctx: &mut VerbContext<'_>, direction: &Direction, player_item: &Item) ->
|
||||||
|
UResult<DoorSituation> {
|
||||||
|
let (loc_type_t, loc_type_c) = player_item.location.split_once("/")
|
||||||
|
.ok_or_else(|| UserError("Invalid location".to_owned()))?;
|
||||||
|
let cur_loc_item = ctx.trans.find_item_by_type_code(loc_type_t, loc_type_c).await?
|
||||||
|
.ok_or_else(|| UserError("Can't find your current location anymore.".to_owned()))?;
|
||||||
|
let new_loc_item = direction_to_item(ctx.trans, &player_item.location, direction).await?
|
||||||
|
.ok_or_else(|| UserError("That exit doesn't really seem to go anywhere!".to_owned()))?;
|
||||||
|
if let Some(door_state) =
|
||||||
|
cur_loc_item.door_states.as_ref()
|
||||||
|
.and_then(|v| v.get(direction)) {
|
||||||
|
return Ok(DoorSituation::DoorOutOfRoom {
|
||||||
|
is_open: door_state.open,
|
||||||
|
room_with_door: cur_loc_item,
|
||||||
|
new_room: new_loc_item
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(door_state) =
|
||||||
|
new_loc_item.door_states.as_ref()
|
||||||
|
.and_then(|v| direction.reverse().as_ref()
|
||||||
|
.and_then(|rev| v.get(rev).map(|door| door.clone()))) {
|
||||||
|
return Ok(DoorSituation::DoorIntoRoom {
|
||||||
|
is_open: door_state.open,
|
||||||
|
room_with_door: new_loc_item,
|
||||||
|
current_room: cur_loc_item
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(DoorSituation::NoDoor)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Verb;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl UserVerb for Verb {
|
||||||
|
async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, remaining: &str) -> UResult<()> {
|
||||||
|
let dir = Direction::parse(remaining)
|
||||||
|
.ok_or_else(|| UserError("Unknown direction".to_owned()))?;
|
||||||
|
info!("Queueing open");
|
||||||
|
queue_command(ctx, &QueueCommand::OpenDoor { direction: dir.clone() }).await?;
|
||||||
|
info!("Returning from open handler");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static VERB_INT: Verb = Verb;
|
||||||
|
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;
|
@ -256,7 +256,8 @@ pub enum LocationActionType {
|
|||||||
Reclining,
|
Reclining,
|
||||||
Worn, // Clothing etc...
|
Worn, // Clothing etc...
|
||||||
Wielded,
|
Wielded,
|
||||||
Attacking(Subattack)
|
Attacking(Subattack),
|
||||||
|
InstalledOnDoorAsLock(Direction),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
@ -269,7 +270,8 @@ pub enum Sex {
|
|||||||
pub enum ItemFlag {
|
pub enum ItemFlag {
|
||||||
NoSay,
|
NoSay,
|
||||||
NoSeeContents,
|
NoSeeContents,
|
||||||
DroppedItemsDontExpire
|
DroppedItemsDontExpire,
|
||||||
|
PrivatePlace,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
@ -307,6 +309,11 @@ pub struct DynamicEntrance {
|
|||||||
pub source_item: String,
|
pub source_item: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)]
|
||||||
|
pub struct DoorState {
|
||||||
|
pub open: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct Item {
|
pub struct Item {
|
||||||
@ -338,6 +345,7 @@ pub struct Item {
|
|||||||
pub special_data: Option<ItemSpecialData>,
|
pub special_data: Option<ItemSpecialData>,
|
||||||
pub dynamic_entrance: Option<DynamicEntrance>,
|
pub dynamic_entrance: Option<DynamicEntrance>,
|
||||||
pub owner: Option<String>,
|
pub owner: Option<String>,
|
||||||
|
pub door_states: Option<BTreeMap<Direction, DoorState>>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Item {
|
impl Item {
|
||||||
@ -384,6 +392,10 @@ impl Item {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn refstr(&self) -> String {
|
||||||
|
format!("{}/{}", &self.item_type, &self.item_code)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Item {
|
impl Default for Item {
|
||||||
@ -417,6 +429,7 @@ impl Default for Item {
|
|||||||
special_data: None,
|
special_data: None,
|
||||||
dynamic_entrance: None,
|
dynamic_entrance: None,
|
||||||
owner: None,
|
owner: None,
|
||||||
|
door_states: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ use serde_json::Value;
|
|||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use crate::services::effect::DelayedHealthEffect;
|
use crate::services::effect::DelayedHealthEffect;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
use crate::static_content::room::Direction;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
pub enum TaskRecurrence {
|
pub enum TaskRecurrence {
|
||||||
@ -40,6 +41,10 @@ pub enum TaskDetails {
|
|||||||
ChargeRoom {
|
ChargeRoom {
|
||||||
zone_item: String,
|
zone_item: String,
|
||||||
daily_price: u64
|
daily_price: u64
|
||||||
|
},
|
||||||
|
SwingShut {
|
||||||
|
room_item: String,
|
||||||
|
direction: Direction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl TaskDetails {
|
impl TaskDetails {
|
||||||
@ -56,6 +61,7 @@ impl TaskDetails {
|
|||||||
DelayedHealth { .. } => "DelayedHealth",
|
DelayedHealth { .. } => "DelayedHealth",
|
||||||
ExpireItem { .. } => "ExpireItem",
|
ExpireItem { .. } => "ExpireItem",
|
||||||
ChargeRoom { .. } => "ChargeRoom",
|
ChargeRoom { .. } => "ChargeRoom",
|
||||||
|
SwingShut { .. } => "SwingShut",
|
||||||
// Don't forget to add to TASK_HANDLER_REGISTRY in regular_tasks.rs too.
|
// Don't forget to add to TASK_HANDLER_REGISTRY in regular_tasks.rs too.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ use crate::{
|
|||||||
listener::{ListenerMap, ListenerSend},
|
listener::{ListenerMap, ListenerSend},
|
||||||
static_content::npc,
|
static_content::npc,
|
||||||
services::{combat, effect},
|
services::{combat, effect},
|
||||||
message_handler::user_commands::{drop, rent},
|
message_handler::user_commands::{drop, rent, open},
|
||||||
};
|
};
|
||||||
#[cfg(not(test))] use crate::models::task::{TaskParse, TaskRecurrence};
|
#[cfg(not(test))] use crate::models::task::{TaskParse, TaskRecurrence};
|
||||||
use mockall_double::double;
|
use mockall_double::double;
|
||||||
@ -45,7 +45,8 @@ fn task_handler_registry() -> &'static BTreeMap<&'static str, &'static (dyn Task
|
|||||||
("RotCorpse", combat::ROT_CORPSE_HANDLER.clone()),
|
("RotCorpse", combat::ROT_CORPSE_HANDLER.clone()),
|
||||||
("DelayedHealth", effect::DELAYED_HEALTH_HANDLER.clone()),
|
("DelayedHealth", effect::DELAYED_HEALTH_HANDLER.clone()),
|
||||||
("ExpireItem", drop::EXPIRE_ITEM_HANDLER.clone()),
|
("ExpireItem", drop::EXPIRE_ITEM_HANDLER.clone()),
|
||||||
("ChargeRoom", rent::CHARGE_ROOM_HANDLER.clone())
|
("ChargeRoom", rent::CHARGE_ROOM_HANDLER.clone()),
|
||||||
|
("SwingShut", open::SWING_SHUT_HANDLER.clone()),
|
||||||
).into_iter().collect()
|
).into_iter().collect()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,8 @@ use crate::message_handler::user_commands::{
|
|||||||
use_cmd,
|
use_cmd,
|
||||||
wield,
|
wield,
|
||||||
user_error,
|
user_error,
|
||||||
get_user_or_fail
|
get_user_or_fail,
|
||||||
|
open
|
||||||
};
|
};
|
||||||
use crate::static_content::room::Direction;
|
use crate::static_content::room::Direction;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
@ -32,6 +33,7 @@ pub enum QueueCommand {
|
|||||||
Use { possession_id: String, target_id: String },
|
Use { possession_id: String, target_id: String },
|
||||||
Get { possession_id: String },
|
Get { possession_id: String },
|
||||||
Drop { possession_id: String },
|
Drop { possession_id: String },
|
||||||
|
OpenDoor { direction: Direction },
|
||||||
}
|
}
|
||||||
impl QueueCommand {
|
impl QueueCommand {
|
||||||
pub fn name(&self) -> &'static str {
|
pub fn name(&self) -> &'static str {
|
||||||
@ -42,6 +44,7 @@ impl QueueCommand {
|
|||||||
Use {..} => "Use",
|
Use {..} => "Use",
|
||||||
Get {..} => "Get",
|
Get {..} => "Get",
|
||||||
Drop {..} => "Drop",
|
Drop {..} => "Drop",
|
||||||
|
OpenDoor {..} => "OpenDoor",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -61,6 +64,7 @@ fn queue_command_registry() -> &'static BTreeMap<&'static str, &'static (dyn Que
|
|||||||
("Movement", &movement::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
|
("Movement", &movement::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
|
||||||
("Use", &use_cmd::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
|
("Use", &use_cmd::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
|
||||||
("Wield", &wield::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
|
("Wield", &wield::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
|
||||||
|
("OpenDoor", &open::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
|
||||||
).into_iter().collect())
|
).into_iter().collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,7 +83,7 @@ pub async fn queue_command(ctx: &mut VerbContext<'_>, command: &QueueCommand) ->
|
|||||||
Err(CommandHandlingError::UserError(err_msg)) => {
|
Err(CommandHandlingError::UserError(err_msg)) => {
|
||||||
ctx.session_dat.queue.truncate(0);
|
ctx.session_dat.queue.truncate(0);
|
||||||
ctx.trans.save_session_model(ctx.session, ctx.session_dat).await?;
|
ctx.trans.save_session_model(ctx.session, ctx.session_dat).await?;
|
||||||
user_error(err_msg)?;
|
ctx.trans.queue_for_session(&ctx.session, Some(&(err_msg + "\r\n"))).await?;
|
||||||
}
|
}
|
||||||
Err(CommandHandlingError::SystemError(e)) => Err(e)?,
|
Err(CommandHandlingError::SystemError(e)) => Err(e)?,
|
||||||
Ok(dur) => {
|
Ok(dur) => {
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
use super::room::{Direction, GridCoords};
|
use super::room::{Direction, GridCoords};
|
||||||
use crate::{
|
use crate::{
|
||||||
message_handler::user_commands::{user_error, UResult},
|
message_handler::user_commands::{user_error, UResult},
|
||||||
models::item::{Item, ItemFlag, ItemSpecialData, DynamicEntrance}
|
models::item::{Item, ItemFlag, ItemSpecialData, DynamicEntrance, DoorState}
|
||||||
};
|
};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
@ -98,6 +98,15 @@ impl Dynzone {
|
|||||||
} else { None },
|
} else { None },
|
||||||
flags: room.item_flags.clone(),
|
flags: room.item_flags.clone(),
|
||||||
owner: Some(owner.clone()),
|
owner: Some(owner.clone()),
|
||||||
|
door_states: Some(room.exits.iter()
|
||||||
|
.filter_map(|ex|
|
||||||
|
if ex.exit_type == ExitType::Doored {
|
||||||
|
Some((ex.direction.clone(), DoorState {
|
||||||
|
open: false
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}).collect()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
).await?;
|
).await?;
|
||||||
@ -118,6 +127,9 @@ impl Default for Dynzone {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note that either the room being entered or the room being left should be
|
||||||
|
// doored, not both. And doors should be on the inner-most room (furthest from
|
||||||
|
// public) - locks only protect entry into the room, not exit from it.
|
||||||
#[derive(Eq, Clone, PartialEq, Ord, PartialOrd)]
|
#[derive(Eq, Clone, PartialEq, Ord, PartialOrd)]
|
||||||
pub enum ExitType {
|
pub enum ExitType {
|
||||||
Doorless,
|
Doorless,
|
||||||
|
@ -8,6 +8,7 @@ use super::{
|
|||||||
super::room::GridCoords
|
super::room::GridCoords
|
||||||
};
|
};
|
||||||
use crate::static_content::room::Direction;
|
use crate::static_content::room::Direction;
|
||||||
|
use crate::models::item::ItemFlag;
|
||||||
|
|
||||||
pub fn zone() -> Dynzone {
|
pub fn zone() -> Dynzone {
|
||||||
Dynzone {
|
Dynzone {
|
||||||
@ -30,7 +31,7 @@ pub fn zone() -> Dynzone {
|
|||||||
Exit {
|
Exit {
|
||||||
direction: Direction::EAST,
|
direction: Direction::EAST,
|
||||||
target: ExitTarget::Intrazone { subcode: "studio" },
|
target: ExitTarget::Intrazone { subcode: "studio" },
|
||||||
exit_type: ExitType::Doored
|
exit_type: ExitType::Doorless
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
grid_coords: GridCoords { x: 0, y: 0, z: 0 },
|
grid_coords: GridCoords { x: 0, y: 0, z: 0 },
|
||||||
@ -53,7 +54,8 @@ pub fn zone() -> Dynzone {
|
|||||||
),
|
),
|
||||||
grid_coords: GridCoords { x: 1, y: 0, z: 0 },
|
grid_coords: GridCoords { x: 1, y: 0, z: 0 },
|
||||||
should_caption: true,
|
should_caption: true,
|
||||||
item_flags: vec!(),
|
item_flags: vec!(ItemFlag::DroppedItemsDontExpire,
|
||||||
|
ItemFlag::PrivatePlace),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
).into_iter().collect(),
|
).into_iter().collect(),
|
||||||
|
@ -14,6 +14,7 @@ mod fangs;
|
|||||||
mod antenna_whip;
|
mod antenna_whip;
|
||||||
mod trauma_kit;
|
mod trauma_kit;
|
||||||
mod corp_licence;
|
mod corp_licence;
|
||||||
|
mod lock;
|
||||||
|
|
||||||
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>>;
|
||||||
@ -135,6 +136,7 @@ pub struct PossessionData {
|
|||||||
pub weight: u64,
|
pub weight: u64,
|
||||||
pub write_handler: Option<&'static (dyn WriteHandler + Sync + Send)>,
|
pub write_handler: Option<&'static (dyn WriteHandler + Sync + Send)>,
|
||||||
pub sign_handler: Option<&'static (dyn ArglessHandler + Sync + Send)>,
|
pub sign_handler: Option<&'static (dyn ArglessHandler + Sync + Send)>,
|
||||||
|
pub lockcheck_handler: Option<&'static (dyn ArglessHandler + Sync + Send)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PossessionData {
|
impl Default for PossessionData {
|
||||||
@ -153,6 +155,7 @@ impl Default for PossessionData {
|
|||||||
use_data: None,
|
use_data: None,
|
||||||
write_handler: None,
|
write_handler: None,
|
||||||
sign_handler: None,
|
sign_handler: None,
|
||||||
|
lockcheck_handler: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -190,6 +193,7 @@ pub enum PossessionType {
|
|||||||
EmptyMedicalBox,
|
EmptyMedicalBox,
|
||||||
NewCorpLicence,
|
NewCorpLicence,
|
||||||
CertificateOfIncorporation,
|
CertificateOfIncorporation,
|
||||||
|
Scanlock,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<Item> for PossessionType {
|
impl Into<Item> for PossessionType {
|
||||||
@ -257,6 +261,7 @@ pub fn possession_data() -> &'static BTreeMap<PossessionType, PossessionData> {
|
|||||||
(EmptyMedicalBox, trauma_kit::empty_data()),
|
(EmptyMedicalBox, trauma_kit::empty_data()),
|
||||||
(NewCorpLicence, corp_licence::data()),
|
(NewCorpLicence, corp_licence::data()),
|
||||||
(CertificateOfIncorporation, corp_licence::cert_data()),
|
(CertificateOfIncorporation, corp_licence::cert_data()),
|
||||||
|
(Scanlock, lock::scan()),
|
||||||
).into_iter().collect()
|
).into_iter().collect()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
11
blastmud_game/src/static_content/possession_type/lock.rs
Normal file
11
blastmud_game/src/static_content/possession_type/lock.rs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
use super::PossessionData;
|
||||||
|
|
||||||
|
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,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
@ -76,9 +76,8 @@ pub trait ExitBlocker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub enum ExitType {
|
pub enum ExitType {
|
||||||
Free, // Anyone can just walk it.
|
Free, // Anyone can just walk it (subject to any door logic).
|
||||||
Blocked(&'static (dyn ExitBlocker + Sync + Send)), // Custom code about who can pass.
|
Blocked(&'static (dyn ExitBlocker + Sync + Send)), // Custom code about who can pass.
|
||||||
// Future ideas: Doors with locks, etc...
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -147,6 +146,22 @@ impl Direction {
|
|||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reverse(&self) -> Option<Direction> {
|
||||||
|
match self {
|
||||||
|
Direction::NORTH => Some(Direction::SOUTH),
|
||||||
|
Direction::SOUTH => Some(Direction::NORTH),
|
||||||
|
Direction::EAST => Some(Direction::WEST),
|
||||||
|
Direction::WEST => Some(Direction::EAST),
|
||||||
|
Direction::NORTHEAST => Some(Direction::SOUTHWEST),
|
||||||
|
Direction::SOUTHEAST => Some(Direction::NORTHWEST),
|
||||||
|
Direction::NORTHWEST => Some(Direction::SOUTHEAST),
|
||||||
|
Direction::SOUTHWEST => Some(Direction::NORTHEAST),
|
||||||
|
Direction::UP => Some(Direction::DOWN),
|
||||||
|
Direction::DOWN => Some(Direction::UP),
|
||||||
|
Direction::IN { .. } => None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Eq,Ord,Debug,PartialEq,PartialOrd,Clone)]
|
#[derive(Eq,Ord,Debug,PartialEq,PartialOrd,Clone)]
|
||||||
|
@ -3283,10 +3283,41 @@ pub fn room_list() -> Vec<Room> {
|
|||||||
target: ExitTarget::UseGPS,
|
target: ExitTarget::UseGPS,
|
||||||
exit_type: ExitType::Free
|
exit_type: ExitType::Free
|
||||||
},
|
},
|
||||||
|
Exit {
|
||||||
|
direction: Direction::SOUTHEAST,
|
||||||
|
target: ExitTarget::UseGPS,
|
||||||
|
exit_type: ExitType::Free
|
||||||
|
}
|
||||||
),
|
),
|
||||||
should_caption: false,
|
should_caption: false,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
Room {
|
||||||
|
zone: "melbs",
|
||||||
|
secondary_zones: vec!(),
|
||||||
|
code: "melbs_lockedandloaded",
|
||||||
|
name: "Locked & Loaded",
|
||||||
|
short: ansi!("<yellow>LL<reset>"),
|
||||||
|
description: "This seems to be some kind of security shop, selling locks from super high-tech to primitive. Behind a counter sits a grizzled old man, who appears eager to sell you something",
|
||||||
|
description_less_explicit: None,
|
||||||
|
grid_coords: GridCoords { x: 7, y: 8, z: 0 },
|
||||||
|
exits: vec!(
|
||||||
|
Exit {
|
||||||
|
direction: Direction::NORTHWEST,
|
||||||
|
target: ExitTarget::UseGPS,
|
||||||
|
exit_type: ExitType::Free
|
||||||
|
},
|
||||||
|
),
|
||||||
|
stock_list: vec!(
|
||||||
|
RoomStock {
|
||||||
|
possession_type: PossessionType::Scanlock,
|
||||||
|
list_price: 200,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
should_caption: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
Room {
|
Room {
|
||||||
zone: "melbs",
|
zone: "melbs",
|
||||||
secondary_zones: vec!(),
|
secondary_zones: vec!(),
|
||||||
|
Loading…
Reference in New Issue
Block a user