blastmud/blastmud_game/src/message_handler/user_commands/open.rs
Condorra 2747dddd90 Allow buying, wearing and removing clothes.
Step 2 will be to make clothes serve a functional purpose as armour.
2023-05-23 20:37:27 +10:00

270 lines
11 KiB
Rust

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, DoorState},
task::{Task, TaskMeta, TaskDetails}
},
services::comms::broadcast_to_room,
};
use std::sync::Arc;
use std::time;
use chrono::{self, Utc};
use mockall_double::double;
#[double] use crate::db::DBTrans;
#[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 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?;
}
Ok(None)
}
}
pub async fn attempt_open_immediate(trans: &DBTrans, ctx_opt: &mut Option<&mut VerbContext<'_>>,
who: &Item, direction: &Direction) -> UResult<()> {
let use_location = if who.death_data.is_some() {
user_error("Your ethereal hands don't seem to be able to move the door.".to_owned())?
} else {
&who.location
};
let (room_1, dir_in_room, room_2) = match is_door_in_direction(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) = trans.find_by_action_and_location(
&entering_room_loc,
&LocationActionType::InstalledOnDoorAsLock(revdir.clone())).await?.first()
{
if let Some(ctx) = ctx_opt {
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, &who, &lock).await?
}
} else {
// NPCs don't unlock doors.
user_error("Can't get through locked doors".to_owned())?;
}
}
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;
}
}
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;
}
}
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(
&trans,
loc,
None,
&format!("{} opens the door to the {}.\n",
&who.display_for_sentence(true, 1, true),
dir
),
Some(
&format!("{} opens the door to the {}.\n",
&who.display_for_sentence(false, 1, true),
dir
)
)
).await?;
}
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 VerbContext<'_>, command: &QueueCommand)
-> UResult<time::Duration> {
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 use_location = if player_item.death_data.is_some() {
user_error("Your ethereal hands don't seem to be able to move the door.".to_owned())?
} else {
&player_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, &player_item, &lock).await?
}
}
}
}
_ => {}
}
Ok(time::Duration::from_secs(1))
}
#[allow(unreachable_patterns)]
async fn finish_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand)
-> UResult<()> {
let direction = match command {
QueueCommand::OpenDoor { direction } => direction,
_ => user_error("Unexpected command".to_owned())?
};
let player_item = get_player_item_or_fail(ctx).await?;
attempt_open_immediate(&ctx.trans, &mut Some(ctx), &player_item, &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()))?;
queue_command(ctx, &QueueCommand::OpenDoor { direction: dir.clone() }).await?;
Ok(())
}
}
static VERB_INT: Verb = Verb;
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;