use super::{combat::change_health, comms::broadcast_to_room}; #[double] use crate::db::DBTrans; use crate::{ models::{ item::Item, task::{Task, TaskDetails, TaskMeta}, }, regular_tasks::{TaskHandler, TaskRunContext}, static_content::possession_type::UseEffect, DResult, }; use async_trait::async_trait; use chrono::Utc; use log::info; use mockall_double::double; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, VecDeque}; use std::time; #[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> { 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, }; if item.death_data.is_some() { return Ok(None); } 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, player: &mut Item, item: &Item, // None if target is player target: &mut Option, level: f64, task_ref: &str, ) -> DResult<()> { let mut target_health_series = BTreeMap::>::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(()) }