diff --git a/blastmud_game/src/db.rs b/blastmud_game/src/db.rs index 2ce912c..3b8a2cb 100644 --- a/blastmud_game/src/db.rs +++ b/blastmud_game/src/db.rs @@ -473,6 +473,7 @@ impl DBTrans { "is_challenge_attack_only", "aliases", "species", + "static_special_data", ] { det_ex = format!("jsonb_set({}, '{{{}}}', ${})", det_ex, to_copy, var_id); params.push(obj_map.get(to_copy).unwrap_or(&Value::Null)); @@ -1975,6 +1976,20 @@ impl DBTrans { .await?.get(0)) } + pub async fn has_key_matching(&self, at_item: &Item, pin_sequence: &str) -> DResult { + Ok(self + .pg_trans()? + .query_one( + "SELECT EXISTS(SELECT 1 FROM items WHERE \ + details->>'location' = $1 AND \ + details->'static_special_data'->'KeyData'->>'pin_sequence' = $2) \ + AS found", + &[&at_item.refstr(), &pin_sequence], + ) + .await? + .get("found")) + } + 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.rs b/blastmud_game/src/message_handler/user_commands.rs index 2cc823c..8666bf2 100644 --- a/blastmud_game/src/message_handler/user_commands.rs +++ b/blastmud_game/src/message_handler/user_commands.rs @@ -66,6 +66,7 @@ pub mod rent; mod report; mod reset_spawns; pub mod say; +mod scan; pub mod scavenge; mod score; mod share; @@ -239,6 +240,8 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! { "\'" => say::VERB, "say" => say::VERB, + "scan" => scan::VERB, + "scavenge" => scavenge::VERB, "search" => scavenge::VERB, @@ -263,7 +266,6 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! { "sign" => sign::VERB, "sit" => sit::VERB, - "stand" => stand::VERB, "st" => status::VERB, diff --git a/blastmud_game/src/message_handler/user_commands/delete.rs b/blastmud_game/src/message_handler/user_commands/delete.rs index 9f94a0d..ef2b826 100644 --- a/blastmud_game/src/message_handler/user_commands/delete.rs +++ b/blastmud_game/src/message_handler/user_commands/delete.rs @@ -94,6 +94,7 @@ async fn reset_stats(ctx: &mut VerbContext<'_>) -> UResult<()> { user_dat.raw_stats = BTreeMap::new(); user_dat.raw_skills = BTreeMap::new(); user_dat.wristpad_hacks = vec![]; + user_dat.scan_codes = vec![]; calculate_total_stats_skills_for_user(&mut player_item, &user_dat); ctx.trans.save_user_model(&user_dat).await?; ctx.trans.save_item_model(&player_item).await?; diff --git a/blastmud_game/src/message_handler/user_commands/get.rs b/blastmud_game/src/message_handler/user_commands/get.rs index 80ea8b7..cd70ec9 100644 --- a/blastmud_game/src/message_handler/user_commands/get.rs +++ b/blastmud_game/src/message_handler/user_commands/get.rs @@ -3,7 +3,7 @@ use super::{ user_error, ItemSearchParams, UResult, UserError, UserVerb, UserVerbRef, VerbContext, }; use crate::{ - models::item::LocationActionType, + models::item::{Item, LocationActionType}, regular_tasks::queued_command::{ queue_command, QueueCommand, QueueCommandHandler, QueuedCommandContext, }, @@ -49,12 +49,15 @@ impl QueueCommandHandler for QueueHandler { broadcast_to_room(ctx.trans, &ctx.item.location, None, &msg).await?; } QueueCommand::GetFromContainer { - from_possession_id, + from_item_id, get_possession_id, } => { + let (from_item_type, from_item_code) = from_item_id + .split_once("/") + .ok_or_else(|| UserError("Bad from_item_id for get".to_owned()))?; let container = ctx .trans - .find_item_by_type_code("possession", &from_possession_id) + .find_item_by_type_code(&from_item_type, &from_item_code) .await? .ok_or_else(|| UserError("Item to get from not found".to_owned()))?; if container.location != ctx.item.location @@ -125,12 +128,15 @@ impl QueueCommandHandler for QueueHandler { (item, None) } QueueCommand::GetFromContainer { - from_possession_id, + from_item_id, get_possession_id, } => { + let (from_item_type, from_item_code) = from_item_id + .split_once("/") + .ok_or_else(|| UserError("Bad from_item_id for get".to_owned()))?; let container = ctx .trans - .find_item_by_type_code("possession", &from_possession_id) + .find_item_by_type_code(from_item_type, from_item_code) .await? .ok_or_else(|| UserError("Item to get from not found".to_owned()))?; if container.location != ctx.item.location @@ -200,6 +206,22 @@ impl QueueCommandHandler for QueueHandler { } } +fn check_get_from_allowed(from_what: &Item) -> UResult<()> { + if from_what.item_type == "player" || from_what.item_type == "npc" { + if from_what.death_data.is_none() { + user_error(format!( + "You don't think {} would let you just take like that!", + &from_what.pronouns.subject + ))?; + } + return Ok(()); + } + if !vec!["possession", "static_item"].contains(&from_what.item_type.as_str()) { + user_error("You can't get from that!".to_owned())?; + } + Ok(()) +} + pub struct Verb; #[async_trait] impl UserVerb for Verb { @@ -229,7 +251,6 @@ impl UserVerb for Verb { &ItemSearchParams { include_loc_contents: true, include_contents: true, - item_type_only: Some("possession"), ..ItemSearchParams::base(&player_item, container_str_raw.trim()) }, ) @@ -255,6 +276,10 @@ impl UserVerb for Verb { )?; } + if include_loc_contents { + check_get_from_allowed(&search_what)?; + } + let mut did_anything: bool = false; let mut player_item_mut = (*player_item).clone(); for target in targets @@ -279,7 +304,7 @@ impl UserVerb for Verb { ctx, &mut player_item_mut, &QueueCommand::GetFromContainer { - from_possession_id: search_what.item_code.clone(), + from_item_id: search_what.refstr(), get_possession_id: target.item_code.clone(), }, ) diff --git a/blastmud_game/src/message_handler/user_commands/movement.rs b/blastmud_game/src/message_handler/user_commands/movement.rs index 31192ab..e962966 100644 --- a/blastmud_game/src/message_handler/user_commands/movement.rs +++ b/blastmud_game/src/message_handler/user_commands/movement.rs @@ -32,6 +32,7 @@ use crate::{ dynzone::{dynzone_by_type, DynzoneType, ExitTarget as DynExitTarget}, npc::check_for_instant_aggro, room::{self, check_for_enter_action, Direction, ExitClimb, ExitType, MaterialType}, + species::species_info_map, }, DResult, }; @@ -185,7 +186,7 @@ async fn move_to_where( match exit.exit_type { ExitType::Free => {} - ExitType::Blocked(blocker) => { + ExitType::Blocked(ref blocker) => { if !blocker.attempt_exit(ctx, exit).await? { user_error("Stopping movement".to_owned())?; } @@ -410,6 +411,13 @@ async fn attempt_move_immediate( check_room_access(ctx.trans, ctx.item, &room_with_door).await?; } _ => { + if !species_info_map() + .get(&ctx.item.species) + .map(|inf| inf.can_open_door) + .unwrap_or(false) + { + return Ok(false); + } attempt_open_immediate(ctx, direction).await?; // Players take an extra step. So tell them to come back. ctx.item.queue.push_front(QueueCommand::Movement { diff --git a/blastmud_game/src/message_handler/user_commands/put.rs b/blastmud_game/src/message_handler/user_commands/put.rs index 9502217..4e7dfcd 100644 --- a/blastmud_game/src/message_handler/user_commands/put.rs +++ b/blastmud_game/src/message_handler/user_commands/put.rs @@ -257,7 +257,7 @@ impl UserVerb for Verb { ctx, &mut player_item_mut, &QueueCommand::GetFromContainer { - from_possession_id: target.item_code.clone(), + from_item_id: target.refstr(), get_possession_id: page.item_code.clone(), }, ) diff --git a/blastmud_game/src/message_handler/user_commands/scan.rs b/blastmud_game/src/message_handler/user_commands/scan.rs new file mode 100644 index 0000000..68ceff0 --- /dev/null +++ b/blastmud_game/src/message_handler/user_commands/scan.rs @@ -0,0 +1,56 @@ +use crate::static_content::room::room_map_by_code; + +use super::{ + get_player_item_or_fail, user_error, UResult, UserError, UserVerb, UserVerbRef, VerbContext, +}; +use async_trait::async_trait; + +pub struct Verb; +#[async_trait] +impl UserVerb for Verb { + async fn handle( + self: &Self, + ctx: &mut VerbContext, + _verb: &str, + _remaining: &str, + ) -> UResult<()> { + let player_item = get_player_item_or_fail(ctx).await?; + let (loc_type, loc_code) = player_item + .location + .split_once("/") + .ok_or_else(|| UserError("Your location is invalid".to_owned()))?; + if loc_type != "room" { + user_error("You can't find anything to scan here.".to_owned())?; + } + + let room = room_map_by_code() + .get(&loc_code) + .ok_or_else(|| UserError("Your location no longer exists!".to_owned()))?; + + let allowed_scan = room + .scan_code + .as_ref() + .ok_or_else(|| UserError("You can't find anything to scan here.".to_owned()))?; + + let user = ctx + .user_dat + .as_mut() + .ok_or(UserError("Please log in first".to_owned()))?; + + if !user.scan_codes.contains(&allowed_scan) { + user.scan_codes.push(allowed_scan.clone()); + ctx.trans.save_user_model(&user).await?; + } + + ctx.trans + .queue_for_session( + &ctx.session, + Some("Your wristpad beeps indicating a successful scan.\n"), + ) + .await?; + + 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 0782a46..92b0da3 100644 --- a/blastmud_game/src/models/item.rs +++ b/blastmud_game/src/models/item.rs @@ -614,6 +614,17 @@ pub enum ItemSpecialData { }, } +// Note that ItemSpecialData and ItemStaticSpecialData are essentially +// the same except for static items. The difference is that ItemSpecialData +// is things that can change dynamically for static items, and so shouldn't +// be reset on game restart, while ItemStaticSpecialData are reset on game +// server startup to the current latest static version of the data. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] +pub enum ItemStaticSpecialData { + KeyData { pin_sequence: String }, + LockData { pin_sequence: String }, +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] pub struct DynamicEntrance { pub direction: Direction, @@ -714,6 +725,7 @@ pub struct Item { pub queue: VecDeque, pub sex: Option, pub special_data: Option, + pub static_special_data: Option, pub species: SpeciesType, pub tactic_use: TacticUse, pub temporary_buffs: Vec, @@ -822,6 +834,7 @@ impl Default for Item { queue: VecDeque::new(), sex: None, special_data: None, + static_special_data: None, species: SpeciesType::Human, tactic_use: Default::default(), temporary_buffs: Vec::new(), diff --git a/blastmud_game/src/models/user.rs b/blastmud_game/src/models/user.rs index efec3b8..fcbf283 100644 --- a/blastmud_game/src/models/user.rs +++ b/blastmud_game/src/models/user.rs @@ -4,7 +4,7 @@ use super::{ }; #[double] use crate::db::DBTrans; -use crate::{message_handler::ListenerSession, DResult}; +use crate::{message_handler::ListenerSession, static_content::room::ScanCode, DResult}; use chrono::{DateTime, Utc}; use mockall_double::double; use once_cell::sync::OnceCell; @@ -83,6 +83,7 @@ pub struct User { pub raw_skills: BTreeMap, pub raw_stats: BTreeMap, pub wristpad_hacks: Vec, + pub scan_codes: Vec, pub last_skill_improve: BTreeMap>, pub last_page_from: Option, pub credits: u64, @@ -215,6 +216,7 @@ impl Default for User { raw_skills: BTreeMap::new(), raw_stats: BTreeMap::new(), wristpad_hacks: vec![], + scan_codes: vec![], last_skill_improve: BTreeMap::new(), last_page_from: None, credits: 500, diff --git a/blastmud_game/src/regular_tasks/queued_command.rs b/blastmud_game/src/regular_tasks/queued_command.rs index d208e74..8089b9d 100644 --- a/blastmud_game/src/regular_tasks/queued_command.rs +++ b/blastmud_game/src/regular_tasks/queued_command.rs @@ -78,7 +78,7 @@ pub enum QueueCommand { possession_id: String, }, GetFromContainer { - from_possession_id: String, + from_item_id: String, get_possession_id: String, }, Make { diff --git a/blastmud_game/src/services/spawn.rs b/blastmud_game/src/services/spawn.rs index 7fd46d7..de226a3 100644 --- a/blastmud_game/src/services/spawn.rs +++ b/blastmud_game/src/services/spawn.rs @@ -75,6 +75,7 @@ async fn add_per_spawn( SpawnDistribution::SpawnPossession { what } => { let mut item: Item = what.clone().into(); item.location = location.refstr(); + item.item_code = format!("{}", trans.alloc_item_code().await?); trans.create_item(&item).await?; Ok(false) } @@ -110,18 +111,34 @@ async fn add_per_spawn( fn spawn_list() -> &'static BTreeMap<&'static str, SpawnDistribution> { static SPAWN_LIST: OnceCell> = OnceCell::new(); SPAWN_LIST.get_or_init(|| { - vec![( - "fixed_item/melbs_king_st_spring_fed_fountain", - SpawnDistribution::SpawnOne { - pvec: vec![( - 1.0, - Box::new(SpawnDistribution::SpawnLiquid { - what: LiquidType::Water, - how_much: 1000000, - }), - )], - }, - )] + vec![ + ( + "fixed_item/melbs_king_st_spring_fed_fountain", + SpawnDistribution::SpawnOne { + pvec: vec![( + 1.0, + Box::new(SpawnDistribution::SpawnLiquid { + what: LiquidType::Water, + how_much: 1000000, + }), + )], + }, + ), + ( + "fixed_item/kings_office_basin", + SpawnDistribution::SpawnAll { + subspawns: vec![ + Box::new(SpawnDistribution::SpawnLiquid { + what: LiquidType::Water, + how_much: 1000000, + }), + Box::new(SpawnDistribution::SpawnPossession { + what: PossessionType::KingsOfficeKey, + }), + ], + }, + ), + ] .into_iter() .collect() }) diff --git a/blastmud_game/src/static_content/dumper.rs b/blastmud_game/src/static_content/dumper.rs index c02039f..32b947e 100644 --- a/blastmud_game/src/static_content/dumper.rs +++ b/blastmud_game/src/static_content/dumper.rs @@ -13,6 +13,7 @@ fn exit_to_simple_exit(exit: &Exit) -> Option { direction: exit.direction.clone(), target: exit.target.clone(), exit_climb: exit.exit_climb.clone(), + needs_scan: None, }) } @@ -47,6 +48,7 @@ fn room_to_simpleroom(room: &Room) -> Option> { wristpad_hack_allowed: room.wristpad_hack_allowed.clone(), journal: room.journal.clone(), scavtable: room.scavtable.clone(), + scan_code: room.scan_code.clone(), extra: (), }) } diff --git a/blastmud_game/src/static_content/fixed_item.rs b/blastmud_game/src/static_content/fixed_item.rs index 7641f7e..6e0e732 100644 --- a/blastmud_game/src/static_content/fixed_item.rs +++ b/blastmud_game/src/static_content/fixed_item.rs @@ -1,8 +1,8 @@ // For things like signs that don't do much except stay where they are and carry a description. use super::{possession_type::PossessionData, StaticItem}; use crate::{ - models::item::{Item, LiquidType, LocationActionType, Pronouns}, - static_content::{possession_type::LiquidContainerData, room::computer_museum}, + models::item::{Item, ItemStaticSpecialData, LocationActionType, Pronouns}, + static_content::room::{computer_museum, melbs}, }; use ansi::ansi; use once_cell::sync::OnceCell; @@ -17,6 +17,7 @@ pub struct FixedItem { pub location: String, pub proper_noun: bool, pub aliases: Vec, + pub static_special_data: Option, pub action_type: LocationActionType, } @@ -29,6 +30,7 @@ impl Default for FixedItem { location: "unset".to_owned(), proper_noun: true, aliases: vec![], + static_special_data: None, action_type: LocationActionType::Normal, } } @@ -58,42 +60,21 @@ fn fixed_item_list() -> &'static Vec { aliases: vec!["poster".to_owned()], ..Default::default() }, - FixedItem { - code: "melbs_king_st_spring_fed_fountain".to_owned(), - name: "spring fed fountain".to_owned(), - description: ansi!("A stainless steel fountain, clearly old, but in surprisingly good \ - condition. A discoloured bronze plaque attached to it proudly declares \ - that it is fed by a natural spring underneath it. It was designed so that \ - unused water runs off it into a dog bowl - presumably in a time long past when \ - dogs were friendly companions and not the menace they are today. It smells \ - faintly of iron. [Try drink from fountain or, if you have a suitable \ - container, fill container from fountain].").to_owned(), - location: "room/melbs_kingst_40".to_owned(), - proper_noun: false, - aliases: vec!["fountain".to_owned()], - ..Default::default() - }, - ].into_iter().chain(computer_museum::fixed_items().into_iter()).collect() + ].into_iter() + .chain(computer_museum::fixed_items().into_iter()) + .chain(melbs::fixed_items().into_iter()) + .collect() }) } pub fn fixed_item_properties() -> &'static BTreeMap<&'static str, PossessionData> { static PROPS: OnceCell> = OnceCell::new(); PROPS.get_or_init(|| { - vec![( - "melbs_king_st_spring_fed_fountain", - PossessionData { - liquid_container_data: Some(LiquidContainerData { - capacity: 5000000, // mL - allowed_contents: Some(vec![LiquidType::Water]), - ..Default::default() - }), - ..Default::default() - }, - )] - .into_iter() - .chain(computer_museum::fixed_item_properties().into_iter()) - .collect() + vec![] + .into_iter() + .chain(computer_museum::fixed_item_properties().into_iter()) + .chain(melbs::fixed_item_properties().into_iter()) + .collect() }) } @@ -113,6 +94,7 @@ pub fn static_items() -> Box> { ..Pronouns::default_inanimate() }, action_type: r.action_type.clone(), + static_special_data: r.static_special_data.clone(), ..Item::default() }), extra_items_on_create: Box::new(|_| Box::new(std::iter::empty())), diff --git a/blastmud_game/src/static_content/npc.rs b/blastmud_game/src/static_content/npc.rs index 13af64a..0cd33ea 100644 --- a/blastmud_game/src/static_content/npc.rs +++ b/blastmud_game/src/static_content/npc.rs @@ -35,8 +35,7 @@ use std::time; use uuid::Uuid; pub mod computer_museum_npcs; -mod melbs_citizen; -mod melbs_dog; +mod melbs_npcs; mod roboporter; mod sewer_npcs; pub mod statbot; @@ -189,8 +188,7 @@ pub fn npc_list() -> &'static Vec { says: vec![], ..Default::default() }]; - npcs.append(&mut melbs_citizen::npc_list()); - npcs.append(&mut melbs_dog::npc_list()); + npcs.append(&mut melbs_npcs::npc_list()); npcs.append(&mut roboporter::npc_list()); npcs.append(&mut computer_museum_npcs::npc_list()); npcs.append(&mut sewer_npcs::npc_list()); diff --git a/blastmud_game/src/static_content/npc/melbs_citizen.rs b/blastmud_game/src/static_content/npc/melbs_citizen.rs deleted file mode 100644 index 3609fe2..0000000 --- a/blastmud_game/src/static_content/npc/melbs_citizen.rs +++ /dev/null @@ -1,63 +0,0 @@ -use super::{NPCPronounType, NPCSayInfo, NPCSayType, NPCSpawnPossession, NPC}; -use crate::{ - models::{consent::ConsentType, item::LocationActionType}, - static_content::{npc::npc_pronoun_type_to_pronouns, possession_type::PossessionType}, -}; -use serde::Deserialize; -use serde_yaml::from_str as from_yaml_str; - -#[derive(Deserialize)] -struct Citizen { - code: String, - name: String, - spawn_loc: String, - gender: NPCPronounType, -} - -pub fn npc_list() -> Vec { - use NPCSayType::FromFixedList; - let melbs_citizen_stdsay = NPCSayInfo { - say_code: "babble", - frequency_secs: 120, - talk_type: FromFixedList(vec![ - "I'm so sick of being cloned.", - "I hope I don't die again today.", - "I wish the so-called king would do something about the damned zombies everywhere.", - "I heard in the olden days before the empire babies grew up naturally instead of being taken away to the body factory.", - "I know people hated the empire, but I kind of wish it was still intact - it was a lot better than what we have now.", - "I wish there wasn't so much radiation outside of Melbs CBD.", - "I heard about a guy who went to a special place somewhere around here, and there was a machine that enhanced his wristpad and gave him basically superpowers.", - "The damn vampire movement... they are all so sneaky, and I never know when they are going to come for my blood.", - ]) - }; - - from_yaml_str::>(include_str!("melbs_citizen.yaml")) - .unwrap() - .into_iter() - .map(|c| - NPC { - code: format!("melbs_citizen_{}", &c.code), - name: c.name, - pronouns: npc_pronoun_type_to_pronouns(&c.gender), - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harsh reality of post-apocalyptic life".to_owned(), - spawn_location: format!("room/melbs_{}", &c.spawn_loc), - spawn_possessions: vec![ - NPCSpawnPossession { - what: PossessionType::Shirt, - action_type: LocationActionType::Worn, - wear_layer: 0, - }, - NPCSpawnPossession { - what: PossessionType::Jeans, - action_type: LocationActionType::Worn, - wear_layer: 0, - }, - ], - message_handler: None, - wander_zones: vec!("melbs".to_owned()), - says: vec!(melbs_citizen_stdsay.clone()), - player_consents: vec!(ConsentType::Medicine, ConsentType::Share), - ..Default::default() - } - ).collect() -} diff --git a/blastmud_game/src/static_content/npc/melbs_citizen.yaml b/blastmud_game/src/static_content/npc/melbs_citizen.yaml deleted file mode 100644 index 63a4e49..0000000 --- a/blastmud_game/src/static_content/npc/melbs_citizen.yaml +++ /dev/null @@ -1,240 +0,0 @@ -- code: "1" - name: Matthew Thomas - spawn_loc: kingst_latrobest - gender: Male -- code: "2" - name: Matthew Perez - spawn_loc: kingst_20 - gender: Male -- code: "3" - name: Kimberly Jackson - spawn_loc: kingst_40 - gender: Female -- code: "4" - name: Michael Sanchez - spawn_loc: kingst_50 - gender: Male -- code: "5" - name: Jessica Davis - spawn_loc: kingst_bourkest - gender: Female -- code: "6" - name: Robert Davis - spawn_loc: kingst_70 - gender: Male -- code: "7" - name: Paul Lewis - spawn_loc: kingst_90 - gender: Male -- code: "8" - name: Andrew Moore - spawn_loc: kingst_collinsst - gender: Male -- code: "9" - name: Betty Thomas - spawn_loc: kingst_100 - gender: Female -- code: "10" - name: Mary Robinson - spawn_loc: kingst_110 - gender: Female -- code: "11" - name: Lisa Lopez - spawn_loc: kingst_flinderst - gender: Female -- code: "12" - name: Kimberly Martinez - spawn_loc: flindersst_200 - gender: Female -- code: "13" - name: Anthony Nguyen - spawn_loc: flindersst_190 - gender: Male -- code: "14" - name: Joshua Green - spawn_loc: flindersst_180 - gender: Male -- code: "15" - name: Emily Wright - spawn_loc: flindersst_170 - gender: Female -- code: "16" - name: Ashley Thomas - spawn_loc: lonsdalest_130 - gender: Male -- code: "17" - name: Jessica Miller - spawn_loc: kingst_80 - gender: Female -- code: "18" - name: Anthony Lopez - spawn_loc: lonsdalest_140 - gender: Male -- code: "19" - name: John Lopez - spawn_loc: elizabethst_lonsdalest - gender: Male -- code: "20" - name: Thomas Garcia - spawn_loc: williamsst_120 - gender: Male -- code: "21" - name: Donna Thompson - spawn_loc: elizabethst_60 - gender: Female -- code: "22" - name: Matthew Davis - spawn_loc: williamsst_100 - gender: Male -- code: "23" - name: Steven Jones - spawn_loc: swanstonst_120 - gender: Male -- code: "24" - name: Linda Smith - spawn_loc: swanstonst_lonsdalest - gender: Male -- code: "25" - name: Karen Rodriguez - spawn_loc: bourkest_180 - gender: Female -- code: "26" - name: Paul Scott - spawn_loc: swanstonst_70 - gender: Male -- code: "27" - name: Ashley Thomas - spawn_loc: lonsdalest_130 - gender: Male -- code: "28" - name: Sandra Scott - spawn_loc: elizabethst_30 - gender: Female -- code: "29" - name: Michael Rodriguez - spawn_loc: swanstonst_70 - gender: Male -- code: "30" - name: Donald Miller - spawn_loc: elizabethst_30 - gender: Male -- code: "31" - name: Charles Moore - spawn_loc: lonsdalest_160 - gender: Male -- code: "32" - name: Ashley Sanchez - spawn_loc: kingst_100 - gender: Male -- code: "33" - name: Margaret Lewis - spawn_loc: flindersst_180 - gender: Female -- code: "34" - name: Sandra Thompson - spawn_loc: swanstonst_80 - gender: Female -- code: "35" - name: Sandra King - spawn_loc: lonsdalest_150 - gender: Female -- code: "36" - name: Lisa Anderson - spawn_loc: lonsdalest_210 - gender: Female -- code: "37" - name: Kimberly Martin - spawn_loc: kingst_80 - gender: Female -- code: "38" - name: Susan Smith - spawn_loc: latrobest_190 - gender: Female -- code: "39" - name: Susan Martin - spawn_loc: collinsst_150 - gender: Female -- code: "40" - name: Linda Scott - spawn_loc: williamsst_30 - gender: Female -- code: "41" - name: Donald Miller - spawn_loc: elizabethst_80 - gender: Male -- code: "42" - name: Mark Hill - spawn_loc: collinsst_120 - gender: Male -- code: "43" - name: William Perez - spawn_loc: queenst_90 - gender: Male -- code: "44" - name: Donald Perez - spawn_loc: queenst_lonsdalest - gender: Male -- code: "45" - name: Lisa Rodriguez - spawn_loc: collinsst_100 - gender: Female -- code: "46" - name: James Adams - spawn_loc: latrobest_150 - gender: Male -- code: "47" - name: James Moore - spawn_loc: latrobest_130 - gender: Male -- code: "48" - name: Joseph Martin - spawn_loc: bourkest_150 - gender: Male -- code: "49" - name: Matthew Jones - spawn_loc: kingst_60 - gender: Male -- code: "50" - name: Michael Sanchez - spawn_loc: queenst_100 - gender: Male -- code: "51" - name: Donna Torres - spawn_loc: flindersst_150 - gender: Female -- code: "52" - name: Barbara Garcia - spawn_loc: swanstonst_50 - gender: Female -- code: "53" - name: Daniel Miller - spawn_loc: bourkest_110 - gender: Male -- code: "54" - name: Robert Young - spawn_loc: kingst_collinsst - gender: Male -- code: "55" - name: Donald Flores - spawn_loc: swanstonst_40 - gender: Male -- code: "56" - name: Charles Thomas - spawn_loc: flindersst_110 - gender: Male -- code: "57" - name: William Torres - spawn_loc: swanstonst_60 - gender: Male -- code: "58" - name: Barbara Gonzalez - spawn_loc: collinsst_190 - gender: Female -- code: "59" - name: Mary Smith - spawn_loc: bourkest_180 - gender: Female -- code: "60" - name: Michael John - spawn_loc: williamsst_110 - gender: Male diff --git a/blastmud_game/src/static_content/npc/melbs_dog.rs b/blastmud_game/src/static_content/npc/melbs_dog.rs deleted file mode 100644 index c6b7453..0000000 --- a/blastmud_game/src/static_content/npc/melbs_dog.rs +++ /dev/null @@ -1,38 +0,0 @@ -use super::{KillBonus, NPC}; -use crate::models::{consent::ConsentType, item::Pronouns}; -use crate::static_content::{possession_type::PossessionType, species::SpeciesType}; -use serde::Deserialize; -use serde_yaml::from_str as from_yaml_str; - -#[derive(Deserialize)] -struct MelbsDog { - code: String, - adjectives: String, - spawn_room: String, -} - -pub fn npc_list() -> Vec { - from_yaml_str::>(include_str!("melbs_dog.yaml")) - .unwrap() - .into_iter() - .map(|d| - NPC { - code: format!("melbs_dog_{}", &d.code), - name: format!("{} dog", &d.adjectives), - pronouns: Pronouns { is_proper: false, ..Pronouns::default_inanimate() }, - aggression: 12, - wander_zones: vec!("melbs".to_owned()), - description: "A malnourished looking dog. Its skeleton is visible through its thin and patchy fur. It smells terrible, and certainly doesn't look tame.".to_owned(), - aliases: vec!("dog".to_owned()), - spawn_location: format!("room/{}", d.spawn_room), - intrinsic_weapon: Some(PossessionType::Fangs), - species: SpeciesType::Dog, - kill_bonus: Some(KillBonus { - msg: "On your wristpad: Thank you for helping Melbs with animal control! Here's your fee.", - payment: 100, - }), - player_consents: vec!(ConsentType::Fight), - ..Default::default() - } - ).collect() -} diff --git a/blastmud_game/src/static_content/npc/melbs_dog.yaml b/blastmud_game/src/static_content/npc/melbs_dog.yaml deleted file mode 100644 index cbb715c..0000000 --- a/blastmud_game/src/static_content/npc/melbs_dog.yaml +++ /dev/null @@ -1,180 +0,0 @@ -- code: "1" - adjectives: smelly black - spawn_room: melbs_williamsst_80 -- code: "2" - adjectives: howling black - spawn_room: melbs_swanstonst_100 -- code: "3" - adjectives: smelly black - spawn_room: melbs_collinsst_160 -- code: "4" - adjectives: growling light brown - spawn_room: melbs_kingst_40 -- code: "5" - adjectives: ferocious white - spawn_room: melbs_swanstonst_110 -- code: "6" - adjectives: mangy grey - spawn_room: melbs_kingst_30 -- code: "7" - adjectives: reeking light brown - spawn_room: melbs_flindersst_210 -- code: "8" - adjectives: feral brown - spawn_room: melbs_elizabethst_40 -- code: "9" - adjectives: reeking grey - spawn_room: melbs_collinsst_190 -- code: "10" - adjectives: ferocious grey - spawn_room: melbs_kingst_60 -- code: "11" - adjectives: howling brown - spawn_room: melbs_collinsst_140 -- code: "12" - adjectives: feral black - spawn_room: melbs_flindersst_160 -- code: "13" - adjectives: smelly grey - spawn_room: melbs_queenst_80 -- code: "14" - adjectives: howling grey - spawn_room: melbs_kingst_70 -- code: "15" - adjectives: smelly grey - spawn_room: melbs_flindersst_110 -- code: "16" - adjectives: feral black - spawn_room: melbs_queenst_latrobest -- code: "17" - adjectives: howling grey - spawn_room: melbs_swanstonst_110 -- code: "18" - adjectives: mangy grey - spawn_room: melbs_swanstonst_80 -- code: "19" - adjectives: reeking light brown - spawn_room: melbs_latrobest_180 -- code: "20" - adjectives: smelly white - spawn_room: melbs_flindersst_130 -- code: "21" - adjectives: reeking grey - spawn_room: melbs_flindersst_180 -- code: "22" - adjectives: growling brown - spawn_room: melbs_williamsst_80 -- code: "23" - adjectives: howling black - spawn_room: melbs_lonsdalest_100 -- code: "24" - adjectives: growling grey - spawn_room: melbs_latrobest_140 -- code: "25" - adjectives: howling light brown - spawn_room: melbs_queenst_30 -- code: "26" - adjectives: howling black - spawn_room: melbs_latrobest_160 -- code: "27" - adjectives: howling grey - spawn_room: melbs_collinsst_170 -- code: "28" - adjectives: growling brown - spawn_room: melbs_elizabethst_latrobest -- code: "29" - adjectives: mangy brown - spawn_room: melbs_kingst_70 -- code: "30" - adjectives: growling black - spawn_room: melbs_swanstonst_120 -- code: "31" - adjectives: reeking light brown - spawn_room: melbs_latrobest_130 -- code: "32" - adjectives: howling white - spawn_room: melbs_bourkest_160 -- code: "33" - adjectives: growling black - spawn_room: melbs_elizabethst_50 -- code: "34" - adjectives: mangy black - spawn_room: melbs_swanstonst_110 -- code: "35" - adjectives: ferocious grey - spawn_room: melbs_collinsst_100 -- code: "36" - adjectives: mangy grey - spawn_room: melbs_flindersst_100 -- code: "37" - adjectives: growling brown - spawn_room: melbs_swanstonst_flindersst -- code: "38" - adjectives: mangy light brown - spawn_room: melbs_lonsdalest_200 -- code: "39" - adjectives: howling light brown - spawn_room: melbs_flindersst_210 -- code: "40" - adjectives: mangy light brown - spawn_room: melbs_queenst_flindersst -- code: "41" - adjectives: reeking white - spawn_room: melbs_collinsst_130 -- code: "42" - adjectives: growling light brown - spawn_room: melbs_lonsdalest_130 -- code: "43" - adjectives: reeking light brown - spawn_room: melbs_elizabethst_70 -- code: "44" - adjectives: mangy brown - spawn_room: melbs_swanstonst_30 -- code: "45" - adjectives: growling light brown - spawn_room: melbs_swanstonst_lonsdalest -- code: "46" - adjectives: smelly brown - spawn_room: melbs_queenst_lonsdalest -- code: "47" - adjectives: growling white - spawn_room: melbs_elizabethst_bourkest -- code: "48" - adjectives: feral brown - spawn_room: melbs_collinsst_140 -- code: "49" - adjectives: ferocious black - spawn_room: melbs_lonsdalest_150 -- code: "50" - adjectives: mangy grey - spawn_room: melbs_kingst_collinsst -- code: "51" - adjectives: ferocious brown - spawn_room: melbs_kingst_120 -- code: "52" - adjectives: growling white - spawn_room: melbs_elizabethst_10 -- code: "53" - adjectives: ferocious white - spawn_room: melbs_lonsdalest_190 -- code: "54" - adjectives: smelly grey - spawn_room: melbs_kingst_collinsst -- code: "55" - adjectives: reeking light brown - spawn_room: melbs_elizabethst_90 -- code: "56" - adjectives: reeking grey - spawn_room: melbs_swanstonst_20 -- code: "57" - adjectives: feral brown - spawn_room: melbs_flindersst_180 -- code: "58" - adjectives: reeking brown - spawn_room: melbs_bourkest_130 -- code: "59" - adjectives: mangy light brown - spawn_room: melbs_queenst_50 -- code: "60" - adjectives: growling white - spawn_room: melbs_kingst_110 diff --git a/blastmud_game/src/static_content/npc/melbs_npcs.rs b/blastmud_game/src/static_content/npc/melbs_npcs.rs new file mode 100644 index 0000000..c3a6a53 --- /dev/null +++ b/blastmud_game/src/static_content/npc/melbs_npcs.rs @@ -0,0 +1,138 @@ +use super::{NPCPronounType, NPCSayInfo, NPCSayType, NPCSpawnPossession, NPC}; +use crate::{ + models::{ + consent::ConsentType, + item::{LocationActionType, Pronouns, SkillType}, + }, + static_content::{ + npc::{npc_pronoun_type_to_pronouns, KillBonus}, + possession_type::PossessionType, + species::SpeciesType, + }, +}; +use serde::Deserialize; +use serde_yaml::from_str as from_yaml_str; + +#[derive(Deserialize)] +enum MelbsNPC { + Citizen { + code: String, + name: String, + spawn_loc: String, + gender: NPCPronounType, + }, + Dog { + code: String, + adjectives: String, + spawn_room: String, + }, + MutantRat { + code: String, + adjectives: String, + spawn_room: String, + }, +} + +pub fn npc_list() -> Vec { + use NPCSayType::FromFixedList; + let melbs_citizen_stdsay = NPCSayInfo { + say_code: "babble", + frequency_secs: 120, + talk_type: FromFixedList(vec![ + "I'm so sick of being cloned.", + "I hope I don't die again today.", + "I wish the so-called king would do something about the damned zombies everywhere.", + "I heard in the olden days before the empire babies grew up naturally instead of being taken away to the body factory.", + "I know people hated the empire, but I kind of wish it was still intact - it was a lot better than what we have now.", + "I wish there wasn't so much radiation outside of Melbs CBD.", + "I heard about a guy who went to a special place somewhere around here, and there was a machine that enhanced his wristpad and gave him basically superpowers.", + "The damn vampire movement... they are all so sneaky, and I never know when they are going to come for my blood.", + ]) + }; + + from_yaml_str::>(include_str!("melbs_npcs.yaml")) + .unwrap() + .into_iter() + .map(|c| + match c { + MelbsNPC::Citizen { + code, name, gender, spawn_loc + } => + NPC { + code: format!("melbs_citizen_{}", &code), + name, + pronouns: npc_pronoun_type_to_pronouns(&gender), + description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harsh reality of post-apocalyptic life".to_owned(), + spawn_location: format!("room/melbs_{}", &spawn_loc), + spawn_possessions: vec![ + NPCSpawnPossession { + what: PossessionType::Shirt, + action_type: LocationActionType::Worn, + wear_layer: 0, + }, + NPCSpawnPossession { + what: PossessionType::Jeans, + action_type: LocationActionType::Worn, + wear_layer: 0, + }, + ], + message_handler: None, + wander_zones: vec!("melbs".to_owned()), + says: vec!(melbs_citizen_stdsay.clone()), + player_consents: vec!(ConsentType::Medicine, ConsentType::Share), + ..Default::default() + }, + MelbsNPC::Dog { code, adjectives, spawn_room } => + NPC { + code: format!("melbs_dog_{}", &code), + name: format!("{} dog", &adjectives), + pronouns: Pronouns { is_proper: false, ..Pronouns::default_inanimate() }, + aggression: 12, + wander_zones: vec!("melbs".to_owned()), + description: "A malnourished looking dog. Its skeleton is visible through its thin and patchy fur. It smells terrible, and certainly doesn't look tame.".to_owned(), + aliases: vec!("dog".to_owned()), + spawn_location: format!("room/{}", spawn_room), + intrinsic_weapon: Some(PossessionType::Fangs), + species: SpeciesType::Dog, + kill_bonus: Some(KillBonus { + msg: "On your wristpad: Thank you for helping Melbs with animal control! Here's your fee.", + payment: 100, + }), + player_consents: vec!(ConsentType::Fight), + ..Default::default() + }, + MelbsNPC::MutantRat { code, adjectives, spawn_room } => + NPC { + code: format!("melbs_rat_{}", &code), + name: format!("{} mutant rat", &adjectives), + pronouns: Pronouns { is_proper: false, ..Pronouns::default_inanimate() }, + aggression: 21, + wander_zones: vec!("kings_office".to_owned()), + description: "Something that resembles a brown rat - except it is about 2 metres tall! Huge teeth poke through its lips, and its feet betray brutal looking claws. Gigantic whiskers poke from its face".to_owned(), + aliases: vec!("rat".to_owned()), + spawn_location: format!("room/{}", spawn_room), + intrinsic_weapon: Some(PossessionType::Fangs), + species: SpeciesType::Rat, + aggro_pc_only: true, + kill_bonus: Some(KillBonus { + msg: "On your wristpad: Thank you for helping Melbs with animal control! Here's your fee.", + payment: 150, + }), + total_skills: SkillType::values() + .into_iter() + .map(|sk| { + ( + sk.clone(), + match sk { + SkillType::Dodge => 15.0, + SkillType::Fists => 15.0, + _ => 8.0 + } + ) + }).collect(), + player_consents: vec!(ConsentType::Fight), + ..Default::default() + }, + } + ).collect() +} diff --git a/blastmud_game/src/static_content/npc/melbs_npcs.yaml b/blastmud_game/src/static_content/npc/melbs_npcs.yaml new file mode 100644 index 0000000..65f10e7 --- /dev/null +++ b/blastmud_game/src/static_content/npc/melbs_npcs.yaml @@ -0,0 +1,548 @@ +- !Citizen + code: "1" + name: Matthew Thomas + spawn_loc: kingst_latrobest + gender: Male +- !Citizen + code: "2" + name: Matthew Perez + spawn_loc: kingst_20 + gender: Male +- !Citizen + code: "3" + name: Kimberly Jackson + spawn_loc: kingst_40 + gender: Female +- !Citizen + code: "4" + name: Michael Sanchez + spawn_loc: kingst_50 + gender: Male +- !Citizen + code: "5" + name: Jessica Davis + spawn_loc: kingst_bourkest + gender: Female +- !Citizen + code: "6" + name: Robert Davis + spawn_loc: kingst_70 + gender: Male +- !Citizen + code: "7" + name: Paul Lewis + spawn_loc: kingst_90 + gender: Male +- !Citizen + code: "8" + name: Andrew Moore + spawn_loc: kingst_collinsst + gender: Male +- !Citizen + code: "9" + name: Betty Thomas + spawn_loc: kingst_100 + gender: Female +- !Citizen + code: "10" + name: Mary Robinson + spawn_loc: kingst_110 + gender: Female +- !Citizen + code: "11" + name: Lisa Lopez + spawn_loc: kingst_flinderst + gender: Female +- !Citizen + code: "12" + name: Kimberly Martinez + spawn_loc: flindersst_200 + gender: Female +- !Citizen + code: "13" + name: Anthony Nguyen + spawn_loc: flindersst_190 + gender: Male +- !Citizen + code: "14" + name: Joshua Green + spawn_loc: flindersst_180 + gender: Male +- !Citizen + code: "15" + name: Emily Wright + spawn_loc: flindersst_170 + gender: Female +- !Citizen + code: "16" + name: Ashley Thomas + spawn_loc: lonsdalest_130 + gender: Male +- !Citizen + code: "17" + name: Jessica Miller + spawn_loc: kingst_80 + gender: Female +- !Citizen + code: "18" + name: Anthony Lopez + spawn_loc: lonsdalest_140 + gender: Male +- !Citizen + code: "19" + name: John Lopez + spawn_loc: elizabethst_lonsdalest + gender: Male +- !Citizen + code: "20" + name: Thomas Garcia + spawn_loc: williamsst_120 + gender: Male +- !Citizen + code: "21" + name: Donna Thompson + spawn_loc: elizabethst_60 + gender: Female +- !Citizen + code: "22" + name: Matthew Davis + spawn_loc: williamsst_100 + gender: Male +- !Citizen + code: "23" + name: Steven Jones + spawn_loc: swanstonst_120 + gender: Male +- !Citizen + code: "24" + name: Linda Smith + spawn_loc: swanstonst_lonsdalest + gender: Male +- !Citizen + code: "25" + name: Karen Rodriguez + spawn_loc: bourkest_180 + gender: Female +- !Citizen + code: "26" + name: Paul Scott + spawn_loc: swanstonst_70 + gender: Male +- !Citizen + code: "27" + name: Ashley Thomas + spawn_loc: lonsdalest_130 + gender: Male +- !Citizen + code: "28" + name: Sandra Scott + spawn_loc: elizabethst_30 + gender: Female +- !Citizen + code: "29" + name: Michael Rodriguez + spawn_loc: swanstonst_70 + gender: Male +- !Citizen + code: "30" + name: Donald Miller + spawn_loc: elizabethst_30 + gender: Male +- !Citizen + code: "31" + name: Charles Moore + spawn_loc: lonsdalest_160 + gender: Male +- !Citizen + code: "32" + name: Ashley Sanchez + spawn_loc: kingst_100 + gender: Male +- !Citizen + code: "33" + name: Margaret Lewis + spawn_loc: flindersst_180 + gender: Female +- !Citizen + code: "34" + name: Sandra Thompson + spawn_loc: swanstonst_80 + gender: Female +- !Citizen + code: "35" + name: Sandra King + spawn_loc: lonsdalest_150 + gender: Female +- !Citizen + code: "36" + name: Lisa Anderson + spawn_loc: lonsdalest_210 + gender: Female +- !Citizen + code: "37" + name: Kimberly Martin + spawn_loc: kingst_80 + gender: Female +- !Citizen + code: "38" + name: Susan Smith + spawn_loc: latrobest_190 + gender: Female +- !Citizen + code: "39" + name: Susan Martin + spawn_loc: collinsst_150 + gender: Female +- !Citizen + code: "40" + name: Linda Scott + spawn_loc: williamsst_30 + gender: Female +- !Citizen + code: "41" + name: Donald Miller + spawn_loc: elizabethst_80 + gender: Male +- !Citizen + code: "42" + name: Mark Hill + spawn_loc: collinsst_120 + gender: Male +- !Citizen + code: "43" + name: William Perez + spawn_loc: queenst_90 + gender: Male +- !Citizen + code: "44" + name: Donald Perez + spawn_loc: queenst_lonsdalest + gender: Male +- !Citizen + code: "45" + name: Lisa Rodriguez + spawn_loc: collinsst_100 + gender: Female +- !Citizen + code: "46" + name: James Adams + spawn_loc: latrobest_150 + gender: Male +- !Citizen + code: "47" + name: James Moore + spawn_loc: latrobest_130 + gender: Male +- !Citizen + code: "48" + name: Joseph Martin + spawn_loc: bourkest_150 + gender: Male +- !Citizen + code: "49" + name: Matthew Jones + spawn_loc: kingst_60 + gender: Male +- !Citizen + code: "50" + name: Michael Sanchez + spawn_loc: queenst_100 + gender: Male +- !Citizen + code: "51" + name: Donna Torres + spawn_loc: flindersst_150 + gender: Female +- !Citizen + code: "52" + name: Barbara Garcia + spawn_loc: swanstonst_50 + gender: Female +- !Citizen + code: "53" + name: Daniel Miller + spawn_loc: bourkest_110 + gender: Male +- !Citizen + code: "54" + name: Robert Young + spawn_loc: kingst_collinsst + gender: Male +- !Citizen + code: "55" + name: Donald Flores + spawn_loc: swanstonst_40 + gender: Male +- !Citizen + code: "56" + name: Charles Thomas + spawn_loc: flindersst_110 + gender: Male +- !Citizen + code: "57" + name: William Torres + spawn_loc: swanstonst_60 + gender: Male +- !Citizen + code: "58" + name: Barbara Gonzalez + spawn_loc: collinsst_190 + gender: Female +- !Citizen + code: "59" + name: Mary Smith + spawn_loc: bourkest_180 + gender: Female +- !Citizen + code: "60" + name: Michael John + spawn_loc: williamsst_110 + gender: Male +- !Dog + code: "1" + adjectives: smelly black + spawn_room: melbs_williamsst_80 +- !Dog + code: "2" + adjectives: howling black + spawn_room: melbs_swanstonst_100 +- !Dog + code: "3" + adjectives: smelly black + spawn_room: melbs_collinsst_160 +- !Dog + code: "4" + adjectives: growling light brown + spawn_room: melbs_kingst_40 +- !Dog + code: "5" + adjectives: ferocious white + spawn_room: melbs_swanstonst_110 +- !Dog + code: "6" + adjectives: mangy grey + spawn_room: melbs_kingst_30 +- !Dog + code: "7" + adjectives: reeking light brown + spawn_room: melbs_flindersst_210 +- !Dog + code: "8" + adjectives: feral brown + spawn_room: melbs_elizabethst_40 +- !Dog + code: "9" + adjectives: reeking grey + spawn_room: melbs_collinsst_190 +- !Dog + code: "10" + adjectives: ferocious grey + spawn_room: melbs_kingst_60 +- !Dog + code: "11" + adjectives: howling brown + spawn_room: melbs_collinsst_140 +- !Dog + code: "12" + adjectives: feral black + spawn_room: melbs_flindersst_160 +- !Dog + code: "13" + adjectives: smelly grey + spawn_room: melbs_queenst_80 +- !Dog + code: "14" + adjectives: howling grey + spawn_room: melbs_kingst_70 +- !Dog + code: "15" + adjectives: smelly grey + spawn_room: melbs_flindersst_110 +- !Dog + code: "16" + adjectives: feral black + spawn_room: melbs_queenst_latrobest +- !Dog + code: "17" + adjectives: howling grey + spawn_room: melbs_swanstonst_110 +- !Dog + code: "18" + adjectives: mangy grey + spawn_room: melbs_swanstonst_80 +- !Dog + code: "19" + adjectives: reeking light brown + spawn_room: melbs_latrobest_180 +- !Dog + code: "20" + adjectives: smelly white + spawn_room: melbs_flindersst_130 +- !Dog + code: "21" + adjectives: reeking grey + spawn_room: melbs_flindersst_180 +- !Dog + code: "22" + adjectives: growling brown + spawn_room: melbs_williamsst_80 +- !Dog + code: "23" + adjectives: howling black + spawn_room: melbs_lonsdalest_100 +- !Dog + code: "24" + adjectives: growling grey + spawn_room: melbs_latrobest_140 +- !Dog + code: "25" + adjectives: howling light brown + spawn_room: melbs_queenst_30 +- !Dog + code: "26" + adjectives: howling black + spawn_room: melbs_latrobest_160 +- !Dog + code: "27" + adjectives: howling grey + spawn_room: melbs_collinsst_170 +- !Dog + code: "28" + adjectives: growling brown + spawn_room: melbs_elizabethst_latrobest +- !Dog + code: "29" + adjectives: mangy brown + spawn_room: melbs_kingst_70 +- !Dog + code: "30" + adjectives: growling black + spawn_room: melbs_swanstonst_120 +- !Dog + code: "31" + adjectives: reeking light brown + spawn_room: melbs_latrobest_130 +- !Dog + code: "32" + adjectives: howling white + spawn_room: melbs_bourkest_160 +- !Dog + code: "33" + adjectives: growling black + spawn_room: melbs_elizabethst_50 +- !Dog + code: "34" + adjectives: mangy black + spawn_room: melbs_swanstonst_110 +- !Dog + code: "35" + adjectives: ferocious grey + spawn_room: melbs_collinsst_100 +- !Dog + code: "36" + adjectives: mangy grey + spawn_room: melbs_flindersst_100 +- !Dog + code: "37" + adjectives: growling brown + spawn_room: melbs_swanstonst_flindersst +- !Dog + code: "38" + adjectives: mangy light brown + spawn_room: melbs_lonsdalest_200 +- !Dog + code: "39" + adjectives: howling light brown + spawn_room: melbs_flindersst_210 +- !Dog + code: "40" + adjectives: mangy light brown + spawn_room: melbs_queenst_flindersst +- !Dog + code: "41" + adjectives: reeking white + spawn_room: melbs_collinsst_130 +- !Dog + code: "42" + adjectives: growling light brown + spawn_room: melbs_lonsdalest_130 +- !Dog + code: "43" + adjectives: reeking light brown + spawn_room: melbs_elizabethst_70 +- !Dog + code: "44" + adjectives: mangy brown + spawn_room: melbs_swanstonst_30 +- !Dog + code: "45" + adjectives: growling light brown + spawn_room: melbs_swanstonst_lonsdalest +- !Dog + code: "46" + adjectives: smelly brown + spawn_room: melbs_queenst_lonsdalest +- !Dog + code: "47" + adjectives: growling white + spawn_room: melbs_elizabethst_bourkest +- !Dog + code: "48" + adjectives: feral brown + spawn_room: melbs_collinsst_140 +- !Dog + code: "49" + adjectives: ferocious black + spawn_room: melbs_lonsdalest_150 +- !Dog + code: "50" + adjectives: mangy grey + spawn_room: melbs_kingst_collinsst +- !Dog + code: "51" + adjectives: ferocious brown + spawn_room: melbs_kingst_120 +- !Dog + code: "52" + adjectives: growling white + spawn_room: melbs_elizabethst_10 +- !Dog + code: "53" + adjectives: ferocious white + spawn_room: melbs_lonsdalest_190 +- !Dog + code: "54" + adjectives: smelly grey + spawn_room: melbs_kingst_collinsst +- !Dog + code: "55" + adjectives: reeking light brown + spawn_room: melbs_elizabethst_90 +- !Dog + code: "56" + adjectives: reeking grey + spawn_room: melbs_swanstonst_20 +- !Dog + code: "57" + adjectives: feral brown + spawn_room: melbs_flindersst_180 +- !Dog + code: "58" + adjectives: reeking brown + spawn_room: melbs_bourkest_130 +- !Dog + code: "59" + adjectives: mangy light brown + spawn_room: melbs_queenst_50 +- !Dog + code: "60" + adjectives: growling white + spawn_room: melbs_kingst_110 +- !MutantRat + code: "1" + adjectives: giant scary + spawn_room: kings_office_hallway +- !MutantRat + code: "2" + adjectives: huge frightening + spawn_room: kings_office_hallway diff --git a/blastmud_game/src/static_content/possession_type.rs b/blastmud_game/src/static_content/possession_type.rs index 186ec8f..76749a5 100644 --- a/blastmud_game/src/static_content/possession_type.rs +++ b/blastmud_game/src/static_content/possession_type.rs @@ -5,7 +5,7 @@ use crate::{ models::{ consent::ConsentType, effect::{EffectSet, EffectType}, - item::{Item, ItemFlag, LiquidType, Pronouns, SkillType}, + item::{Item, ItemFlag, ItemStaticSpecialData, LiquidType, Pronouns, SkillType}, }, regular_tasks::queued_command::QueuedCommandContext, static_content::{room::Direction, species::BodyPart}, @@ -28,6 +28,7 @@ mod fangs; mod food; pub mod head_armour; mod junk; +mod keys; pub mod lights; pub mod lock; pub mod lower_armour; @@ -344,6 +345,7 @@ pub struct PossessionData { pub default_flags: Vec, pub eat_data: Option, pub sit_data: Option, + pub static_special_data: Option, } impl Default for PossessionData { @@ -371,6 +373,7 @@ impl Default for PossessionData { default_flags: vec![], eat_data: None, sit_data: None, + static_special_data: None, } } } @@ -431,6 +434,8 @@ pub enum PossessionType { // Security Basiclock, Scanlock, + // Keys + KingsOfficeKey, // Food GrilledSteak, GreasyBurger, @@ -473,6 +478,7 @@ impl Into for PossessionType { .as_ref() .map(|cd| cd.max_charges) .unwrap_or(0), + static_special_data: possession_dat.static_special_data.clone(), ..Default::default() } } @@ -541,6 +547,7 @@ pub fn possession_data() -> &'static BTreeMap &'static Vec<(PossessionType, PossessionData)> { + static D: OnceCell> = OnceCell::new(); + &D.get_or_init(|| vec![(PossessionType::KingsOfficeKey, PossessionData { + display: "King's Office key", + details: "A sturdy silver metal key attached by a metal ring to a pink plastic key tag reading: Kings Office Key", + aliases: vec!["key", "kings office key"], + weight: 50, + static_special_data: Some( + ItemStaticSpecialData::KeyData { + pin_sequence: "KINGSOFF".to_owned() + } + ), + ..Default::default() + })]) +} diff --git a/blastmud_game/src/static_content/possession_type/lock.rs b/blastmud_game/src/static_content/possession_type/lock.rs index efebd5c..118525f 100644 --- a/blastmud_game/src/static_content/possession_type/lock.rs +++ b/blastmud_game/src/static_content/possession_type/lock.rs @@ -1,7 +1,7 @@ use super::{LockcheckHandler, PossessionData, PossessionType}; use crate::{ message_handler::user_commands::{user_error, UResult, VerbContext}, - models::item::{Item, LocationActionType}, + models::item::{Item, ItemStaticSpecialData, LocationActionType}, regular_tasks::queued_command::QueuedCommandContext, services::{ capacity::{check_item_capacity, CapacityLevel}, @@ -15,8 +15,15 @@ use once_cell::sync::OnceCell; struct BasicLockLockcheck; #[async_trait] impl LockcheckHandler for BasicLockLockcheck { - async fn cmd(&self, _ctx: &mut QueuedCommandContext, _what: &Item) -> UResult<()> { - // TODO implement actual check for key once we have them. + async fn cmd(&self, ctx: &mut QueuedCommandContext, what: &Item) -> UResult<()> { + match what.static_special_data.as_ref() { + Some(ItemStaticSpecialData::LockData { pin_sequence }) => { + if ctx.trans.has_key_matching(&ctx.item, &pin_sequence).await? { + return Ok(()); + } + } + _ => {} + } user_error( "You search your possessions for a key that fits the door, but end up just rattling the handle uselessly.".to_owned())?; Ok(()) diff --git a/blastmud_game/src/static_content/room.rs b/blastmud_game/src/static_content/room.rs index 572430f..ab8b1b2 100644 --- a/blastmud_game/src/static_content/room.rs +++ b/blastmud_game/src/static_content/room.rs @@ -5,7 +5,7 @@ use super::{ #[double] use crate::db::DBTrans; use crate::{ - message_handler::user_commands::UResult, + message_handler::user_commands::{CommandHandlingError, UResult}, models::{ item::{DoorState, Item, ItemFlag}, journal::JournalType, @@ -80,6 +80,11 @@ pub fn zone_details() -> &'static BTreeMap<&'static str, Zone> { display: "Melbs Sewers", outdoors: false, }, + Zone { + code: "kings_office", + display: "King's Office", + outdoors: false, + }, ] .into_iter() .map(|x| (x.code, x)) @@ -158,7 +163,7 @@ pub trait ExitBlocker { pub enum ExitType { Free, // Anyone can just walk it (subject to any door logic). - Blocked(&'static (dyn ExitBlocker + Sync + Send)), // Custom code about who can pass. + Blocked(Box), // Custom code about who can pass. } #[allow(dead_code)] @@ -297,12 +302,56 @@ impl Default for Exit { } } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[serde(default)] +pub struct ScanBlockerInfo { + need_scancode: ScanCode, + block_message: String, +} + +impl Default for ScanBlockerInfo { + fn default() -> Self { + Self { + need_scancode: ScanCode::SewerAccess, + block_message: "You aren't allowed in there".to_owned(), + } + } +} + +#[async_trait] +impl ExitBlocker for ScanBlockerInfo { + async fn attempt_exit(&self, ctx: &mut QueuedCommandContext, _exit: &Exit) -> UResult { + if ctx.item.item_type != "player" { + return Ok(false); + } + let user = ctx + .trans + .find_by_username(&ctx.item.item_code) + .await? + .ok_or_else(|| CommandHandlingError::UserError("No user exists".to_owned()))?; + if !user.scan_codes.contains(&self.need_scancode) { + if let Some((sess, _sess_dat)) = ctx + .trans + .find_session_for_player(&ctx.item.item_code) + .await? + { + ctx.trans + .queue_for_session(&sess, Some(&format!("{}\n", &self.block_message))) + .await?; + } + return Ok(false); + } + Ok(true) + } +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] #[serde(default)] pub struct SimpleExit { pub direction: Direction, pub target: ExitTarget, pub exit_climb: Option, + pub needs_scan: Option, } impl Default for SimpleExit { @@ -311,6 +360,7 @@ impl Default for SimpleExit { direction: Direction::NORTH, target: ExitTarget::UseGPS, exit_climb: None, + needs_scan: None, } } } @@ -320,7 +370,13 @@ impl Into for SimpleExit { Exit { direction: self.direction, target: self.target, - exit_type: ExitType::Free, + exit_type: match self.needs_scan { + None => ExitType::Free, + Some(s) => ExitType::Blocked(Box::new(ScanBlockerInfo { + block_message: parse_ansi_markup(&s.block_message).unwrap(), + ..s + })), + }, exit_climb: self.exit_climb, } } @@ -341,6 +397,11 @@ pub struct RoomStock { pub poverty_discount: bool, } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum ScanCode { + SewerAccess, +} + impl Default for RoomStock { fn default() -> Self { Self { @@ -401,6 +462,7 @@ pub struct Room { pub has_power: bool, pub door_states: Option>, pub wristpad_hack_allowed: Option, + pub scan_code: Option, pub journal: Option, pub enter_trigger: Option<&'static (dyn RoomEnterTrigger + Sync + Send)>, pub scavtable: ScavtableType, @@ -426,6 +488,7 @@ impl Default for Room { has_power: false, door_states: None, wristpad_hack_allowed: None, + scan_code: None, journal: None, enter_trigger: None, scavtable: ScavtableType::Nothing, @@ -456,6 +519,7 @@ pub struct SimpleRoom { pub has_power: bool, pub door_states: Option>, pub wristpad_hack_allowed: Option, + pub scan_code: Option, pub journal: Option, pub scavtable: ScavtableType, pub extra: T, @@ -490,6 +554,7 @@ impl Into for SimpleRoom { has_power: self.has_power, door_states: self.door_states, wristpad_hack_allowed: self.wristpad_hack_allowed, + scan_code: self.scan_code, journal: self.journal, enter_trigger: None, scavtable: self.scavtable, @@ -517,6 +582,7 @@ impl<'a, T: Default> Default for SimpleRoom { has_power: false, door_states: None, wristpad_hack_allowed: None, + scan_code: None, journal: None, scavtable: ScavtableType::Nothing, extra: Default::default(), diff --git a/blastmud_game/src/static_content/room/melbs.rs b/blastmud_game/src/static_content/room/melbs.rs index dde5cec..88d8ad8 100644 --- a/blastmud_game/src/static_content/room/melbs.rs +++ b/blastmud_game/src/static_content/room/melbs.rs @@ -1,8 +1,14 @@ use super::{Room, SimpleRoom}; use crate::{ - models::item::Scavtype, - static_content::{possession_type::PossessionType, scavtable::Scavinfo}, + models::item::{ItemStaticSpecialData, LiquidType, LocationActionType, Scavtype}, + static_content::{ + fixed_item::FixedItem, + possession_type::{possession_data, LiquidContainerData, PossessionData, PossessionType}, + room::Direction, + scavtable::Scavinfo, + }, }; +use ansi::ansi; use serde_yaml::from_str as from_yaml_str; pub fn street_scavtable() -> Vec { @@ -15,6 +21,86 @@ pub fn street_scavtable() -> Vec { }] } +pub fn fixed_items() -> Vec { + vec![ + FixedItem { + code: "melbs_king_st_spring_fed_fountain".to_owned(), + name: "spring fed fountain".to_owned(), + description: ansi!("A stainless steel fountain, clearly old, but in surprisingly good \ + condition. A discoloured bronze plaque attached to it proudly declares \ + that it is fed by a natural spring underneath it. It was designed so that \ + unused water runs off it into a dog bowl - presumably in a time long past when \ + dogs were friendly companions and not the menace they are today. It smells \ + faintly of iron. [Try drink from fountain or, if you have a suitable \ + container, fill container from fountain].").to_owned(), + location: "room/melbs_kingst_40".to_owned(), + proper_noun: false, + aliases: vec!["fountain".to_owned()], + ..Default::default() + }, + FixedItem { + code: "kings_office_basin".to_owned(), + name: "basin".to_owned(), + description: ansi!("A white porcelain basin, with chrome mixer taps in the centre, that you \ + can imagine was once pristine and very fancy, but is now stained from \ + years of grime. [Try drink from basin or, if you have a suitable \ + container, fill container from basin].").to_owned(), + location: "room/kings_office_bathroom".to_owned(), + proper_noun: false, + aliases: vec!["sink".to_owned()], + ..Default::default() + }, + FixedItem { + code: "kings_office_hallway_door_lock".to_owned(), + name: "basic keyed lock".to_owned(), + description: "A basic lock that looks like it needs a key to open".to_owned(), + location: "room/kings_office_hallway".to_owned(), + proper_noun: false, + aliases: vec!["lock".to_owned()], + action_type: LocationActionType::InstalledOnDoorAsLock(Direction::SOUTH), + static_special_data: Some(ItemStaticSpecialData::LockData { + pin_sequence: "KINGSOFF".to_owned() + }), + ..Default::default() + } + ] +} + +pub fn fixed_item_properties() -> Vec<(&'static str, PossessionData)> { + let lock_tmpl = possession_data().get(&PossessionType::Basiclock).unwrap(); + vec![ + ( + "melbs_king_st_spring_fed_fountain", + PossessionData { + liquid_container_data: Some(LiquidContainerData { + capacity: 5000000, // mL + allowed_contents: Some(vec![LiquidType::Water]), + ..Default::default() + }), + ..Default::default() + }, + ), + ( + "kings_office_basin", + PossessionData { + liquid_container_data: Some(LiquidContainerData { + capacity: 1100000, // mL + allowed_contents: Some(vec![LiquidType::Water]), + ..Default::default() + }), + ..Default::default() + }, + ), + ( + "kings_office_hallway_door_lock", + PossessionData { + lockcheck_handler: lock_tmpl.lockcheck_handler, + ..Default::default() + }, + ), + ] +} + pub fn room_list() -> Vec { from_yaml_str::>>(include_str!("melbs.yaml")) .unwrap() diff --git a/blastmud_game/src/static_content/room/melbs.yaml b/blastmud_game/src/static_content/room/melbs.yaml index 30483a8..0139842 100644 --- a/blastmud_game/src/static_content/room/melbs.yaml +++ b/blastmud_game/src/static_content/room/melbs.yaml @@ -12,6 +12,9 @@ - direction: east - direction: down target: !Custom room/melbs_sewers_1a + needs_scan: + need_scancode: !SewerAccess + block_message: "You try to open the sewer grate, but a mechanical voice built into the lock on it intones: For your safety, only authorised people are allowed into the sewers. Officially authorised cleaners should refer to the corkboard in the cleaners room in the Kings Office for further instructions." should_caption: false scavtable: CityStreet - zone: melbs @@ -182,6 +185,9 @@ - direction: east - direction: down target: !Custom room/melbs_sewers_1i + needs_scan: + need_scancode: !SewerAccess + block_message: "You try to open the sewer grate, but a mechanical voice built into the lock on it intones: For your safety, only authorised people are allowed into the sewers. Officially authorised cleaners should refer to the corkboard in the cleaners room in the Kings Office for further instructions." should_caption: false scavtable: CityStreet - zone: melbs @@ -306,6 +312,9 @@ - direction: east - direction: down target: !Custom room/melbs_sewers_2o + needs_scan: + need_scancode: !SewerAccess + block_message: "You try to open the sewer grate, but a mechanical voice built into the lock on it intones: For your safety, only authorised people are allowed into the sewers. Officially authorised cleaners should refer to the corkboard in the cleaners room in the Kings Office for further instructions." should_caption: false scavtable: CityStreet - zone: melbs @@ -478,6 +487,9 @@ - direction: east - direction: down target: !Custom room/melbs_sewers_9q + needs_scan: + need_scancode: !SewerAccess + block_message: "You try to open the sewer grate, but a mechanical voice built into the lock on it intones: For your safety, only authorised people are allowed into the sewers. Officially authorised cleaners should refer to the corkboard in the cleaners room in the Kings Office for further instructions." should_caption: false scavtable: CityStreet - zone: melbs @@ -593,6 +605,9 @@ - direction: north - direction: down target: !Custom room/melbs_sewers_17q + needs_scan: + need_scancode: !SewerAccess + block_message: "You try to open the sewer grate, but a mechanical voice built into the lock on it intones: For your safety, only authorised people are allowed into the sewers. Officially authorised cleaners should refer to the corkboard in the cleaners room in the Kings Office for further instructions." should_caption: false scavtable: CityStreet - zone: melbs @@ -609,6 +624,9 @@ - direction: west - direction: down target: !Custom room/melbs_sewers_17a + needs_scan: + need_scancode: !SewerAccess + block_message: "You try to open the sewer grate, but a mechanical voice built into the lock on it intones: For your safety, only authorised people are allowed into the sewers. Officially authorised cleaners should refer to the corkboard in the cleaners room in the Kings Office for further instructions." should_caption: false scavtable: CityStreet - zone: melbs @@ -725,6 +743,9 @@ - direction: west - direction: down target: !Custom room/melbs_sewers_17i + needs_scan: + need_scancode: !SewerAccess + block_message: "You try to open the sewer grate, but a mechanical voice built into the lock on it intones: For your safety, only authorised people are allowed into the sewers. Officially authorised cleaners should refer to the corkboard in the cleaners room in the Kings Office for further instructions." should_caption: false scavtable: CityStreet - zone: melbs @@ -939,6 +960,9 @@ - direction: east - direction: down target: !Custom room/melbs_sewers_9a + needs_scan: + need_scancode: !SewerAccess + block_message: "You try to open the sewer grate, but a mechanical voice built into the lock on it intones: For your safety, only authorised people are allowed into the sewers. Officially authorised cleaners should refer to the corkboard in the cleaners room in the Kings Office for further instructions." should_caption: false scavtable: CityStreet - zone: melbs @@ -1458,23 +1482,105 @@ - direction: north - direction: south - direction: east + target: !Custom room/kings_office_reception should_caption: false scavtable: CityStreet -- zone: melbs - code: kings_office - name: Kings Office - short: KO + secondary_zones: + - zone: kings_office + short: EX + grid_coords: + x: -1 + y: 0 + z: 0 + caption: Exit +- zone: kings_office + code: kings_office_reception + name: King's Office - Reception + short: RC + secondary_zones: + - zone: melbs + short: KO + grid_coords: + x: 6 + y: 5 + z: 0 + caption: King's Office grid_coords: - x: 6 - y: 5 + x: 0 + y: 0 z: 0 description: A dilapidated office that clearly was once a grand governor's office under the empire. It is now barely maintained. The self-styled king slouches behind a desk in the corner, shuffling paperwork around his desk. [Use list to see contracts for sale here] exits: - direction: west + target: !Custom room/melbs_williamsst_80 + - direction: north stock_list: - possession_type: NewCorpLicence list_price: 5000 poverty_discount: false +- zone: kings_office + code: kings_office_bathroom + name: Public Bathroom + short: BR + grid_coords: + x: 0 + y: -1 + z: 0 + description: A once luxurious unisex bathroom, now greatly degraded through the action of time. Despite the dusty floor and crumbling tiles, the room still retains some of the wonder it once held. Bathroom stalls line one side of the bathroom, while a series of large white porcelain basins, now stained, provide for hand washing. Near the corner of the room, a door labelled "Danger! Staff Only" leads north + exits: + - direction: south + - direction: north +- zone: kings_office + code: kings_office_hallway + name: Rat Infested Hallway + should_caption: false + short: == + grid_coords: + x: 0 + y: -2 + z: 0 + description: A hallway showing signs of damage by rodents... except the bite marks in everything are huge + exits: + - direction: south + - direction: east + door_states: + south: + open: false + description: a solid door with a keyhole in it +- zone: kings_office + code: kings_office_hallway_2 + name: Rat Infested Hallway + should_caption: false + short: == + grid_coords: + x: 1 + y: -2 + z: 0 + description: A hallway showing signs of damage by rodents... except the bite marks in everything are huge + exits: + - direction: east + - direction: west + door_states: + south: + open: false + description: a solid door with a keyhole in it +- zone: kings_office + code: kings_office_cleaners_office + name: Cleaner's Office + should_caption: true + short: CO + grid_coords: + x: 2 + y: -2 + z: 0 + description: "A relatively small room full of metal sinks, buckets, and mops. You notice a small corkboard with a message pinned on the wall, reading: \"Cleaners - to gain access to the sewers, scan the QR code with your wristpad\". Below that is a QR code, and red text reading: Beware - sewers are infested with dangerous mutants. Take a source of light. [To gain sewer access, try scan]" + exits: + - direction: west + scan_code: !SewerAccess + door_states: + west: + open: false + description: a solid wooden door with a handle - zone: melbs code: melbs_williamsst_90 name: Williams St - 90 block diff --git a/blastmud_game/src/static_content/room/repro_xv.rs b/blastmud_game/src/static_content/room/repro_xv.rs index a71a47f..1bf2559 100644 --- a/blastmud_game/src/static_content/room/repro_xv.rs +++ b/blastmud_game/src/static_content/room/repro_xv.rs @@ -25,7 +25,7 @@ pub fn room_list() -> Vec { grid_coords: GridCoords { x: 0, y: 0, z: -1 }, exits: vec!(Exit { direction: Direction::EAST, - exit_type: ExitType::Blocked(&npc::statbot::ChoiceRoomBlocker), + exit_type: ExitType::Blocked(Box::new(npc::statbot::ChoiceRoomBlocker)), ..Default::default() }), item_flags: vec![ItemFlag::NoUrgesHere], diff --git a/blastmud_game/src/static_content/species.rs b/blastmud_game/src/static_content/species.rs index 23fa35a..de45648 100644 --- a/blastmud_game/src/static_content/species.rs +++ b/blastmud_game/src/static_content/species.rs @@ -9,6 +9,7 @@ pub enum SpeciesType { Human, Dog, Robot, + Rat, } impl SpeciesType { @@ -73,6 +74,7 @@ impl BodyPart { pub struct SpeciesInfo { pub body_parts: Vec, pub corpse_butchers_into: Vec, + pub can_open_door: bool, } pub fn species_info_map() -> &'static BTreeMap { @@ -99,6 +101,7 @@ pub fn species_info_map() -> &'static BTreeMap { PossessionType::AnimalSkin, PossessionType::SeveredHead, ], + can_open_door: true, }, ), ( @@ -118,6 +121,27 @@ pub fn species_info_map() -> &'static BTreeMap { PossessionType::AnimalSkin, PossessionType::SeveredHead, ], + can_open_door: false, + }, + ), + ( + SpeciesType::Rat, + SpeciesInfo { + body_parts: vec![ + BodyPart::Head, + BodyPart::Face, + BodyPart::Chest, + BodyPart::Back, + BodyPart::Groin, + BodyPart::Legs, + BodyPart::Feet, + ], + corpse_butchers_into: vec![ + PossessionType::Steak, + PossessionType::AnimalSkin, + PossessionType::SeveredHead, + ], + can_open_door: false, }, ), ( @@ -132,6 +156,7 @@ pub fn species_info_map() -> &'static BTreeMap { BodyPart::Feet, ], corpse_butchers_into: vec![], + can_open_door: false, }, ), ]