diff --git a/blastmud_game/src/db.rs b/blastmud_game/src/db.rs index cbf9dda..89837c9 100644 --- a/blastmud_game/src/db.rs +++ b/blastmud_game/src/db.rs @@ -739,11 +739,10 @@ impl DBTrans { self.pg_trans()?.execute("UPDATE items SET details=\ JSONB_SET(details, '{action_type}', $1) \ WHERE details->>'location' = $2 AND \ - details->>'action_type' = $3", + details->>'action_type' = $3::JSONB::TEXT", &[&serde_json::to_value(other_item_action_type)?, &item.location, &serde_json::to_value(new_action_type)? - .as_str().unwrap() ]).await?; self.pg_trans()?.execute("UPDATE items SET details=\ JSONB_SET(details, '{action_type}', $1) \ @@ -760,9 +759,9 @@ impl DBTrans { if let Some(item) = self.pg_trans()?.query_opt( "SELECT details FROM items WHERE \ details->>'location' = $1 AND \ - details->>'action_type' = $2", + details->>'action_type' = $2::JSONB::TEXT", &[&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.get("details"))?))); } Ok(None) diff --git a/blastmud_game/src/message_handler/user_commands.rs b/blastmud_game/src/message_handler/user_commands.rs index 5aac543..bfd2445 100644 --- a/blastmud_game/src/message_handler/user_commands.rs +++ b/blastmud_game/src/message_handler/user_commands.rs @@ -29,6 +29,7 @@ mod login; mod look; mod map; pub mod movement; +pub mod open; mod page; pub mod parsing; mod quit; @@ -145,6 +146,8 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! { "gm" => map::VERB, "gmap" => map::VERB, + "open" => open::VERB, + "p" => page::VERB, "page" => page::VERB, "pg" => page::VERB, diff --git a/blastmud_game/src/message_handler/user_commands/help.rs b/blastmud_game/src/message_handler/user_commands/help.rs index 762a9b9..5835ff0 100644 --- a/blastmud_game/src/message_handler/user_commands/help.rs +++ b/blastmud_game/src/message_handler/user_commands/help.rs @@ -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\ You control your character, and can tell your character to move around the\n\ world, and see things through their eyes.\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 + 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\ northeast, northwest, southeast, southwest, up and down (sometimes you can also go in).\n\ \n\ Try look (or l) 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! { l 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\ the full name of the direction (e.g. northeast or south,\n\ - or a short form (n, e, s, w, ne, nw, se, sw, up, down)."), + or a short form (n, e, s, w, ne, nw, se, sw, up, down).\n\n\ + Also try the map commands - lmap for a local map including exits, and gmap for a giant \ + map to let you see the broader context."), "movement" => ansi!("Once you know what direction to move, you can type the direction, using either\n\ the full name of the direction (e.g. northeast or south,\n\ diff --git a/blastmud_game/src/message_handler/user_commands/look.rs b/blastmud_game/src/message_handler/user_commands/look.rs index 1335a3e..fe36aec 100644 --- a/blastmud_game/src/message_handler/user_commands/look.rs +++ b/blastmud_game/src/message_handler/user_commands/look.rs @@ -241,7 +241,7 @@ async fn list_room_contents<'l>(ctx: &'l VerbContext<'_>, item: &'l Item) -> URe Ok(buf) } -async fn direction_to_item( +pub async fn direction_to_item( trans: &DBTrans, use_location: &str, direction: &Direction, diff --git a/blastmud_game/src/message_handler/user_commands/open.rs b/blastmud_game/src/message_handler/user_commands/open.rs new file mode 100644 index 0000000..747410a --- /dev/null +++ b/blastmud_game/src/message_handler/user_commands/open.rs @@ -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> { + 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 { + 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, current_room: Arc }, // Can be locked etc... + DoorOutOfRoom { is_open: bool, room_with_door: Arc, new_room: Arc } // No lockable. +} + +pub async fn is_door_in_direction(ctx: &mut VerbContext<'_>, direction: &Direction, player_item: &Item) -> + UResult { + 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; diff --git a/blastmud_game/src/models/item.rs b/blastmud_game/src/models/item.rs index 08d1934..82a5077 100644 --- a/blastmud_game/src/models/item.rs +++ b/blastmud_game/src/models/item.rs @@ -256,7 +256,8 @@ pub enum LocationActionType { Reclining, Worn, // Clothing etc... Wielded, - Attacking(Subattack) + Attacking(Subattack), + InstalledOnDoorAsLock(Direction), } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] @@ -269,7 +270,8 @@ pub enum Sex { pub enum ItemFlag { NoSay, NoSeeContents, - DroppedItemsDontExpire + DroppedItemsDontExpire, + PrivatePlace, } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] @@ -307,6 +309,11 @@ pub struct DynamicEntrance { pub source_item: String, } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] +pub struct DoorState { + pub open: bool, +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] #[serde(default)] pub struct Item { @@ -338,6 +345,7 @@ pub struct Item { pub special_data: Option, pub dynamic_entrance: Option, pub owner: Option, + pub door_states: Option> } impl Item { @@ -384,6 +392,10 @@ impl Item { ) ) } + + pub fn refstr(&self) -> String { + format!("{}/{}", &self.item_type, &self.item_code) + } } impl Default for Item { @@ -417,6 +429,7 @@ impl Default for Item { special_data: None, dynamic_entrance: None, owner: None, + door_states: None, } } } diff --git a/blastmud_game/src/models/task.rs b/blastmud_game/src/models/task.rs index 1cb841e..2db7aba 100644 --- a/blastmud_game/src/models/task.rs +++ b/blastmud_game/src/models/task.rs @@ -3,6 +3,7 @@ use serde_json::Value; use chrono::{DateTime, Utc}; use crate::services::effect::DelayedHealthEffect; use std::collections::VecDeque; +use crate::static_content::room::Direction; #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum TaskRecurrence { @@ -40,6 +41,10 @@ pub enum TaskDetails { ChargeRoom { zone_item: String, daily_price: u64 + }, + SwingShut { + room_item: String, + direction: Direction } } impl TaskDetails { @@ -56,6 +61,7 @@ impl TaskDetails { DelayedHealth { .. } => "DelayedHealth", ExpireItem { .. } => "ExpireItem", ChargeRoom { .. } => "ChargeRoom", + SwingShut { .. } => "SwingShut", // Don't forget to add to TASK_HANDLER_REGISTRY in regular_tasks.rs too. } } diff --git a/blastmud_game/src/regular_tasks.rs b/blastmud_game/src/regular_tasks.rs index 0b39f9b..4ef5310 100644 --- a/blastmud_game/src/regular_tasks.rs +++ b/blastmud_game/src/regular_tasks.rs @@ -7,7 +7,7 @@ use crate::{ listener::{ListenerMap, ListenerSend}, static_content::npc, 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}; 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()), ("DelayedHealth", effect::DELAYED_HEALTH_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() ) } diff --git a/blastmud_game/src/regular_tasks/queued_command.rs b/blastmud_game/src/regular_tasks/queued_command.rs index f2e3550..3ed243b 100644 --- a/blastmud_game/src/regular_tasks/queued_command.rs +++ b/blastmud_game/src/regular_tasks/queued_command.rs @@ -20,7 +20,8 @@ use crate::message_handler::user_commands::{ use_cmd, wield, user_error, - get_user_or_fail + get_user_or_fail, + open }; use crate::static_content::room::Direction; use once_cell::sync::OnceCell; @@ -32,6 +33,7 @@ pub enum QueueCommand { Use { possession_id: String, target_id: String }, Get { possession_id: String }, Drop { possession_id: String }, + OpenDoor { direction: Direction }, } impl QueueCommand { pub fn name(&self) -> &'static str { @@ -42,6 +44,7 @@ impl QueueCommand { Use {..} => "Use", Get {..} => "Get", 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)), ("Use", &use_cmd::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()) } @@ -79,7 +83,7 @@ pub async fn queue_command(ctx: &mut VerbContext<'_>, command: &QueueCommand) -> Err(CommandHandlingError::UserError(err_msg)) => { ctx.session_dat.queue.truncate(0); 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)?, Ok(dur) => { diff --git a/blastmud_game/src/static_content/dynzone.rs b/blastmud_game/src/static_content/dynzone.rs index b4b6ef6..68b7328 100644 --- a/blastmud_game/src/static_content/dynzone.rs +++ b/blastmud_game/src/static_content/dynzone.rs @@ -5,7 +5,7 @@ use super::room::{Direction, GridCoords}; use crate::{ 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 std::collections::BTreeMap; @@ -98,6 +98,15 @@ impl Dynzone { } else { None }, flags: room.item_flags.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() } ).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)] pub enum ExitType { Doorless, diff --git a/blastmud_game/src/static_content/dynzone/cokmurl_apartment.rs b/blastmud_game/src/static_content/dynzone/cokmurl_apartment.rs index f9c3aaa..d4c7f03 100644 --- a/blastmud_game/src/static_content/dynzone/cokmurl_apartment.rs +++ b/blastmud_game/src/static_content/dynzone/cokmurl_apartment.rs @@ -8,6 +8,7 @@ use super::{ super::room::GridCoords }; use crate::static_content::room::Direction; +use crate::models::item::ItemFlag; pub fn zone() -> Dynzone { Dynzone { @@ -30,7 +31,7 @@ pub fn zone() -> Dynzone { Exit { direction: Direction::EAST, target: ExitTarget::Intrazone { subcode: "studio" }, - exit_type: ExitType::Doored + exit_type: ExitType::Doorless } ), 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 }, should_caption: true, - item_flags: vec!(), + item_flags: vec!(ItemFlag::DroppedItemsDontExpire, + ItemFlag::PrivatePlace), ..Default::default() }) ).into_iter().collect(), diff --git a/blastmud_game/src/static_content/possession_type.rs b/blastmud_game/src/static_content/possession_type.rs index 98fcd17..27b3f40 100644 --- a/blastmud_game/src/static_content/possession_type.rs +++ b/blastmud_game/src/static_content/possession_type.rs @@ -14,6 +14,7 @@ mod fangs; mod antenna_whip; mod trauma_kit; mod corp_licence; +mod lock; pub type AttackMessageChoice = Vec String + 'static + Sync + Send>>; pub type AttackMessageChoicePart = Vec String + 'static + Sync + Send>>; @@ -135,6 +136,7 @@ pub struct PossessionData { pub weight: u64, pub write_handler: Option<&'static (dyn WriteHandler + Sync + Send)>, pub sign_handler: Option<&'static (dyn ArglessHandler + Sync + Send)>, + pub lockcheck_handler: Option<&'static (dyn ArglessHandler + Sync + Send)>, } impl Default for PossessionData { @@ -153,6 +155,7 @@ impl Default for PossessionData { use_data: None, write_handler: None, sign_handler: None, + lockcheck_handler: None, } } } @@ -190,6 +193,7 @@ pub enum PossessionType { EmptyMedicalBox, NewCorpLicence, CertificateOfIncorporation, + Scanlock, } impl Into for PossessionType { @@ -257,6 +261,7 @@ pub fn possession_data() -> &'static BTreeMap { (EmptyMedicalBox, trauma_kit::empty_data()), (NewCorpLicence, corp_licence::data()), (CertificateOfIncorporation, corp_licence::cert_data()), + (Scanlock, lock::scan()), ).into_iter().collect() }) } diff --git a/blastmud_game/src/static_content/possession_type/lock.rs b/blastmud_game/src/static_content/possession_type/lock.rs new file mode 100644 index 0000000..b8dc57b --- /dev/null +++ b/blastmud_game/src/static_content/possession_type/lock.rs @@ -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() + } +} diff --git a/blastmud_game/src/static_content/room.rs b/blastmud_game/src/static_content/room.rs index 4bb06b5..eba9d80 100644 --- a/blastmud_game/src/static_content/room.rs +++ b/blastmud_game/src/static_content/room.rs @@ -76,9 +76,8 @@ pub trait ExitBlocker { } 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. - // Future ideas: Doors with locks, etc... } #[allow(dead_code)] @@ -147,6 +146,22 @@ impl Direction { _ => None } } + + pub fn reverse(&self) -> Option { + 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)] diff --git a/blastmud_game/src/static_content/room/melbs.rs b/blastmud_game/src/static_content/room/melbs.rs index e16bf02..a0c4b69 100644 --- a/blastmud_game/src/static_content/room/melbs.rs +++ b/blastmud_game/src/static_content/room/melbs.rs @@ -3283,10 +3283,41 @@ pub fn room_list() -> Vec { target: ExitTarget::UseGPS, exit_type: ExitType::Free }, + Exit { + direction: Direction::SOUTHEAST, + target: ExitTarget::UseGPS, + exit_type: ExitType::Free + } ), should_caption: false, ..Default::default() }, + Room { + zone: "melbs", + secondary_zones: vec!(), + code: "melbs_lockedandloaded", + name: "Locked & Loaded", + short: ansi!("LL"), + 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 { zone: "melbs", secondary_zones: vec!(),