Implement use command for trauma kits.

This commit is contained in:
Condorra 2023-02-25 23:49:46 +11:00
parent 70dae5b853
commit 385d2d1fd8
10 changed files with 668 additions and 22 deletions

View File

@ -30,6 +30,7 @@ pub mod parsing;
mod quit;
mod register;
pub mod say;
pub mod use_cmd;
mod whisper;
mod who;
pub mod wield;
@ -127,6 +128,8 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! {
"\'" => say::VERB,
"say" => say::VERB,
"use" => use_cmd::VERB,
"-" => whisper::VERB,
"whisper" => whisper::VERB,
"tell" => whisper::VERB,

View File

@ -77,6 +77,14 @@ pub fn parse_username(input: &str) -> Result<(&str, &str), &'static str> {
}
}
pub fn parse_on_or_default<'l>(input: &'l str, default_on: &'l str) -> (&'l str, &'l str) {
if let Some((a, b)) = input.split_once(" on ") {
(a, b)
} else {
(input, default_on)
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -0,0 +1,238 @@
use super::{
VerbContext,
UserVerb,
UserVerbRef,
UResult,
ItemSearchParams,
user_error,
get_player_item_or_fail,
search_item_for_user,
parsing,
};
use crate::{
static_content::possession_type::{
possession_data,
},
regular_tasks::queued_command::{
QueueCommandHandler,
QueueCommand,
queue_command
},
models::item::{
SkillType,
},
services::{
broadcast_to_room,
skills::skill_check_and_grind,
effect::run_effects,
},
};
use async_trait::async_trait;
use std::time;
pub struct QueueHandler;
#[async_trait]
impl QueueCommandHandler for QueueHandler {
async fn start_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand)
-> UResult<time::Duration> {
let player_item = get_player_item_or_fail(ctx).await?;
if player_item.is_dead {
user_error("You try to use it, but your ghostly hands slip through it uselessly".to_owned())?;
}
let (item_id, target_type_code) = match command {
QueueCommand::Use { possession_id, target_id } => (possession_id, target_id),
_ => user_error("Unexpected command".to_owned())?
};
let item = match ctx.trans.find_item_by_type_code("possession", &item_id).await? {
None => user_error("Item not found".to_owned())?,
Some(it) => it
};
if item.location != format!("player/{}", player_item.item_code) {
user_error(format!("You try to use {} but realise you no longer have it",
item.display_for_sentence(
!ctx.session_dat.less_explicit_mode,
1, false
)
))?
}
let (target_type, target_code) = match target_type_code.split_once("/") {
None => user_error("Couldn't handle use command (invalid target)".to_owned())?,
Some(spl) => spl
};
let is_self_use = target_type == "player" && target_code == player_item.item_code;
let target =
if is_self_use {
player_item.clone()
} else {
match ctx.trans.find_item_by_type_code(&target_type, &target_code).await? {
None => user_error(format!("Couldn't handle use command (target {} missing)",
target_type_code))?,
Some(it) => it
}
};
if !is_self_use && target.location != player_item.location &&
target.location != format!("player/{}", player_item.item_code) {
let target_name = target.display_for_sentence(!ctx.session_dat.less_explicit_mode,
1, false);
user_error(format!("You try to use {} on {}, but realise {} is no longer here",
item.display_for_sentence(
!ctx.session_dat.less_explicit_mode,
1, false),
target_name, target_name
))?
}
let msg_exp = format!("{} prepares to use {} {} on {}\n",
&player_item.display_for_sentence(true, 1, true),
&player_item.pronouns.possessive,
&item.display_for_sentence(true, 1, false),
&if is_self_use { player_item.pronouns.intensive.clone() } else {
player_item.display_for_sentence(true, 1, false)
});
let msg_nonexp = format!("{} prepares to use {} {} on {}\n",
&player_item.display_for_sentence(false, 1, true),
&player_item.pronouns.possessive,
&item.display_for_sentence(false, 1, false),
&if is_self_use { player_item.pronouns.intensive.clone() } else {
player_item.display_for_sentence(true, 1, false)
});
broadcast_to_room(ctx.trans, &player_item.location, None, &msg_exp, Some(&msg_nonexp)).await?;
let mut draw_level: f64 = *player_item.total_skills.get(&SkillType::Quickdraw).to_owned().unwrap_or(&8.0);
let mut player_item_mut = (*player_item).clone();
let skill_result =
skill_check_and_grind(ctx.trans, &mut player_item_mut, &SkillType::Quickdraw, draw_level).await?;
if skill_result < -0.5 {
draw_level -= 2.0;
} else if skill_result < -0.25 {
draw_level -= 1.0;
} else if skill_result > 0.5 {
draw_level += 2.0;
} else if skill_result > 0.25 {
draw_level += 1.0;
}
ctx.trans.save_item_model(&player_item_mut).await?;
let wait_ticks = (12.0 - (draw_level / 2.0)).min(8.0).max(1.0);
Ok(time::Duration::from_millis((wait_ticks * 500.0).round() as u64))
}
#[allow(unreachable_patterns)]
async fn finish_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand)
-> UResult<()> {
let player_item = get_player_item_or_fail(ctx).await?;
if player_item.is_dead {
user_error("You try to use it, but your ghostly hands slip through it uselessly".to_owned())?;
}
let (ref item_id, ref target_type_code) = match command {
QueueCommand::Use { possession_id, target_id } => (possession_id, target_id),
_ => user_error("Unexpected command".to_owned())?
};
let item = match ctx.trans.find_item_by_type_code("possession", &item_id).await? {
None => user_error("Item not found".to_owned())?,
Some(it) => it
};
if item.location != format!("player/{}", player_item.item_code) {
user_error(format!("You try to use {} but realise you no longer have it",
item.display_for_sentence(!ctx.session_dat.less_explicit_mode,
1, false)
))?
}
let (ref target_type, ref target_code) = match target_type_code.split_once("/") {
None => user_error("Couldn't handle use command (invalid target)".to_owned())?,
Some(ref sp) => sp.clone()
};
let target = match ctx.trans.find_item_by_type_code(&target_type, &target_code).await? {
None => user_error("Couldn't handle use command (target missing)".to_owned())?,
Some(it) => it
};
if target.location != player_item.location &&
target.location != format!("player/{}", player_item.item_code) {
let target_name = target.display_for_sentence(!ctx.session_dat.less_explicit_mode,
1, false);
user_error(format!("You try to use {} on {}, but realise {} is no longer here",
item.display_for_sentence(
!ctx.session_dat.less_explicit_mode,
1, false),
target_name, target_name
))?
}
let use_data = match item.possession_type.as_ref()
.and_then(|poss_type| possession_data().get(&poss_type))
.and_then(|poss_data| poss_data.use_data.as_ref()) {
None => user_error("You can't use that!".to_owned())?,
Some(d) => d
};
if let Some(err) = (use_data.errorf)(&item, &target) {
user_error(err)?;
}
let is_self_use = target_type == &"player" && target_code == &player_item.item_code;
let mut player_mut = (*player_item).clone();
let skillcheck = skill_check_and_grind(&ctx.trans, &mut player_mut,
&use_data.uses_skill, use_data.diff_level).await?;
let (effects, skilllvl) = if skillcheck <= -0.5 {
// 0-1 how bad was the crit fail?
(&use_data.crit_fail_effects, (-0.5-skillcheck) * 2.0)
} else if skillcheck < 0.0 {
(&use_data.fail_effects, -skillcheck * 2.0)
} else {
(&use_data.success_effects, skillcheck)
};
let mut target_mut = if is_self_use { None } else { Some((*target).clone()) };
run_effects(ctx.trans, &effects, &mut player_mut, &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?;
}
ctx.trans.save_item_model(&player_mut).await?;
Ok(())
}
}
pub struct Verb;
#[async_trait]
impl UserVerb for Verb {
async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, remaining: &str) -> UResult<()> {
let player_item = get_player_item_or_fail(ctx).await?;
if player_item.is_dead {
user_error("You try to use it, but your ghostly hands slip through it uselessly".to_owned())?;
}
let (what_name, whom_name) = parsing::parse_on_or_default(remaining, "me");
let item = search_item_for_user(ctx, &ItemSearchParams {
include_contents: true,
item_type_only: Some("possession"),
limit: 1,
..ItemSearchParams::base(&player_item, &what_name)
}).await?;
let target = if whom_name == "me" || whom_name == "self" { player_item.clone() } else {
search_item_for_user(ctx, &ItemSearchParams {
include_contents: true,
include_loc_contents: true,
limit: 1,
..ItemSearchParams::base(&player_item, &whom_name)
}).await?
};
let use_data = match item.possession_type.as_ref()
.and_then(|poss_type| possession_data().get(&poss_type))
.and_then(|poss_data| poss_data.use_data.as_ref()) {
None => user_error("You can't use that!".to_owned())?,
Some(d) => d
};
if let Some(err) = (use_data.errorf)(&item, &target) {
user_error(err)?;
}
queue_command(ctx, &QueueCommand::Use {
possession_id: item.item_code.clone(),
target_id: format!("{}/{}", target.item_type, target.item_code)}).await?;
Ok(())
}
}
static VERB_INT: Verb = Verb;
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;

View File

@ -1,6 +1,8 @@
use serde::{Serialize, Deserialize};
use serde_json::Value;
use chrono::{DateTime, Utc};
use crate::services::effect::DelayedHealthEffect;
use std::collections::VecDeque;
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum TaskRecurrence {
@ -28,6 +30,10 @@ pub enum TaskDetails {
RotCorpse {
corpse_code: String
},
DelayedHealth {
item: String,
effect_series: VecDeque<DelayedHealthEffect>
}
}
impl TaskDetails {
pub fn name(self: &Self) -> &'static str {
@ -40,6 +46,7 @@ impl TaskDetails {
AttackTick => "AttackTick",
RecloneNPC { .. } => "RecloneNPC",
RotCorpse { .. } => "RotCorpse",
DelayedHealth { .. } => "DelayedHealth",
}
}
}

View File

@ -6,7 +6,7 @@ use crate::{
models::task::Task,
listener::{ListenerMap, ListenerSend},
static_content::npc,
services::combat,
services::{combat, effect},
};
#[cfg(not(test))] use crate::models::task::{TaskParse, TaskRecurrence};
use mockall_double::double;
@ -42,6 +42,7 @@ fn task_handler_registry() -> &'static BTreeMap<&'static str, &'static (dyn Task
("AttackTick", combat::TASK_HANDLER.clone()),
("RecloneNPC", npc::RECLONE_HANDLER.clone()),
("RotCorpse", combat::ROT_CORPSE_HANDLER.clone()),
("DelayedHealth", effect::DELAYED_HEALTH_HANDLER.clone()),
).into_iter().collect()
)
}

View File

@ -17,6 +17,7 @@ use crate::message_handler::user_commands::{
get,
drop,
movement,
use_cmd,
wield,
user_error,
get_user_or_fail
@ -28,6 +29,7 @@ use once_cell::sync::OnceCell;
pub enum QueueCommand {
Movement { direction: Direction },
Wield { possession_id: String },
Use { possession_id: String, target_id: String },
Get { possession_id: String },
Drop { possession_id: String },
}
@ -37,6 +39,7 @@ impl QueueCommand {
match self {
Movement {..} => "Movement",
Wield {..} => "Wield",
Use {..} => "Use",
Get {..} => "Get",
Drop {..} => "Drop",
}
@ -56,6 +59,7 @@ fn queue_command_registry() -> &'static BTreeMap<&'static str, &'static (dyn Que
("Drop", &drop::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
("Get", &get::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
("Movement", &movement::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
("Use", &use_cmd::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
("Wield", &wield::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
).into_iter().collect())
}

View File

@ -8,6 +8,8 @@ use mockall_double::double;
pub mod skills;
pub mod combat;
pub mod capacity;
pub mod effect;
pub async fn broadcast_to_room(trans: &DBTrans, location: &str, from_item: Option<&Item>,
message_explicit_ok: &str, message_nonexplicit: Option<&str>) -> DResult<()> {
for item in trans.find_items_by_location(location).await? {

View File

@ -80,6 +80,8 @@ impl TaskHandler for AttackTaskHandler {
victim_item.display_for_sentence(false, 1, true),
attacker_item.display_for_sentence(false, 1, false));
broadcast_to_room(ctx.trans, &attacker_item.location, None, &msg_exp, Some(&msg_nonexp)).await?;
ctx.trans.save_item_model(&attacker_item).await?;
ctx.trans.save_item_model(&victim_item).await?;
} else {
// TODO: Parry system of some kind?
@ -99,38 +101,60 @@ impl TaskHandler for AttackTaskHandler {
let actual_damage = Normal::new(mean_damage,
weapon.normal_attack_stdev_damage)?
.sample(&mut rand::thread_rng()).floor().max(1.0) as u64;
let new_health = if actual_damage > victim_item.health { 0 } else { victim_item.health - actual_damage };
let msg_exp = format!(ansi!("[ <red>{}<reset> <bold>{}/{}<reset> ] {}.\n"),
actual_damage,
new_health,
max_health(&victim_item),
weapon.normal_attack_success_message(&attacker_item, &victim_item, &part, true));
let msg_nonexp =
format!(ansi!("[ <red>{}<reset> <bold>{}/{}<reset> ] {}.\n"),
actual_damage,
new_health,
max_health(&victim_item),
weapon.normal_attack_success_message(&attacker_item, &victim_item, &part, false));
broadcast_to_room(ctx.trans, &attacker_item.location, None, &msg_exp, Some(&msg_nonexp)).await?;
victim_item.health = new_health;
if new_health == 0 {
.sample(&mut rand::thread_rng()).floor().max(1.0) as i64;
ctx.trans.save_item_model(&attacker_item).await?;
handle_death(ctx.trans, &mut victim_item).await?;
let msg_exp = weapon.normal_attack_success_message(&attacker_item, &victim_item,
&part, true);
let msg_nonexp = weapon.normal_attack_success_message(&attacker_item, &victim_item,
&part, false);
if change_health(ctx.trans, -actual_damage, &mut victim_item,
&msg_exp, &msg_nonexp).await? {
ctx.trans.save_item_model(&victim_item).await?;
return Ok(None);
}
ctx.trans.save_item_model(&victim_item).await?;
}
let msg_exp = &(weapon.normal_attack_start_message(&attacker_item, &victim_item, true) + ".\n");
let msg_nonexp = &(weapon.normal_attack_start_message(&attacker_item, &victim_item, false) + ".\n");
broadcast_to_room(ctx.trans, &attacker_item.location, None, msg_exp, Some(msg_nonexp)).await?;
ctx.trans.save_item_model(&attacker_item).await?;
ctx.trans.save_item_model(&victim_item).await?;
Ok(Some(attack_speed(&attacker_item)))
}
}
pub async fn change_health(trans: &DBTrans,
change: i64,
victim: &mut Item,
reason_exp: &str, reason_nonexp: &str) -> DResult<bool> {
let maxh = max_health(victim);
let new_health = ((victim.health as i64 + change).max(0) as u64).min(maxh);
if change >= 0 && new_health == victim.health {
return Ok(false);
}
let colour = if change > 0 { ansi!("<green>") } else { ansi!("<red>") };
let msg_exp = format!(ansi!("[ {}{}<reset> <bold>{}/{}<reset> ] {}.\n"),
colour,
change,
new_health,
max_health(&victim),
reason_exp);
let msg_nonexp =
format!(ansi!("[ {}{}<reset> <bold>{}/{}<reset> ] {}.\n"),
colour,
change,
new_health,
maxh,
reason_nonexp);
broadcast_to_room(trans, &victim.location, None, &msg_exp, Some(&msg_nonexp)).await?;
victim.health = new_health;
if new_health == 0 {
handle_death(trans, victim).await?;
Ok(true)
} else {
Ok(false)
}
}
pub async fn consider_reward_for(trans: &DBTrans, by_item: &mut Item, for_item: &Item) -> DResult<()> {
if by_item.item_type != "player" {
return Ok(());

View File

@ -0,0 +1,131 @@
use crate::{
db::DBTrans,
models::{
item::Item,
task::{
Task,
TaskMeta,
TaskDetails,
}
},
DResult,
static_content::{
possession_type::UseEffect,
},
regular_tasks::{
TaskHandler,
TaskRunContext,
}
};
use super::{
broadcast_to_room,
combat::change_health,
};
use async_trait::async_trait;
use std::time;
use serde::{Serialize, Deserialize};
use std::collections::{BTreeMap, VecDeque};
use chrono::Utc;
use log::info;
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct DelayedHealthEffect {
magnitude: i64,
delay: u64,
message: String,
message_nonexp: String
}
pub struct DelayedHealthTaskHandler;
#[async_trait]
impl TaskHandler for DelayedHealthTaskHandler {
async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
let ref mut item_effect_series = match &mut ctx.task.details {
TaskDetails::DelayedHealth { item, ref mut effect_series } => (item, effect_series),
_ => Err("Expected DelayedHealth type")?
};
let (item_type, item_code) = match item_effect_series.0.split_once("/") {
None => {
info!("Invalid item {} to DelayedHealthTaskHandler", 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
};
match item_effect_series.1.pop_front() {
None => Ok(None),
Some(DelayedHealthEffect { magnitude, message, message_nonexp, .. }) => {
let mut item_mut = (*item).clone();
change_health(ctx.trans, magnitude, &mut item_mut, &message, &message_nonexp).await?;
ctx.trans.save_item_model(&item_mut).await?;
Ok(item_effect_series.1.front().map(|it| time::Duration::from_secs(it.delay)))
}
}
}
}
pub static DELAYED_HEALTH_HANDLER: &'static (dyn TaskHandler + Sync + Send) = &DelayedHealthTaskHandler;
pub async fn run_effects(
trans: &DBTrans, effects: &Vec<UseEffect>,
player: &mut Item,
item: &Item,
// None if target is player
target: &mut Option<Item>,
level: f64,
task_ref: &str
) -> DResult<()> {
let mut target_health_series = BTreeMap::<String, VecDeque<DelayedHealthEffect>>::new();
for effect in effects {
match effect {
UseEffect::BroadcastMessage { 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?;
},
UseEffect::ChangeTargetHealth { delay_secs, base_effect, skill_multiplier, max_effect,
message } => {
let health_impact =
(*base_effect + ((skill_multiplier * level) as i64).min(*max_effect)) as i64;
let (msg, msg_nonexp) = message(target.as_ref().unwrap_or(player));
if *delay_secs == 0 {
change_health(trans, health_impact, target.as_mut().unwrap_or(player), &msg,
&msg_nonexp).await?;
} else {
let target_it = target.as_ref().unwrap_or(player);
let fx = DelayedHealthEffect {
magnitude: health_impact,
delay: *delay_secs,
message: msg,
message_nonexp: msg_nonexp
};
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]));
}
}
}
}
for (eff_item, l) in target_health_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::DelayedHealth {
effect_series: l,
item: eff_item,
}
}).await?;
}
Ok(())
}

View File

@ -69,6 +69,38 @@ 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 errorf: Box<dyn Fn(&Item, &Item) -> Option<String> + Sync + Send>,
pub task_ref: &'static str,
}
impl Default for UseData {
fn default() -> Self {
Self {
uses_skill: SkillType::Medic,
diff_level: 10.0,
crit_fail_effects: vec!(),
fail_effects: vec!(),
success_effects: vec!(),
errorf: Box::new(|_it, _target| None),
task_ref: "set me",
}
}
}
pub struct PossessionData {
pub weapon_data: Option<WeaponData>,
pub display: &'static str,
@ -78,6 +110,7 @@ pub struct PossessionData {
pub aliases: Vec<&'static str>,
pub max_health: u64,
pub charge_data: Option<ChargeData>,
pub use_data: Option<UseData>,
pub weight: u64,
}
@ -93,6 +126,7 @@ impl Default for PossessionData {
max_health: 10,
weight: 100,
charge_data: None,
use_data: None,
}
}
}
@ -258,6 +292,200 @@ pub fn possession_data() -> &'static BTreeMap<PossessionType, PossessionData> {
charge_name_suffix: "worth of supplies",
..Default::default()
}),
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),
&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: -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),
&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: 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 {
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),
))
)
},
),
..Default::default()
}),
..Default::default()
}),
).into_iter().collect()