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> { 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 { 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, current_room: Arc }, // Can be locked etc... DoorOutOfRoom { state: DoorState, room_with_door: Arc, new_room: Arc } // No lockable. } pub async fn is_door_in_direction(trans: &DBTrans, direction: &Direction, use_location: &str) -> UResult { 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;