Add Geiger Counter and Radsuit

This commit is contained in:
Condorra 2024-04-04 21:35:29 +11:00
parent f37baf187e
commit 36086b809c
9 changed files with 161 additions and 42 deletions

View File

@ -255,12 +255,19 @@ 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())
{
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 {
@ -270,6 +277,7 @@ pub async fn describe_normal_item(
};
contents_desc.push_str(&format!("It has {} {} left.\n", item.charges, unit));
}
}
if let Some(recipe_craft_data) = item
.possession_type

View File

@ -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)
}
}

View File

@ -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",

View File

@ -79,7 +79,8 @@ pub async fn soak_damage<DamageDist: DamageDistribution>(
break;
}
let soak_amount: f64 = ((soak.max_soak - soak.min_soak)
* rand::thread_rng().gen::<f64>())
* rand::thread_rng().gen::<f64>()
+ soak.min_soak)
.min(damage_amount);
damage_amount -= soak_amount;
let clothes_damage = ((0..(soak_amount as i64))

View File

@ -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::<f64>() + sd.min_soak
})
})
.sum::<f64>() 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())

View File

@ -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<Box<dyn Fn(&Item, &Item) -> 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<String>;
}
pub struct PossessionData {
pub weapon_data: Option<WeaponData>,
pub display: &'static str,
@ -347,6 +354,7 @@ pub struct PossessionData {
pub eat_data: Option<EatData>,
pub sit_data: Option<SitData>,
pub static_special_data: Option<ItemStaticSpecialData>,
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<Item> for PossessionType {
@ -579,7 +590,7 @@ pub fn possession_data() -> &'static BTreeMap<PossessionType, &'static Possessio
.map(|v| ((*v).0.clone(), &(*v).1)),
)
.chain(books::data().iter().map(|v| ((*v).0.clone(), &(*v).1)))
.chain(lights::data().iter().map(|v| ((*v).0.clone(), &(*v).1)))
.chain(utilities::data().iter().map(|v| ((*v).0.clone(), &(*v).1)))
.collect()
})
}

View File

@ -65,5 +65,39 @@ pub fn data() -> &'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()
}
),
))
}

View File

@ -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,9 +166,34 @@ 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<String> {
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<Vec<(PossessionType, PossessionData)>> = OnceCell::new();
&D.get_or_init(|| vec![(PossessionType::ElectricLantern, PossessionData {
&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.",
@ -179,5 +207,14 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
..ChargeData::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()
})
])
}

View File

@ -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