From 5c2462a396589e81bf06ab4b4b155a36fe1d0a61 Mon Sep 17 00:00:00 2001 From: Condorra Date: Wed, 19 Jun 2024 22:28:09 +1000 Subject: [PATCH] Enable first chemistry crafting --- blastmud_game/src/language.rs | 4 +- .../message_handler/user_commands/movement.rs | 4 + blastmud_game/src/models/effect.rs | 22 +- blastmud_game/src/models/item.rs | 3 + blastmud_game/src/services/effect.rs | 94 +++- blastmud_game/src/services/environment.rs | 25 +- .../src/static_content/crafting.yaml | 29 + .../src/static_content/npc/roboporter.rs | 2 +- .../src/static_content/possession_type.rs | 34 +- .../static_content/possession_type/benches.rs | 60 +- .../static_content/possession_type/books.rs | 38 +- .../possession_type/chemicals.rs | 61 ++ .../possession_type/head_armour.rs | 41 +- .../possession_type/torso_armour.rs | 5 +- .../possession_type/trauma_kit.rs | 519 +++++++++++------- blastmud_game/src/static_content/room.rs | 4 +- .../static_content/room/general_hospital.rs | 4 + .../src/static_content/room/melbs.rs | 40 ++ .../src/static_content/room/melbs.yaml | 67 ++- blastmud_game/src/static_content/scavtable.rs | 7 +- 20 files changed, 832 insertions(+), 231 deletions(-) create mode 100644 blastmud_game/src/static_content/crafting.yaml create mode 100644 blastmud_game/src/static_content/possession_type/chemicals.rs diff --git a/blastmud_game/src/language.rs b/blastmud_game/src/language.rs index 96ce6d54..9bc2d63f 100644 --- a/blastmud_game/src/language.rs +++ b/blastmud_game/src/language.rs @@ -10,7 +10,7 @@ struct PluralRule<'l> { pub fn pluralise(orig_input: &str) -> String { let mut extra_suffix: &str = ""; let mut input: &str = orig_input; - 'wordsplit: for split_word in vec!["pair"] { + 'wordsplit: for split_word in vec!["pair", "box", "jar", "tube"] { for (idx, _) in input.match_indices(split_word) { let end_idx = idx + split_word.len(); if end_idx == input.len() { @@ -322,6 +322,8 @@ mod test { ("brown pair of pants", "brown pairs of pants"), ("good pair", "good pairs"), ("repair kit", "repair kits"), + ("box of wolves", "boxes of wolves"), + ("jar of acid", "jars of acid"), ] { assert_eq!(super::pluralise(word), plural); } diff --git a/blastmud_game/src/message_handler/user_commands/movement.rs b/blastmud_game/src/message_handler/user_commands/movement.rs index b1e47457..32fe8ffe 100644 --- a/blastmud_game/src/message_handler/user_commands/movement.rs +++ b/blastmud_game/src/message_handler/user_commands/movement.rs @@ -217,6 +217,10 @@ pub async fn check_room_access(trans: &DBTrans, player: &Item, room: &Item) -> U return Ok(()); } + if player.flags.contains(&ItemFlag::EnterWithoutConsent) { + return Ok(()); + } + if owner_t == "corp" { let corp = match trans.find_corp_by_name(owner_c).await? { None => return Ok(()), // Defunct corp HQ somehow... diff --git a/blastmud_game/src/models/effect.rs b/blastmud_game/src/models/effect.rs index f27966ac..23f180ed 100644 --- a/blastmud_game/src/models/effect.rs +++ b/blastmud_game/src/models/effect.rs @@ -1,4 +1,4 @@ -use super::item::Item; +use super::item::{Item, ItemFlag}; use ansi_markup::parse_ansi_markup; use serde::{Deserialize, Serialize}; @@ -25,6 +25,19 @@ pub enum EffectParameter { Raddamage, } +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub enum EffectMitigant { + MitigantClothing { with_flag: ItemFlag }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub struct EffectMitigation { + // Pre-multiplies the magnitude by the factor if the mitigant matches. + // In 1000ths. + pub amplitude_multiplier: u64, + pub mitigant: EffectMitigant, +} + pub enum Effect { // messagef takes player, causative item, target as the 3 parameters. Returns message. BroadcastMessage { @@ -42,8 +55,12 @@ pub enum Effect { skill_multiplier: f64, max_effect: i64, parameter: EffectParameter, + mitigations: Vec, message: Box String + Sync + Send>, }, + CancelEffect { + other_effect: EffectType, + }, } #[derive(Serialize, Deserialize)] @@ -63,6 +80,7 @@ pub enum SimpleEffect { skill_multiplier: f64, max_effect: i64, parameter: EffectParameter, + mitigations: Vec, message: String, }, } @@ -96,6 +114,7 @@ impl From<&SimpleEffect> for Effect { skill_multiplier, max_effect, parameter, + mitigations, message, } => { let messagem = parse_ansi_markup(message).unwrap() + "\n"; @@ -105,6 +124,7 @@ impl From<&SimpleEffect> for Effect { skill_multiplier: *skill_multiplier, max_effect: *max_effect, parameter: parameter.clone(), + mitigations: (*mitigations).clone(), message: Box::new(move |_| messagem.clone()), } } diff --git a/blastmud_game/src/models/item.rs b/blastmud_game/src/models/item.rs index cb655ebd..d5a90abd 100644 --- a/blastmud_game/src/models/item.rs +++ b/blastmud_game/src/models/item.rs @@ -344,6 +344,9 @@ pub enum ItemFlag { NoIdlePark, DontCounterattack, EnableCombatAi, + HalvesToxicFumes, + ProtectsToxicFumes, + EnterWithoutConsent, } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] diff --git a/blastmud_game/src/services/effect.rs b/blastmud_game/src/services/effect.rs index cccecd42..0abba10b 100644 --- a/blastmud_game/src/services/effect.rs +++ b/blastmud_game/src/services/effect.rs @@ -3,8 +3,10 @@ use super::{combat::change_health, comms::broadcast_to_room}; use crate::db::DBTrans; use crate::{ models::{ - effect::{Effect, EffectParameter, EffectSet, EffectType}, - item::Item, + effect::{ + Effect, EffectMitigant, EffectMitigation, EffectParameter, EffectSet, EffectType, + }, + item::{Item, LocationActionType}, task::{Task, TaskDetails, TaskMeta}, }, regular_tasks::{TaskHandler, TaskRunContext}, @@ -18,14 +20,18 @@ use log::info; use mockall_double::double; use once_cell::sync::OnceCell; use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, VecDeque}; use std::time; +use std::{ + collections::{BTreeMap, VecDeque}, + sync::Arc, +}; #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub struct DelayedParameterEffect { magnitude: i64, delay: u64, parameter: EffectParameter, + mitigations: Vec, message: String, } @@ -76,10 +82,13 @@ impl TaskHandler for DelayedParameterTaskHandler { magnitude, message, parameter, + ref mitigations, delay, .. }) => { let mut item_mut = (*item).clone(); + let magnitude = + apply_effect_mitigations(&ctx.trans, &item, mitigations, magnitude).await?; match parameter { EffectParameter::Health => { change_health(ctx.trans, magnitude, &mut item_mut, &message).await?; @@ -200,6 +209,42 @@ impl TaskHandler for DispelEffectTaskHandler { pub static DISPEL_EFFECT_HANDLER: &'static (dyn TaskHandler + Sync + Send) = &DispelEffectTaskHandler; +pub async fn apply_effect_mitigations( + trans: &DBTrans, + player: &Item, + mitigations: &Vec, + initial_value: i64, +) -> DResult { + let mut value = initial_value as f64; + let mut clothing_cache: Option>> = None; + + for mitigation in mitigations { + let is_mitigated = match &mitigation.mitigant { + EffectMitigant::MitigantClothing { with_flag } => { + let clothing = match clothing_cache { + None => { + let res = trans + .find_by_action_and_location( + &player.refstr(), + &LocationActionType::Worn, + ) + .await?; + clothing_cache = Some(res); + clothing_cache.as_ref().unwrap() + } + Some(ref v) => v, + }; + clothing.iter().any(|cl| cl.flags.contains(&with_flag)) + } + }; + if is_mitigated { + value *= mitigation.amplitude_multiplier as f64 / 1000.0; + } + } + + Ok(value as i64) +} + pub async fn run_effects( trans: &DBTrans, effects: &EffectSet, @@ -286,6 +331,7 @@ pub async fn run_effects( skill_multiplier, max_effect, parameter, + mitigations, message, } => { let param_impact = *base_effect + ((skill_multiplier * level) as i64); @@ -294,6 +340,8 @@ pub async fn run_effects( } else { param_impact.max(*max_effect) }; + let param_impact = + apply_effect_mitigations(trans, player, &mitigations, param_impact).await?; let msg = message(target.as_ref().map(|t| &**t).unwrap_or(player)); if *delay_secs == 0 { match parameter { @@ -320,6 +368,7 @@ pub async fn run_effects( magnitude: param_impact, delay: *delay_secs, parameter: (*parameter).clone(), + mitigations: mitigations.clone(), message: msg, }; target_health_series @@ -328,6 +377,16 @@ pub async fn run_effects( .or_insert(VecDeque::from([fx])); } } + Effect::CancelEffect { other_effect } => { + let actual_target: &mut Item = *target.as_mut().unwrap_or(&mut player); + for (etype, effnum) in actual_target + .active_effects + .iter() + .filter(|(etype, _effnum)| etype == other_effect) + { + cancel_effect(trans, actual_target, &(etype.clone(), *effnum)).await?; + } + } } } @@ -447,6 +506,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -12, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{} pulses from {}'s wound", if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" }, @@ -460,6 +520,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -10, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{} pulses from {}'s wound", if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" }, @@ -473,6 +534,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -8, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{} pulses from {}'s wound", if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" }, @@ -486,6 +548,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -6, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{} pulses from {}'s wound", if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" }, @@ -499,6 +562,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -4, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{} pulses from {}'s wound", if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" }, @@ -512,6 +576,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -2, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("A final tiny drop of {} oozes from {}'s wound as it heals", if target.species == SpeciesType::Robot { "coolant" } else { "blood" }, @@ -560,6 +625,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -6, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{} looks a bit unwell", target.display_for_sentence(1, false), @@ -572,6 +638,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -12, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{} looks pretty unwell", target.display_for_sentence(1, false), @@ -584,6 +651,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -18, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{} looks very sick", target.display_for_sentence(1, false), @@ -596,6 +664,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -18, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{} looks very sick", target.display_for_sentence(1, false), @@ -608,6 +677,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -12, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{} looks pretty unwell", target.display_for_sentence(1, false), @@ -620,6 +690,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -12, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{} looks pretty unwell", target.display_for_sentence(1, false), @@ -632,6 +703,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -6, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{} looks a bit unwell", target.display_for_sentence(1, false), @@ -644,6 +716,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -6, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{} feels the final effects of snake venom as it leaves {} body", target.display_for_sentence(1, false), @@ -662,6 +735,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -12, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{} howls out in pain from a sting", target.display_for_sentence(1, false), @@ -674,6 +748,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -12, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{} howls out in pain from a sting", target.display_for_sentence(1, false), @@ -686,6 +761,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -12, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{} howls out in pain from a sting", target.display_for_sentence(1, false), @@ -698,6 +774,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -12, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{} yelps in pain from the sting", target.display_for_sentence(1, false), @@ -710,6 +787,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -12, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{} yelps in pain from the sting", target.display_for_sentence(1, false), @@ -722,6 +800,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -12, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{} yelps in pain from the sting", target.display_for_sentence(1, false), @@ -734,6 +813,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -10, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{}'s cries as the sting really hurts", target.display_for_sentence(1, false), @@ -746,6 +826,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -10, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{}'s cries as the sting really hurts", target.display_for_sentence(1, false), @@ -758,6 +839,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -10, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{}'s cries as the sting really hurts", target.display_for_sentence(1, false), @@ -770,6 +852,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -8, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{} sobs as the sting still hurts", target.display_for_sentence(1, false), @@ -782,6 +865,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -8, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{} sobs as the sting still hurts", target.display_for_sentence(1, false), @@ -794,6 +878,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -8, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{} sobs as the sting still hurts", target.display_for_sentence(1, false), @@ -806,6 +891,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -8, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{} whimpers as the sting starts to heal", target.display_for_sentence(1, false), @@ -818,6 +904,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -8, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{} whimpers as the sting is almost healed", target.display_for_sentence(1, false), @@ -830,6 +917,7 @@ pub fn default_effects_for_type() -> &'static BTreeMap { skill_multiplier: 0.0, max_effect: -8, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new(|target| format!("{} whimpers as the sting is finally healed", target.display_for_sentence(1, false), diff --git a/blastmud_game/src/services/environment.rs b/blastmud_game/src/services/environment.rs index ec744638..40d86dde 100644 --- a/blastmud_game/src/services/environment.rs +++ b/blastmud_game/src/services/environment.rs @@ -25,7 +25,10 @@ use crate::{ DResult, }; -use super::{combat::change_health, comms::broadcast_to_room, skills::skill_check_and_grind}; +use super::{ + combat::change_health, comms::broadcast_to_room, effect::apply_effect_mitigations, + skills::skill_check_and_grind, +}; pub struct EffectEnvironmentHandler; pub struct ConsequenceEnvironmentHandler; @@ -316,18 +319,24 @@ pub async fn passive_health_environment_effects( return Ok(false); } + let adjusted_passive_health = apply_effect_mitigations( + &ctx.trans, + player_item, + &room.environment.passive_health_mitigation, + room.environment.passive_health as i64, + ) + .await?; + + if adjusted_passive_health == 0 { + return Ok(false); + } + let msg = format!( "{} {}", player_item.display_for_sentence(1, true), room.environment.passive_health_message ); - change_health( - ctx.trans, - room.environment.passive_health as i64, - player_item, - &msg, - ) - .await?; + change_health(ctx.trans, adjusted_passive_health, player_item, &msg).await?; Ok(true) } diff --git a/blastmud_game/src/static_content/crafting.yaml b/blastmud_game/src/static_content/crafting.yaml new file mode 100644 index 00000000..b038681c --- /dev/null +++ b/blastmud_game/src/static_content/crafting.yaml @@ -0,0 +1,29 @@ +- craft_data: + skill: !Craft + difficulty: 5.0 + inputs: + - Steak + output: GrilledSteak + recipe: GrilledSteakRecipe + bench: KitchenStove +- craft_data: + skill: !Chemistry + difficulty: 6.0 + inputs: + - VenomSac + - CausticSoda + - VinylAcetate + - Azobisisobutyronitrile + - Borax + output: VenomExtractionKit + recipe: VenomExtractionKitInstructions + bench: ChemistrySet +- craft_data: + skill: !Chemistry + difficulty: 6.0 + inputs: + - CausticSoda + - MediumTraumaKit + output: LargeTraumaKit + recipe: SuperchargeTraumaKit + bench: ChemistrySet diff --git a/blastmud_game/src/static_content/npc/roboporter.rs b/blastmud_game/src/static_content/npc/roboporter.rs index e648179a..d17ba0d7 100644 --- a/blastmud_game/src/static_content/npc/roboporter.rs +++ b/blastmud_game/src/static_content/npc/roboporter.rs @@ -109,7 +109,7 @@ pub fn npc_list() -> Vec { frequency_secs: 600, price: 100, }), - extra_flags: vec![ItemFlag::CanLoad], + extra_flags: vec![ItemFlag::CanLoad, ItemFlag::EnterWithoutConsent], total_stats: vec![(StatType::Brawn, 20.0)].into_iter().collect(), ..Default::default() } diff --git a/blastmud_game/src/static_content/possession_type.rs b/blastmud_game/src/static_content/possession_type.rs index 9a912be9..047c05c4 100644 --- a/blastmud_game/src/static_content/possession_type.rs +++ b/blastmud_game/src/static_content/possession_type.rs @@ -15,6 +15,7 @@ use mockall_double::double; use once_cell::sync::OnceCell; use rand::seq::SliceRandom; use serde::{Deserialize, Serialize}; +use serde_yaml::from_str as from_yaml_str; use std::collections::{BTreeMap, BTreeSet}; mod bags; @@ -22,6 +23,7 @@ mod benches; mod blade; mod books; mod bottles; +mod chemicals; mod club; mod corp_licence; mod fangs; @@ -452,6 +454,7 @@ pub enum PossessionType { // Armour / Clothes RustyMetalPot, HockeyMask, + GasMask, CombatHelmet, CombatBoots, Shirt, @@ -485,8 +488,10 @@ pub enum PossessionType { Z3000Pistol, // Medical MediumTraumaKit, + LargeTraumaKit, EmptyMedicalBox, RadDetox, + VenomExtractionKit, // Corporate NewCorpLicence, CertificateOfIncorporation, @@ -511,11 +516,21 @@ pub enum PossessionType { RustySpike, VenomSac, SnakeSkin, + // Chemicals... + CausticSoda, + VinylAcetate, + Azobisisobutyronitrile, + Borax, + HydrochloricAcid, // Craft benches KitchenStove, + ChemistrySet, // Recipes CulinaryEssentials, GrilledSteakRecipe, + EverydayChemistry, + VenomExtractionKitInstructions, + SuperchargeTraumaKit, // Utilities ElectricLantern, GeigerCounter, @@ -614,6 +629,7 @@ pub fn possession_data() -> &'static BTreeMap &'static Vec { }) } -#[derive(PartialEq, Debug, Clone)] +#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] pub struct CraftData { pub skill: SkillType, pub difficulty: f64, @@ -691,6 +707,7 @@ pub struct CraftData { pub output: PossessionType, } +#[derive(Serialize, Deserialize)] pub struct RecipeCraftData { pub craft_data: CraftData, pub recipe: PossessionType, @@ -747,16 +764,7 @@ pub fn improv_by_output() -> &'static BTreeMap &'static Vec { static RECIPE_CELL: OnceCell> = OnceCell::new(); RECIPE_CELL.get_or_init(|| { - vec![RecipeCraftData { - craft_data: CraftData { - skill: SkillType::Craft, - difficulty: 5.0, - inputs: vec![PossessionType::Steak], - output: PossessionType::GrilledSteak, - }, - recipe: PossessionType::GrilledSteakRecipe, - bench: Some(PossessionType::KitchenStove), - }] + from_yaml_str::>(include_str!("crafting.yaml")).unwrap() }) } @@ -902,4 +910,8 @@ mod tests { } } } + + fn craft_table_populated() { + assert!(recipe_craft_table().len() > 0) + } } diff --git a/blastmud_game/src/static_content/possession_type/benches.rs b/blastmud_game/src/static_content/possession_type/benches.rs index ed3f405e..c67bc982 100644 --- a/blastmud_game/src/static_content/possession_type/benches.rs +++ b/blastmud_game/src/static_content/possession_type/benches.rs @@ -15,6 +15,9 @@ use once_cell::sync::OnceCell; struct NeedPowerBench; static NEED_POWER_BENCH: NeedPowerBench = NeedPowerBench; +struct UnpoweredBench; +static UNPOWER_BENCH: UnpoweredBench = UnpoweredBench; + #[async_trait] impl BenchData for NeedPowerBench { async fn check_make(&self, trans: &DBTrans, bench: &Item, _recipe: &Item) -> UResult<()> { @@ -30,24 +33,51 @@ impl BenchData for NeedPowerBench { } } +#[async_trait] +impl BenchData for UnpoweredBench { + async fn check_make(&self, _trans: &DBTrans, _bench: &Item, _recipe: &Item) -> UResult<()> { + Ok(()) + } +} + pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { static D: OnceCell> = OnceCell::new(); &D.get_or_init(|| { - vec![( - PossessionType::KitchenStove, - PossessionData { - display: "kitchen stove", - aliases: vec!["stove"], - details: ansi!("A four-element electric stove, with an oven underneath, capable of cooking nutritious meals - or burning a careless amateur cook! [To use, try putting a recipe and the ingredients in it with the put command, and turning it on with the make command]"), - weight: 40000, - container_data: Some(ContainerData { - base_weight: 40000, + vec![ + ( + PossessionType::KitchenStove, + PossessionData { + display: "kitchen stove", + aliases: vec!["stove"], + details: ansi!("A four-element electric stove, with an oven underneath, capable of cooking nutritious meals - or burning a careless amateur cook! [To use, try putting a recipe and the ingredients in it with the put command, and turning it on with the make command]"), + weight: 40000, + container_data: Some(ContainerData { + base_weight: 40000, + max_weight: 15000, + ..Default::default() + }), + bench_data: Some(&NEED_POWER_BENCH), + default_flags: vec![ItemFlag::Bench], + ..Default::default() + }, + ), + ( + PossessionType::ChemistrySet, + PossessionData { + display:"chemistry set", + details: ansi!("A rigid black case that opens to reveal to collection of burners, glassware, and tools that seems to be designed to help a skilled chemist to run a chemical synthesis"), + aliases: vec!["workbench"], + weight: 15000, + bench_data: Some(&UNPOWER_BENCH), + container_data: Some(ContainerData { + base_weight: 15000, + max_weight: 15000, + ..Default::default() + }), + default_flags: vec![ItemFlag::Bench], ..Default::default() - }), - bench_data: Some(&NEED_POWER_BENCH), - default_flags: vec![ItemFlag::Bench], - ..Default::default() - }, - )] + } + ) + ] }) } diff --git a/blastmud_game/src/static_content/possession_type/books.rs b/blastmud_game/src/static_content/possession_type/books.rs index 280a0ccc..3bbd77c8 100644 --- a/blastmud_game/src/static_content/possession_type/books.rs +++ b/blastmud_game/src/static_content/possession_type/books.rs @@ -59,11 +59,47 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { (PossessionType::GrilledSteakRecipe, PossessionData { display: "recipe for Grilled Steak", - aliases: vec!["grilled steak"], + aliases: vec!["grilled steak", "recipe"], details: "Instructions for how to make a basic but mouthwatering steak", weight: 10, default_flags: vec![ItemFlag::Instructions], ..Default::default() }), + (PossessionType::EverydayChemistry, + PossessionData { + display: "Everyday Chemistry", + aliases: vec!["book", "chemistry book"], + details: "A weathered collection of instructions on various forms of chemical synthesis", + weight: 310, + container_data: Some(ContainerData { + base_weight: 290, + default_contents: vec![ + PossessionType::VenomExtractionKitInstructions, + PossessionType::SuperchargeTraumaKit, + ], + checker: &RECIPES_ONLY_CHECKER, + ..Default::default() + }), + default_flags: vec![ItemFlag::Book], + ..Default::default() + }), + (PossessionType::VenomExtractionKitInstructions, + PossessionData { + display: "venom extraction kit instructions", + aliases: vec!["recipe", "instructions"], + details: "Instructions for how to manufacture a special polymer that sucks all the venom from a bite or sting site", + weight: 10, + default_flags: vec![ItemFlag::Instructions], + ..Default::default() + }), + (PossessionType::SuperchargeTraumaKit, + PossessionData { + display: "how to supercharge a trauma kit", + aliases: vec!["trauma kit", "large trauma kit", "supercharge a trauma kit", "recipe", "instructions"], + details: "A detailed written procedure for supercharging a trauma kit. Apparently, the empire deliberately reduced the capacity of the batteries in the equipment in the kit to keep people under its thumb, but a simple replacement of some alkaline solution in the batteries can double the number of treatments", + weight: 10, + default_flags: vec![ItemFlag::Instructions], + ..Default::default() + }), )) } diff --git a/blastmud_game/src/static_content/possession_type/chemicals.rs b/blastmud_game/src/static_content/possession_type/chemicals.rs new file mode 100644 index 00000000..2233faa4 --- /dev/null +++ b/blastmud_game/src/static_content/possession_type/chemicals.rs @@ -0,0 +1,61 @@ +use super::{PossessionData, PossessionType}; +use ansi::ansi; +use once_cell::sync::OnceCell; + +pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { + static D: OnceCell> = OnceCell::new(); + &D.get_or_init(|| { + vec![ + ( + PossessionType::CausticSoda, + PossessionData { + display: "bottle of caustic soda", + details: ansi!("A rigid blue plastic bottle labelled as containing caustic soda. Apparently it's highly corrosive and shouldn't be eaten."), + aliases: vec!["chemical", "sodium hydroxide", "caustic soda"], + weight: 100, + ..Default::default() + }, + ), + ( + PossessionType::VinylAcetate, + PossessionData { + display: "jar of vinyl acetate", + details: ansi!("A glass jar containing a colourless liquid, labelled as vinyl acetate. Apparently it's both flammable and harmful."), + aliases: vec!["chemical", "monomer", "vinyl acetate"], + weight: 100, + ..Default::default() + }, + ), + ( + PossessionType::Azobisisobutyronitrile, + PossessionData { + display: "tube of azobisisobutyronitrile", + details: ansi!("A small tube containing some white crystals. Apparently they are flammable and harmful, and are also known as AIBN."), + aliases: vec!["chemical", "initiator", "AIBN", "azobisisobutyronitrile"], + weight: 20, + ..Default::default() + }, + ), + ( + PossessionType::Borax, + PossessionData { + display: "box of borax", + details: ansi!("A cardbox box containing some white powder, labelled as borax. Apparently it's very poisonous to ants."), + aliases: vec!["chemical", "borax"], + weight: 100, + ..Default::default() + }, + ), + ( + PossessionType::HydrochloricAcid, + PossessionData { + display: "jar of hydrochloric acid", + details: ansi!("A glass jar containing some liquid. It's labelled as being concentrated hydrochloric acid, and apparently it's very corrosive and can cause damage to the skin, lungs and eyes."), + aliases: vec!["chemical", "acid", "hydrochloric acid"], + weight: 100, + ..Default::default() + }, + ), + ] + }) +} diff --git a/blastmud_game/src/static_content/possession_type/head_armour.rs b/blastmud_game/src/static_content/possession_type/head_armour.rs index f8d95e13..6cb60cbd 100644 --- a/blastmud_game/src/static_content/possession_type/head_armour.rs +++ b/blastmud_game/src/static_content/possession_type/head_armour.rs @@ -1,5 +1,5 @@ use super::{DamageType, PossessionData, PossessionType, SoakData, WearData}; -use crate::static_content::species::BodyPart; +use crate::{models::item::ItemFlag, static_content::species::BodyPart}; use once_cell::sync::OnceCell; pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { @@ -81,6 +81,45 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { ..Default::default() } ), + ( + PossessionType::GasMask, + PossessionData { + display: "gas mask", + details: "A green mask that fits snugly over the face with two elastic bands around the back of the head. Two large round white filters at the front that give the wear something of a bug like look.", + aliases: vec!("mask"), + weight: 350, + default_flags: vec![ItemFlag::HalvesToxicFumes], + wear_data: Some(WearData { + covers_parts: vec!(BodyPart::Face), + thickness: 7.0, + dodge_penalty: 0.25, + soaks: vec!( + (DamageType::Beat, + SoakData { + min_soak: 1.0, + max_soak: 2.0, + damage_probability_per_soak: 0.1 + } + ), + (DamageType::Slash, + SoakData { + min_soak: 1.0, + max_soak: 2.0, + damage_probability_per_soak: 0.1 + } + ), + (DamageType::Pierce, + SoakData { + min_soak: 1.0, + max_soak: 2.0, + damage_probability_per_soak: 0.1 + } + ), + ).into_iter().collect() + }), + ..Default::default() + } + ), ( PossessionType::CombatHelmet, PossessionData { diff --git a/blastmud_game/src/static_content/possession_type/torso_armour.rs b/blastmud_game/src/static_content/possession_type/torso_armour.rs index 20987d38..8ed2c172 100644 --- a/blastmud_game/src/static_content/possession_type/torso_armour.rs +++ b/blastmud_game/src/static_content/possession_type/torso_armour.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use super::{DamageType, PossessionData, PossessionType, SoakData, WearData}; -use crate::static_content::species::BodyPart; +use crate::{models::item::ItemFlag, static_content::species::BodyPart}; use once_cell::sync::OnceCell; pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { @@ -69,9 +69,10 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { PossessionType::RadSuit, PossessionData { display: "radiation suit", - details: "A suit with a yellow outer layer and a foil lining that covers every part of the body from head to toe, featuring a tight fitting respirator, a hood that covers the head, gloves, plastic overalls, and socks. It seems designed not to let anything in from the outside except through the filter of the respirator, although it probably will hinder movement and won't protect against anything except chemicals or radiation", + details: "A suit with a yellow outer layer and a foil lining that covers every part of the body from head to toe, featuring a tight fitting respirator, a hood that covers the head, gloves, plastic overalls, and socks. It seems designed not to let anything in from the outside except through the filter of the respirator, although it probably will hinder movement and won't protect against anything except chemicals or radiation.", aliases: vec!("rad suit", "radsuit"), weight: 2000, + default_flags: vec![ItemFlag::ProtectsToxicFumes], wear_data: Some(WearData { covers_parts: vec!( BodyPart::Head, diff --git a/blastmud_game/src/static_content/possession_type/trauma_kit.rs b/blastmud_game/src/static_content/possession_type/trauma_kit.rs index d2ff4ff2..2aee9767 100644 --- a/blastmud_game/src/static_content/possession_type/trauma_kit.rs +++ b/blastmud_game/src/static_content/possession_type/trauma_kit.rs @@ -8,6 +8,221 @@ use once_cell::sync::OnceCell; use super::PossessionType::*; +fn trau_use_data() -> UseData { + UseData { + uses_skill: SkillType::Medic, + diff_level: 10.0, + crit_fail_effects: Some(EffectSet { + effect_type: EffectType::Ephemeral, + effects: vec![ + Effect::BroadcastMessage { + delay_secs: 0, + messagef: Box::new(|player, _item, target| { + format!( + "{} attempts to heal {} with a trauma kit, but fucks it up badly\n", + &player.display_for_sentence(1, true), + &if target.item_type == player.item_type + && target.item_code == player.item_code + { + player.pronouns.intensive.clone() + } else { + target.display_for_sentence(1, false) + } + ) + }), + }, + Effect::ChangeTargetParameter { + delay_secs: 0, + base_effect: -2, + skill_multiplier: -3.0, + max_effect: -5, + parameter: EffectParameter::Health, + mitigations: vec![], + message: Box::new(|target| { + format!( + "Fuck! The trauma kit makes {}'s condition worse", + target.display_for_sentence(1, false) + ) + }), + }, + ], + }), + fail_effects: Some(EffectSet { + effect_type: EffectType::Ephemeral, + effects: vec![Effect::BroadcastMessage { + delay_secs: 0, + messagef: Box::new(|player, _item, target| { + format!( + "{} attempts unsuccessfully to heal {} with a trauma kit\n", + &player.display_for_sentence(1, true), + &if target.item_type == player.item_type + && target.item_code == player.item_code + { + player.pronouns.intensive.clone() + } else { + target.display_for_sentence(1, false) + } + ) + }), + }], + }), + success_effects: Some(EffectSet { + effect_type: EffectType::Bandages, + effects: vec![ + Effect::BroadcastMessage { + delay_secs: 0, + messagef: Box::new(|player, _item, target| { + format!( + "{} expertly heals {} with a trauma kit\n", + &player.display_for_sentence(1, true), + &if target.item_type == player.item_type + && target.item_code == player.item_code + { + player.pronouns.intensive.clone() + } else { + target.display_for_sentence(1, false) + } + ) + }), + }, + Effect::ChangeTargetParameter { + delay_secs: 0, + base_effect: 2, + skill_multiplier: 8.0, + max_effect: 10, + parameter: EffectParameter::Health, + mitigations: vec![], + message: Box::new(|target| { + format!( + "FUUUCK! It hurts {}, but also starts to soothe {}", + target.display_for_sentence(1, false), + &target.pronouns.object + ) + }), + }, + Effect::ChangeTargetParameter { + delay_secs: 10, + base_effect: 2, + skill_multiplier: 7.0, + max_effect: 9, + parameter: EffectParameter::Health, + mitigations: vec![], + message: Box::new(|target| { + format!( + "FUUUCK! It hurts {}, but also starts to soothe {}", + target.display_for_sentence(1, false), + &target.pronouns.object + ) + }), + }, + Effect::ChangeTargetParameter { + delay_secs: 20, + base_effect: 1, + skill_multiplier: 6.0, + max_effect: 7, + parameter: EffectParameter::Health, + mitigations: vec![], + message: Box::new(|target| { + format!( + "The bandages soothe {}'s wounds", + target.display_for_sentence(1, false), + ) + }), + }, + Effect::ChangeTargetParameter { + delay_secs: 30, + base_effect: 1, + skill_multiplier: 5.0, + parameter: EffectParameter::Health, + mitigations: vec![], + max_effect: 6, + message: Box::new(|target| { + format!( + "The bandages soothe {}'s wounds", + target.display_for_sentence(1, false), + ) + }), + }, + Effect::ChangeTargetParameter { + delay_secs: 40, + base_effect: 0, + skill_multiplier: 4.0, + max_effect: 4, + parameter: EffectParameter::Health, + mitigations: vec![], + message: Box::new(|target| { + format!( + "The bandages soothe {}'s wounds", + target.display_for_sentence(1, false), + ) + }), + }, + Effect::ChangeTargetParameter { + delay_secs: 50, + base_effect: 0, + skill_multiplier: 3.0, + max_effect: 3, + parameter: EffectParameter::Health, + mitigations: vec![], + message: Box::new(|target| { + format!( + "The bandages soothe {}'s wounds", + target.display_for_sentence(1, false), + ) + }), + }, + Effect::ChangeTargetParameter { + delay_secs: 60, + base_effect: 0, + skill_multiplier: 2.0, + max_effect: 2, + parameter: EffectParameter::Health, + mitigations: vec![], + message: Box::new(|target| { + format!( + "The bandages soothe {}'s wounds", + target.display_for_sentence(1, false), + ) + }), + }, + Effect::BroadcastMessage { + delay_secs: 60, + messagef: Box::new(|_player, _item, target| { + format!( + "The bandages wrapping {} crumble and fall away, their healing capabilities fully expended.\n", + target.display_for_sentence(1, false) + ) + }), + }, + ], + }), + errorf: Box::new(|item, target| { + if target.death_data.is_some() { + Some(format!( + "It is too late, {}'s dead", + target.pronouns.subject + )) + } else if target.item_type != "player" && target.item_type != "npc" { + Some("It only works on animals.".to_owned()) + } else if target + .active_effects + .iter() + .any(|e| e.0 == EffectType::Bandages) + { + Some(format!( + "You see no reason to use {} on {}", + item.display_for_sentence(1, false), + target.display_for_sentence(1, false) + )) + } else { + None + } + }), + needs_consent_check: Some(ConsentType::Medicine), + ..Default::default() + } +} + pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { static D: OnceCell> = OnceCell::new(); &D.get_or_init(|| vec!( @@ -22,190 +237,23 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { charge_name_suffix: "worth of supplies", ..Default::default() }), - use_data: Some(UseData { - uses_skill: SkillType::Medic, - diff_level: 10.0, - crit_fail_effects: Some(EffectSet { - effect_type: EffectType::Ephemeral, - effects: vec!( - Effect::BroadcastMessage { - delay_secs: 0, - messagef: Box::new(|player, _item, target| - format!( - "{} attempts to heal {} with a trauma kit, but fucks it up badly\n", - &player.display_for_sentence(1, true), - &if target.item_type == player.item_type && target.item_code == player.item_code { - player.pronouns.intensive.clone() - } else { - target.display_for_sentence(1, false) - } - ), - ) - }, - Effect::ChangeTargetParameter { - delay_secs: 0, base_effect: -2, skill_multiplier: -3.0, - max_effect: -5, - parameter: EffectParameter::Health, - message: Box::new( - |target| - format!( - "Fuck! The trauma kit makes {}'s condition worse", - target.display_for_sentence(1, false)) - ) - } - ) - }), - fail_effects: Some(EffectSet { - effect_type: EffectType::Ephemeral, - effects: vec!( - Effect::BroadcastMessage { - delay_secs: 0, - messagef: Box::new(|player, _item, target| - format!( - "{} attempts unsuccessfully to heal {} with a trauma kit\n", - &player.display_for_sentence(1, true), - &if target.item_type == player.item_type && target.item_code == player.item_code { - player.pronouns.intensive.clone() - } else { - target.display_for_sentence(1, false) - } - ) - ) - }, - ) - }), - success_effects: Some(EffectSet { - effect_type: EffectType::Bandages, - effects: vec!( - Effect::BroadcastMessage { - delay_secs: 0, - messagef: Box::new(|player, _item, target| - format!( - "{} expertly heals {} with a trauma kit\n", - &player.display_for_sentence(1, true), - &if target.item_type == player.item_type && target.item_code == player.item_code { - player.pronouns.intensive.clone() - } else { - target.display_for_sentence(1, false) - } - ) - ) - }, - Effect::ChangeTargetParameter { - delay_secs: 0, base_effect: 2, skill_multiplier: 8.0, - max_effect: 10, - parameter: EffectParameter::Health, - message: Box::new( - |target| - format!( - "FUUUCK! It hurts {}, but also starts to soothe {}", - target.display_for_sentence(1, false), - &target.pronouns.object - ) - ) - }, - Effect::ChangeTargetParameter { - delay_secs: 10, base_effect: 2, skill_multiplier: 7.0, - max_effect: 9, - parameter: EffectParameter::Health, - message: Box::new( - |target| - format!( - "FUUUCK! It hurts {}, but also starts to soothe {}", - target.display_for_sentence(1, false), - &target.pronouns.object - ) - ) - }, - Effect::ChangeTargetParameter { - delay_secs: 20, base_effect: 1, skill_multiplier: 6.0, - max_effect: 7, - parameter: EffectParameter::Health, - message: Box::new( - |target| - format!( - "The bandages soothe {}'s wounds", - target.display_for_sentence(1, false), - ) - ) - }, - Effect::ChangeTargetParameter { - delay_secs: 30, base_effect: 1, skill_multiplier: 5.0, - parameter: EffectParameter::Health, - max_effect: 6, - message: Box::new( - |target| - format!( - "The bandages soothe {}'s wounds", - target.display_for_sentence(1, false), - ) - ) - }, - Effect::ChangeTargetParameter { - delay_secs: 40, base_effect: 0, skill_multiplier: 4.0, - max_effect: 4, - parameter: EffectParameter::Health, - message: Box::new( - |target| - format!( - "The bandages soothe {}'s wounds", - target.display_for_sentence(1, false), - ) - ) - }, - Effect::ChangeTargetParameter { - delay_secs: 50, base_effect: 0, skill_multiplier: 3.0, - max_effect: 3, - parameter: EffectParameter::Health, - message: Box::new( - |target| - format!( - "The bandages soothe {}'s wounds", - target.display_for_sentence(1, false), - ) - ) - }, - Effect::ChangeTargetParameter { - delay_secs: 60, base_effect: 0, skill_multiplier: 2.0, - max_effect: 2, - parameter: EffectParameter::Health, - message: Box::new( - |target| - format!( - "The bandages soothe {}'s wounds", - target.display_for_sentence(1, false), - ) - ) - }, - Effect::BroadcastMessage { - delay_secs: 60, - messagef: Box::new(|_player, _item, target| - format!( - "The bandages wrapping {} crumble and fall away, their healing capabilities fully expended.\n", - target.display_for_sentence(1, false) - ) - ) - } - ), - }), - errorf: Box::new( - |item, target| - if target.death_data.is_some() { - Some(format!("It is too late, {}'s dead", target.pronouns.subject)) - } else if target.item_type != "player" && target.item_type != "npc" { - Some("It only works on animals.".to_owned()) - } else if target.active_effects.iter().any(|e| e.0 == EffectType::Bandages) { - Some(format!( - "You see no reason to use {} on {}", - item.display_for_sentence(1, false), - target.display_for_sentence(1, false) - )) - } else { - None - }), - needs_consent_check: Some(ConsentType::Medicine), + use_data: Some(trau_use_data()), + becomes_on_spent: Some(EmptyMedicalBox), + ..Default::default() + } + ), + (LargeTraumaKit, + PossessionData { + display: "large trauma kit", + details: "A collection of bandages and and small gadgets that look like they could, in the right hands, make an unhealthy person healthy again. It looks like when brand new, it could be used 10 times.", + aliases: vec!("trauma"), + charge_data: Some(ChargeData { + max_charges: 10, + charge_name_prefix: "treatment", + charge_name_suffix: "worth of supplies", ..Default::default() }), + use_data: Some(trau_use_data()), becomes_on_spent: Some(EmptyMedicalBox), ..Default::default() } @@ -255,10 +303,11 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { delay_secs: 0, base_effect: -2, skill_multiplier: -3.0, max_effect: -5, parameter: EffectParameter::Health, + mitigations: vec![], message: Box::new( |target| format!( - "Fuck! The trauma kit makes {}'s condition worse", + "Fuck! The rad detox makes {}'s condition worse", target.display_for_sentence(1, false)) ) } @@ -304,6 +353,7 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { delay_secs: 0, base_effect: -200, skill_multiplier: -8.0, max_effect: -1000, parameter: EffectParameter::Raddamage, + mitigations: vec![], message: Box::new( |target| format!( @@ -316,6 +366,7 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { delay_secs: 10, base_effect: -200, skill_multiplier: -7.0, max_effect: -900, parameter: EffectParameter::Raddamage, + mitigations: vec![], message: Box::new( |target| format!( @@ -328,6 +379,7 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { delay_secs: 20, base_effect: -100, skill_multiplier: -6.0, max_effect: -700, parameter: EffectParameter::Raddamage, + mitigations: vec![], message: Box::new( |target| format!( @@ -340,6 +392,7 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { delay_secs: 30, base_effect: -100, skill_multiplier: -5.0, max_effect: -600, parameter: EffectParameter::Raddamage, + mitigations: vec![], message: Box::new( |target| format!( @@ -352,6 +405,7 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { delay_secs: 40, base_effect: -100, skill_multiplier: -4.0, max_effect: -600, parameter: EffectParameter::Raddamage, + mitigations: vec![], message: Box::new( |target| format!( @@ -364,6 +418,7 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { delay_secs: 50, base_effect: 0, skill_multiplier: -3.0, max_effect: -300, parameter: EffectParameter::Raddamage, + mitigations: vec![], message: Box::new( |target| format!( @@ -376,6 +431,7 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { delay_secs: 60, base_effect: 0, skill_multiplier: -2.0, max_effect: -200, parameter: EffectParameter::Raddamage, + mitigations: vec![], message: Box::new( |target| format!( @@ -416,6 +472,101 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { becomes_on_spent: Some(EmptyMedicalBox), ..Default::default() } - ) + ), + ( + VenomExtractionKit, + PossessionData { + display: "venom extraction kit", + details: "A medical box labelled \"Venom Extraction\". It seems to have some kind of polymer slime that can suck any kind of venom out of a bite or sting. It looks like, in the hands of a skilled medic, it might be useful for treating up to 5 envenomations", + aliases: vec!("extraction"), + charge_data: Some(ChargeData { + max_charges: 5, + charge_name_prefix: "treatment", + charge_name_suffix: "worth of supplies", + ..Default::default() + }), + use_data: Some(UseData { + uses_skill: SkillType::Medic, + diff_level: 12.0, + crit_fail_effects: Some(EffectSet { + effect_type: EffectType::Ephemeral, + effects: vec!( + Effect::BroadcastMessage { + delay_secs: 0, + messagef: Box::new(|player, _item, target| + format!( + "{} attempts to treat {} with a venom extraction kit, but fucks it up badly\n", + &player.display_for_sentence(1, true), + &if target.item_type == player.item_type && target.item_code == player.item_code { + player.pronouns.intensive.clone() + } else { + target.display_for_sentence(1, false) + } + ), + ) + }, + Effect::ChangeTargetParameter { + delay_secs: 0, base_effect: -2, skill_multiplier: -3.0, + max_effect: -5, + parameter: EffectParameter::Health, + mitigations: vec![], + message: Box::new( + |target| + format!( + "Fuck! The venom extraction makes {}'s condition worse", + target.display_for_sentence(1, false)) + ) + } + ) + }), + fail_effects: Some(EffectSet { + effect_type: EffectType::Ephemeral, + effects: vec!( + Effect::BroadcastMessage { + delay_secs: 0, + messagef: Box::new(|player, _item, target| + format!( + "{} attempts unsuccessfully to treat {} with a venom extraction kit\n", + &player.display_for_sentence(1, true), + &if target.item_type == player.item_type && target.item_code == player.item_code { + player.pronouns.intensive.clone() + } else { + target.display_for_sentence(1, false) + } + ) + ) + }, + ) + }), + success_effects: Some(EffectSet { + effect_type: EffectType::Ephemeral, + effects: vec!( + Effect::BroadcastMessage { + delay_secs: 0, + messagef: Box::new(|player, _item, target| + format!( + "{} expertly treats {} with a venom extraction kit\n", + &player.display_for_sentence(1, true), + &if target.item_type == player.item_type && target.item_code == player.item_code { + player.pronouns.intensive.clone() + } else { + target.display_for_sentence(1, false) + } + ) + ) + }, + Effect::CancelEffect { + other_effect: EffectType::SnakePoisoned, + }, + Effect::CancelEffect { + other_effect: EffectType::Stung, + } + ) + }), + needs_consent_check: Some(ConsentType::Medicine), + ..Default::default() + }), + ..Default::default() + }) )) } diff --git a/blastmud_game/src/static_content/room.rs b/blastmud_game/src/static_content/room.rs index 6f4c0f20..0f61aca7 100644 --- a/blastmud_game/src/static_content/room.rs +++ b/blastmud_game/src/static_content/room.rs @@ -7,7 +7,7 @@ use crate::db::DBTrans; use crate::{ message_handler::user_commands::{CommandHandlingError, UResult, VerbContext}, models::{ - effect::SimpleEffect, + effect::{EffectMitigation, SimpleEffect}, item::{DoorState, Item, ItemFlag}, journal::JournalType, user::WristpadHack, @@ -589,6 +589,7 @@ pub struct RoomEnvironment { pub temperature: u16, // in 100ths of degrees celsius. pub passive_health_message: String, pub passive_health: i16, // in health points/10s tick. Can be negative to harm. + pub passive_health_mitigation: Vec, } impl Default for RoomEnvironment { @@ -598,6 +599,7 @@ impl Default for RoomEnvironment { temperature: 2000, // 20 degrees celsius passive_health_message: "feels healthy".to_owned(), passive_health: 0, + passive_health_mitigation: vec![], } } } diff --git a/blastmud_game/src/static_content/room/general_hospital.rs b/blastmud_game/src/static_content/room/general_hospital.rs index c42ff8ab..4d99d0f6 100644 --- a/blastmud_game/src/static_content/room/general_hospital.rs +++ b/blastmud_game/src/static_content/room/general_hospital.rs @@ -120,6 +120,7 @@ impl TaskHandler for SeePatientTaskHandler { base_effect: 10, skill_multiplier: 0.0, parameter: EffectParameter::Health, + mitigations: vec![], max_effect: 10, message: Box::new(|_item| "That feels better".to_owned()), }, @@ -128,12 +129,14 @@ impl TaskHandler for SeePatientTaskHandler { base_effect: 9, skill_multiplier: 0.0, parameter: EffectParameter::Health, + mitigations: vec![], max_effect: 10, message: Box::new(|_item| "That feels better".to_owned()), }, Effect::ChangeTargetParameter { delay_secs: 20, parameter: EffectParameter::Health, + mitigations: vec![], base_effect: 8, skill_multiplier: 0.0, max_effect: 10, @@ -142,6 +145,7 @@ impl TaskHandler for SeePatientTaskHandler { Effect::ChangeTargetParameter { delay_secs: 30, parameter: EffectParameter::Health, + mitigations: vec![], base_effect: 7, skill_multiplier: 0.0, max_effect: 10, diff --git a/blastmud_game/src/static_content/room/melbs.rs b/blastmud_game/src/static_content/room/melbs.rs index 2b637b61..2b204395 100644 --- a/blastmud_game/src/static_content/room/melbs.rs +++ b/blastmud_game/src/static_content/room/melbs.rs @@ -21,6 +21,46 @@ pub fn street_scavtable() -> Vec { }] } +pub fn toxic_dump_scavtable() -> Vec { + vec![ + Scavinfo { + possession_type: PossessionType::CausticSoda, + p_present: 1.0, + difficulty_mean: 8.0, + difficulty_stdev: 1.0, + scavtype: Scavtype::Scavenge, + }, + Scavinfo { + possession_type: PossessionType::VinylAcetate, + p_present: 1.0, + difficulty_mean: 8.0, + difficulty_stdev: 1.0, + scavtype: Scavtype::Scavenge, + }, + Scavinfo { + possession_type: PossessionType::Azobisisobutyronitrile, + p_present: 1.0, + difficulty_mean: 8.0, + difficulty_stdev: 1.0, + scavtype: Scavtype::Scavenge, + }, + Scavinfo { + possession_type: PossessionType::Borax, + p_present: 1.0, + difficulty_mean: 8.0, + difficulty_stdev: 1.0, + scavtype: Scavtype::Scavenge, + }, + Scavinfo { + possession_type: PossessionType::HydrochloricAcid, + p_present: 1.0, + difficulty_mean: 8.0, + difficulty_stdev: 1.0, + scavtype: Scavtype::Scavenge, + }, + ] +} + pub fn fixed_items() -> Vec { vec![ FixedItem { diff --git a/blastmud_game/src/static_content/room/melbs.yaml b/blastmud_game/src/static_content/room/melbs.yaml index f655a013..49684b64 100644 --- a/blastmud_game/src/static_content/room/melbs.yaml +++ b/blastmud_game/src/static_content/room/melbs.yaml @@ -630,7 +630,7 @@ should_caption: false scavtable: CityStreet - zone: melbs - code: melbs_swansonst_10 + code: melbs_swanstonst_10 name: Swanston Street - 10 block short: || grid_coords: @@ -641,8 +641,49 @@ exits: - direction: north - direction: south + - direction: east should_caption: false scavtable: CityStreet +- zone: melbs + code: melbs_bhopal_laneway + name: Bhopal Laneway + short: == + grid_coords: + x: 18 + y: -4 + z: 0 + description: A laneway of slightly newer construction to the surrounding streets. If an empire-era plaque is to be believed, the laneway and attached chemical waste dump was built on the order of the emperor to celebrate his belief in the prioritisation of profitability for the elites over human health and safety, and the exemplary demonstration of this value by Union Carbide in Bhopal in the early 1980s. Apparently, as a further monument to these values, to the east he also ordered the construction of an enormous chemical waste dump to allow empire industries to cheaply dispose of unwanted toxic chemicals. It smells acrid here + repel_npc: true + exits: + - direction: east + - direction: west + should_caption: false + scavtable: CityStreet +- zone: melbs + code: melbs_toxic_dump_1 + name: Toxic Dump West + short: TD + grid_coords: + x: 19 + y: -4 + z: 0 + description: A yard that appears to be some kind of toxic waste dump, enclosed only by a chain link fence. Signs on the fence indicate that lethal toxic waste is buried here. Rusting drums lie in mud around the open air facility, while the dirt shows signs of having been disturbed + exits: + - direction: west + should_caption: true + scavtable: ToxicDump + environment: + passive_health: -5 + passive_health_message: The toxins work their way into the lungs + passive_health_mitigation: + - amplitude_multiplier: 500 # Half + mitigant: + !MitigantClothing + with_flag: HalvesToxicFumes + - amplitude_multiplier: 0 + mitigant: + !MitigantClothing + with_flag: ProtectsToxicFumes - zone: melbs code: melbs_swanstonst_20 name: Swanston Street - 20 block @@ -1454,8 +1495,29 @@ - direction: north - direction: south - direction: east + - direction: west should_caption: false scavtable: CityStreet +- zone: melbs + code: melbs_psi_eldritch + name: Psi Eldritch Scientific + short: PE + grid_coords: + x: 4 + y: 4 + z: 0 + description: |- + A warehouse-style shop that appears to sell all sorts of scientific equipment, much of it made of glass. Row after row of metal shelves house glassware in every imaginable shape. Another section has bottles of variously coloured liquids, many featuring triangular skull-and-crossbone pictograms on them. + + It smells slightly acrid here. + + Behind a counter, a serious looking tall man clad in a labcoat stands behind a counter, waiting patiently for you to select your equipment and pay him + exits: + - direction: east + stock_list: + - possession_type: ChemistrySet + list_price: 5000 + poverty_discount: false - zone: melbs code: melbs_dustypages name: The Dusty Pages @@ -1478,6 +1540,9 @@ - possession_type: CulinaryEssentials list_price: 200 poverty_discount: false + - possession_type: EverydayChemistry + list_price: 400 + poverty_discount: false - zone: melbs code: melbs_williamsst_80 name: Williams St - 80 block diff --git a/blastmud_game/src/static_content/scavtable.rs b/blastmud_game/src/static_content/scavtable.rs index 6724a4d9..4ec40a23 100644 --- a/blastmud_game/src/static_content/scavtable.rs +++ b/blastmud_game/src/static_content/scavtable.rs @@ -1,6 +1,9 @@ use crate::{ models::item::Scavtype, - static_content::room::{melbs_sewers::sewer_scavtable, ronalds_house::ronalds_house_scavtable}, + static_content::room::{ + melbs::toxic_dump_scavtable, melbs_sewers::sewer_scavtable, + ronalds_house::ronalds_house_scavtable, + }, }; use super::{possession_type::PossessionType, room::melbs::street_scavtable}; @@ -21,6 +24,7 @@ pub struct Scavinfo { pub enum ScavtableType { Nothing, CityStreet, + ToxicDump, CitySewer, RonaldsHouse, } @@ -31,6 +35,7 @@ pub fn scavtable_map() -> &'static BTreeMap> { vec![ (ScavtableType::Nothing, vec![]), (ScavtableType::CityStreet, street_scavtable()), + (ScavtableType::ToxicDump, toxic_dump_scavtable()), (ScavtableType::CitySewer, sewer_scavtable()), (ScavtableType::RonaldsHouse, ronalds_house_scavtable()), ]