Beginning of light mechanic implementation

This commit is contained in:
Condorra 2024-01-12 22:23:02 +11:00
parent 760598e3a1
commit 28654a5130
10 changed files with 207 additions and 27 deletions

View File

@ -73,6 +73,12 @@ pub struct LocationStats {
pub total_weight: u64, 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))] #[cfg_attr(test, allow(dead_code))]
impl DBPool { impl DBPool {
pub async fn record_listener_ping(self: &DBPool, listener: Uuid) -> DResult<()> { pub async fn record_listener_ping(self: &DBPool, listener: Uuid) -> DResult<()> {
@ -401,6 +407,7 @@ impl DBTrans {
Ok(()) Ok(())
} }
#[deny(dead_code)]
pub async fn find_by_username<'a>(self: &'a Self, username: &'a str) -> DResult<Option<User>> { pub async fn find_by_username<'a>(self: &'a Self, username: &'a str) -> DResult<Option<User>> {
if let Some(details_json) = self if let Some(details_json) = self
.pg_trans()? .pg_trans()?
@ -1906,6 +1913,68 @@ impl DBTrans {
.collect()) .collect())
} }
pub async fn is_illuminated(
&self,
player: &Item,
location: &Item,
consider_adjacent: &Vec<ObjectRef>,
) -> DResult<bool> {
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<()> { pub async fn commit(mut self: Self) -> DResult<()> {
let trans_opt = self.with_trans_mut(|t| std::mem::replace(t, None)); let trans_opt = self.with_trans_mut(|t| std::mem::replace(t, None));
if let Some(trans) = trans_opt { if let Some(trans) = trans_opt {

View File

@ -7,7 +7,7 @@ use super::{
#[double] #[double]
use crate::db::DBTrans; use crate::db::DBTrans;
use crate::{ use crate::{
db::ItemSearchParams, db::{ItemSearchParams, ObjectRef},
language, language,
models::{ models::{
effect::EffectType, 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<bool> {
let consider_exits: Vec<ObjectRef> = 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( pub async fn describe_room(
ctx: &VerbContext<'_>, ctx: &VerbContext<'_>,
player_item: &Item,
item: &Item, item: &Item,
room: &room::Room, room: &room::Room,
contents: &str, contents: &str,
@ -386,30 +409,36 @@ pub async fn describe_room(
.get(room.zone.as_str()) .get(room.zone.as_str())
.map(|z| z.display) .map(|z| z.display)
.unwrap_or("Outside of time"); .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!("<reset> "),
&word_wrap(
&format!(
ansi!("<yellow>{}<reset> (<blue>{}<reset>)\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 ctx.trans
.queue_for_session( .queue_for_session(
ctx.session, ctx.session,
Some(&flow_around( Some(if illum {
&render_map(room, 5, 5), &desc
10, } else {
ansi!("<reset> "), "It's too dark to see much.\n"
&word_wrap( }),
&format!(
ansi!("<yellow>{}<reset> (<blue>{}<reset>)\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,
)),
) )
.await?; .await?;
Ok(()) Ok(())
@ -768,7 +797,14 @@ impl UserVerb for Verb {
let room = room::room_map_by_code() let room = room::room_map_by_code()
.get(item.item_code.as_str()) .get(item.item_code.as_str())
.ok_or_else(|| UserError("Sorry, that room no longer exists".to_owned()))?; .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" { } else if item.item_type == "dynroom" {
let (dynzone, dynroom) = match &item.special_data { let (dynzone, dynroom) = match &item.special_data {
Some(ItemSpecialData::DynroomData { Some(ItemSpecialData::DynroomData {

View File

@ -1,5 +1,6 @@
use super::{ 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::{ use crate::{
models::item::{Item, ItemSpecialData}, models::item::{Item, ItemSpecialData},
@ -541,6 +542,9 @@ impl UserVerb for Verb {
let room = room::room_map_by_code() let room = room::room_map_by_code()
.get(room_item.item_code.as_str()) .get(room_item.item_code.as_str())
.ok_or_else(|| UserError("Sorry, that room no longer exists".to_owned()))?; .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?; map_type.map_room(ctx, &room).await?;
} else if room_item.item_type == "dynroom" { } else if room_item.item_type == "dynroom" {
let (dynzone, dynroom) = match &room_item.special_data { let (dynzone, dynroom) = match &room_item.special_data {

View File

@ -325,6 +325,11 @@ pub enum ItemFlag {
NoSeeContents, NoSeeContents,
DroppedItemsDontExpire, DroppedItemsDontExpire,
PrivatePlace, PrivatePlace,
DarkPlace,
IlluminatesRoom,
IlluminatesRoomForPlayer,
IlluminatesAdjacentRoom,
IlluminatesAdjacentRoomForPlayer,
Hireable, Hireable,
NPCsDontAttack, NPCsDontAttack,
CanLoad, CanLoad,

View File

@ -26,6 +26,7 @@ mod cok_murl;
pub mod computer_museum; pub mod computer_museum;
pub mod general_hospital; pub mod general_hospital;
pub mod melbs; pub mod melbs;
pub mod melbs_sewers;
mod repro_xv; mod repro_xv;
mod special; mod special;
@ -74,6 +75,11 @@ pub fn zone_details() -> &'static BTreeMap<&'static str, Zone> {
display: "General Hospital", display: "General Hospital",
outdoors: false, outdoors: false,
}, },
Zone {
code: "melbs_sewers",
display: "Melbs Sewers",
outdoors: false,
},
] ]
.into_iter() .into_iter()
.map(|x| (x.code, x)) .map(|x| (x.code, x))
@ -528,6 +534,7 @@ pub fn room_list() -> &'static Vec<Room> {
rooms.append(&mut special::room_list()); rooms.append(&mut special::room_list());
rooms.append(&mut computer_museum::room_list()); rooms.append(&mut computer_museum::room_list());
rooms.append(&mut general_hospital::room_list()); rooms.append(&mut general_hospital::room_list());
rooms.append(&mut melbs_sewers::room_list());
rooms.into_iter().collect() rooms.into_iter().collect()
}) })
} }
@ -769,4 +776,21 @@ mod test {
.collect::<Vec<String>>(); .collect::<Vec<String>>();
assert_eq!(bad_scav_rooms, Vec::<String>::new()); assert_eq!(bad_scav_rooms, Vec::<String>::new());
} }
#[test]
fn exits_resolve() {
let rl = room_list();
let unresolved_exits: Vec<String> = 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::<Vec<String>>();
assert_eq!(unresolved_exits, Vec::<String>::new());
}
} }

View File

@ -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 <bold>in<reset> 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 <bold>rent deluxe for<reset> corpname, and I'll set you up\"" 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 <bold>in<reset> 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 <bold>rent deluxe for<reset> corpname, and I'll set you up\""
exits: exits:
- direction: west - direction: west
- direction: south
rentable_dynzone: rentable_dynzone:
- rent_what: deluxe - rent_what: deluxe
suite_type: Commercial suite_type: Commercial

View File

@ -10,6 +10,8 @@
exits: exits:
- direction: south - direction: south
- direction: east - direction: east
- direction: down
target: !Custom room/melbs_sewers_1a
should_caption: false should_caption: false
scavtable: CityStreet scavtable: CityStreet
- zone: melbs - zone: melbs
@ -808,7 +810,6 @@
z: 0 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 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: exits:
- direction: west
- direction: south - direction: south
should_caption: false should_caption: false
scavtable: CityStreet scavtable: CityStreet

View File

@ -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<Scavinfo> {
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<Room> {
from_yaml_str::<Vec<SimpleRoom<()>>>(include_str!("melbs_sewers.yaml"))
.unwrap()
.into_iter()
.map(|r| r.into())
.collect()
}

View File

@ -0,0 +1,16 @@
- zone: melbs_sewers
code: melbs_sewers_1a
name: Dank narrow sewerpipe
short: <yellow>==<reset>
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

View File

@ -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 super::{possession_type::PossessionType, room::melbs::street_scavtable};
use std::collections::BTreeMap; use std::collections::BTreeMap;
@ -18,6 +18,7 @@ pub struct Scavinfo {
pub enum ScavtableType { pub enum ScavtableType {
Nothing, Nothing,
CityStreet, CityStreet,
CitySewer,
} }
pub fn scavtable_map() -> &'static BTreeMap<ScavtableType, Vec<Scavinfo>> { pub fn scavtable_map() -> &'static BTreeMap<ScavtableType, Vec<Scavinfo>> {
@ -26,6 +27,7 @@ pub fn scavtable_map() -> &'static BTreeMap<ScavtableType, Vec<Scavinfo>> {
vec![ vec![
(ScavtableType::Nothing, vec![]), (ScavtableType::Nothing, vec![]),
(ScavtableType::CityStreet, street_scavtable()), (ScavtableType::CityStreet, street_scavtable()),
(ScavtableType::CitySewer, sewer_scavtable()),
] ]
.into_iter() .into_iter()
.collect() .collect()