forked from blasthavers/blastmud
Condorra
078519be95
Also fix up bugs with navigation during death, and awarding payouts when you don't get any XP.
537 lines
22 KiB
Rust
537 lines
22 KiB
Rust
use crate::{
|
|
services::{
|
|
comms::broadcast_to_room,
|
|
skills::skill_check_and_grind,
|
|
skills::skill_check_only,
|
|
destroy_container,
|
|
},
|
|
models::{
|
|
item::{Item, LocationActionType, Subattack, SkillType, DeathData},
|
|
task::{Task, TaskMeta, TaskDetails},
|
|
journal::JournalType,
|
|
},
|
|
static_content::{
|
|
possession_type::{WeaponData, possession_data, fist},
|
|
npc::npc_by_code,
|
|
species::species_info_map,
|
|
journals::{check_journal_for_kill, award_journal_if_needed}
|
|
},
|
|
message_handler::user_commands::{user_error, UResult},
|
|
regular_tasks::{TaskRunContext, TaskHandler},
|
|
DResult,
|
|
};
|
|
use mockall_double::double;
|
|
#[double] use crate::db::DBTrans;
|
|
use async_trait::async_trait;
|
|
use chrono::Utc;
|
|
use async_recursion::async_recursion;
|
|
use std::time;
|
|
use ansi::ansi;
|
|
use rand::prelude::IteratorRandom;
|
|
use rand_distr::{Normal, Distribution};
|
|
|
|
#[derive(Clone)]
|
|
pub struct AttackTaskHandler;
|
|
#[async_trait]
|
|
impl TaskHandler for AttackTaskHandler {
|
|
async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
|
|
let (ctype, ccode) = ctx.task.meta.task_code.split_once("/")
|
|
.ok_or("Invalid AttackTick task code")?;
|
|
let mut attacker_item = match ctx.trans.find_item_by_type_code(ctype, ccode).await? {
|
|
None => { return Ok(None); } // Player is gone
|
|
Some(item) => (*item).clone()
|
|
};
|
|
|
|
let (vtype, vcode) =
|
|
match attacker_item.active_combat.as_ref().and_then(|ac| ac.attacking.as_ref()).and_then(|v| v.split_once("/")) {
|
|
None => return Ok(None),
|
|
Some(x) => x
|
|
};
|
|
let mut victim_item = match ctx.trans.find_item_by_type_code(vtype, vcode).await? {
|
|
None => { return Ok(None); }
|
|
Some(item) => (*item).clone()
|
|
};
|
|
|
|
if attacker_item.death_data.is_some() || victim_item.death_data.is_some() {
|
|
return Ok(None)
|
|
}
|
|
|
|
let weapon = what_wielded(ctx.trans, &attacker_item).await?;
|
|
|
|
let attack_skill = *attacker_item.total_skills.get(&weapon.uses_skill).unwrap_or(&0.0);
|
|
let victim_dodge_skill = *victim_item.total_skills.get(&SkillType::Dodge).unwrap_or(&0.0);
|
|
|
|
let dodge_result = skill_check_and_grind(ctx.trans, &mut victim_item, &SkillType::Dodge,
|
|
attack_skill).await?;
|
|
let user_opt = if ctype == "player" { ctx.trans.find_by_username(ccode).await? } else { None };
|
|
let attack_result = if let Some(user) = user_opt {
|
|
let raw_skill = *user.raw_skills.get(&weapon.uses_skill).unwrap_or(&0.0);
|
|
if raw_skill >= weapon.raw_min_to_learn && raw_skill <= weapon.raw_max_to_learn {
|
|
skill_check_and_grind(ctx.trans, &mut attacker_item, &weapon.uses_skill,
|
|
victim_dodge_skill).await?
|
|
} else {
|
|
skill_check_only(&attacker_item, &weapon.uses_skill, victim_dodge_skill)
|
|
}
|
|
} else {
|
|
skill_check_only(&attacker_item, &weapon.uses_skill, victim_dodge_skill)
|
|
};
|
|
|
|
if dodge_result > attack_result {
|
|
let msg_exp = format!("{} dodges out of the way of {}'s attack.\n",
|
|
victim_item.display_for_sentence(true, 1, true),
|
|
attacker_item.display_for_sentence(true, 1, false));
|
|
let msg_nonexp = format!("{} dodges out of the way of {}'s attack.\n",
|
|
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?
|
|
|
|
// Determine body part...
|
|
let part = victim_item.species.sample_body_part();
|
|
|
|
// TODO: Armour / soaks
|
|
|
|
// TODO: Calculate damage etc... and display health impact.
|
|
let mut mean_damage: f64 = weapon.normal_attack_mean_damage;
|
|
for scaling in weapon.normal_attack_skill_scaling.iter() {
|
|
let skill = *attacker_item.total_skills.get(&scaling.skill).unwrap_or(&0.0);
|
|
if skill >= scaling.min_skill {
|
|
mean_damage += (skill - scaling.min_skill) * scaling.mean_damage_per_point_over_min;
|
|
}
|
|
}
|
|
|
|
let actual_damage = Normal::new(mean_damage,
|
|
weapon.normal_attack_stdev_damage)?
|
|
.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?;
|
|
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(());
|
|
}
|
|
let (session, _) = match trans.find_session_for_player(&by_item.item_code).await? {
|
|
None => return Ok(()),
|
|
Some(r) => r
|
|
};
|
|
|
|
let mut user = match trans.find_by_username(&by_item.item_code).await? {
|
|
None => return Ok(()),
|
|
Some(r) => r
|
|
};
|
|
let xp_gain = if by_item.total_xp >= for_item.total_xp {
|
|
0
|
|
} else {
|
|
let xp_gain =
|
|
(((for_item.total_xp - by_item.total_xp) as f64 * 10.0 / (by_item.total_xp + 1) as f64) as u64)
|
|
.min(100);
|
|
|
|
by_item.total_xp += xp_gain;
|
|
user.experience.xp_change_for_this_reroll += xp_gain as i64;
|
|
xp_gain
|
|
};
|
|
|
|
// Now consider kill bonuses...
|
|
if for_item.item_type == "npc" {
|
|
if let Some(npc) = npc_by_code().get(for_item.item_code.as_str()) {
|
|
if let Some(bonus) = &npc.kill_bonus {
|
|
user.credits += bonus.payment;
|
|
trans.queue_for_session(&session, Some(&format!("{}\nYour wristpad beeps for a credit of {} for that.\n", bonus.msg, bonus.payment))).await?;
|
|
}
|
|
}
|
|
}
|
|
|
|
trans.save_user_model(&user).await?;
|
|
if xp_gain == 0 {
|
|
trans.queue_for_session(&session, Some("[You didn't gain any experience for that]\n")).await?;
|
|
} else {
|
|
trans.queue_for_session(&session, Some(&format!("You gained {} experience points!\n", xp_gain))).await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn handle_death(trans: &DBTrans, whom: &mut Item) -> DResult<()> {
|
|
let msg_exp = format!(
|
|
ansi!("<red>{} stiffens and collapses dead.<reset>\n"),
|
|
&whom.display_for_sentence(true, 1, true)
|
|
);
|
|
let msg_nonexp = format!(
|
|
ansi!("<red>{} stiffens and collapses dead.<reset>\n"),
|
|
&whom.display_for_sentence(false, 1, true)
|
|
);
|
|
broadcast_to_room(trans, &whom.location, None, &msg_exp, Some(&msg_nonexp)).await?;
|
|
|
|
whom.death_data = Some(DeathData {
|
|
parts_remaining: species_info_map().get(&whom.species)
|
|
.map(|sp| sp.corpse_butchers_into.clone()).unwrap_or_else(|| vec!()),
|
|
..Default::default()
|
|
});
|
|
let vic_is_npc = whom.item_type == "npc";
|
|
if let Some(ac) = &whom.active_combat {
|
|
let at_str = ac.attacking.clone();
|
|
for attacker in ac.attacked_by.clone().iter() {
|
|
if let Some((atype, acode)) = attacker.split_once("/") {
|
|
if let Some(aitem) = trans.find_item_by_type_code(atype, acode).await? {
|
|
let mut new_aitem = (*aitem).clone();
|
|
consider_reward_for(trans, &mut new_aitem, &whom).await?;
|
|
if vic_is_npc {
|
|
check_journal_for_kill(trans, &mut new_aitem, whom).await?;
|
|
}
|
|
stop_attacking_mut(trans, &mut new_aitem, whom, true).await?;
|
|
trans.save_item_model(&new_aitem).await?;
|
|
}
|
|
}
|
|
}
|
|
if let Some((vtype, vcode)) = at_str.as_ref().and_then(|a| a.split_once("/")) {
|
|
if let Some(vitem) = trans.find_item_by_type_code(vtype, vcode).await? {
|
|
let mut new_vitem = (*vitem).clone();
|
|
stop_attacking_mut(trans, whom, &mut new_vitem, false).await?;
|
|
trans.save_item_model(&new_vitem).await?;
|
|
}
|
|
}
|
|
}
|
|
if vic_is_npc {
|
|
trans.upsert_task(&Task {
|
|
meta: TaskMeta {
|
|
task_code: whom.item_code.clone(),
|
|
next_scheduled: Utc::now() + chrono::Duration::seconds(120),
|
|
..Default::default()
|
|
},
|
|
details: TaskDetails::RecloneNPC {
|
|
npc_code: whom.item_code.clone()
|
|
}
|
|
}).await?;
|
|
} else if whom.item_type == "player" {
|
|
trans.revoke_until_death_consent(&whom.item_code).await?;
|
|
match trans.find_by_username(&whom.item_code).await? {
|
|
None => {},
|
|
Some(mut user) => {
|
|
if award_journal_if_needed(trans, &mut user, whom, JournalType::Died).await? {
|
|
trans.save_user_model(&user).await?;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn handle_resurrect(trans: &DBTrans, player: &mut Item) -> DResult<bool> {
|
|
corpsify_item(trans, &player).await?;
|
|
player.death_data = None;
|
|
let lost_xp = (player.total_xp / 200).max(10).min(player.total_xp);
|
|
let (session, _) = match trans.find_session_for_player(&player.item_code).await? {
|
|
None => return Ok(false),
|
|
Some(r) => r
|
|
};
|
|
let mut user = match trans.find_by_username(&player.item_code).await? {
|
|
None => return Ok(false),
|
|
Some(r) => r
|
|
};
|
|
trans.queue_for_session(&session,
|
|
Some(&format!("You lost {} experience points by dying.\n", lost_xp))).await?;
|
|
player.total_xp -= lost_xp;
|
|
user.experience.xp_change_for_this_reroll -= lost_xp as i64;
|
|
player.health = max_health(&player);
|
|
|
|
trans.save_user_model(&user).await?;
|
|
Ok(true)
|
|
}
|
|
|
|
pub fn max_health(whom: &Item) -> u64 {
|
|
if whom.item_type == "npc" {
|
|
npc_by_code().get(whom.item_code.as_str())
|
|
.map(|npc| npc.max_health)
|
|
.unwrap_or(24)
|
|
} else if whom.item_type == "player" {
|
|
(22.0 + (whom.total_xp as f64).log(1.4)).min(60.0) as u64
|
|
} else if whom.item_type == "possession" {
|
|
whom.possession_type.as_ref().and_then(|pt| possession_data().get(&pt))
|
|
.map(|poss| poss.max_health)
|
|
.unwrap_or(10)
|
|
} else {
|
|
24
|
|
}
|
|
}
|
|
|
|
pub static TASK_HANDLER: &(dyn TaskHandler + Sync + Send) = &AttackTaskHandler;
|
|
|
|
pub async fn stop_attacking_mut(trans: &DBTrans, new_by_whom: &mut Item, new_to_whom: &mut Item,
|
|
auto_refocus: bool) ->
|
|
DResult<()>
|
|
{
|
|
trans.delete_task("AttackTick",
|
|
&format!("{}/{}",
|
|
new_by_whom.item_type,
|
|
new_by_whom.item_code)).await?;
|
|
if let Some(ac) = new_to_whom.active_combat.as_mut() {
|
|
let old_attacker = format!("{}/{}", new_by_whom.item_type, new_by_whom.item_code);
|
|
ac.attacked_by.retain(|v| v != &old_attacker);
|
|
}
|
|
if let Some(ac) = new_by_whom.active_combat.as_mut() {
|
|
ac.attacking = None;
|
|
if auto_refocus {
|
|
let old_vic = format!("{}/{}", new_to_whom.item_type, new_to_whom.item_code);
|
|
let new_vic_opt = ac.attacked_by.iter().filter(|i| **i != old_vic).choose(&mut rand::thread_rng());
|
|
if let Some(new_vic) = new_vic_opt {
|
|
if let Some((vtype, vcode)) = new_vic.split_once("/") {
|
|
if let Some(vic_item) = trans.find_item_by_type_code(vtype, vcode).await? {
|
|
let mut new_vic_item = (*vic_item).clone();
|
|
start_attack_mut(trans, new_by_whom, &mut new_vic_item);
|
|
trans.save_item_model(&new_vic_item).await?;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
new_by_whom.action_type = LocationActionType::Normal;
|
|
Ok(())
|
|
}
|
|
|
|
|
|
pub async fn stop_attacking(trans: &DBTrans, by_whom: &Item, to_whom: &Item) -> DResult<()> {
|
|
let mut new_by_whom = (*by_whom).clone();
|
|
let mut new_to_whom = (*to_whom).clone();
|
|
stop_attacking_mut(trans, &mut new_by_whom, &mut new_to_whom, false).await?;
|
|
trans.save_item_model(&new_by_whom).await?;
|
|
trans.save_item_model(&new_to_whom).await?;
|
|
Ok(())
|
|
}
|
|
|
|
async fn what_wielded(trans: &DBTrans, who: &Item) -> DResult<&'static WeaponData> {
|
|
if let Some(item) = trans.find_by_action_and_location(
|
|
&who.refstr(), &LocationActionType::Wielded).await? {
|
|
if let Some(dat) = item.possession_type.as_ref()
|
|
.and_then(|pt| possession_data().get(&pt))
|
|
.and_then(|pd| pd.weapon_data.as_ref()) {
|
|
return Ok(dat);
|
|
}
|
|
}
|
|
|
|
// TODO: Search inventory for wielded item first.
|
|
if who.item_type == "npc" {
|
|
if let Some(intrinsic) = npc_by_code().get(who.item_code.as_str())
|
|
.and_then(|npc| npc.intrinsic_weapon.as_ref()) {
|
|
if let Some(weapon) = possession_data().get(intrinsic).and_then(|p| p.weapon_data.as_ref()) {
|
|
return Ok(weapon)
|
|
}
|
|
}
|
|
}
|
|
Ok(fist())
|
|
}
|
|
|
|
fn attack_speed(_who: &Item) -> time::Duration {
|
|
time::Duration::from_secs(5)
|
|
}
|
|
|
|
#[async_recursion]
|
|
pub async fn start_attack(trans: &DBTrans, by_whom: &Item, to_whom: &Item) -> UResult<()> {
|
|
let mut by_whom_for_update = by_whom.clone();
|
|
let mut to_whom_for_update = to_whom.clone();
|
|
start_attack_mut(trans, &mut by_whom_for_update, &mut to_whom_for_update).await?;
|
|
trans.save_item_model(&by_whom_for_update).await?;
|
|
trans.save_item_model(&to_whom_for_update).await?;
|
|
Ok(())
|
|
}
|
|
|
|
#[async_recursion]
|
|
pub async fn start_attack_mut(trans: &DBTrans, by_whom: &mut Item, to_whom: &mut Item) -> UResult<()> {
|
|
let mut msg_exp = String::new();
|
|
let mut msg_nonexp = String::new();
|
|
let mut verb: String = "attacks".to_string();
|
|
match by_whom.action_type {
|
|
LocationActionType::Sitting | LocationActionType::Reclining => {
|
|
msg_exp.push_str(&format!(ansi!("{} stands up.\n"), &by_whom.display));
|
|
msg_nonexp.push_str(&format!(ansi!("{} stands up.\n"),
|
|
by_whom.display_less_explicit.as_ref().unwrap_or(&by_whom.display)));
|
|
},
|
|
LocationActionType::Attacking(_) => {
|
|
match by_whom.active_combat.as_ref().and_then(|ac| ac.attacking.as_ref().and_then(|s| s.split_once("/"))) {
|
|
Some((cur_type, cur_code)) if cur_type == to_whom.item_type && cur_code == to_whom.item_code =>
|
|
user_error(format!("You're already attacking {}!", to_whom.pronouns.object))?,
|
|
Some((cur_type, cur_code)) => {
|
|
if let Some(cur_item_arc) = trans.find_item_by_type_code(cur_type, cur_code).await? {
|
|
stop_attacking(trans, by_whom, &cur_item_arc).await?;
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
verb = "refocuses ".to_string() + &by_whom.pronouns.possessive + " attacks on";
|
|
},
|
|
_ => {}
|
|
}
|
|
|
|
msg_exp.push_str(&format!(
|
|
ansi!("<red>{} {} {}.<reset>\n"),
|
|
&by_whom.display_for_sentence(true, 1, true),
|
|
verb,
|
|
&to_whom.display_for_sentence(true, 1, false))
|
|
);
|
|
msg_nonexp.push_str(&format!(
|
|
ansi!("<red>{} {} {}.<reset>\n"),
|
|
&by_whom.display_for_sentence(false, 1, true),
|
|
verb,
|
|
&to_whom.display_for_sentence(false, 1, false))
|
|
);
|
|
|
|
let wielded = what_wielded(trans, by_whom).await?;
|
|
msg_exp.push_str(&(wielded.normal_attack_start_message(by_whom, to_whom, true) + ".\n"));
|
|
msg_nonexp.push_str(&(wielded.normal_attack_start_message(by_whom, to_whom, false) + ".\n"));
|
|
|
|
broadcast_to_room(trans, &by_whom.location, None::<&Item>, &msg_exp, Some(msg_nonexp.as_str())).await?;
|
|
|
|
by_whom.active_combat.get_or_insert_with(|| Default::default()).attacking =
|
|
Some(format!("{}/{}",
|
|
&to_whom.item_type, &to_whom.item_code));
|
|
by_whom.action_type = LocationActionType::Attacking(Subattack::Normal);
|
|
to_whom.active_combat.get_or_insert_with(|| Default::default()).attacked_by.push(
|
|
format!("{}/{}",
|
|
&by_whom.item_type, &by_whom.item_code)
|
|
);
|
|
|
|
trans.upsert_task(&Task {
|
|
meta: TaskMeta {
|
|
task_code: format!("{}/{}", by_whom.item_type, by_whom.item_code),
|
|
next_scheduled: Utc::now() + chrono::Duration::milliseconds(
|
|
attack_speed(by_whom).as_millis() as i64),
|
|
..Default::default()
|
|
},
|
|
details: TaskDetails::AttackTick
|
|
}).await?;
|
|
// Auto-counterattack if victim isn't busy.
|
|
if to_whom.active_combat.as_ref().and_then(|ac| ac.attacking.as_ref()) == None {
|
|
start_attack_mut(trans, to_whom, by_whom).await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn corpsify_item(trans: &DBTrans, base_item: &Item) -> DResult<Item> {
|
|
let mut new_item = base_item.clone();
|
|
new_item.item_type = "corpse".to_owned();
|
|
new_item.item_code = format!("{}", trans.alloc_item_code().await?);
|
|
new_item.is_static = false;
|
|
trans.save_item_model(&new_item).await?;
|
|
trans.upsert_task(&Task {
|
|
meta: TaskMeta {
|
|
task_code: new_item.item_code.clone(),
|
|
next_scheduled: Utc::now() + chrono::Duration::minutes(5),
|
|
..Default::default()
|
|
},
|
|
details: TaskDetails::RotCorpse { corpse_code: new_item.item_code.clone() }
|
|
}).await?;
|
|
|
|
trans.transfer_all_possessions(base_item, &new_item).await?;
|
|
|
|
Ok(new_item)
|
|
}
|
|
|
|
pub struct NPCRecloneTaskHandler;
|
|
#[async_trait]
|
|
impl TaskHandler for NPCRecloneTaskHandler {
|
|
async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
|
|
let npc_code = match &ctx.task.details {
|
|
TaskDetails::RecloneNPC { npc_code } => npc_code.clone(),
|
|
_ => Err("Expected RecloneNPC type")?
|
|
};
|
|
let mut npc_item = match ctx.trans.find_item_by_type_code("npc", &npc_code).await? {
|
|
None => { return Ok(None) },
|
|
Some(r) => (*r).clone()
|
|
};
|
|
|
|
let npc = match npc_by_code().get(npc_code.as_str()) {
|
|
None => { return Ok(None) },
|
|
Some(r) => r
|
|
};
|
|
|
|
if npc_item.death_data.is_none() {
|
|
return Ok(None);
|
|
}
|
|
|
|
corpsify_item(ctx.trans, &npc_item).await?;
|
|
|
|
npc_item.death_data = None;
|
|
npc_item.health = max_health(&npc_item);
|
|
npc_item.location = npc.spawn_location.to_owned();
|
|
ctx.trans.save_item_model(&npc_item).await?;
|
|
return Ok(None);
|
|
}
|
|
}
|
|
pub struct RotCorpseTaskHandler;
|
|
#[async_trait]
|
|
impl TaskHandler for RotCorpseTaskHandler {
|
|
async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
|
|
let corpse_code = match &ctx.task.details {
|
|
TaskDetails::RotCorpse { corpse_code } => corpse_code.clone(),
|
|
_ => Err("Expected RotCorpse type")?
|
|
};
|
|
let corpse = match ctx.trans.find_item_by_type_code("corpse", &corpse_code).await? {
|
|
None => { return Ok(None) }
|
|
Some(r) => r
|
|
};
|
|
destroy_container(ctx.trans, &corpse).await?;
|
|
|
|
let msg_exp = format!("{} rots away to nothing.\n",
|
|
corpse.display_for_sentence(true, 1, true));
|
|
let msg_nonexp = format!("{} rots away to nothing.\n",
|
|
corpse.display_for_sentence(false, 1, true));
|
|
broadcast_to_room(ctx.trans, &corpse.location, None, &msg_exp, Some(&msg_nonexp)).await?;
|
|
Ok(None)
|
|
}
|
|
}
|
|
pub static ROT_CORPSE_HANDLER: &'static (dyn TaskHandler + Sync + Send) = &RotCorpseTaskHandler;
|