Implement use command for trauma kits.
This commit is contained in:
parent
70dae5b853
commit
385d2d1fd8
@ -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;
|
||||
@ -126,6 +127,8 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! {
|
||||
|
||||
"\'" => say::VERB,
|
||||
"say" => say::VERB,
|
||||
|
||||
"use" => use_cmd::VERB,
|
||||
|
||||
"-" => whisper::VERB,
|
||||
"whisper" => whisper::VERB,
|
||||
|
@ -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::*;
|
||||
|
238
blastmud_game/src/message_handler/user_commands/use_cmd.rs
Normal file
238
blastmud_game/src/message_handler/user_commands/use_cmd.rs
Normal 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;
|
@ -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",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
)
|
||||
}
|
||||
|
@ -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",
|
||||
}
|
||||
@ -55,7 +58,8 @@ fn queue_command_registry() -> &'static BTreeMap<&'static str, &'static (dyn Que
|
||||
REGISTRY.get_or_init(|| vec!(
|
||||
("Drop", &drop::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
|
||||
("Get", &get::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
|
||||
("Movement", &movement::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())
|
||||
}
|
||||
|
@ -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? {
|
||||
|
@ -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 {
|
||||
ctx.trans.save_item_model(&attacker_item).await?;
|
||||
handle_death(ctx.trans, &mut victim_item).await?;
|
||||
.sample(&mut rand::thread_rng()).floor().max(1.0) as i64;
|
||||
ctx.trans.save_item_model(&attacker_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(());
|
||||
|
131
blastmud_game/src/services/effect.rs
Normal file
131
blastmud_game/src/services/effect.rs
Normal 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(())
|
||||
}
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user