forked from blasthavers/blastmud
847 lines
34 KiB
Rust
847 lines
34 KiB
Rust
use super::{combat::change_health, comms::broadcast_to_room};
|
|
#[double]
|
|
use crate::db::DBTrans;
|
|
use crate::{
|
|
models::{
|
|
effect::{Effect, EffectParameter, EffectSet, EffectType},
|
|
item::Item,
|
|
task::{Task, TaskDetails, TaskMeta},
|
|
},
|
|
regular_tasks::{TaskHandler, TaskRunContext},
|
|
static_content::species::SpeciesType,
|
|
DResult,
|
|
};
|
|
use ansi::ansi;
|
|
use async_trait::async_trait;
|
|
use chrono::Utc;
|
|
use log::info;
|
|
use mockall_double::double;
|
|
use once_cell::sync::OnceCell;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::{BTreeMap, VecDeque};
|
|
use std::time;
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
|
pub struct DelayedParameterEffect {
|
|
magnitude: i64,
|
|
delay: u64,
|
|
parameter: EffectParameter,
|
|
message: String,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
|
pub struct DelayedMessageEffect {
|
|
delay: u64,
|
|
message: String,
|
|
is_direct: bool,
|
|
}
|
|
|
|
pub struct DelayedParameterTaskHandler;
|
|
#[async_trait]
|
|
impl TaskHandler for DelayedParameterTaskHandler {
|
|
async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
|
|
let ref mut item_effect_series = match &mut ctx.task.details {
|
|
TaskDetails::DelayedParameter {
|
|
item,
|
|
ref mut effect_series,
|
|
} => (item, effect_series),
|
|
_ => Err("Expected DelayedParameter type")?,
|
|
};
|
|
let (item_type, item_code) = match item_effect_series.0.split_once("/") {
|
|
None => {
|
|
info!(
|
|
"Invalid item {} to DelayedParameterTaskHandler",
|
|
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(DelayedParameterEffect {
|
|
magnitude,
|
|
message,
|
|
parameter,
|
|
delay,
|
|
..
|
|
}) => {
|
|
let mut item_mut = (*item).clone();
|
|
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?;
|
|
Ok(item_effect_series
|
|
.1
|
|
.front()
|
|
.map(|it| time::Duration::from_secs(it.delay - delay)))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
pub static DELAYED_PARAMETER_HANDLER: &'static (dyn TaskHandler + Sync + Send) =
|
|
&DelayedParameterTaskHandler;
|
|
|
|
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, is_direct, ..
|
|
}) if is_direct => {
|
|
match ctx.trans.find_session_for_player(&item_code).await? {
|
|
None => {}
|
|
Some((sess, _)) => {
|
|
ctx.trans.queue_for_session(&sess, Some(&message)).await?;
|
|
}
|
|
}
|
|
Ok(item_effect_series
|
|
.1
|
|
.front()
|
|
.map(|it| time::Duration::from_secs(it.delay)))
|
|
}
|
|
Some(DelayedMessageEffect { message, .. }) => {
|
|
broadcast_to_room(&ctx.trans, &item.location, None, &message).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.0 == *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: &EffectSet,
|
|
mut player: &mut Item,
|
|
item: &Item,
|
|
// None if target is player
|
|
mut target: Option<&mut Item>,
|
|
level: f64,
|
|
) -> DResult<()> {
|
|
let mut target_health_series = BTreeMap::<String, VecDeque<DelayedParameterEffect>>::new();
|
|
let mut target_message_series = BTreeMap::<String, VecDeque<DelayedMessageEffect>>::new();
|
|
let mut dispel_time_secs: u64 = 0;
|
|
|
|
for effect in &effects.effects {
|
|
match effect {
|
|
Effect::BroadcastMessage {
|
|
delay_secs,
|
|
messagef,
|
|
} => {
|
|
let msg = messagef(
|
|
player,
|
|
item,
|
|
target.as_ref().map(|t| &**t).unwrap_or(player),
|
|
);
|
|
if *delay_secs == 0 {
|
|
broadcast_to_room(trans, &player.location, None, &msg).await?;
|
|
} else {
|
|
dispel_time_secs = dispel_time_secs.max(*delay_secs);
|
|
let fx = DelayedMessageEffect {
|
|
delay: *delay_secs,
|
|
message: msg,
|
|
is_direct: false,
|
|
};
|
|
let actual_target: &mut Item = *target.as_mut().unwrap_or(&mut player);
|
|
target_message_series
|
|
.entry(format!(
|
|
"{}/{}",
|
|
actual_target.item_type, actual_target.item_code
|
|
))
|
|
.and_modify(|l| l.push_back(fx.clone()))
|
|
.or_insert(VecDeque::from([fx]));
|
|
}
|
|
}
|
|
Effect::DirectMessage {
|
|
delay_secs,
|
|
messagef,
|
|
} => {
|
|
let msg = messagef(
|
|
player,
|
|
item,
|
|
target.as_ref().map(|t| &**t).unwrap_or(player),
|
|
);
|
|
if *delay_secs == 0 {
|
|
let actual_target: &mut Item = *target.as_mut().unwrap_or(&mut player);
|
|
match trans
|
|
.find_session_for_player(&actual_target.item_code)
|
|
.await?
|
|
{
|
|
None => {}
|
|
Some((sess, _)) => {
|
|
trans.queue_for_session(&sess, Some(&msg)).await?;
|
|
}
|
|
}
|
|
} else {
|
|
dispel_time_secs = dispel_time_secs.max(*delay_secs);
|
|
let fx = DelayedMessageEffect {
|
|
delay: *delay_secs,
|
|
message: msg,
|
|
is_direct: true,
|
|
};
|
|
let actual_target: &mut Item = *target.as_mut().unwrap_or(&mut player);
|
|
target_message_series
|
|
.entry(format!(
|
|
"{}/{}",
|
|
actual_target.item_type, actual_target.item_code
|
|
))
|
|
.and_modify(|l| l.push_back(fx.clone()))
|
|
.or_insert(VecDeque::from([fx]));
|
|
}
|
|
}
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs,
|
|
base_effect,
|
|
skill_multiplier,
|
|
max_effect,
|
|
parameter,
|
|
message,
|
|
} => {
|
|
let param_impact = *base_effect + ((skill_multiplier * level) 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));
|
|
if *delay_secs == 0 {
|
|
match parameter {
|
|
EffectParameter::Health => {
|
|
change_health(
|
|
trans,
|
|
param_impact,
|
|
*target.as_mut().unwrap_or(&mut player),
|
|
&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 {
|
|
dispel_time_secs = dispel_time_secs.max(*delay_secs);
|
|
let target_it = target.as_ref().map(|t| &**t).unwrap_or(player);
|
|
let fx = DelayedParameterEffect {
|
|
magnitude: param_impact,
|
|
delay: *delay_secs,
|
|
parameter: (*parameter).clone(),
|
|
message: msg,
|
|
};
|
|
target_health_series
|
|
.entry(format!("{}/{}", target_it.item_type, target_it.item_code))
|
|
.and_modify(|l| l.push_back(fx.clone()))
|
|
.or_insert(VecDeque::from([fx]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let task_ref = trans.alloc_task_code().await?;
|
|
if dispel_time_secs > 0 && effects.effect_type != EffectType::Ephemeral {
|
|
let actual_target: &mut Item = *target.as_mut().unwrap_or(&mut player);
|
|
actual_target
|
|
.active_effects
|
|
.push((effects.effect_type.clone(), task_ref.clone()));
|
|
|
|
trans
|
|
.upsert_task(&Task {
|
|
meta: TaskMeta {
|
|
task_code: format!("{}/{}", &actual_target.refstr(), task_ref),
|
|
next_scheduled: Utc::now()
|
|
+ chrono::TimeDelta::try_seconds(dispel_time_secs as i64).unwrap(),
|
|
..Default::default()
|
|
},
|
|
details: TaskDetails::DispelEffect {
|
|
target: actual_target.refstr(),
|
|
effect_type: effects.effect_type.clone(),
|
|
},
|
|
})
|
|
.await?;
|
|
}
|
|
|
|
for (eff_item, l) in target_health_series.into_iter() {
|
|
trans
|
|
.upsert_task(&Task {
|
|
meta: TaskMeta {
|
|
task_code: format!("{}/{}", eff_item, trans.alloc_task_code().await?),
|
|
next_scheduled: Utc::now()
|
|
+ chrono::TimeDelta::try_seconds(l[0].delay as i64).unwrap(),
|
|
..Default::default()
|
|
},
|
|
details: TaskDetails::DelayedParameter {
|
|
effect_series: l,
|
|
item: eff_item,
|
|
},
|
|
})
|
|
.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::TimeDelta::try_seconds(l[0].delay as i64).unwrap(),
|
|
..Default::default()
|
|
},
|
|
details: TaskDetails::DelayedMessage {
|
|
effect_series: l,
|
|
item: eff_item,
|
|
},
|
|
})
|
|
.await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn cancel_effect(
|
|
trans: &DBTrans,
|
|
character: &Item,
|
|
effect: &(EffectType, i64),
|
|
) -> DResult<()> {
|
|
trans
|
|
.delete_task(
|
|
"DelayedMessage",
|
|
&format!(
|
|
"{}/{}/{}",
|
|
&character.item_type, &character.item_code, effect.1
|
|
),
|
|
)
|
|
.await?;
|
|
trans
|
|
.delete_task(
|
|
"DelayedParameter",
|
|
&format!(
|
|
"{}/{}/{}",
|
|
&character.item_type, &character.item_code, effect.1
|
|
),
|
|
)
|
|
.await?;
|
|
trans
|
|
.delete_task(
|
|
"DispelEffect",
|
|
&format!(
|
|
"{}/{}/{}",
|
|
&character.item_type, &character.item_code, effect.1
|
|
),
|
|
)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn default_effects_for_type() -> &'static BTreeMap<EffectType, EffectSet> {
|
|
static MAP: OnceCell<BTreeMap<EffectType, EffectSet>> = OnceCell::new();
|
|
MAP.get_or_init(|| {
|
|
vec![
|
|
EffectSet {
|
|
effect_type: EffectType::Bleed,
|
|
effects: vec![
|
|
Effect::BroadcastMessage {
|
|
delay_secs: 0,
|
|
messagef: Box::new(|_player, _item, target|
|
|
format!(ansi!("<red>You notice that {} has a fresh gaping wound that looks like it's about to {}!<reset>\n"),
|
|
target.display_for_sentence(1, false),
|
|
if target.species == SpeciesType::Robot { "leak coolant" } else { "bleed" }
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 10,
|
|
base_effect: -12,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -12,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{} pulses from {}'s wound",
|
|
if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" },
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 20,
|
|
base_effect: -10,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -10,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{} pulses from {}'s wound",
|
|
if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" },
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 30,
|
|
base_effect: -8,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -8,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{} pulses from {}'s wound",
|
|
if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" },
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 40,
|
|
base_effect: -6,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -6,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{} pulses from {}'s wound",
|
|
if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" },
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 50,
|
|
base_effect: -4,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -4,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{} pulses from {}'s wound",
|
|
if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" },
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 60,
|
|
base_effect: -2,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -2,
|
|
parameter: EffectParameter::Health,
|
|
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" },
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
],
|
|
},
|
|
EffectSet {
|
|
effect_type: EffectType::Stunned,
|
|
effects: vec![
|
|
Effect::BroadcastMessage {
|
|
delay_secs: 0,
|
|
messagef: Box::new(|_player, _item, target|
|
|
format!(ansi!("<blue>{} is stunned!<reset>\n"),
|
|
target.display_for_sentence(1, true),
|
|
)
|
|
)
|
|
},
|
|
Effect::BroadcastMessage {
|
|
delay_secs: 30,
|
|
messagef: Box::new(|_player, _item, target|
|
|
format!(ansi!("<blue>{} seems to have returned to {} senses!<reset>\n"),
|
|
target.display_for_sentence(1, true),
|
|
&target.pronouns.object,
|
|
)
|
|
)
|
|
},
|
|
]
|
|
},
|
|
EffectSet {
|
|
effect_type: EffectType::SnakePoisoned,
|
|
effects: vec![
|
|
Effect::BroadcastMessage {
|
|
delay_secs: 0,
|
|
messagef: Box::new(|_player, _item, target|
|
|
format!(ansi!("<red>You notice that {} has a fresh bite wound with some kind of yellow liquid on the bite site!<reset>\n"),
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 60,
|
|
base_effect: -6,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -6,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{} looks a bit unwell",
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 120,
|
|
base_effect: -12,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -12,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{} looks pretty unwell",
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 180,
|
|
base_effect: -18,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -18,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{} looks very sick",
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 240,
|
|
base_effect: -18,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -18,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{} looks very sick",
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 300,
|
|
base_effect: -12,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -12,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{} looks pretty unwell",
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 360,
|
|
base_effect: -12,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -12,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{} looks pretty unwell",
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 420,
|
|
base_effect: -6,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -6,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{} looks a bit unwell",
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 480,
|
|
base_effect: -6,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -6,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{} feels the final effects of snake venom as it leaves {} body",
|
|
target.display_for_sentence(1, false),
|
|
target.pronouns.possessive,
|
|
)
|
|
)
|
|
},
|
|
],
|
|
},
|
|
EffectSet {
|
|
effect_type: EffectType::Stung,
|
|
effects: vec![
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 0,
|
|
base_effect: -12,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -12,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{} howls out in pain from a sting",
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 20,
|
|
base_effect: -12,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -12,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{} howls out in pain from a sting",
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 40,
|
|
base_effect: -12,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -12,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{} howls out in pain from a sting",
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 60,
|
|
base_effect: -8,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -12,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{} yelps in pain from the sting",
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 80,
|
|
base_effect: -8,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -12,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{} yelps in pain from the sting",
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 100,
|
|
base_effect: -8,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -12,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{} yelps in pain from the sting",
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 120,
|
|
base_effect: -6,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -10,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{}'s cries as the sting really hurts",
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 140,
|
|
base_effect: -6,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -10,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{}'s cries as the sting really hurts",
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 160,
|
|
base_effect: -6,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -10,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{}'s cries as the sting really hurts",
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 170,
|
|
base_effect: -4,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -8,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{} sobs as the sting still hurts",
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 180,
|
|
base_effect: -4,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -8,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{} sobs as the sting still hurts",
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 190,
|
|
base_effect: -4,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -8,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{} sobs as the sting still hurts",
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 200,
|
|
base_effect: -2,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -8,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{} whimpers as the sting starts to heal",
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 220,
|
|
base_effect: -2,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -8,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{} whimpers as the sting is almost healed",
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
Effect::ChangeTargetParameter {
|
|
delay_secs: 230,
|
|
base_effect: -2,
|
|
skill_multiplier: 0.0,
|
|
max_effect: -8,
|
|
parameter: EffectParameter::Health,
|
|
message: Box::new(|target|
|
|
format!("{} whimpers as the sting is finally healed",
|
|
target.display_for_sentence(1, false),
|
|
)
|
|
)
|
|
},
|
|
],
|
|
},
|
|
]
|
|
.into_iter()
|
|
.map(|et| (et.effect_type.clone(), et))
|
|
.collect()
|
|
})
|
|
}
|