forked from blasthavers/blastmud
362 lines
12 KiB
Rust
362 lines
12 KiB
Rust
use super::{
|
|
get_player_item_or_fail, look::direction_to_item, user_error, UResult, UserError, UserVerb,
|
|
UserVerbRef, VerbContext,
|
|
};
|
|
#[double]
|
|
use crate::db::DBTrans;
|
|
use crate::{
|
|
models::{
|
|
item::{DoorState, Item, LocationActionType},
|
|
task::{Task, TaskDetails, TaskMeta},
|
|
},
|
|
regular_tasks::{
|
|
queued_command::{
|
|
queue_command_and_save, QueueCommand, QueueCommandHandler, QueuedCommandContext,
|
|
},
|
|
TaskHandler, TaskRunContext,
|
|
},
|
|
services::comms::broadcast_to_room,
|
|
static_content::{possession_type::possession_data, room::Direction},
|
|
DResult,
|
|
};
|
|
use async_trait::async_trait;
|
|
use chrono::{self, Utc};
|
|
use mockall_double::double;
|
|
use std::sync::Arc;
|
|
use std::time;
|
|
|
|
#[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>> {
|
|
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 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?;
|
|
}
|
|
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
pub async fn attempt_open_immediate(
|
|
ctx: &mut QueuedCommandContext<'_>,
|
|
direction: &Direction,
|
|
) -> UResult<()> {
|
|
let use_location = if ctx.item.death_data.is_some() {
|
|
user_error("Your ethereal hands don't seem to be able to move the door.".to_owned())?
|
|
} else {
|
|
&ctx.item.location
|
|
};
|
|
let (room_1, dir_in_room, room_2) =
|
|
match is_door_in_direction(ctx.trans, &direction, use_location).await? {
|
|
DoorSituation::NoDoor => user_error("There is no door to open.".to_owned())?,
|
|
DoorSituation::DoorIntoRoom {
|
|
state: DoorState { open: true, .. },
|
|
..
|
|
}
|
|
| DoorSituation::DoorOutOfRoom {
|
|
state: DoorState { 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?
|
|
.first()
|
|
{
|
|
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, &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(&revdir) {
|
|
(*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",
|
|
&ctx.item.display_for_sentence(true, 1, true),
|
|
dir
|
|
),
|
|
Some(&format!(
|
|
"{} opens the door to the {}.\n",
|
|
&ctx.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?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub struct QueueHandler;
|
|
#[async_trait]
|
|
impl QueueCommandHandler for QueueHandler {
|
|
async fn start_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<time::Duration> {
|
|
let direction = match ctx.command {
|
|
QueueCommand::OpenDoor { direction } => direction,
|
|
_ => user_error("Unexpected command".to_owned())?,
|
|
};
|
|
let use_location = if ctx.item.death_data.is_some() {
|
|
user_error("Your ethereal hands don't seem to be able to move the door.".to_owned())?
|
|
} else {
|
|
&ctx.item.location
|
|
};
|
|
match is_door_in_direction(&ctx.trans, &direction, use_location).await? {
|
|
DoorSituation::NoDoor => user_error("There is no door to open.".to_owned())?,
|
|
DoorSituation::DoorIntoRoom {
|
|
state: DoorState { open: true, .. },
|
|
..
|
|
}
|
|
| DoorSituation::DoorOutOfRoom {
|
|
state: DoorState { 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?
|
|
.first()
|
|
{
|
|
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, &lock).await?
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
Ok(time::Duration::from_secs(1))
|
|
}
|
|
|
|
#[allow(unreachable_patterns)]
|
|
async fn finish_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<()> {
|
|
let direction = match ctx.command {
|
|
QueueCommand::OpenDoor { direction } => direction,
|
|
_ => user_error("Unexpected command".to_owned())?,
|
|
};
|
|
attempt_open_immediate(ctx, &direction).await?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub enum DoorSituation {
|
|
NoDoor,
|
|
DoorIntoRoom {
|
|
state: DoorState,
|
|
room_with_door: Arc<Item>,
|
|
current_room: Arc<Item>,
|
|
}, // Can be locked etc...
|
|
DoorOutOfRoom {
|
|
state: DoorState,
|
|
room_with_door: Arc<Item>,
|
|
new_room: Arc<Item>,
|
|
}, // No lockable.
|
|
}
|
|
|
|
pub async fn is_door_in_direction(
|
|
trans: &DBTrans,
|
|
direction: &Direction,
|
|
use_location: &str,
|
|
) -> UResult<DoorSituation> {
|
|
let (loc_type_t, loc_type_c) = use_location
|
|
.split_once("/")
|
|
.ok_or_else(|| UserError("Invalid location".to_owned()))?;
|
|
let cur_loc_item = 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(trans, use_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 {
|
|
state: door_state.clone(),
|
|
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 {
|
|
state: door_state.clone(),
|
|
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()))?;
|
|
let player_item = get_player_item_or_fail(ctx).await?;
|
|
queue_command_and_save(
|
|
ctx,
|
|
&player_item,
|
|
&QueueCommand::OpenDoor {
|
|
direction: dir.clone(),
|
|
},
|
|
)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
}
|
|
static VERB_INT: Verb = Verb;
|
|
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;
|