use common::{ comp::{ aura::{AuraChange, AuraKey, AuraKind, AuraTarget}, buff::{self, BuffCategory}, group::Group, Auras, BuffKind, Buffs, CharacterState, Health, Pos, }, event::{EventBus, ServerEvent}, resources::DeltaTime, uid::{Uid, UidAllocator}, }; use common_ecs::{Job, Origin, Phase, System}; use specs::{ saveload::MarkerAllocator, shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData, World, WriteStorage, }; use std::time::Duration; #[derive(SystemData)] pub struct ReadData<'a> { entities: Entities<'a>, dt: Read<'a, DeltaTime>, server_bus: Read<'a, EventBus>, uid_allocator: Read<'a, UidAllocator>, positions: ReadStorage<'a, Pos>, char_states: ReadStorage<'a, CharacterState>, healths: ReadStorage<'a, Health>, groups: ReadStorage<'a, Group>, uids: ReadStorage<'a, Uid>, } #[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { type SystemData = ( ReadData<'a>, WriteStorage<'a, Auras>, WriteStorage<'a, Buffs>, ); const NAME: &'static str = "aura"; const ORIGIN: Origin = Origin::Common; const PHASE: Phase = Phase::Create; fn run(_job: &mut Job, (read_data, mut auras, mut buffs): Self::SystemData) { let mut server_emitter = read_data.server_bus.emitter(); let dt = read_data.dt.0; auras.set_event_emission(false); buffs.set_event_emission(false); // Iterate through all buffs, on any buffs that are from an aura, sets the check // for whether the buff recently set by aura to false for (_, mut buffs_comp) in (&read_data.entities, &mut buffs).join() { for (_, buff) in buffs_comp.buffs.iter_mut() { if let Some(cat_id) = buff .cat_ids .iter_mut() .find(|cat_id| matches!(cat_id, BuffCategory::FromAura(true))) { *cat_id = BuffCategory::FromAura(false); } } } // Iterate through all entities with an aura for (entity, pos, mut auras_comp) in (&read_data.entities, &read_data.positions, &mut auras).join() { let mut expired_auras = Vec::::new(); // Iterate through the auras attached to this entity for (key, aura) in auras_comp.auras.iter_mut() { // Tick the aura and subtract dt from it if let Some(remaining_time) = &mut aura.duration { if let Some(new_duration) = remaining_time.checked_sub(Duration::from_secs_f32(dt)) { *remaining_time = new_duration; } else { *remaining_time = Duration::default(); expired_auras.push(key); } } for (target, target_pos, mut target_buffs, health, target_uid) in ( &read_data.entities, &read_data.positions, &mut buffs, &read_data.healths, &read_data.uids, ) .join() { // Ensure entity is within the aura radius if target_pos.0.distance_squared(pos.0) < aura.radius.powi(2) { if let AuraTarget::GroupOf(uid) = aura.target { let same_group = read_data .uid_allocator .retrieve_entity_internal(uid.into()) .and_then(|e| read_data.groups.get(e)) .map_or(false, |owner_group| { Some(owner_group) == read_data.groups.get(target) }) || *target_uid == uid; if !same_group { continue; } } // TODO: When more aura kinds (besides Buff) are // implemented, match on them here match aura.aura_kind { AuraKind::Buff { kind, data, category, source, } => { let apply_buff = match kind { BuffKind::CampfireHeal => { matches!( read_data.char_states.get(target), Some(CharacterState::Sit) ) && health.current() < health.maximum() }, // Add other specific buff conditions here _ => true, }; if apply_buff { // Checks that target is not already receiving a buff from an // aura, where the buff is of the same kind, and is of at least // the same strength and of at least the same duration // If no such buff is present, adds the buff let emit_buff = !target_buffs.buffs.iter().any(|(_, buff)| { buff.cat_ids.iter().any(|cat_id| { matches!(cat_id, BuffCategory::FromAura(_)) }) && buff.kind == kind && buff.data.strength >= data.strength && buff.time.map_or(true, |dur| { data.duration.map_or(false, |dur_2| dur >= dur_2) }) }); if emit_buff { use buff::*; server_emitter.emit(ServerEvent::Buff { entity: target, buff_change: BuffChange::Add(Buff::new( kind, data, vec![category, BuffCategory::FromAura(true)], source, )), }); } // Finds all buffs on target that are from an aura, are of the // same buff kind, and are of at most the same strength // For any such buffs, marks it as recently applied for (_, buff) in target_buffs.buffs.iter_mut().filter(|(_, buff)| { buff.cat_ids.iter().any(|cat_id| { matches!(cat_id, BuffCategory::FromAura(_)) }) && buff.kind == kind && buff.data.strength <= data.strength }) { if let Some(cat_id) = buff.cat_ids.iter_mut().find(|cat_id| { matches!(cat_id, BuffCategory::FromAura(false)) }) { *cat_id = BuffCategory::FromAura(true); } } } }, } } } } if !expired_auras.is_empty() { server_emitter.emit(ServerEvent::Aura { entity, aura_change: AuraChange::RemoveByKey(expired_auras), }); } } auras.set_event_emission(true); buffs.set_event_emission(true); } }