use crate::{ 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; use mockall_double::double; #[double] use crate::db::DBTrans; #[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.is_dead { 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(()) }