Improve effect system, including messages when effects wear off.

This commit is contained in:
Condorra 2023-10-14 22:23:06 +11:00
parent b06f045d10
commit 83a18cfd63
12 changed files with 517 additions and 307 deletions

View File

@ -9,9 +9,12 @@ use crate::db::DBTrans;
use crate::{
db::ItemSearchParams,
language,
models::item::{
DoorState, Item, ItemFlag, ItemSpecialData, LiquidDetails, LiquidType, LocationActionType,
Subattack,
models::{
effect::EffectType,
item::{
DoorState, Item, ItemFlag, ItemSpecialData, LiquidDetails, LiquidType,
LocationActionType, Subattack,
},
},
services::{combat::max_health, skills::calc_level_gap},
static_content::{
@ -225,14 +228,7 @@ pub async fn describe_normal_item(
));
}
if ctx
.trans
.check_task_by_type_code(
"DelayedHealth",
&format!("{}/{}/bandage", &item.item_type, &item.item_code),
)
.await?
{
if item.active_effects.contains(&EffectType::Bandages) {
contents_desc.push_str(&format!(
"{} is wrapped up in bandages.\n",
&language::caps_first(&item.pronouns.subject)

View File

@ -224,24 +224,6 @@ impl QueueCommandHandler for QueueHandler {
if let Some(err) = (use_data.errorf)(&item, &target) {
user_error(err)?;
}
if ctx
.trans
.check_task_by_type_code(
"DelayedHealth",
&format!(
"{}/{}/{}",
&target.item_type, &target.item_code, use_data.task_ref
),
)
.await?
{
let explicit = ctx.explicit().await?;
user_error(format!(
"You see no reason to use {} on {}",
item.display_for_sentence(explicit, 1, false),
target.display_for_sentence(explicit, 1, false)
))?;
}
let is_self_use = target_type == &"player" && target_code == &ctx.item.item_code;
let skillcheck = skill_check_and_grind(
&ctx.trans,
@ -264,16 +246,18 @@ impl QueueCommandHandler for QueueHandler {
} else {
Some((*target).clone())
};
run_effects(
ctx.trans,
&effects,
ctx.item,
&item,
&mut target_mut,
skilllvl,
use_data.task_ref,
)
.await?;
if let Some(actual_effects) = effects {
run_effects(
ctx.trans,
&actual_effects,
ctx.item,
&item,
&mut target_mut,
skilllvl,
use_data.task_ref,
)
.await?;
}
if let Some(target_mut_save) = target_mut {
ctx.trans.save_item_model(&target_mut_save).await?;
}

View File

@ -1,5 +1,6 @@
pub mod consent;
pub mod corp;
pub mod effect;
pub mod item;
pub mod journal;
pub mod session;

View File

@ -0,0 +1,29 @@
use super::item::Item;
use serde::{Deserialize, Serialize};
#[derive(PartialEq, Eq, PartialOrd, Clone, Serialize, Deserialize, Debug, Ord)]
pub enum EffectType {
Ephemeral, // i.e. no enduring impact to show in status.
Bandages,
}
pub struct EffectSet {
pub effect_type: EffectType,
pub effects: Vec<Effect>,
}
pub enum Effect {
// messagef takes player, causative item, target as the 3 parameters. Returns (explicit, non explicit) message.
BroadcastMessage {
delay_secs: u64,
messagef: Box<dyn Fn(&Item, &Item, &Item) -> (String, String) + Sync + Send>,
},
// skill_multiplier is always positive - sign flipped for crit fails.
ChangeTargetHealth {
delay_secs: u64,
base_effect: i64,
skill_multiplier: f64,
max_effect: i64,
message: Box<dyn Fn(&Item) -> (String, String) + Sync + Send>,
},
}

View File

@ -1,4 +1,4 @@
use super::session::Session;
use super::{effect::EffectType, session::Session};
use crate::{
language,
regular_tasks::queued_command::QueueCommand,
@ -495,6 +495,7 @@ pub struct Item {
pub queue: VecDeque<QueueCommand>,
pub urges: Option<Urges>,
pub liquid_details: Option<LiquidDetails>,
pub active_effects: Vec<EffectType>,
}
impl Item {
@ -618,6 +619,7 @@ impl Default for Item {
queue: VecDeque::new(),
urges: None,
liquid_details: None,
active_effects: vec![],
}
}
}

View File

@ -1,10 +1,12 @@
use crate::services::effect::DelayedHealthEffect;
use crate::services::effect::{DelayedHealthEffect, DelayedMessageEffect};
use crate::static_content::room::Direction;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::VecDeque;
use super::effect::EffectType;
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum TaskRecurrence {
FixedDuration { seconds: u32 },
@ -35,6 +37,10 @@ pub enum TaskDetails {
item: String,
effect_series: VecDeque<DelayedHealthEffect>,
},
DelayedMessage {
item: String,
effect_series: VecDeque<DelayedMessageEffect>,
},
ExpireItem {
item_code: String,
},
@ -58,6 +64,10 @@ pub enum TaskDetails {
HospitalERSeePatient {
item: String,
},
DispelEffect {
target: String,
effect_type: EffectType,
},
}
impl TaskDetails {
pub fn name(self: &Self) -> &'static str {
@ -71,6 +81,8 @@ impl TaskDetails {
RecloneNPC { .. } => "RecloneNPC",
RotCorpse { .. } => "RotCorpse",
DelayedHealth { .. } => "DelayedHealth",
DelayedMessage { .. } => "DelayedMessage",
DispelEffect { .. } => "DispelEffect",
ExpireItem { .. } => "ExpireItem",
ChargeRoom { .. } => "ChargeRoom",
SwingShut { .. } => "SwingShut",

View File

@ -55,6 +55,8 @@ fn task_handler_registry(
("RecloneNPC", npc::RECLONE_HANDLER),
("RotCorpse", combat::ROT_CORPSE_HANDLER),
("DelayedHealth", effect::DELAYED_HEALTH_HANDLER),
("DelayedMessage", effect::DELAYED_MESSAGE_HANDLER),
("DispelEffect", effect::DISPEL_EFFECT_HANDLER),
("ExpireItem", drop::EXPIRE_ITEM_HANDLER),
("ChargeRoom", rent::CHARGE_ROOM_HANDLER),
("SwingShut", open::SWING_SHUT_HANDLER),

View File

@ -3,11 +3,11 @@ use super::{combat::change_health, comms::broadcast_to_room};
use crate::db::DBTrans;
use crate::{
models::{
effect::{Effect, EffectSet, EffectType},
item::Item,
task::{Task, TaskDetails, TaskMeta},
},
regular_tasks::{TaskHandler, TaskRunContext},
static_content::possession_type::UseEffect,
DResult,
};
use async_trait::async_trait;
@ -26,6 +26,13 @@ pub struct DelayedHealthEffect {
message_nonexp: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct DelayedMessageEffect {
delay: u64,
message: String,
message_nonexp: String,
}
pub struct DelayedHealthTaskHandler;
#[async_trait]
impl TaskHandler for DelayedHealthTaskHandler {
@ -89,9 +96,105 @@ impl TaskHandler for DelayedHealthTaskHandler {
pub static DELAYED_HEALTH_HANDLER: &'static (dyn TaskHandler + Sync + Send) =
&DelayedHealthTaskHandler;
pub struct DelayedMessageTaskHandler;
#[async_trait]
impl TaskHandler for DelayedMessageTaskHandler {
async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
let ref mut item_effect_series = match &mut ctx.task.details {
TaskDetails::DelayedMessage {
item,
ref mut effect_series,
} => (item, effect_series),
_ => Err("Expected DelayedMessage type")?,
};
let (item_type, item_code) = match item_effect_series.0.split_once("/") {
None => {
info!(
"Invalid item {} to DelayedMessageTaskHandler",
item_effect_series.0
);
return Ok(None);
}
Some((item_type, item_code)) => (item_type, item_code),
};
let item = match ctx
.trans
.find_item_by_type_code(item_type, item_code)
.await?
{
None => {
return Ok(None);
}
Some(it) => it,
};
if item.death_data.is_some() {
return Ok(None);
}
match item_effect_series.1.pop_front() {
None => Ok(None),
Some(DelayedMessageEffect {
message,
message_nonexp,
..
}) => {
broadcast_to_room(
&ctx.trans,
&item.location,
None,
&message,
Some(&message_nonexp),
)
.await?;
Ok(item_effect_series
.1
.front()
.map(|it| time::Duration::from_secs(it.delay)))
}
}
}
}
pub static DELAYED_MESSAGE_HANDLER: &'static (dyn TaskHandler + Sync + Send) =
&DelayedMessageTaskHandler;
pub struct DispelEffectTaskHandler;
#[async_trait]
impl TaskHandler for DispelEffectTaskHandler {
async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
let (target_str, effect_type) = match ctx.task.details {
TaskDetails::DispelEffect {
target: ref t,
effect_type: ref e,
} => (t.as_str(), e),
_ => Err("Expected DispelEffect type")?,
};
let (target_type, target_code) = match target_str.split_once("/") {
None => Err("Invalid DispelEffect target")?,
Some(v) => v,
};
let target = match ctx
.trans
.find_item_by_type_code(target_type, target_code)
.await?
{
None => return Ok(None),
Some(t) => t,
};
let mut target_mut = (*target).clone();
target_mut
.active_effects
.iter()
.position(|e| e == effect_type)
.map(|p| target_mut.active_effects.remove(p));
ctx.trans.save_item_model(&target_mut).await?;
Ok(None)
}
}
pub static DISPEL_EFFECT_HANDLER: &'static (dyn TaskHandler + Sync + Send) =
&DispelEffectTaskHandler;
pub async fn run_effects(
trans: &DBTrans,
effects: &Vec<UseEffect>,
effects: &EffectSet,
player: &mut Item,
item: &Item,
// None if target is player
@ -100,15 +203,34 @@ pub async fn run_effects(
task_ref: &str,
) -> DResult<()> {
let mut target_health_series = BTreeMap::<String, VecDeque<DelayedHealthEffect>>::new();
for effect in effects {
let mut target_message_series = BTreeMap::<String, VecDeque<DelayedMessageEffect>>::new();
let mut dispel_time_secs: u64 = 0;
for effect in &effects.effects {
match effect {
UseEffect::BroadcastMessage { messagef } => {
Effect::BroadcastMessage {
delay_secs,
messagef,
} => {
let (msg_exp, msg_nonexp) =
messagef(player, item, target.as_ref().unwrap_or(player));
broadcast_to_room(trans, &player.location, None, &msg_exp, Some(&msg_nonexp))
.await?;
if *delay_secs == 0 {
broadcast_to_room(trans, &player.location, None, &msg_exp, Some(&msg_nonexp))
.await?;
} else {
dispel_time_secs = dispel_time_secs.max(*delay_secs);
let target_it = target.as_ref().unwrap_or(player);
let fx = DelayedMessageEffect {
delay: *delay_secs,
message: msg_exp,
message_nonexp: msg_nonexp,
};
target_message_series
.entry(format!("{}/{}", target_it.item_type, target_it.item_code))
.and_modify(|l| l.push_back(fx.clone()))
.or_insert(VecDeque::from([fx]));
}
}
UseEffect::ChangeTargetHealth {
Effect::ChangeTargetHealth {
delay_secs,
base_effect,
skill_multiplier,
@ -128,6 +250,7 @@ pub async fn run_effects(
)
.await?;
} else {
dispel_time_secs = dispel_time_secs.max(*delay_secs);
let target_it = target.as_ref().unwrap_or(player);
let fx = DelayedHealthEffect {
magnitude: health_impact,
@ -144,6 +267,25 @@ pub async fn run_effects(
}
}
if dispel_time_secs > 0 && effects.effect_type != EffectType::Ephemeral {
let target_item = target.as_mut().unwrap_or(player);
target_item.active_effects.push(effects.effect_type.clone());
trans
.upsert_task(&Task {
meta: TaskMeta {
task_code: format!("{}/{}", &target_item.refstr(), task_ref),
next_scheduled: Utc::now() + chrono::Duration::seconds(dispel_time_secs as i64),
..Default::default()
},
details: TaskDetails::DispelEffect {
target: target_item.refstr(),
effect_type: effects.effect_type.clone(),
},
})
.await?;
}
for (eff_item, l) in target_health_series.into_iter() {
trans
.upsert_task(&Task {
@ -159,6 +301,21 @@ pub async fn run_effects(
})
.await?;
}
for (eff_item, l) in target_message_series.into_iter() {
trans
.upsert_task(&Task {
meta: TaskMeta {
task_code: format!("{}/{}", eff_item, task_ref),
next_scheduled: Utc::now() + chrono::Duration::seconds(l[0].delay as i64),
..Default::default()
},
details: TaskDetails::DelayedMessage {
effect_series: l,
item: eff_item,
},
})
.await?;
}
Ok(())
}

View File

@ -2,8 +2,11 @@
use crate::db::DBTrans;
use crate::{
message_handler::user_commands::{UResult, VerbContext},
models::consent::ConsentType,
models::item::{Item, ItemFlag, LiquidType, Pronouns, SkillType},
models::{
consent::ConsentType,
effect::EffectSet,
item::{Item, ItemFlag, LiquidType, Pronouns, SkillType},
},
regular_tasks::queued_command::QueuedCommandContext,
static_content::{room::Direction, species::BodyPart},
};
@ -160,27 +163,12 @@ impl Default for ChargeData {
}
}
pub enum UseEffect {
// messagef takes player, item used, target as the 3 parameters. Returns (explicit, non explicit) message.
BroadcastMessage {
messagef: Box<dyn Fn(&Item, &Item, &Item) -> (String, String) + Sync + Send>,
},
// skill_multiplier is always positive - sign flipped for crit fails.
ChangeTargetHealth {
delay_secs: u64,
base_effect: i64,
skill_multiplier: f64,
max_effect: i64,
message: Box<dyn Fn(&Item) -> (String, String) + Sync + Send>,
},
}
pub struct UseData {
pub uses_skill: SkillType,
pub diff_level: f64,
pub crit_fail_effects: Vec<UseEffect>,
pub fail_effects: Vec<UseEffect>,
pub success_effects: Vec<UseEffect>,
pub crit_fail_effects: Option<EffectSet>,
pub fail_effects: Option<EffectSet>,
pub success_effects: Option<EffectSet>,
pub errorf: Box<dyn Fn(&Item, &Item) -> Option<String> + Sync + Send>,
pub task_ref: &'static str,
pub needs_consent_check: Option<ConsentType>,
@ -191,9 +179,9 @@ impl Default for UseData {
Self {
uses_skill: SkillType::Medic,
diff_level: 10.0,
crit_fail_effects: vec![],
fail_effects: vec![],
success_effects: vec![],
crit_fail_effects: None,
fail_effects: None,
success_effects: None,
errorf: Box::new(|_it, _target| None),
task_ref: "set me",
needs_consent_check: None,

View File

@ -1,5 +1,9 @@
use super::{ChargeData, PossessionData, PossessionType, UseData, UseEffect};
use crate::models::{consent::ConsentType, item::SkillType};
use super::{ChargeData, PossessionData, PossessionType, UseData};
use crate::models::{
consent::ConsentType,
effect::{Effect, EffectSet, EffectType},
item::SkillType,
};
use once_cell::sync::OnceCell;
use super::PossessionType::*;
@ -21,202 +25,232 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
use_data: Some(UseData {
uses_skill: SkillType::Medic,
diff_level: 10.0,
crit_fail_effects: vec!(
UseEffect::BroadcastMessage {
messagef: Box::new(|player, _item, target| (
format!(
"{} attempts to heal {} with a trauma kit, but fucks it up badly\n",
&player.display_for_sentence(true, 1, true),
&if target.item_type == player.item_type && target.item_code == player.item_code {
player.pronouns.intensive.clone()
} else {
target.display_for_sentence(true, 1, false)
}
),
format!("{} attempts to heal {} with a trauma kit, but messes it up badly\n",
&player.display_for_sentence(false, 1, true),
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(true, 1, true),
&if target.item_type == player.item_type && target.item_code == player.item_code {
player.pronouns.intensive.clone()
} else {
target.display_for_sentence(false, 1, false)
target.display_for_sentence(true, 1, false)
}
)))
},
UseEffect::ChangeTargetHealth {
delay_secs: 0, base_effect: -2, skill_multiplier: -3.0,
max_effect: -5,
message: Box::new(
|target|
(format!(
"Fuck! The trauma kit makes {}'s condition worse",
target.display_for_sentence(true, 1, false)),
format!(
"The trauma kit makes {}'s condition worse",
target.display_for_sentence(false, 1, false)
)
))
}
),
fail_effects: vec!(
UseEffect::BroadcastMessage {
messagef: Box::new(|player, _item, target| (
format!(
"{} attempts unsuccessfully to heal {} with a trauma kit\n",
&player.display_for_sentence(true, 1, true),
&if target.item_type == player.item_type && target.item_code == player.item_code {
player.pronouns.intensive.clone()
} else {
target.display_for_sentence(true, 1, false)
}
),
format!("{} attempts unsuccessfully to heal {} with a trauma kit\n",
&player.display_for_sentence(false, 1, true),
),
format!("{} attempts to heal {} with a trauma kit, but messes it up badly\n",
&player.display_for_sentence(false, 1, true),
&if target.item_type == player.item_type && target.item_code == player.item_code {
player.pronouns.intensive.clone()
} else {
target.display_for_sentence(false, 1, false)
}
)))
},
Effect::ChangeTargetHealth {
delay_secs: 0, base_effect: -2, skill_multiplier: -3.0,
max_effect: -5,
message: Box::new(
|target|
(format!(
"Fuck! The trauma kit makes {}'s condition worse",
target.display_for_sentence(true, 1, false)),
format!(
"The trauma kit makes {}'s condition worse",
target.display_for_sentence(false, 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(true, 1, true),
&if target.item_type == player.item_type && target.item_code == player.item_code {
player.pronouns.intensive.clone()
} else {
target.display_for_sentence(true, 1, false)
}
),
format!("{} attempts unsuccessfully to heal {} with a trauma kit\n",
&player.display_for_sentence(false, 1, true),
&if target.item_type == player.item_type && target.item_code == player.item_code {
player.pronouns.intensive.clone()
} else {
target.display_for_sentence(false, 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(true, 1, true),
&if target.item_type == player.item_type && target.item_code == player.item_code {
player.pronouns.intensive.clone()
} else {
target.display_for_sentence(false, 1, false)
target.display_for_sentence(true, 1, false)
}
)))
},
),
success_effects: vec!(
UseEffect::BroadcastMessage {
messagef: Box::new(|player, _item, target| (
format!(
"{} expertly heals {} with a trauma kit\n",
&player.display_for_sentence(true, 1, true),
&if target.item_type == player.item_type && target.item_code == player.item_code {
player.pronouns.intensive.clone()
} else {
),
format!("{} expertly heals {} with a trauma kit\n",
&player.display_for_sentence(false, 1, true),
&if target.item_type == player.item_type && target.item_code == player.item_code {
player.pronouns.intensive.clone()
} else {
target.display_for_sentence(false, 1, false)
}
)))
},
Effect::ChangeTargetHealth {
delay_secs: 0, base_effect: 2, skill_multiplier: 8.0,
max_effect: 10,
message: Box::new(
|target|
(format!(
"FUUUCK! It hurts {}, but also starts to soothe {}",
target.display_for_sentence(true, 1, false),
&target.pronouns.object
),
format!(
"It hurts {}, but also starts to soothe {}",
target.display_for_sentence(true, 1, false),
&target.pronouns.object
))
)
},
Effect::ChangeTargetHealth {
delay_secs: 10, base_effect: 2, skill_multiplier: 7.0,
max_effect: 9,
message: Box::new(
|target|
(format!(
"FUUUCK! It hurts {}, but also starts to soothe {}",
target.display_for_sentence(true, 1, false),
&target.pronouns.object
),
format!(
"It hurts {}, but also starts to soothe {}",
target.display_for_sentence(true, 1, false),
&target.pronouns.object
))
)
},
Effect::ChangeTargetHealth {
delay_secs: 20, base_effect: 1, skill_multiplier: 6.0,
max_effect: 7,
message: Box::new(
|target|
(format!(
"The bandages soothe {}'s wounds",
target.display_for_sentence(true, 1, false),
),
format!(
"The bandages soothe {}'s wounds",
target.display_for_sentence(false, 1, false),
))
)
},
Effect::ChangeTargetHealth {
delay_secs: 30, base_effect: 1, skill_multiplier: 5.0,
max_effect: 6,
message: Box::new(
|target|
(format!(
"The bandages soothe {}'s wounds",
target.display_for_sentence(true, 1, false),
),
format!(
"The bandages soothe {}'s wounds",
target.display_for_sentence(false, 1, false),
))
)
},
Effect::ChangeTargetHealth {
delay_secs: 40, base_effect: 0, skill_multiplier: 4.0,
max_effect: 4,
message: Box::new(
|target|
(format!(
"The bandages soothe {}'s wounds",
target.display_for_sentence(true, 1, false),
),
format!(
"The bandages soothe {}'s wounds",
target.display_for_sentence(false, 1, false),
))
)
},
Effect::ChangeTargetHealth {
delay_secs: 50, base_effect: 0, skill_multiplier: 3.0,
max_effect: 3,
message: Box::new(
|target|
(format!(
"The bandages soothe {}'s wounds",
target.display_for_sentence(true, 1, false),
),
format!(
"The bandages soothe {}'s wounds",
target.display_for_sentence(false, 1, false),
))
)
},
Effect::ChangeTargetHealth {
delay_secs: 60, base_effect: 0, skill_multiplier: 2.0,
max_effect: 2,
message: Box::new(
|target|
(format!(
"The bandages soothe {}'s wounds",
target.display_for_sentence(true, 1, false),
),
format!(
"The bandages soothe {}'s wounds",
target.display_for_sentence(false, 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(true, 1, false)
}
),
format!("{} expertly heals {} with a trauma kit\n",
&player.display_for_sentence(false, 1, true),
&if target.item_type == player.item_type && target.item_code == player.item_code {
player.pronouns.intensive.clone()
} else {
target.display_for_sentence(false, 1, false)
}
)))
},
UseEffect::ChangeTargetHealth {
delay_secs: 0, base_effect: 2, skill_multiplier: 8.0,
max_effect: 10,
message: Box::new(
|target|
(format!(
"FUUUCK! It hurts {}, but also starts to soothe {}",
target.display_for_sentence(true, 1, false),
&target.pronouns.object
),
format!(
"It hurts {}, but also starts to soothe {}",
target.display_for_sentence(true, 1, false),
&target.pronouns.object
))
)
},
UseEffect::ChangeTargetHealth {
delay_secs: 10, base_effect: 2, skill_multiplier: 7.0,
max_effect: 9,
message: Box::new(
|target|
(format!(
"FUUUCK! It hurts {}, but also starts to soothe {}",
target.display_for_sentence(true, 1, false),
&target.pronouns.object
),
format!(
"It hurts {}, but also starts to soothe {}",
target.display_for_sentence(true, 1, false),
&target.pronouns.object
))
)
},
UseEffect::ChangeTargetHealth {
delay_secs: 20, base_effect: 1, skill_multiplier: 6.0,
max_effect: 7,
message: Box::new(
|target|
(format!(
"The bandages soothe {}'s wounds",
target.display_for_sentence(true, 1, false),
),
format!(
"The bandages soothe {}'s wounds",
target.display_for_sentence(false, 1, false),
))
)
},
UseEffect::ChangeTargetHealth {
delay_secs: 30, base_effect: 1, skill_multiplier: 5.0,
max_effect: 6,
message: Box::new(
|target|
(format!(
"The bandages soothe {}'s wounds",
target.display_for_sentence(true, 1, false),
),
format!(
"The bandages soothe {}'s wounds",
target.display_for_sentence(false, 1, false),
))
)
},
UseEffect::ChangeTargetHealth {
delay_secs: 40, base_effect: 0, skill_multiplier: 4.0,
max_effect: 4,
message: Box::new(
|target|
(format!(
"The bandages soothe {}'s wounds",
target.display_for_sentence(true, 1, false),
),
format!(
"The bandages soothe {}'s wounds",
target.display_for_sentence(false, 1, false),
))
)
},
UseEffect::ChangeTargetHealth {
delay_secs: 50, base_effect: 0, skill_multiplier: 3.0,
max_effect: 3,
message: Box::new(
|target|
(format!(
"The bandages soothe {}'s wounds",
target.display_for_sentence(true, 1, false),
),
format!(
"The bandages soothe {}'s wounds",
target.display_for_sentence(false, 1, false),
))
)
},
UseEffect::ChangeTargetHealth {
delay_secs: 60, base_effect: 0, skill_multiplier: 2.0,
max_effect: 2,
message: Box::new(
|target|
(format!(
"The bandages soothe {}'s wounds",
target.display_for_sentence(true, 1, false),
),
format!(
"The bandages soothe {}'s wounds",
target.display_for_sentence(false, 1, false),
))
)
},
),
),
format!(
"The bandages wrapping {} crumble and fall away, their healing capabilities fully expended.\n",
target.display_for_sentence(false, 1, false)
)))
}
),
}),
task_ref: "bandage",
errorf: Box::new(
|_item, target|
|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.contains(&EffectType::Bandages) {
Some(format!(
"You see no reason to use {} on {}",
item.display_for_sentence(false, 1, false),
target.display_for_sentence(false, 1, false)
))
} else {
None
}),

View File

@ -82,7 +82,7 @@
z: -1
description: |
This appears to be a security zone protecting access to a door to the north. A long corridor stretches to the west. The corridor echoes with screams of torment and the sound of metal cutting into flesh.
Mounted on the door is a large screen, labelled as "Doorbot".to_owned(), showing primative ASCII art of some towers, with with some kind of electronic screen on it, showing some kind of towers with coloured discs stacked on them.
Mounted on the door is a large screen, labelled as "Doorbot", showing primative ASCII art of some towers, with with some kind of electronic screen on it, showing some kind of towers with coloured discs stacked on them.
[Hint: try <bold>-doorbot move from 1 to 2<reset> to ask Doorbot to move the top disc from tower 1 to tower 2]
The discs are arranged as follows:
exits:

View File

@ -1,9 +1,11 @@
use crate::{
message_handler::user_commands::UResult,
models::task::{Task, TaskDetails, TaskMeta},
models::{
effect::{Effect, EffectSet, EffectType},
task::{Task, TaskDetails, TaskMeta},
},
regular_tasks::{queued_command::QueuedCommandContext, TaskHandler, TaskRunContext},
services::{combat::max_health, effect::run_effects},
static_content::possession_type::UseEffect,
DResult,
};
@ -111,56 +113,59 @@ impl TaskHandler for SeePatientTaskHandler {
let mut who_mut = (*who).clone();
run_effects(
&ctx.trans,
&vec![
UseEffect::ChangeTargetHealth {
delay_secs: 0,
base_effect: 10,
skill_multiplier: 0.0,
max_effect: 10,
message: Box::new(|_item| {
(
"That feels better".to_owned(),
"That feels better".to_owned(),
)
}),
},
UseEffect::ChangeTargetHealth {
delay_secs: 10,
base_effect: 9,
skill_multiplier: 0.0,
max_effect: 10,
message: Box::new(|_item| {
(
"That feels better".to_owned(),
"That feels better".to_owned(),
)
}),
},
UseEffect::ChangeTargetHealth {
delay_secs: 20,
base_effect: 8,
skill_multiplier: 0.0,
max_effect: 10,
message: Box::new(|_item| {
(
"That feels better".to_owned(),
"That feels better".to_owned(),
)
}),
},
UseEffect::ChangeTargetHealth {
delay_secs: 30,
base_effect: 7,
skill_multiplier: 0.0,
max_effect: 10,
message: Box::new(|_item| {
(
"That feels better".to_owned(),
"That feels better".to_owned(),
)
}),
},
],
&EffectSet {
effect_type: EffectType::Bandages,
effects: vec![
Effect::ChangeTargetHealth {
delay_secs: 0,
base_effect: 10,
skill_multiplier: 0.0,
max_effect: 10,
message: Box::new(|_item| {
(
"That feels better".to_owned(),
"That feels better".to_owned(),
)
}),
},
Effect::ChangeTargetHealth {
delay_secs: 10,
base_effect: 9,
skill_multiplier: 0.0,
max_effect: 10,
message: Box::new(|_item| {
(
"That feels better".to_owned(),
"That feels better".to_owned(),
)
}),
},
Effect::ChangeTargetHealth {
delay_secs: 20,
base_effect: 8,
skill_multiplier: 0.0,
max_effect: 10,
message: Box::new(|_item| {
(
"That feels better".to_owned(),
"That feels better".to_owned(),
)
}),
},
Effect::ChangeTargetHealth {
delay_secs: 30,
base_effect: 7,
skill_multiplier: 0.0,
max_effect: 10,
message: Box::new(|_item| {
(
"That feels better".to_owned(),
"That feels better".to_owned(),
)
}),
},
],
},
&mut who_mut,
&who,
&mut None,