Add rad detox + assorted fixups

Now ChangeTargetHealth effect is generalised to ChangeTargetParameter
that can also change raddamage (and in the future could cover other
things).
Also fixes a long-standing lmap bug - only show exits where both ends
are in the zone of the current room, rather than just requiring one of
the rooms to be in the zone.
This commit is contained in:
Condorra 2024-04-07 00:37:39 +11:00
parent 36086b809c
commit 5d008bb52a
9 changed files with 344 additions and 82 deletions

View File

@ -108,6 +108,20 @@ pub fn render_map_dyn(
buf buf
} }
fn has_exit_to_dir_in_zone(room: &Option<&&room::Room>, zone: &str, direction: &Direction) -> bool {
match room.as_ref().and_then(|r| {
r.exits
.iter()
.find(|ex| ex.direction == *direction)
.and_then(|ex| room::resolve_exit(r, ex))
}) {
None => false,
Some(other_room) => {
other_room.zone == zone || other_room.secondary_zones.iter().any(|z| z.zone == zone)
}
}
}
pub fn render_lmap( pub fn render_lmap(
room: &room::Room, room: &room::Room,
width: usize, width: usize,
@ -174,38 +188,25 @@ pub fn render_lmap(
} }
} }
} }
match coord_room.and_then(|r| r.exits.iter().find(|ex| ex.direction == Direction::EAST)) if has_exit_to_dir_in_zone(&coord_room, &room.zone, &Direction::EAST) {
{ buf.push('-')
None => buf.push(' '), } else {
Some(_) => buf.push('-'), buf.push(' ')
} }
} }
buf.push('\n'); buf.push('\n');
for x in min_x..max_x { for x in min_x..max_x {
let mut coord = room::GridCoords { x, y, z: my_loc.z }; let mut coord = room::GridCoords { x, y, z: my_loc.z };
let coord_room = room::room_map_by_zloc().get(&(&room.zone, &coord)); let coord_room = room::room_map_by_zloc().get(&(&room.zone, &coord));
match coord_room if has_exit_to_dir_in_zone(&coord_room, &room.zone, &Direction::SOUTH) {
.and_then(|r| r.exits.iter().find(|ex| ex.direction == Direction::SOUTH)) buf.push_str(" | ");
{ } else {
None => buf.push_str(" "), buf.push_str(" ");
Some(_) => buf.push_str(" | "),
} }
let has_se = coord_room let has_se = has_exit_to_dir_in_zone(&coord_room, &room.zone, &Direction::SOUTHEAST);
.and_then(|r| {
r.exits
.iter()
.find(|ex| ex.direction == Direction::SOUTHEAST)
})
.is_some();
coord.x += 1; coord.x += 1;
let coord_room_s = room::room_map_by_zloc().get(&(&room.zone, &coord)); let coord_room_s = room::room_map_by_zloc().get(&(&room.zone, &coord));
let has_sw = coord_room_s let has_sw = has_exit_to_dir_in_zone(&coord_room_s, &room.zone, &Direction::SOUTHWEST);
.and_then(|r| {
r.exits
.iter()
.find(|ex| ex.direction == Direction::SOUTHWEST)
})
.is_some();
if has_se && has_sw { if has_se && has_sw {
buf.push('X'); buf.push('X');
} else if has_se { } else if has_se {

View File

@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
pub enum EffectType { pub enum EffectType {
Ephemeral, // i.e. no enduring impact to show in status. Ephemeral, // i.e. no enduring impact to show in status.
Bandages, Bandages,
RadDetox,
Bleed, Bleed,
Stunned, Stunned,
CurrentRoom, CurrentRoom,
@ -16,6 +17,12 @@ pub struct EffectSet {
pub effects: Vec<Effect>, pub effects: Vec<Effect>,
} }
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum EffectParameter {
Health,
Raddamage,
}
pub enum Effect { pub enum Effect {
// messagef takes player, causative item, target as the 3 parameters. Returns message. // messagef takes player, causative item, target as the 3 parameters. Returns message.
BroadcastMessage { BroadcastMessage {
@ -27,11 +34,12 @@ pub enum Effect {
messagef: Box<dyn Fn(&Item, &Item, &Item) -> String + Sync + Send>, messagef: Box<dyn Fn(&Item, &Item, &Item) -> String + Sync + Send>,
}, },
// skill_multiplier is always positive - sign flipped for crit fails. // skill_multiplier is always positive - sign flipped for crit fails.
ChangeTargetHealth { ChangeTargetParameter {
delay_secs: u64, delay_secs: u64,
base_effect: i64, base_effect: i64,
skill_multiplier: f64, skill_multiplier: f64,
max_effect: i64, max_effect: i64,
parameter: EffectParameter,
message: Box<dyn Fn(&Item) -> String + Sync + Send>, message: Box<dyn Fn(&Item) -> String + Sync + Send>,
}, },
} }
@ -47,11 +55,12 @@ pub enum SimpleEffect {
message: String, message: String,
}, },
// skill_multiplier is always positive - sign flipped for crit fails. // skill_multiplier is always positive - sign flipped for crit fails.
ChangeTargetHealth { ChangeTargetParameter {
delay_secs: u64, delay_secs: u64,
base_effect: i64, base_effect: i64,
skill_multiplier: f64, skill_multiplier: f64,
max_effect: i64, max_effect: i64,
parameter: EffectParameter,
message: String, message: String,
}, },
} }
@ -79,19 +88,21 @@ impl From<&SimpleEffect> for Effect {
messagef: Box::new(move |_, _, _| messagem.clone()), messagef: Box::new(move |_, _, _| messagem.clone()),
} }
} }
SimpleEffect::ChangeTargetHealth { SimpleEffect::ChangeTargetParameter {
delay_secs, delay_secs,
base_effect, base_effect,
skill_multiplier, skill_multiplier,
max_effect, max_effect,
parameter,
message, message,
} => { } => {
let messagem = parse_ansi_markup(message).unwrap() + "\n"; let messagem = parse_ansi_markup(message).unwrap() + "\n";
Effect::ChangeTargetHealth { Effect::ChangeTargetParameter {
delay_secs: *delay_secs, delay_secs: *delay_secs,
base_effect: *base_effect, base_effect: *base_effect,
skill_multiplier: *skill_multiplier, skill_multiplier: *skill_multiplier,
max_effect: *max_effect, max_effect: *max_effect,
parameter: parameter.clone(),
message: Box::new(move |_| messagem.clone()), message: Box::new(move |_| messagem.clone()),
} }
} }

View File

@ -1,4 +1,4 @@
use crate::services::effect::{DelayedHealthEffect, DelayedMessageEffect}; use crate::services::effect::{DelayedMessageEffect, DelayedParameterEffect};
use crate::static_content::room::Direction; use crate::static_content::room::Direction;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -34,9 +34,9 @@ pub enum TaskDetails {
RotCorpse { RotCorpse {
corpse_code: String, corpse_code: String,
}, },
DelayedHealth { DelayedParameter {
item: String, item: String,
effect_series: VecDeque<DelayedHealthEffect>, effect_series: VecDeque<DelayedParameterEffect>,
}, },
DelayedMessage { DelayedMessage {
item: String, item: String,
@ -103,7 +103,7 @@ impl TaskDetails {
ShareTick => "ShareTick", ShareTick => "ShareTick",
RecloneNPC { .. } => "RecloneNPC", RecloneNPC { .. } => "RecloneNPC",
RotCorpse { .. } => "RotCorpse", RotCorpse { .. } => "RotCorpse",
DelayedHealth { .. } => "DelayedHealth", DelayedParameter { .. } => "DelayedParameter",
DelayedMessage { .. } => "DelayedMessage", DelayedMessage { .. } => "DelayedMessage",
DispelEffect { .. } => "DispelEffect", DispelEffect { .. } => "DispelEffect",
ExpireItem { .. } => "ExpireItem", ExpireItem { .. } => "ExpireItem",

View File

@ -56,7 +56,7 @@ fn task_handler_registry(
("ShareTick", sharing::TASK_HANDLER), ("ShareTick", sharing::TASK_HANDLER),
("RecloneNPC", npc::RECLONE_HANDLER), ("RecloneNPC", npc::RECLONE_HANDLER),
("RotCorpse", combat::ROT_CORPSE_HANDLER), ("RotCorpse", combat::ROT_CORPSE_HANDLER),
("DelayedHealth", effect::DELAYED_HEALTH_HANDLER), ("DelayedParameter", effect::DELAYED_PARAMETER_HANDLER),
("DelayedMessage", effect::DELAYED_MESSAGE_HANDLER), ("DelayedMessage", effect::DELAYED_MESSAGE_HANDLER),
("DispelEffect", effect::DISPEL_EFFECT_HANDLER), ("DispelEffect", effect::DISPEL_EFFECT_HANDLER),
("ExpireItem", drop::EXPIRE_ITEM_HANDLER), ("ExpireItem", drop::EXPIRE_ITEM_HANDLER),

View File

@ -3,7 +3,7 @@ use super::{combat::change_health, comms::broadcast_to_room};
use crate::db::DBTrans; use crate::db::DBTrans;
use crate::{ use crate::{
models::{ models::{
effect::{Effect, EffectSet, EffectType}, effect::{Effect, EffectParameter, EffectSet, EffectType},
item::Item, item::Item,
task::{Task, TaskDetails, TaskMeta}, task::{Task, TaskDetails, TaskMeta},
}, },
@ -22,9 +22,10 @@ use std::collections::{BTreeMap, VecDeque};
use std::time; use std::time;
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct DelayedHealthEffect { pub struct DelayedParameterEffect {
magnitude: i64, magnitude: i64,
delay: u64, delay: u64,
parameter: EffectParameter,
message: String, message: String,
} }
@ -35,21 +36,21 @@ pub struct DelayedMessageEffect {
is_direct: bool, is_direct: bool,
} }
pub struct DelayedHealthTaskHandler; pub struct DelayedParameterTaskHandler;
#[async_trait] #[async_trait]
impl TaskHandler for DelayedHealthTaskHandler { impl TaskHandler for DelayedParameterTaskHandler {
async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> { async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
let ref mut item_effect_series = match &mut ctx.task.details { let ref mut item_effect_series = match &mut ctx.task.details {
TaskDetails::DelayedHealth { TaskDetails::DelayedParameter {
item, item,
ref mut effect_series, ref mut effect_series,
} => (item, effect_series), } => (item, effect_series),
_ => Err("Expected DelayedHealth type")?, _ => Err("Expected DelayedParameter type")?,
}; };
let (item_type, item_code) = match item_effect_series.0.split_once("/") { let (item_type, item_code) = match item_effect_series.0.split_once("/") {
None => { None => {
info!( info!(
"Invalid item {} to DelayedHealthTaskHandler", "Invalid item {} to DelayedParameterTaskHandler",
item_effect_series.0 item_effect_series.0
); );
return Ok(None); return Ok(None);
@ -71,11 +72,22 @@ impl TaskHandler for DelayedHealthTaskHandler {
} }
match item_effect_series.1.pop_front() { match item_effect_series.1.pop_front() {
None => Ok(None), None => Ok(None),
Some(DelayedHealthEffect { Some(DelayedParameterEffect {
magnitude, message, .. magnitude,
message,
parameter,
..
}) => { }) => {
let mut item_mut = (*item).clone(); let mut item_mut = (*item).clone();
change_health(ctx.trans, magnitude, &mut item_mut, &message).await?; match parameter {
EffectParameter::Health => {
change_health(ctx.trans, magnitude, &mut item_mut, &message).await?;
()
}
EffectParameter::Raddamage => {
item_mut.raddamage = (item_mut.raddamage as i64 + magnitude).max(0) as u64
}
}
ctx.trans.save_item_model(&item_mut).await?; ctx.trans.save_item_model(&item_mut).await?;
Ok(item_effect_series Ok(item_effect_series
.1 .1
@ -85,8 +97,8 @@ impl TaskHandler for DelayedHealthTaskHandler {
} }
} }
} }
pub static DELAYED_HEALTH_HANDLER: &'static (dyn TaskHandler + Sync + Send) = pub static DELAYED_PARAMETER_HANDLER: &'static (dyn TaskHandler + Sync + Send) =
&DelayedHealthTaskHandler; &DelayedParameterTaskHandler;
pub struct DelayedMessageTaskHandler; pub struct DelayedMessageTaskHandler;
#[async_trait] #[async_trait]
@ -196,7 +208,7 @@ pub async fn run_effects(
mut target: Option<&mut Item>, mut target: Option<&mut Item>,
level: f64, level: f64,
) -> DResult<()> { ) -> DResult<()> {
let mut target_health_series = BTreeMap::<String, VecDeque<DelayedHealthEffect>>::new(); let mut target_health_series = BTreeMap::<String, VecDeque<DelayedParameterEffect>>::new();
let mut target_message_series = BTreeMap::<String, VecDeque<DelayedMessageEffect>>::new(); let mut target_message_series = BTreeMap::<String, VecDeque<DelayedMessageEffect>>::new();
let mut dispel_time_secs: u64 = 0; let mut dispel_time_secs: u64 = 0;
@ -267,30 +279,46 @@ pub async fn run_effects(
.or_insert(VecDeque::from([fx])); .or_insert(VecDeque::from([fx]));
} }
} }
Effect::ChangeTargetHealth { Effect::ChangeTargetParameter {
delay_secs, delay_secs,
base_effect, base_effect,
skill_multiplier, skill_multiplier,
max_effect, max_effect,
parameter,
message, message,
} => { } => {
let health_impact = let param_impact = *base_effect + ((skill_multiplier * level) as i64);
(*base_effect + ((skill_multiplier * level) as i64)).min(*max_effect) as i64; let param_impact = if *max_effect >= 0 {
param_impact.min(*max_effect)
} else {
param_impact.max(*max_effect)
};
let msg = message(target.as_ref().map(|t| &**t).unwrap_or(player)); let msg = message(target.as_ref().map(|t| &**t).unwrap_or(player));
if *delay_secs == 0 { if *delay_secs == 0 {
change_health( match parameter {
trans, EffectParameter::Health => {
health_impact, change_health(
*target.as_mut().unwrap_or(&mut player), trans,
&msg, param_impact,
) *target.as_mut().unwrap_or(&mut player),
.await?; &msg,
)
.await?;
()
}
EffectParameter::Raddamage => {
let eff_target = target.as_mut().unwrap_or(&mut player);
eff_target.raddamage =
(eff_target.raddamage as i64 + param_impact).max(0) as u64
}
}
} else { } else {
dispel_time_secs = dispel_time_secs.max(*delay_secs); dispel_time_secs = dispel_time_secs.max(*delay_secs);
let target_it = target.as_ref().map(|t| &**t).unwrap_or(player); let target_it = target.as_ref().map(|t| &**t).unwrap_or(player);
let fx = DelayedHealthEffect { let fx = DelayedParameterEffect {
magnitude: health_impact, magnitude: param_impact,
delay: *delay_secs, delay: *delay_secs,
parameter: (*parameter).clone(),
message: msg, message: msg,
}; };
target_health_series target_health_series
@ -313,7 +341,8 @@ pub async fn run_effects(
.upsert_task(&Task { .upsert_task(&Task {
meta: TaskMeta { meta: TaskMeta {
task_code: format!("{}/{}", &actual_target.refstr(), task_ref), task_code: format!("{}/{}", &actual_target.refstr(), task_ref),
next_scheduled: Utc::now() + chrono::TimeDelta::try_seconds(dispel_time_secs as i64).unwrap(), next_scheduled: Utc::now()
+ chrono::TimeDelta::try_seconds(dispel_time_secs as i64).unwrap(),
..Default::default() ..Default::default()
}, },
details: TaskDetails::DispelEffect { details: TaskDetails::DispelEffect {
@ -329,10 +358,11 @@ pub async fn run_effects(
.upsert_task(&Task { .upsert_task(&Task {
meta: TaskMeta { meta: TaskMeta {
task_code: format!("{}/{}", eff_item, trans.alloc_task_code().await?), task_code: format!("{}/{}", eff_item, trans.alloc_task_code().await?),
next_scheduled: Utc::now() + chrono::TimeDelta::try_seconds(l[0].delay as i64).unwrap(), next_scheduled: Utc::now()
+ chrono::TimeDelta::try_seconds(l[0].delay as i64).unwrap(),
..Default::default() ..Default::default()
}, },
details: TaskDetails::DelayedHealth { details: TaskDetails::DelayedParameter {
effect_series: l, effect_series: l,
item: eff_item, item: eff_item,
}, },
@ -344,7 +374,8 @@ pub async fn run_effects(
.upsert_task(&Task { .upsert_task(&Task {
meta: TaskMeta { meta: TaskMeta {
task_code: format!("{}/{}", eff_item, task_ref), task_code: format!("{}/{}", eff_item, task_ref),
next_scheduled: Utc::now() + chrono::TimeDelta::try_seconds(l[0].delay as i64).unwrap(), next_scheduled: Utc::now()
+ chrono::TimeDelta::try_seconds(l[0].delay as i64).unwrap(),
..Default::default() ..Default::default()
}, },
details: TaskDetails::DelayedMessage { details: TaskDetails::DelayedMessage {
@ -374,7 +405,7 @@ pub async fn cancel_effect(
.await?; .await?;
trans trans
.delete_task( .delete_task(
"DelayedHealth", "DelayedParameter",
&format!( &format!(
"{}/{}/{}", "{}/{}/{}",
&character.item_type, &character.item_code, effect.1 &character.item_type, &character.item_code, effect.1
@ -409,11 +440,12 @@ pub fn default_effects_for_type() -> &'static BTreeMap<EffectType, EffectSet> {
) )
) )
}, },
Effect::ChangeTargetHealth { Effect::ChangeTargetParameter {
delay_secs: 10, delay_secs: 10,
base_effect: -12, base_effect: -12,
skill_multiplier: 0.0, skill_multiplier: 0.0,
max_effect: -12, max_effect: -12,
parameter: EffectParameter::Health,
message: Box::new(|target| message: Box::new(|target|
format!("{} pulses from {}'s wound", format!("{} pulses from {}'s wound",
if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" }, if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" },
@ -421,11 +453,12 @@ pub fn default_effects_for_type() -> &'static BTreeMap<EffectType, EffectSet> {
) )
) )
}, },
Effect::ChangeTargetHealth { Effect::ChangeTargetParameter {
delay_secs: 20, delay_secs: 20,
base_effect: -10, base_effect: -10,
skill_multiplier: 0.0, skill_multiplier: 0.0,
max_effect: -10, max_effect: -10,
parameter: EffectParameter::Health,
message: Box::new(|target| message: Box::new(|target|
format!("{} pulses from {}'s wound", format!("{} pulses from {}'s wound",
if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" }, if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" },
@ -433,11 +466,12 @@ pub fn default_effects_for_type() -> &'static BTreeMap<EffectType, EffectSet> {
) )
) )
}, },
Effect::ChangeTargetHealth { Effect::ChangeTargetParameter {
delay_secs: 30, delay_secs: 30,
base_effect: -8, base_effect: -8,
skill_multiplier: 0.0, skill_multiplier: 0.0,
max_effect: -8, max_effect: -8,
parameter: EffectParameter::Health,
message: Box::new(|target| message: Box::new(|target|
format!("{} pulses from {}'s wound", format!("{} pulses from {}'s wound",
if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" }, if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" },
@ -445,11 +479,12 @@ pub fn default_effects_for_type() -> &'static BTreeMap<EffectType, EffectSet> {
) )
) )
}, },
Effect::ChangeTargetHealth { Effect::ChangeTargetParameter {
delay_secs: 40, delay_secs: 40,
base_effect: -6, base_effect: -6,
skill_multiplier: 0.0, skill_multiplier: 0.0,
max_effect: -6, max_effect: -6,
parameter: EffectParameter::Health,
message: Box::new(|target| message: Box::new(|target|
format!("{} pulses from {}'s wound", format!("{} pulses from {}'s wound",
if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" }, if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" },
@ -457,11 +492,12 @@ pub fn default_effects_for_type() -> &'static BTreeMap<EffectType, EffectSet> {
) )
) )
}, },
Effect::ChangeTargetHealth { Effect::ChangeTargetParameter {
delay_secs: 50, delay_secs: 50,
base_effect: -4, base_effect: -4,
skill_multiplier: 0.0, skill_multiplier: 0.0,
max_effect: -4, max_effect: -4,
parameter: EffectParameter::Health,
message: Box::new(|target| message: Box::new(|target|
format!("{} pulses from {}'s wound", format!("{} pulses from {}'s wound",
if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" }, if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" },
@ -469,11 +505,12 @@ pub fn default_effects_for_type() -> &'static BTreeMap<EffectType, EffectSet> {
) )
) )
}, },
Effect::ChangeTargetHealth { Effect::ChangeTargetParameter {
delay_secs: 60, delay_secs: 60,
base_effect: -2, base_effect: -2,
skill_multiplier: 0.0, skill_multiplier: 0.0,
max_effect: -2, max_effect: -2,
parameter: EffectParameter::Health,
message: Box::new(|target| message: Box::new(|target|
format!("A final tiny drop of {} oozes from {}'s wound as it heals", format!("A final tiny drop of {} oozes from {}'s wound as it heals",
if target.species == SpeciesType::Robot { "coolant" } else { "blood" }, if target.species == SpeciesType::Robot { "coolant" } else { "blood" },

View File

@ -437,6 +437,7 @@ pub enum PossessionType {
// Medical // Medical
MediumTraumaKit, MediumTraumaKit,
EmptyMedicalBox, EmptyMedicalBox,
RadDetox,
// Corporate // Corporate
NewCorpLicence, NewCorpLicence,
CertificateOfIncorporation, CertificateOfIncorporation,

View File

@ -1,7 +1,7 @@
use super::{ChargeData, PossessionData, PossessionType, UseData}; use super::{ChargeData, PossessionData, PossessionType, UseData};
use crate::models::{ use crate::models::{
consent::ConsentType, consent::ConsentType,
effect::{Effect, EffectSet, EffectType}, effect::{Effect, EffectParameter, EffectSet, EffectType},
item::SkillType, item::SkillType,
}; };
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
@ -42,9 +42,10 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
), ),
) )
}, },
Effect::ChangeTargetHealth { Effect::ChangeTargetParameter {
delay_secs: 0, base_effect: -2, skill_multiplier: -3.0, delay_secs: 0, base_effect: -2, skill_multiplier: -3.0,
max_effect: -5, max_effect: -5,
parameter: EffectParameter::Health,
message: Box::new( message: Box::new(
|target| |target|
format!( format!(
@ -90,9 +91,10 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
) )
) )
}, },
Effect::ChangeTargetHealth { Effect::ChangeTargetParameter {
delay_secs: 0, base_effect: 2, skill_multiplier: 8.0, delay_secs: 0, base_effect: 2, skill_multiplier: 8.0,
max_effect: 10, max_effect: 10,
parameter: EffectParameter::Health,
message: Box::new( message: Box::new(
|target| |target|
format!( format!(
@ -102,9 +104,10 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
) )
) )
}, },
Effect::ChangeTargetHealth { Effect::ChangeTargetParameter {
delay_secs: 10, base_effect: 2, skill_multiplier: 7.0, delay_secs: 10, base_effect: 2, skill_multiplier: 7.0,
max_effect: 9, max_effect: 9,
parameter: EffectParameter::Health,
message: Box::new( message: Box::new(
|target| |target|
format!( format!(
@ -114,9 +117,10 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
) )
) )
}, },
Effect::ChangeTargetHealth { Effect::ChangeTargetParameter {
delay_secs: 20, base_effect: 1, skill_multiplier: 6.0, delay_secs: 20, base_effect: 1, skill_multiplier: 6.0,
max_effect: 7, max_effect: 7,
parameter: EffectParameter::Health,
message: Box::new( message: Box::new(
|target| |target|
format!( format!(
@ -125,8 +129,9 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
) )
) )
}, },
Effect::ChangeTargetHealth { Effect::ChangeTargetParameter {
delay_secs: 30, base_effect: 1, skill_multiplier: 5.0, delay_secs: 30, base_effect: 1, skill_multiplier: 5.0,
parameter: EffectParameter::Health,
max_effect: 6, max_effect: 6,
message: Box::new( message: Box::new(
|target| |target|
@ -136,9 +141,10 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
) )
) )
}, },
Effect::ChangeTargetHealth { Effect::ChangeTargetParameter {
delay_secs: 40, base_effect: 0, skill_multiplier: 4.0, delay_secs: 40, base_effect: 0, skill_multiplier: 4.0,
max_effect: 4, max_effect: 4,
parameter: EffectParameter::Health,
message: Box::new( message: Box::new(
|target| |target|
format!( format!(
@ -147,9 +153,10 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
) )
) )
}, },
Effect::ChangeTargetHealth { Effect::ChangeTargetParameter {
delay_secs: 50, base_effect: 0, skill_multiplier: 3.0, delay_secs: 50, base_effect: 0, skill_multiplier: 3.0,
max_effect: 3, max_effect: 3,
parameter: EffectParameter::Health,
message: Box::new( message: Box::new(
|target| |target|
format!( format!(
@ -158,9 +165,10 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
) )
) )
}, },
Effect::ChangeTargetHealth { Effect::ChangeTargetParameter {
delay_secs: 60, base_effect: 0, skill_multiplier: 2.0, delay_secs: 60, base_effect: 0, skill_multiplier: 2.0,
max_effect: 2, max_effect: 2,
parameter: EffectParameter::Health,
message: Box::new( message: Box::new(
|target| |target|
format!( format!(
@ -210,6 +218,204 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
aliases: vec!("box"), aliases: vec!("box"),
..Default::default() ..Default::default()
} }
),
(
RadDetox,
PossessionData {
display: "rad detox kit",
details: "A medical box labelled \"Radiation Detox\". It looks like, in the hands of a skilled medic, it might be useful for treating someone suffering from acute radiation poisoning",
aliases: vec!("detox"),
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 rad detox 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 treat {} with a rad detox 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::RadDetox,
effects: vec!(
Effect::BroadcastMessage {
delay_secs: 0,
messagef: Box::new(|player, _item, target|
format!(
"{} expertly treats {} with a rad detox 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: -200, skill_multiplier: -8.0,
max_effect: -1000,
parameter: EffectParameter::Raddamage,
message: Box::new(
|target|
format!(
"A drip from a rad detox kit taped to {}'s arm bubbles away",
target.display_for_sentence(1, false),
)
)
},
Effect::ChangeTargetParameter {
delay_secs: 10, base_effect: -200, skill_multiplier: -7.0,
max_effect: -900,
parameter: EffectParameter::Raddamage,
message: Box::new(
|target|
format!(
"A drip from a rad detox kit taped to {}'s arm bubbles away",
target.display_for_sentence(1, false),
)
)
},
Effect::ChangeTargetParameter {
delay_secs: 20, base_effect: -100, skill_multiplier: -6.0,
max_effect: -700,
parameter: EffectParameter::Raddamage,
message: Box::new(
|target|
format!(
"A drip from a rad detox kit taped to {}'s arm bubbles away",
target.display_for_sentence(1, false),
)
)
},
Effect::ChangeTargetParameter {
delay_secs: 30, base_effect: -100, skill_multiplier: -5.0,
max_effect: -600,
parameter: EffectParameter::Raddamage,
message: Box::new(
|target|
format!(
"A drip from a rad detox kit taped to {}'s arm bubbles away",
target.display_for_sentence(1, false),
)
)
},
Effect::ChangeTargetParameter {
delay_secs: 40, base_effect: -100, skill_multiplier: -4.0,
max_effect: -600,
parameter: EffectParameter::Raddamage,
message: Box::new(
|target|
format!(
"A drip from a rad detox kit taped to {}'s arm bubbles away",
target.display_for_sentence(1, false),
)
)
},
Effect::ChangeTargetParameter {
delay_secs: 50, base_effect: 0, skill_multiplier: -3.0,
max_effect: -300,
parameter: EffectParameter::Raddamage,
message: Box::new(
|target|
format!(
"A drip from a rad detox kit taped to {}'s arm bubbles away",
target.display_for_sentence(1, false),
)
)
},
Effect::ChangeTargetParameter {
delay_secs: 60, base_effect: 0, skill_multiplier: -2.0,
max_effect: -200,
parameter: EffectParameter::Raddamage,
message: Box::new(
|target|
format!(
"A drip from a rad detox kit taped to {}'s arm bubbles away",
target.display_for_sentence(1, false),
)
)
},
Effect::BroadcastMessage {
delay_secs: 60,
messagef: Box::new(|_player, _item, target|
format!(
"The rad detox drip attached to {} crumbles and fall away, their detoxifying 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()
}),
becomes_on_spent: Some(EmptyMedicalBox),
..Default::default()
}
) )
)) ))
} }

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
message_handler::user_commands::UResult, message_handler::user_commands::UResult,
models::{ models::{
effect::{Effect, EffectSet, EffectType}, effect::{Effect, EffectParameter, EffectSet, EffectType},
task::{Task, TaskDetails, TaskMeta}, task::{Task, TaskDetails, TaskMeta},
}, },
regular_tasks::{queued_command::QueuedCommandContext, TaskHandler, TaskRunContext}, regular_tasks::{queued_command::QueuedCommandContext, TaskHandler, TaskRunContext},
@ -115,29 +115,33 @@ impl TaskHandler for SeePatientTaskHandler {
&EffectSet { &EffectSet {
effect_type: EffectType::Bandages, effect_type: EffectType::Bandages,
effects: vec![ effects: vec![
Effect::ChangeTargetHealth { Effect::ChangeTargetParameter {
delay_secs: 0, delay_secs: 0,
base_effect: 10, base_effect: 10,
skill_multiplier: 0.0, skill_multiplier: 0.0,
parameter: EffectParameter::Health,
max_effect: 10, max_effect: 10,
message: Box::new(|_item| "That feels better".to_owned()), message: Box::new(|_item| "That feels better".to_owned()),
}, },
Effect::ChangeTargetHealth { Effect::ChangeTargetParameter {
delay_secs: 10, delay_secs: 10,
base_effect: 9, base_effect: 9,
skill_multiplier: 0.0, skill_multiplier: 0.0,
parameter: EffectParameter::Health,
max_effect: 10, max_effect: 10,
message: Box::new(|_item| "That feels better".to_owned()), message: Box::new(|_item| "That feels better".to_owned()),
}, },
Effect::ChangeTargetHealth { Effect::ChangeTargetParameter {
delay_secs: 20, delay_secs: 20,
parameter: EffectParameter::Health,
base_effect: 8, base_effect: 8,
skill_multiplier: 0.0, skill_multiplier: 0.0,
max_effect: 10, max_effect: 10,
message: Box::new(|_item| "That feels better".to_owned()), message: Box::new(|_item| "That feels better".to_owned()),
}, },
Effect::ChangeTargetHealth { Effect::ChangeTargetParameter {
delay_secs: 30, delay_secs: 30,
parameter: EffectParameter::Health,
base_effect: 7, base_effect: 7,
skill_multiplier: 0.0, skill_multiplier: 0.0,
max_effect: 10, max_effect: 10,

View File

@ -1700,6 +1700,8 @@
list_price: 2500 list_price: 2500
- possession_type: !RadSuit - possession_type: !RadSuit
list_price: 4000 list_price: 4000
- possession_type: !RadDetox
list_price: 100
- zone: melbs - zone: melbs
code: melbs_williamsst_collinsst code: melbs_williamsst_collinsst
name: Williams St & Collins St name: Williams St & Collins St