diff --git a/blastmud_game/src/message_handler/user_commands/look.rs b/blastmud_game/src/message_handler/user_commands/look.rs index bd293cc..8a79bb1 100644 --- a/blastmud_game/src/message_handler/user_commands/look.rs +++ b/blastmud_game/src/message_handler/user_commands/look.rs @@ -255,20 +255,28 @@ pub async fn describe_normal_item( } if item.item_type == "possession" { - if let Some(charge_data) = item + if let Some(poss_data) = item .possession_type .as_ref() .and_then(|pt| possession_data().get(&pt)) - .and_then(|pd| pd.charge_data.as_ref()) { - let unit = if item.charges == 1 { - charge_data.charge_name_prefix.to_owned() + " " + charge_data.charge_name_suffix - } else { - language::pluralise(charge_data.charge_name_prefix) - + " " - + charge_data.charge_name_suffix - }; - contents_desc.push_str(&format!("It has {} {} left.\n", item.charges, unit)); + if let Some(describer) = poss_data.computed_extra_details { + contents_desc.push_str( + &describer + .describe_for(&ctx.trans, item, player_item) + .await?, + ); + } + if let Some(charge_data) = poss_data.charge_data.as_ref() { + let unit = if item.charges == 1 { + charge_data.charge_name_prefix.to_owned() + " " + charge_data.charge_name_suffix + } else { + language::pluralise(charge_data.charge_name_prefix) + + " " + + charge_data.charge_name_suffix + }; + contents_desc.push_str(&format!("It has {} {} left.\n", item.charges, unit)); + } } if let Some(recipe_craft_data) = item diff --git a/blastmud_game/src/message_handler/user_commands/remove.rs b/blastmud_game/src/message_handler/user_commands/remove.rs index 2719265..306e236 100644 --- a/blastmud_game/src/message_handler/user_commands/remove.rs +++ b/blastmud_game/src/message_handler/user_commands/remove.rs @@ -61,7 +61,7 @@ async fn check_removeable(ctx: &mut QueuedCommandContext<'_>, item: &Item) -> UR covers_parts.contains(&part) && other_item .action_type_started - .map(|other_worn_since| other_worn_since < my_worn_since) + .map(|other_worn_since| other_worn_since > my_worn_since) .unwrap_or(false) } } diff --git a/blastmud_game/src/regular_tasks.rs b/blastmud_game/src/regular_tasks.rs index b783e65..27b6455 100644 --- a/blastmud_game/src/regular_tasks.rs +++ b/blastmud_game/src/regular_tasks.rs @@ -12,7 +12,7 @@ use crate::{ services::{charging, combat, effect, environment, idlepark, sharing, spawn, tempbuff, urges}, static_content::{ npc::{self, computer_museum_npcs}, - possession_type::lights, + possession_type::utilities, room::general_hospital, }, DResult, @@ -70,7 +70,7 @@ fn task_handler_registry( ("IdlePark", idlepark::IDLEPARK_HANDLER), ("HospitalERSeePatient", general_hospital::SEE_PATIENT_TASK), ("ExpireBuff", tempbuff::EXPIRE_BUFF_TASK), - ("DischargeLight", lights::DISCHARGE_TASK), + ("DischargeLight", utilities::DISCHARGE_TASK), ("ChargeItem", charging::TASK_HANDLER), ( "ApplyEnvironmentalEffects", diff --git a/blastmud_game/src/services/combat.rs b/blastmud_game/src/services/combat.rs index 8bb210a..0741709 100644 --- a/blastmud_game/src/services/combat.rs +++ b/blastmud_game/src/services/combat.rs @@ -79,8 +79,9 @@ pub async fn soak_damage( break; } let soak_amount: f64 = ((soak.max_soak - soak.min_soak) - * rand::thread_rng().gen::()) - .min(damage_amount); + * rand::thread_rng().gen::() + + soak.min_soak) + .min(damage_amount); damage_amount -= soak_amount; let clothes_damage = ((0..(soak_amount as i64)) .filter(|_| rand::thread_rng().gen::() < soak.damage_probability_per_soak) diff --git a/blastmud_game/src/services/environment.rs b/blastmud_game/src/services/environment.rs index b6db859..aca6d8b 100644 --- a/blastmud_game/src/services/environment.rs +++ b/blastmud_game/src/services/environment.rs @@ -3,6 +3,7 @@ use std::time; use async_trait::async_trait; use chrono::Utc; use log::warn; +use rand::Rng; use uuid::Uuid; use crate::{ @@ -10,14 +11,17 @@ use crate::{ movement::attempt_move_immediate, CommandHandlingError, UResult, }, models::{ - item::{Item, SkillType}, + item::{Item, LocationActionType, SkillType}, task::{Task, TaskDetails, TaskMeta}, }, regular_tasks::{ queued_command::{MovementSource, QueueCommand, QueuedCommandContext}, TaskHandler, TaskRunContext, }, - static_content::room::{room_map_by_code, Direction, MaterialType, Room}, + static_content::{ + possession_type::{possession_data, DamageType}, + room::{room_map_by_code, Direction, MaterialType, Room}, + }, DResult, }; @@ -257,7 +261,28 @@ pub async fn rad_environment_effects( return Ok(false); } - player_item.raddamage += room.environment.radiation; + let clothes = ctx + .trans + .find_by_action_and_location(&player_item.refstr(), &LocationActionType::Worn) + .await?; + let shielding: u64 = clothes + .iter() + .filter_map(|c| { + c.possession_type + .as_ref() + .and_then(|pt| possession_data().get(&pt)) + .and_then(|pd| pd.wear_data.as_ref()) + .and_then(|wd| wd.soaks.get(&DamageType::Radiation)) + .map(|sd| { + (sd.max_soak - sd.min_soak) * rand::thread_rng().gen::() + sd.min_soak + }) + }) + .sum::() as u64; + if shielding >= room.environment.radiation { + return Ok(false); + } + + player_item.raddamage += room.environment.radiation - shielding; let existing_task = ctx .trans .fetch_specific_task("ApplyEnvironmentalConsequences", &player_item.refstr()) diff --git a/blastmud_game/src/static_content/possession_type.rs b/blastmud_game/src/static_content/possession_type.rs index ccfeed6..166d74e 100644 --- a/blastmud_game/src/static_content/possession_type.rs +++ b/blastmud_game/src/static_content/possession_type.rs @@ -29,13 +29,13 @@ mod food; pub mod head_armour; mod junk; mod keys; -pub mod lights; pub mod lock; pub mod lower_armour; mod meat; mod testpapers; pub mod torso_armour; mod trauma_kit; +pub mod utilities; mod whip; pub type AttackMessageChoice = Vec String + 'static + Sync + Send>>; @@ -57,6 +57,7 @@ pub enum DamageType { Pierce, Shock, Bullet, + Radiation, } impl DamageType { @@ -69,6 +70,7 @@ impl DamageType { Pierce => "pierce", Shock => "shock", Bullet => "bullet", + Radiation => "radiation", } } } @@ -323,6 +325,11 @@ pub struct SitData { pub stress_impact: i16, } +#[async_trait] +pub trait Describer { + async fn describe_for(&self, trans: &DBTrans, item: &Item, player: &Item) -> UResult; +} + pub struct PossessionData { pub weapon_data: Option, pub display: &'static str, @@ -347,6 +354,7 @@ pub struct PossessionData { pub eat_data: Option, pub sit_data: Option, pub static_special_data: Option, + pub computed_extra_details: Option<&'static (dyn Describer + Sync + Send)>, } impl Default for PossessionData { @@ -375,6 +383,7 @@ impl Default for PossessionData { eat_data: None, sit_data: None, static_special_data: None, + computed_extra_details: None, } } } @@ -402,13 +411,14 @@ pub enum PossessionType { // Special values that substitute for possessions. Fangs, // Default weapon for certain animals // Real possessions from here on: - // Armour + // Armour / Clothes RustyMetalPot, HockeyMask, Shirt, LeatherJacket, Jeans, LeatherPants, + RadSuit, // Weapons: Whips AntennaWhip, LeatherWhip, @@ -454,8 +464,9 @@ pub enum PossessionType { // Recipes CulinaryEssentials, GrilledSteakRecipe, - // Lights + // Utilities ElectricLantern, + GeigerCounter, } impl Into for PossessionType { @@ -579,7 +590,7 @@ pub fn possession_data() -> &'static BTreeMap &'static Vec<(PossessionType, PossessionData)> { ..Default::default() } ), + ( + 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", + aliases: vec!("rad suit", "radsuit"), + weight: 2000, + wear_data: Some(WearData { + covers_parts: vec!( + BodyPart::Head, + BodyPart::Face, + BodyPart::Hands, + BodyPart::Arms, + BodyPart::Chest, + BodyPart::Back, + BodyPart::Groin, + BodyPart::Legs, + BodyPart::Feet + ), + thickness: 4.0, + dodge_penalty: 1.0, + soaks: vec!( + (DamageType::Radiation, + SoakData { + min_soak: 500.0, + max_soak: 600.0, + damage_probability_per_soak: 0.01 + } + ), + ).into_iter().collect(), + }), + ..Default::default() + } + ), )) } diff --git a/blastmud_game/src/static_content/possession_type/lights.rs b/blastmud_game/src/static_content/possession_type/utilities.rs similarity index 70% rename from blastmud_game/src/static_content/possession_type/lights.rs rename to blastmud_game/src/static_content/possession_type/utilities.rs index 02e40dd..6788567 100644 --- a/blastmud_game/src/static_content/possession_type/lights.rs +++ b/blastmud_game/src/static_content/possession_type/utilities.rs @@ -1,3 +1,5 @@ +#[double] +use crate::db::DBTrans; use crate::{ message_handler::user_commands::{user_error, UResult, VerbContext}, models::{ @@ -6,16 +8,17 @@ use crate::{ }, regular_tasks::{TaskHandler, TaskRunContext}, services::comms::broadcast_to_room, - static_content::possession_type::ChargeData, + static_content::{possession_type::ChargeData, room::room_map_by_code}, DResult, }; use async_trait::async_trait; use chrono::Utc; use log::warn; +use mockall_double::double; use once_cell::sync::OnceCell; use std::time; -use super::{PossessionData, PossessionType, TurnToggleHandler}; +use super::{Describer, PossessionData, PossessionType, TurnToggleHandler}; pub struct LanternHandler {} @@ -163,21 +166,55 @@ impl TaskHandler for DischargeTaskHandler { } pub static DISCHARGE_TASK: &(dyn TaskHandler + Sync + Send) = &DischargeTaskHandler; +pub struct GeigerCounterDescriber; + +#[async_trait] +impl Describer for GeigerCounterDescriber { + async fn describe_for(&self, _trans: &DBTrans, _item: &Item, player: &Item) -> UResult { + let (loc_type, loc_code) = match player.location.split_once("/") { + None => return Ok("It doesn't currently show a reading.\n".to_owned()), + Some(v) => v, + }; + let level = if loc_type != "room" { + 0 + } else { + room_map_by_code() + .get(loc_code) + .map(|r| r.environment.radiation) + .unwrap_or(0) + }; + Ok(format!( + "It shows a reading of {} millisieverts/hour.\n", + level * 360 + )) + } +} + pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { static D: OnceCell> = OnceCell::new(); - &D.get_or_init(|| vec![(PossessionType::ElectricLantern, PossessionData { - display: "electric lantern", - aliases: vec!["lantern"], - details: "A rechargeable electric lantern. It is made of yellow plastic, interspersed with banks of tiny flat white LEDs. It looks like it would illuminate a dark room well, although it probably isn't bright enough to let you see anything in the next room over.", - weight: 300, - turn_toggle_handler: Some(&LANTERN_HANDLER), - charge_data: Some(ChargeData { - max_charges: 20, - charge_name_prefix: "bar", - charge_name_suffix: "of power", - electric_recharge: true, - ..ChargeData::default() + &D.get_or_init(|| vec![ + (PossessionType::ElectricLantern, PossessionData { + display: "electric lantern", + aliases: vec!["lantern"], + details: "A rechargeable electric lantern. It is made of yellow plastic, interspersed with banks of tiny flat white LEDs. It looks like it would illuminate a dark room well, although it probably isn't bright enough to let you see anything in the next room over.", + weight: 300, + turn_toggle_handler: Some(&LANTERN_HANDLER), + charge_data: Some(ChargeData { + max_charges: 20, + charge_name_prefix: "bar", + charge_name_suffix: "of power", + electric_recharge: true, + ..ChargeData::default() + }), + ..Default::default() }), - ..Default::default() - })]) + (PossessionType::GeigerCounter, PossessionData { + display: "geiger counter", + aliases: vec!["counter"], + details: "A sturdy black rectangular box with a yellow rubber protective case. On the front is a display showing numbers. Beneath the display is the text: 'OORANS approved Geiger Counter. Calibrated to predict effective dose of radiation from exposure to standard fallout. For use by trained personnel only'. ", + weight: 300, + computed_extra_details: Some(&GeigerCounterDescriber), + ..Default::default() + }) + ]) } diff --git a/blastmud_game/src/static_content/room/melbs.yaml b/blastmud_game/src/static_content/room/melbs.yaml index 3ad0269..2f80411 100644 --- a/blastmud_game/src/static_content/room/melbs.yaml +++ b/blastmud_game/src/static_content/room/melbs.yaml @@ -1631,9 +1631,10 @@ y: 0 z: 0 exits: - - direction: west - - direction: east - direction: north + - direction: east + - direction: south + - direction: west - zone: oorans code: oorans_training name: OORANS Training Centre @@ -1695,8 +1696,10 @@ exits: - direction: north stock_list: - - possession_type: !RadSafetyTest - list_price: 1000 + - possession_type: !GeigerCounter + list_price: 2500 + - possession_type: !RadSuit + list_price: 4000 - zone: melbs code: melbs_williamsst_collinsst name: Williams St & Collins St