diff --git a/blastmud_game/src/db.rs b/blastmud_game/src/db.rs index c213e4d2..3a43fd93 100644 --- a/blastmud_game/src/db.rs +++ b/blastmud_game/src/db.rs @@ -73,6 +73,12 @@ pub struct LocationStats { pub total_weight: u64, } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ObjectRef { + pub item_type: String, + pub item_code: String, +} + #[cfg_attr(test, allow(dead_code))] impl DBPool { pub async fn record_listener_ping(self: &DBPool, listener: Uuid) -> DResult<()> { @@ -401,6 +407,7 @@ impl DBTrans { Ok(()) } + #[deny(dead_code)] pub async fn find_by_username<'a>(self: &'a Self, username: &'a str) -> DResult> { if let Some(details_json) = self .pg_trans()? @@ -1906,6 +1913,68 @@ impl DBTrans { .collect()) } + pub async fn is_illuminated( + &self, + player: &Item, + location: &Item, + consider_adjacent: &Vec, + ) -> DResult { + if !location.flags.contains(&ItemFlag::DarkPlace) { + return Ok(true); + } + + let player_ref = player.refstr(); + let loc_ref = location.refstr(); + let adjacent_json = serde_json::to_value(consider_adjacent)?; + Ok(self + .pg_trans()? + .query_one( + &format!("WITH + player_lights AS ( + SELECT 1 FROM items WHERE details->>'location' = $1 AND + details->'flags' @> '\"{}\"' + ), + location_direct_lights AS ( + SELECT 1 FROM items WHERE details->>'location' = $2 AND + details->'flags' @> '\"IlluminatesRoom\"' + ), + location_players AS ( + SELECT * FROM items WHERE details->>'location' = $2 AND details->>'item_type' = 'player' + ), + location_player_lights AS ( + SELECT 1 FROM items i JOIN location_players p ON i.details->>'location' = CONCAT(p.details->>'item_type', '/', p.details->>'item_code') WHERE i.details->'flags' @> '\"IlluminatesRoom\"' + ), + adjacent AS ( + SELECT value->>'item_type' AS item_type, value->>'item_code' AS item_code FROM jsonb_array_elements($3 :: JSONB) + ), + adjacent_direct_lights AS ( + SELECT 1 FROM items i JOIN adjacent a ON i.details->>'item_type' = a.item_type AND i.details->>'item_code' = a.item_code + WHERE i.details->'flags' @> '\"IlluminatesAdjacentRoom\"' + ), + adjacent_players AS ( + SELECT i.* FROM items i JOIN adjacent a ON i.details->>'location' = CONCAT(a.item_type, '/', a.item_code) + ), + adjacent_player_lights AS ( + SELECT 1 FROM items i JOIN adjacent_players p ON i.details->>'location' = CONCAT(p.details->>'item_type', '/', p.details->>'item_code') + WHERE i.details->'flags' @> '\"IlluminatesAdjacentRoom\"' + ), + relevant_lights AS ( + SELECT * FROM player_lights UNION ALL + SELECT * FROM location_direct_lights UNION ALL + SELECT * FROM location_player_lights UNION ALL + SELECT * FROM adjacent_direct_lights UNION ALL + SELECT * FROM adjacent_player_lights + ) + SELECT EXISTS(SELECT 1 FROM relevant_lights);", + if player.location == loc_ref { "IlluminatesRoomForPlayer" } else { "IlluminatesAdjacentRoomForPlayer"} + ), + &[&player_ref, + &loc_ref, + &adjacent_json], + ) + .await?.get(0)) + } + pub async fn commit(mut self: Self) -> DResult<()> { let trans_opt = self.with_trans_mut(|t| std::mem::replace(t, None)); if let Some(trans) = trans_opt { diff --git a/blastmud_game/src/message_handler/user_commands/look.rs b/blastmud_game/src/message_handler/user_commands/look.rs index 110ccc89..bd293ccf 100644 --- a/blastmud_game/src/message_handler/user_commands/look.rs +++ b/blastmud_game/src/message_handler/user_commands/look.rs @@ -7,7 +7,7 @@ use super::{ #[double] use crate::db::DBTrans; use crate::{ - db::ItemSearchParams, + db::{ItemSearchParams, ObjectRef}, language, models::{ effect::EffectType, @@ -376,8 +376,31 @@ fn exits_for_dyn(dynroom: &dynzone::Dynroom) -> String { ) } +pub async fn is_room_illuminated( + ctx: &VerbContext<'_>, + player_item: &Item, + location: &Item, + room: &room::Room, +) -> UResult { + let consider_exits: Vec = room + .exits + .iter() + .filter_map(|ex| room::resolve_exit(room, ex)) + // Do we want to consider skipping exits behind closed doors? + .map(|r| ObjectRef { + item_type: "room".to_owned(), + item_code: r.code.to_owned(), + }) + .collect(); + Ok(ctx + .trans + .is_illuminated(player_item, location, &consider_exits) + .await?) +} + pub async fn describe_room( ctx: &VerbContext<'_>, + player_item: &Item, item: &Item, room: &room::Room, contents: &str, @@ -386,30 +409,36 @@ pub async fn describe_room( .get(room.zone.as_str()) .map(|z| z.display) .unwrap_or("Outside of time"); + let illum = is_room_illuminated(ctx, player_item, item, room).await?; + let desc = flow_around( + &render_map(room, 5, 5), + 10, + ansi!(" "), + &word_wrap( + &format!( + ansi!("{} ({})\n{}{}.{}\n{}\n"), + item.display_for_sentence(1, false), + zone, + &item.details.as_ref().map(|d| d.as_str()).unwrap_or(""), + item.details_dyn_suffix + .as_ref() + .map(|d| d.as_str()) + .unwrap_or(""), + contents, + exits_for(room) + ), + |row| if row >= 5 { 80 } else { 68 }, + ), + 68, + ); ctx.trans .queue_for_session( ctx.session, - Some(&flow_around( - &render_map(room, 5, 5), - 10, - ansi!(" "), - &word_wrap( - &format!( - ansi!("{} ({})\n{}{}.{}\n{}\n"), - item.display_for_sentence(1, false), - zone, - &item.details.as_ref().map(|d| d.as_str()).unwrap_or(""), - item.details_dyn_suffix - .as_ref() - .map(|d| d.as_str()) - .unwrap_or(""), - contents, - exits_for(room) - ), - |row| if row >= 5 { 80 } else { 68 }, - ), - 68, - )), + Some(if illum { + &desc + } else { + "It's too dark to see much.\n" + }), ) .await?; Ok(()) @@ -768,7 +797,14 @@ impl UserVerb for Verb { let room = room::room_map_by_code() .get(item.item_code.as_str()) .ok_or_else(|| UserError("Sorry, that room no longer exists".to_owned()))?; - describe_room(ctx, &item, &room, &list_room_contents(ctx, &item).await?).await?; + describe_room( + ctx, + &player_item, + &item, + &room, + &list_room_contents(ctx, &item).await?, + ) + .await?; } else if item.item_type == "dynroom" { let (dynzone, dynroom) = match &item.special_data { Some(ItemSpecialData::DynroomData { diff --git a/blastmud_game/src/message_handler/user_commands/map.rs b/blastmud_game/src/message_handler/user_commands/map.rs index ad0406ff..50af2882 100644 --- a/blastmud_game/src/message_handler/user_commands/map.rs +++ b/blastmud_game/src/message_handler/user_commands/map.rs @@ -1,5 +1,6 @@ use super::{ - get_player_item_or_fail, user_error, UResult, UserError, UserVerb, UserVerbRef, VerbContext, + get_player_item_or_fail, look::is_room_illuminated, user_error, UResult, UserError, UserVerb, + UserVerbRef, VerbContext, }; use crate::{ models::item::{Item, ItemSpecialData}, @@ -541,6 +542,9 @@ impl UserVerb for Verb { let room = room::room_map_by_code() .get(room_item.item_code.as_str()) .ok_or_else(|| UserError("Sorry, that room no longer exists".to_owned()))?; + if !is_room_illuminated(ctx, &player_item, &room_item, room).await? { + user_error("It's too dark here to make out the map.".to_owned())?; + } map_type.map_room(ctx, &room).await?; } else if room_item.item_type == "dynroom" { let (dynzone, dynroom) = match &room_item.special_data { diff --git a/blastmud_game/src/models/item.rs b/blastmud_game/src/models/item.rs index 25146e3e..d5762312 100644 --- a/blastmud_game/src/models/item.rs +++ b/blastmud_game/src/models/item.rs @@ -325,6 +325,11 @@ pub enum ItemFlag { NoSeeContents, DroppedItemsDontExpire, PrivatePlace, + DarkPlace, + IlluminatesRoom, + IlluminatesRoomForPlayer, + IlluminatesAdjacentRoom, + IlluminatesAdjacentRoomForPlayer, Hireable, NPCsDontAttack, CanLoad, diff --git a/blastmud_game/src/static_content/room.rs b/blastmud_game/src/static_content/room.rs index 195898fe..572430fd 100644 --- a/blastmud_game/src/static_content/room.rs +++ b/blastmud_game/src/static_content/room.rs @@ -26,6 +26,7 @@ mod cok_murl; pub mod computer_museum; pub mod general_hospital; pub mod melbs; +pub mod melbs_sewers; mod repro_xv; mod special; @@ -74,6 +75,11 @@ pub fn zone_details() -> &'static BTreeMap<&'static str, Zone> { display: "General Hospital", outdoors: false, }, + Zone { + code: "melbs_sewers", + display: "Melbs Sewers", + outdoors: false, + }, ] .into_iter() .map(|x| (x.code, x)) @@ -528,6 +534,7 @@ pub fn room_list() -> &'static Vec { rooms.append(&mut special::room_list()); rooms.append(&mut computer_museum::room_list()); rooms.append(&mut general_hospital::room_list()); + rooms.append(&mut melbs_sewers::room_list()); rooms.into_iter().collect() }) } @@ -769,4 +776,21 @@ mod test { .collect::>(); assert_eq!(bad_scav_rooms, Vec::::new()); } + + #[test] + fn exits_resolve() { + let rl = room_list(); + let unresolved_exits: Vec = rl + .iter() + .flat_map(|r: &'static Room| { + let r2: &'static Room = r; + r.exits.iter().map(move |ex| (r2, ex)) + }) + .filter_map(|(r, ex)| match resolve_exit(r, ex) { + Some(_) => None, + None => Some(format!("{} {}", r.code, &ex.direction.describe())), + }) + .collect::>(); + assert_eq!(unresolved_exits, Vec::::new()); + } } diff --git a/blastmud_game/src/static_content/room/cok_murl.yaml b/blastmud_game/src/static_content/room/cok_murl.yaml index d59b47f4..6f237a8d 100644 --- a/blastmud_game/src/static_content/room/cok_murl.yaml +++ b/blastmud_game/src/static_content/room/cok_murl.yaml @@ -36,7 +36,6 @@ description: "A sleek reception that could have been the bridge of a 2000s era sci-fi spaceship. Linished metal plates are lit up by ambient blue LEDs, while stone tiles cover the floor. You see a white android, complete with elegant rounded corners and glowing blue eyes.\n\n\"Welcome to Murlison Suites - the best a business can rent\", purs the bot pleasantly. \"Just say in corpname\" and I'll guide you to the suite for that corp before you can blink! Or if you hold a corp and would like to rent the best suite money can buy for it, just say rent deluxe for corpname, and I'll set you up\"" exits: - direction: west - - direction: south rentable_dynzone: - rent_what: deluxe suite_type: Commercial diff --git a/blastmud_game/src/static_content/room/melbs.yaml b/blastmud_game/src/static_content/room/melbs.yaml index 2c393d6b..98ca8ff5 100644 --- a/blastmud_game/src/static_content/room/melbs.yaml +++ b/blastmud_game/src/static_content/room/melbs.yaml @@ -10,6 +10,8 @@ exits: - direction: south - direction: east + - direction: down + target: !Custom room/melbs_sewers_1a should_caption: false scavtable: CityStreet - zone: melbs @@ -808,7 +810,6 @@ z: 0 description: A road that looks to have been a major tram thoroughfare before the collapse. Cracks line the filthy concrete footpaths and rusted tram tracks, and weeds poke out from holes in the concrete exits: - - direction: west - direction: south should_caption: false scavtable: CityStreet diff --git a/blastmud_game/src/static_content/room/melbs_sewers.rs b/blastmud_game/src/static_content/room/melbs_sewers.rs new file mode 100644 index 00000000..5834f3ed --- /dev/null +++ b/blastmud_game/src/static_content/room/melbs_sewers.rs @@ -0,0 +1,24 @@ +use super::{Room, SimpleRoom}; +use crate::{ + models::item::Scavtype, + static_content::{possession_type::PossessionType, scavtable::Scavinfo}, +}; +use serde_yaml::from_str as from_yaml_str; + +pub fn sewer_scavtable() -> Vec { + vec![Scavinfo { + possession_type: PossessionType::RustySpike, + p_present: 1.0, + difficulty_mean: 7.0, + difficulty_stdev: 1.0, + scavtype: Scavtype::Scavenge, + }] +} + +pub fn room_list() -> Vec { + from_yaml_str::>>(include_str!("melbs_sewers.yaml")) + .unwrap() + .into_iter() + .map(|r| r.into()) + .collect() +} diff --git a/blastmud_game/src/static_content/room/melbs_sewers.yaml b/blastmud_game/src/static_content/room/melbs_sewers.yaml new file mode 100644 index 00000000..b03d82a6 --- /dev/null +++ b/blastmud_game/src/static_content/room/melbs_sewers.yaml @@ -0,0 +1,16 @@ +- zone: melbs_sewers + code: melbs_sewers_1a + name: Dank narrow sewerpipe + short: == + grid_coords: + x: 1 + y: -5 + z: -1 + description: A relatively narrow sewer pipe, featuring a ladder up to an access hole in a street. It smells like sewage in here. Foul smelling gloop, presumably sewage, flows along the bottom of the pipe. This sewer is a veritable ecosystem of its own; every now and then, you feel something living brush against you + exits: + - direction: up + target: !Custom room/melbs_kingst_latrobest + should_caption: false + scavtable: CitySewer + item_flags: + - !DarkPlace diff --git a/blastmud_game/src/static_content/scavtable.rs b/blastmud_game/src/static_content/scavtable.rs index 8a662139..c37907c2 100644 --- a/blastmud_game/src/static_content/scavtable.rs +++ b/blastmud_game/src/static_content/scavtable.rs @@ -1,4 +1,4 @@ -use crate::models::item::Scavtype; +use crate::{models::item::Scavtype, static_content::room::melbs_sewers::sewer_scavtable}; use super::{possession_type::PossessionType, room::melbs::street_scavtable}; use std::collections::BTreeMap; @@ -18,6 +18,7 @@ pub struct Scavinfo { pub enum ScavtableType { Nothing, CityStreet, + CitySewer, } pub fn scavtable_map() -> &'static BTreeMap> { @@ -26,6 +27,7 @@ pub fn scavtable_map() -> &'static BTreeMap> { vec![ (ScavtableType::Nothing, vec![]), (ScavtableType::CityStreet, street_scavtable()), + (ScavtableType::CitySewer, sewer_scavtable()), ] .into_iter() .collect()