2023-01-22 22:43:44 +11:00
|
|
|
use crate::{
|
|
|
|
services::{
|
|
|
|
broadcast_to_room,
|
|
|
|
skills::skill_check_and_grind,
|
|
|
|
skills::skill_check_only,
|
|
|
|
},
|
|
|
|
models::{
|
|
|
|
item::{Item, LocationActionType, Subattack, SkillType},
|
|
|
|
task::{Task, TaskMeta, TaskDetails}
|
|
|
|
},
|
|
|
|
static_content::{
|
|
|
|
possession_type::{WeaponData, possession_data, fist},
|
|
|
|
npc::npc_by_code,
|
|
|
|
},
|
|
|
|
message_handler::user_commands::{user_error, UResult},
|
|
|
|
regular_tasks::{TaskRunContext, TaskHandler},
|
|
|
|
DResult,
|
|
|
|
db::DBTrans,
|
|
|
|
};
|
|
|
|
use async_trait::async_trait;
|
|
|
|
use chrono::Utc;
|
|
|
|
use async_recursion::async_recursion;
|
|
|
|
use std::time;
|
|
|
|
use ansi::ansi;
|
2023-01-27 00:36:49 +11:00
|
|
|
use rand::prelude::IteratorRandom;
|
2023-01-22 22:43:44 +11:00
|
|
|
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.is_dead || victim_item.is_dead {
|
|
|
|
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?;
|
|
|
|
} 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 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?;
|
2023-01-25 23:59:19 +11:00
|
|
|
handle_death(ctx.trans, &mut victim_item).await?;
|
2023-01-22 22:43:44 +11:00
|
|
|
ctx.trans.save_item_model(&victim_item).await?;
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-10 22:30:37 +11:00
|
|
|
pub async fn consider_reward_for(trans: &DBTrans, by_item: &mut Item, for_item: &Item) -> DResult<()> {
|
2023-01-27 00:36:49 +11:00
|
|
|
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
|
|
|
|
};
|
|
|
|
if by_item.total_xp >= for_item.total_xp {
|
|
|
|
trans.queue_for_session(&session, Some("[You didn't gain any experience for that]\n")).await?;
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
|
|
|
|
if xp_gain == 0 {
|
|
|
|
trans.queue_for_session(&session, Some("[You didn't gain any experience for that]\n")).await?;
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
by_item.total_xp += xp_gain;
|
|
|
|
let mut user = match trans.find_by_username(&by_item.item_code).await? {
|
|
|
|
None => return Ok(()),
|
|
|
|
Some(r) => r
|
|
|
|
};
|
|
|
|
user.experience.xp_change_for_this_reroll += xp_gain as i64;
|
2023-02-10 22:30:37 +11:00
|
|
|
|
|
|
|
// 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?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-27 00:36:49 +11:00
|
|
|
trans.save_user_model(&user).await?;
|
|
|
|
trans.queue_for_session(&session, Some(&format!("You gained {} experience points!\n", xp_gain))).await?;
|
2023-02-10 22:30:37 +11:00
|
|
|
|
2023-01-27 00:36:49 +11:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-01-22 22:43:44 +11:00
|
|
|
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)
|
|
|
|
);
|
2023-01-27 00:36:49 +11:00
|
|
|
broadcast_to_room(trans, &whom.location, None, &msg_exp, Some(&msg_nonexp)).await?;
|
2023-01-22 22:43:44 +11:00
|
|
|
|
2023-01-27 00:36:49 +11:00
|
|
|
whom.is_dead = true;
|
2023-01-22 22:43:44 +11:00
|
|
|
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();
|
2023-02-10 22:30:37 +11:00
|
|
|
consider_reward_for(trans, &mut new_aitem, &whom).await?;
|
2023-01-27 00:36:49 +11:00
|
|
|
stop_attacking_mut(trans, &mut new_aitem, whom, true).await?;
|
2023-01-22 22:43:44 +11:00
|
|
|
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();
|
2023-01-27 00:36:49 +11:00
|
|
|
stop_attacking_mut(trans, whom, &mut new_vitem, false).await?;
|
2023-01-22 22:43:44 +11:00
|
|
|
trans.save_item_model(&new_vitem).await?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-01-23 22:52:01 +11:00
|
|
|
if whom.item_type == "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?;
|
|
|
|
}
|
2023-01-27 00:36:49 +11:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn handle_resurrect(trans: &DBTrans, player: &mut Item) -> DResult<bool> {
|
|
|
|
corpsify_item(trans, &player).await?;
|
|
|
|
player.is_dead = false;
|
|
|
|
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);
|
2023-01-22 22:43:44 +11:00
|
|
|
|
2023-01-27 00:36:49 +11:00
|
|
|
trans.save_user_model(&user).await?;
|
|
|
|
Ok(true)
|
2023-01-22 22:43:44 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn max_health(_whom: &Item) -> u64 {
|
|
|
|
24
|
|
|
|
}
|
|
|
|
|
|
|
|
pub static TASK_HANDLER: &(dyn TaskHandler + Sync + Send) = &AttackTaskHandler;
|
|
|
|
|
2023-01-27 00:36:49 +11:00
|
|
|
pub async fn stop_attacking_mut(trans: &DBTrans, new_by_whom: &mut Item, new_to_whom: &mut Item,
|
|
|
|
auto_refocus: bool) ->
|
2023-01-22 22:43:44 +11:00
|
|
|
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;
|
2023-01-27 00:36:49 +11:00
|
|
|
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?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-01-22 22:43:44 +11:00
|
|
|
}
|
|
|
|
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();
|
2023-01-27 00:36:49 +11:00
|
|
|
stop_attacking_mut(trans, &mut new_by_whom, &mut new_to_whom, false).await?;
|
2023-01-22 22:43:44 +11:00
|
|
|
trans.save_item_model(&new_by_whom).await?;
|
|
|
|
trans.save_item_model(&new_to_whom).await?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-02-19 01:18:08 +11:00
|
|
|
async fn what_wielded(trans: &DBTrans, who: &Item) -> DResult<&'static WeaponData> {
|
|
|
|
if let Some(item) = trans.find_by_action_and_location(
|
|
|
|
&format!("{}/{}", &who.item_type, &who.item_code), &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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-22 22:43:44 +11:00
|
|
|
// 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<()> {
|
2023-01-27 00:36:49 +11:00
|
|
|
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<()> {
|
2023-01-22 22:43:44 +11:00
|
|
|
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?;
|
|
|
|
|
2023-01-27 00:36:49 +11:00
|
|
|
by_whom.active_combat.get_or_insert_with(|| Default::default()).attacking =
|
2023-01-22 22:43:44 +11:00
|
|
|
Some(format!("{}/{}",
|
|
|
|
&to_whom.item_type, &to_whom.item_code));
|
2023-01-27 00:36:49 +11:00
|
|
|
by_whom.action_type = LocationActionType::Attacking(Subattack::Normal);
|
|
|
|
to_whom.active_combat.get_or_insert_with(|| Default::default()).attacked_by.push(
|
2023-01-22 22:43:44 +11:00
|
|
|
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.
|
2023-01-27 00:36:49 +11:00
|
|
|
if to_whom.active_combat.as_ref().and_then(|ac| ac.attacking.as_ref()) == None {
|
2023-02-19 11:17:05 +11:00
|
|
|
start_attack_mut(trans, to_whom, by_whom).await?;
|
2023-01-22 22:43:44 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2023-01-23 22:52:01 +11:00
|
|
|
|
|
|
|
pub async fn corpsify_item(trans: &DBTrans, base_item: &Item) -> DResult<()> {
|
|
|
|
let mut new_item = base_item.clone();
|
|
|
|
new_item.item_type = "corpse".to_owned();
|
|
|
|
new_item.item_code = format!("{}", trans.alloc_item_code().await?);
|
2023-01-25 23:59:19 +11:00
|
|
|
new_item.is_static = false;
|
2023-01-23 22:52:01 +11:00
|
|
|
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?;
|
2023-01-25 23:59:19 +11:00
|
|
|
|
|
|
|
trans.transfer_all_possessions(base_item, &new_item).await?;
|
2023-01-23 22:52:01 +11:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
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.is_dead {
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
|
|
|
|
corpsify_item(ctx.trans, &npc_item).await?;
|
|
|
|
|
|
|
|
npc_item.is_dead = false;
|
|
|
|
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
|
|
|
|
};
|
|
|
|
ctx.trans.delete_item("corpse", &corpse_code).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));
|
2023-01-25 23:59:19 +11:00
|
|
|
ctx.trans.transfer_all_possessions_code(
|
|
|
|
&format!("{}/{}", &corpse.item_type, &corpse.item_code),
|
|
|
|
&corpse.location).await?;
|
2023-01-23 22:52:01 +11:00
|
|
|
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;
|