use common::{ comp::{projectile, Group, HealthSource, Inventory, Ori, PhysicsState, Pos, Projectile, Vel}, event::{EventBus, ServerEvent}, metrics::SysMetrics, resources::DeltaTime, span, uid::UidAllocator, GroupTarget, }; use specs::{ saveload::MarkerAllocator, Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage, }; use std::time::Duration; /// This system is responsible for handling projectile effect triggers pub struct Sys; impl<'a> System<'a> for Sys { #[allow(clippy::type_complexity)] type SystemData = ( Entities<'a>, Read<'a, DeltaTime>, Read<'a, UidAllocator>, Read<'a, EventBus>, ReadExpect<'a, SysMetrics>, ReadStorage<'a, Pos>, ReadStorage<'a, PhysicsState>, ReadStorage<'a, Vel>, WriteStorage<'a, Ori>, WriteStorage<'a, Projectile>, ReadStorage<'a, Inventory>, ReadStorage<'a, Group>, ); fn run( &mut self, ( entities, dt, uid_allocator, server_bus, sys_metrics, positions, physics_states, velocities, mut orientations, mut projectiles, inventories, groups, ): Self::SystemData, ) { let start_time = std::time::Instant::now(); span!(_guard, "run", "projectile::Sys::run"); let mut server_emitter = server_bus.emitter(); // Attacks 'projectile_loop: for (entity, pos, physics, ori, mut projectile) in ( &entities, &positions, &physics_states, &mut orientations, &mut projectiles, ) .join() { let mut projectile_vanished: bool = false; // Hit entity for other in physics.touch_entities.iter().copied() { let same_group = projectile .owner // Note: somewhat inefficient since we do the lookup for every touching // entity, but if we pull this out of the loop we would want to do it only // if there is at least one touching entity .and_then(|uid| uid_allocator.retrieve_entity_internal(uid.into())) .and_then(|e| groups.get(e)) .map_or(false, |owner_group| Some(owner_group) == uid_allocator .retrieve_entity_internal(other.into()) .and_then(|e| groups.get(e)) ); let target_group = if same_group { GroupTarget::InGroup } else { GroupTarget::OutOfGroup }; if projectile.ignore_group // Skip if in the same group && same_group { continue; } if projectile.owner == Some(other) { continue; } let projectile = &mut *projectile; for effect in projectile.hit_entity.drain(..) { match effect { projectile::Effect::Attack(attack) => { if let Some(owner) = projectile.owner { if let (Some(owner_entity), Some(target_entity)) = ( uid_allocator.retrieve_entity_internal(owner.into()), uid_allocator.retrieve_entity_internal(other.into()), ) { let server_events = attack.apply_attack( target_group, owner_entity, target_entity, inventories.get(target_entity), owner, ori.0, false, ); for event in server_events { server_emitter.emit(event); } } } }, // projectile::Effect::Knockback(knockback) => { // if let Some(other_entity) = // uid_allocator.retrieve_entity_internal(other.into()) // { // let impulse = knockback.calculate_impulse(ori.0); // if !impulse.is_approx_zero() { // server_emitter.emit(ServerEvent::Knockback { // entity: other_entity, // impulse, // }); // } // } // }, // projectile::Effect::RewardEnergy(energy) => { // if let Some(entity_owner) = projectile // .owner // .and_then(|u| uid_allocator.retrieve_entity_internal(u.into())) // { // server_emitter.emit(ServerEvent::EnergyChange { // entity: entity_owner, // change: EnergyChange { // amount: energy as i32, // source: EnergySource::HitEnemy, // }, // }); // } // }, projectile::Effect::Explode(e) => { server_emitter.emit(ServerEvent::Explosion { pos: pos.0, explosion: e, owner: projectile.owner, reagent: None, }) }, projectile::Effect::Vanish => { server_emitter.emit(ServerEvent::Destroy { entity, cause: HealthSource::World, }); projectile_vanished = true; }, projectile::Effect::Possess => { if other != projectile.owner.unwrap() { if let Some(owner) = projectile.owner { server_emitter.emit(ServerEvent::Possess(owner, other)); } } }, // TODO: Change to effect after !1472 merges // projectile::Effect::Buff { buff, chance } => { // if let Some(entity) = // uid_allocator.retrieve_entity_internal(other.into()) // { // if chance.map_or(true, |c| thread_rng().gen::() < c) { // let source = if let Some(owner) = projectile.owner { // BuffSource::Character { by: owner } // } else { // BuffSource::Unknown // }; // let buff = // Buff::new(buff.kind, buff.data, buff.cat_ids, source); // server_emitter.emit(ServerEvent::Buff { // entity, // buff_change: BuffChange::Add(buff), // }); // } // } // }, _ => {}, } } if projectile_vanished { continue 'projectile_loop; } } // Hit something solid if physics.on_wall.is_some() || physics.on_ground || physics.on_ceiling { let projectile = &mut *projectile; for effect in projectile.hit_solid.drain(..) { match effect { projectile::Effect::Explode(e) => { server_emitter.emit(ServerEvent::Explosion { pos: pos.0, explosion: e, owner: projectile.owner, reagent: None, }) }, projectile::Effect::Vanish => { server_emitter.emit(ServerEvent::Destroy { entity, cause: HealthSource::World, }); projectile_vanished = true; }, _ => {}, } } if projectile_vanished { continue 'projectile_loop; } } else if let Some(dir) = velocities .get(entity) .and_then(|vel| vel.0.try_normalized()) { ori.0 = dir.into(); } if projectile.time_left == Duration::default() { server_emitter.emit(ServerEvent::Destroy { entity, cause: HealthSource::World, }); } projectile.time_left = projectile .time_left .checked_sub(Duration::from_secs_f32(dt.0)) .unwrap_or_default(); } sys_metrics.projectile_ns.store( start_time.elapsed().as_nanos() as u64, std::sync::atomic::Ordering::Relaxed, ); } }