blastmud/blastmud_game/src/message_handler/user_commands/open.rs
Condorra 590d4640dd Implement craft on benches
Initially just a stove
Also update Rust.
2023-07-24 22:46:50 +10:00

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;