Start modelling and supporting rooms / zones.

This commit is contained in:
Condorra 2022-12-27 23:35:27 +11:00
parent c4f9577645
commit d362c2f371
5 changed files with 175 additions and 29 deletions

1
Cargo.lock generated
View File

@ -116,6 +116,7 @@ dependencies = [
"log", "log",
"nix", "nix",
"nom", "nom",
"once_cell",
"ouroboros", "ouroboros",
"phf", "phf",
"ring", "ring",

View File

@ -33,3 +33,4 @@ chrono = { version = "0.4.23", features = ["serde"] }
bcrypt = "0.13.0" bcrypt = "0.13.0"
validator = "0.16.0" validator = "0.16.0"
itertools = "0.10.5" itertools = "0.10.5"
once_cell = "1.16.0"

View File

@ -34,7 +34,7 @@ impl UserVerb for Verb {
item_type: "player".to_owned(), item_type: "player".to_owned(),
item_code: username.to_lowercase(), item_code: username.to_lowercase(),
display: username.to_owned(), display: username.to_owned(),
location: "room/chargen_room".to_owned(), location: "room/repro_xv_chargen".to_owned(),
..Item::default() ..Item::default()
}).await?; }).await?;

View File

@ -1,15 +1,15 @@
use crate::DResult; use crate::DResult;
use crate::db::DBPool; use crate::db::DBPool;
use crate::models::item::Item; use crate::models::item::Item;
use log::error;
use itertools::Itertools; use itertools::Itertools;
use std::collections::{BTreeSet, BTreeMap}; use std::collections::{BTreeSet, BTreeMap};
use log::info;
mod room; mod room;
pub struct StaticItem { pub struct StaticItem {
pub item_code: &'static str, pub item_code: &'static str,
pub initial_item: fn () -> Item pub initial_item: Box<dyn Fn() -> Item>
} }
struct StaticItemTypeGroup { struct StaticItemTypeGroup {
@ -26,25 +26,15 @@ fn static_item_registry() -> Vec<StaticItemTypeGroup> {
}, },
StaticItemTypeGroup { StaticItemTypeGroup {
item_type: "room", item_type: "room",
items: || room::static_items() items: || room::room_static_items()
}, },
) )
} }
async fn refresh_static_items(pool: &DBPool) -> DResult<()> { async fn refresh_static_items(pool: &DBPool) -> DResult<()> {
let mut registry = static_item_registry(); let registry = static_item_registry();
registry.sort_unstable_by(|x, y| x.item_type.cmp(y.item_type));
let duplicates: Vec<&'static str> =
registry.iter()
.group_by(|x| x.item_type).into_iter()
.filter_map(|(k, v)| if v.count() <= 1 { None } else { Some(k) })
.collect();
if duplicates.len() > 0 {
error!("static_item_registry has duplicate item_types: {:}", duplicates.join(", "));
Err("Duplicate item_types in static_item_registry")?;
}
let expected_type: BTreeSet<String> = let expected_type: BTreeSet<String> =
registry.iter().map(|x| x.item_type.to_owned()).collect(); registry.iter().map(|x| x.item_type.to_owned()).collect();
let cur_types: Box<BTreeSet<String>> = pool.find_static_item_types().await?; let cur_types: Box<BTreeSet<String>> = pool.find_static_item_types().await?;
@ -53,18 +43,7 @@ async fn refresh_static_items(pool: &DBPool) -> DResult<()> {
} }
for type_group in registry.iter() { for type_group in registry.iter() {
let iterator : Box<dyn Iterator<Item = StaticItem>> = (type_group.items)(); info!("Checking static_content of item_type {}", type_group.item_type);
let duplicates: Vec<&'static str> = iterator
.group_by(|x| x.item_code)
.into_iter()
.filter_map(|(k, v)| if v.count() <= 1 { None } else { Some(k) })
.collect();
if duplicates.len() > 0 {
error!("static_item_registry has duplicate item_codes for {}: {:}",
type_group.item_type,
duplicates.join(", "));
Err("Duplicate item_types in static_item_registry")?;
}
let tx = pool.start_transaction().await?; let tx = pool.start_transaction().await?;
let existing_items = tx.find_static_items_by_type(type_group.item_type).await?; let existing_items = tx.find_static_items_by_type(type_group.item_type).await?;
let expected_items: BTreeMap<String, StaticItem> = let expected_items: BTreeMap<String, StaticItem> =
@ -77,6 +56,8 @@ async fn refresh_static_items(pool: &DBPool) -> DResult<()> {
tx.create_item(&(expected_items.get(new_item_code) tx.create_item(&(expected_items.get(new_item_code)
.unwrap().initial_item)()).await?; .unwrap().initial_item)()).await?;
} }
tx.commit().await?;
info!("Committed any changes for static_content of item_type {}", type_group.item_type);
} }
Ok(()) Ok(())
} }
@ -85,3 +66,37 @@ pub async fn refresh_static_content(pool: &DBPool) -> DResult<()> {
refresh_static_items(pool).await?; refresh_static_items(pool).await?;
Ok(()) Ok(())
} }
#[cfg(test)]
mod test {
use super::*;
#[test]
fn no_duplicate_static_content() {
let mut registry = static_item_registry();
registry.sort_unstable_by(|x, y| x.item_type.cmp(y.item_type));
let duplicates: Vec<&'static str> =
registry.iter()
.group_by(|x| x.item_type).into_iter()
.filter_map(|(k, v)| if v.count() <= 1 { None } else { Some(k) })
.collect();
if duplicates.len() > 0 {
panic!("static_item_registry has duplicate item_types: {:}", duplicates.join(", "));
}
for type_group in registry.iter() {
let iterator : Box<dyn Iterator<Item = StaticItem>> = (type_group.items)();
let duplicates: Vec<&'static str> = iterator
.group_by(|x| x.item_code)
.into_iter()
.filter_map(|(k, v)| if v.count() <= 1 { None } else { Some(k) })
.collect();
if duplicates.len() > 0 {
panic!("static_item_registry has duplicate item_codes for {}: {:}",
type_group.item_type,
duplicates.join(", "));
}
}
}
}

View File

@ -1,5 +1,134 @@
use super::StaticItem; use super::StaticItem;
use once_cell::sync::OnceCell;
use std::collections::BTreeMap;
use ansi_macro::ansi;
use crate::models::item::Item;
pub fn static_items() -> Box<dyn Iterator<Item = StaticItem>> { pub struct Zone {
Box::new(vec!().into_iter()) pub code: &'static str,
pub display: &'static str,
pub outdoors: bool,
}
static STATIC_ZONE_DETAILS: OnceCell<BTreeMap<&'static str, Zone>> = OnceCell::new();
pub fn zone_details() -> &'static BTreeMap<&'static str, Zone> {
STATIC_ZONE_DETAILS.get_or_init(
|| vec!(
Zone { code: "melbs",
display: "Melbs",
outdoors: true },
Zone { code: "repro_xv",
display: "Reprolabs XV",
outdoors: true },
).into_iter().map(|x|(x.code, x)).collect())
}
pub struct GridCoords {
x: i64,
y: i64,
z: i64
}
pub enum ExitType {
Free, // Anyone can just walk it.
// Future ideas: Doors with locks, etc...
}
pub enum Direction {
NORTH,
SOUTH,
EAST,
WEST,
NORTHEAST,
SOUTEAST,
NORTHWEST,
SOUTHWEST,
UP,
DOWN,
IN(&'static str)
}
pub enum ExitTarget {
UseGPS,
Custom(&'static str)
}
pub struct Exit {
direction: Direction,
target: ExitTarget,
exit_type: ExitType
}
pub struct Room {
pub zone: &'static str,
pub code: &'static str,
pub name: &'static str,
pub short: &'static str,
pub grid_coords: GridCoords,
pub description: &'static str,
pub exits: Vec<Exit>
}
static STATIC_ROOM_LIST: OnceCell<Vec<Room>> = OnceCell::new();
pub fn room_list() -> &'static Vec<Room> {
STATIC_ROOM_LIST.get_or_init(
|| vec!(
Room {
zone: "repro_xv",
code: "repro_xv_chargen",
name: "Choice Room",
short: ansi!("<green>CR<reset>"),
description: "A room brightly lit in unnaturally white light, covered in sparkling \
white tiles from floor to \
ceiling. A loudspeaker plays a message on loop:\r\n\
\t\"Citizen, you are here because your memory has been wiped and you \
are ready to start a fresh life. As a being enhanced by Gazos-Murlison \
Co technology, the emperor has granted you the power to choose 14 points \
of upgrades to yourself. Choose wisely, as it will impact who you end up \
being, and you would need to completely wipe your brain again to change \
them. Talk to Statbot to spend your 14 points and create your body.\"\r\n\
[Try <bold>\"statbot hi<reset>, to send hi to statbot - the \" means to \
whisper to a particular person in the room]",
grid_coords: GridCoords { x: 0, y: 0, z: 2 },
exits: vec!()
},
).into_iter().collect())
}
static STATIC_ROOM_MAP_BY_CODE: OnceCell<BTreeMap<&'static str, &'static Room>> = OnceCell::new();
pub fn room_map_by_code() -> &'static BTreeMap<&'static str, &'static Room> {
STATIC_ROOM_MAP_BY_CODE.get_or_init(
|| room_list().iter().map(|r| (r.code, r)).collect())
}
pub fn room_static_items() -> Box<dyn Iterator<Item = StaticItem>> {
Box::new(room_list().iter().map(|r| StaticItem {
item_code: r.code,
initial_item: Box::new(|| Item {
item_code: r.code.to_owned(),
item_type: "room".to_owned(),
display: r.description.to_owned(),
location: r.code.to_owned(),
is_static: true,
..Item::default()
})
}))
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn room_zones_should_exist() {
for room in room_list() {
zone_details().get(room.zone).expect(
&format!("zone {} for room {} should exist", room.zone, room.code));
}
}
#[test]
fn room_map_by_code_should_have_repro_xv_chargen() {
assert_eq!(room_map_by_code().get("repro_xv_chargen").expect("repro_xv_chargen to exist").code,
"repro_xv_chargen");
}
} }