veloren_server/events/
entity_manipulation.rs

1#[cfg(feature = "worldgen")]
2use crate::rtsim::RtSim;
3use crate::{
4    Server, Settings, SpawnPoint,
5    client::Client,
6    comp::{
7        BuffKind, BuffSource, PhysicsState,
8        agent::{Agent, AgentEvent, Sound, SoundKind},
9        loot_owner::LootOwner,
10        skillset::SkillGroupKind,
11    },
12    error,
13    events::entity_creation::handle_create_npc,
14    persistence::character_updater::CharacterUpdater,
15    pet::tame_pet,
16    state_ext::StateExt,
17    sys::terrain::{NpcData, SAFE_ZONE_RADIUS, SpawnEntityData},
18};
19#[cfg(feature = "worldgen")]
20use common::rtsim::{Actor, RtSimEntity};
21use common::{
22    CachedSpatialGrid, Damage, DamageKind, DamageSource, GroupTarget, RadiusEffect,
23    assets::AssetExt,
24    combat::{
25        self, AttackSource, BASE_PARRIED_POISE_PUNISHMENT, DamageContributor, DeathEffect,
26        DeathEffects,
27    },
28    comp::{
29        self, Alignment, Auras, BASE_ABILITY_LIMIT, Body, BuffCategory, BuffEffect, CharacterState,
30        Energy, Group, Hardcore, Health, Inventory, Object, PickupItem, Player, Poise, PoiseChange,
31        Pos, Presence, PresenceKind, ProjectileConstructor, SkillSet, Stats,
32        aura::{self, EnteredAuras},
33        buff,
34        chat::{KillSource, KillType},
35        inventory::item::{AbilityMap, MaterialStatManifest},
36        item::flatten_counted_items,
37        loot_owner::LootOwnerKind,
38        projectile::{ProjectileAttack, ProjectileConstructorKind},
39    },
40    consts::TELEPORTER_RADIUS,
41    event::{
42        AuraEvent, BonkEvent, BuffEvent, ChangeAbilityEvent, ChangeBodyEvent, ChangeStanceEvent,
43        ChatEvent, ComboChangeEvent, CreateItemDropEvent, CreateNpcEvent, CreateObjectEvent,
44        DeleteEvent, DestroyEvent, DownedEvent, EmitExt, Emitter, EnergyChangeEvent,
45        EntityAttackedHookEvent, EventBus, ExplosionEvent, HealthChangeEvent, HelpDownedEvent,
46        KillEvent, KnockbackEvent, LandOnGroundEvent, MakeAdminEvent, ParryHookEvent,
47        PermanentChange, PoiseChangeEvent, RegrowHeadEvent, RemoveLightEmitterEvent, RespawnEvent,
48        ShootEvent, SoundEvent, StartInteractionEvent, StartTeleportingEvent, TeleportToEvent,
49        TeleportToPositionEvent, TransformEvent, UpdateMapMarkerEvent,
50    },
51    event_emitters,
52    explosion::ColorPreset,
53    generation::{EntityConfig, EntityInfo},
54    link::Is,
55    lottery::distribute_many,
56    mounting::{Mounting, Rider, VolumeRider},
57    outcome::{HealthChangeInfo, Outcome},
58    resources::{EntitiesDiedLastTick, ProgramTime, Secs, Time},
59    spiral::Spiral2d,
60    states::utils::StageSection,
61    terrain::{Block, BlockKind, TerrainGrid},
62    trade::{TradeResult, Trades},
63    uid::{IdMaps, Uid},
64    util::Dir,
65    vol::ReadVol,
66};
67use common_net::{msg::ServerGeneral, sync::WorldSyncExt, synced_components::Heads};
68use common_state::{AreasContainer, BlockChange, NoDurabilityArea};
69use hashbrown::HashSet;
70use rand::Rng;
71use specs::{
72    DispatcherBuilder, Entities, Entity as EcsEntity, Entity, Join, LendJoin, Read, ReadExpect,
73    ReadStorage, SystemData, WorldExt, Write, WriteExpect, WriteStorage, shred,
74};
75#[cfg(feature = "worldgen")] use std::sync::Arc;
76use std::{borrow::Cow, collections::HashMap, iter, time::Duration};
77use tracing::{debug, warn};
78use vek::{Vec2, Vec3};
79#[cfg(feature = "worldgen")]
80use world::{IndexOwned, World};
81
82use super::{ServerEvent, event_dispatch, event_sys_name};
83
84pub(super) fn register_event_systems(builder: &mut DispatcherBuilder) {
85    event_dispatch::<PoiseChangeEvent>(builder, &[]);
86    event_dispatch::<HealthChangeEvent>(builder, &[]);
87    event_dispatch::<KillEvent>(builder, &[]);
88    event_dispatch::<HelpDownedEvent>(builder, &[]);
89    event_dispatch::<DownedEvent>(builder, &[&event_sys_name::<HealthChangeEvent>()]);
90    event_dispatch::<KnockbackEvent>(builder, &[]);
91    event_dispatch::<DestroyEvent>(builder, &[&event_sys_name::<HealthChangeEvent>()]);
92    event_dispatch::<LandOnGroundEvent>(builder, &[]);
93    event_dispatch::<RespawnEvent>(builder, &[]);
94    event_dispatch::<ExplosionEvent>(builder, &[]);
95    event_dispatch::<BonkEvent>(builder, &[]);
96    event_dispatch::<AuraEvent>(builder, &[]);
97    event_dispatch::<BuffEvent>(builder, &[&event_sys_name::<DownedEvent>()]);
98    event_dispatch::<EnergyChangeEvent>(builder, &[]);
99    event_dispatch::<ComboChangeEvent>(builder, &[]);
100    event_dispatch::<ParryHookEvent>(builder, &[]);
101    event_dispatch::<TeleportToEvent>(builder, &[]);
102    event_dispatch::<EntityAttackedHookEvent>(builder, &[]);
103    event_dispatch::<ChangeAbilityEvent>(builder, &[]);
104    event_dispatch::<UpdateMapMarkerEvent>(builder, &[]);
105    event_dispatch::<MakeAdminEvent>(builder, &[]);
106    event_dispatch::<ChangeStanceEvent>(builder, &[]);
107    event_dispatch::<ChangeBodyEvent>(builder, &[]);
108    event_dispatch::<RemoveLightEmitterEvent>(builder, &[]);
109    event_dispatch::<TeleportToPositionEvent>(builder, &[]);
110    event_dispatch::<StartTeleportingEvent>(builder, &[]);
111    event_dispatch::<RegrowHeadEvent>(builder, &[]);
112}
113
114event_emitters! {
115    struct ReadExplosionEvents[ExplosionEmitters] {
116        health_change: HealthChangeEvent,
117        energy_change: EnergyChangeEvent,
118        poise_change: PoiseChangeEvent,
119        sound: SoundEvent,
120        parry_hook: ParryHookEvent,
121        knockback: KnockbackEvent,
122        entity_attack_hoow: EntityAttackedHookEvent,
123        combo_change: ComboChangeEvent,
124        buff: BuffEvent,
125        bonk: BonkEvent,
126        change_body: ChangeBodyEvent,
127        outcome: Outcome,
128    }
129
130    struct ReadEntityAttackedHookEvents[EntityAttackedHookEmitters] {
131        buff: BuffEvent,
132        combo_change: ComboChangeEvent,
133        knockback: KnockbackEvent,
134        energy_change: EnergyChangeEvent,
135    }
136
137    struct HealthChangeEvents[HealthChangeEmitters] {
138        destroy: DestroyEvent,
139        downed: DownedEvent,
140        outcome: Outcome,
141    }
142}
143
144pub fn handle_delete(server: &mut Server, DeleteEvent(entity): DeleteEvent) {
145    let _ = server
146        .state_mut()
147        .delete_entity_recorded(entity)
148        .map_err(|e| error!(?e, ?entity, "Failed to delete destroyed entity"));
149}
150
151#[derive(Hash, Eq, PartialEq)]
152enum DamageContrib {
153    Solo(EcsEntity),
154    Group(Group),
155    NotFound,
156}
157
158impl ServerEvent for PoiseChangeEvent {
159    type SystemData<'a> = (
160        Entities<'a>,
161        ReadStorage<'a, CharacterState>,
162        WriteStorage<'a, Poise>,
163    );
164
165    fn handle(
166        events: impl ExactSizeIterator<Item = Self>,
167        (entities, character_states, mut poises): Self::SystemData<'_>,
168    ) {
169        for ev in events {
170            if let Some((character_state, mut poise)) = (&character_states, &mut poises)
171                .lend_join()
172                .get(ev.entity, &entities)
173            {
174                // Entity is invincible to poise change during stunned character state.
175                if !matches!(character_state, CharacterState::Stunned(_)) {
176                    poise.change(ev.change);
177                }
178            }
179        }
180    }
181}
182
183#[cfg(feature = "worldgen")]
184pub fn entity_as_actor(
185    entity: Entity,
186    rtsim_entities: &ReadStorage<RtSimEntity>,
187    presences: &ReadStorage<Presence>,
188) -> Option<Actor> {
189    if let Some(rtsim_entity) = rtsim_entities.get(entity).copied() {
190        Some(Actor::Npc(rtsim_entity.0))
191    } else if let Some(PresenceKind::Character(character)) = presences.get(entity).map(|p| p.kind) {
192        Some(Actor::Character(character))
193    } else {
194        None
195    }
196}
197
198#[derive(SystemData)]
199pub struct HealthChangeEventData<'a> {
200    entities: Entities<'a>,
201    #[cfg(feature = "worldgen")]
202    rtsim: WriteExpect<'a, RtSim>,
203    events: HealthChangeEvents<'a>,
204    time: Read<'a, Time>,
205    #[cfg(feature = "worldgen")]
206    id_maps: Read<'a, IdMaps>,
207    #[cfg(feature = "worldgen")]
208    world: ReadExpect<'a, Arc<World>>,
209    #[cfg(feature = "worldgen")]
210    index: ReadExpect<'a, IndexOwned>,
211    positions: ReadStorage<'a, Pos>,
212    uids: ReadStorage<'a, Uid>,
213    #[cfg(feature = "worldgen")]
214    presences: ReadStorage<'a, Presence>,
215    #[cfg(feature = "worldgen")]
216    rtsim_entities: ReadStorage<'a, RtSimEntity>,
217    agents: WriteStorage<'a, Agent>,
218    healths: WriteStorage<'a, Health>,
219    heads: WriteStorage<'a, Heads>,
220}
221
222impl ServerEvent for HealthChangeEvent {
223    type SystemData<'a> = HealthChangeEventData<'a>;
224
225    fn handle(events: impl ExactSizeIterator<Item = Self>, mut data: Self::SystemData<'_>) {
226        let mut emitters = data.events.get_emitters();
227        let mut rng = rand::thread_rng();
228        for ev in events {
229            if let Some((mut health, pos, uid, heads)) = (
230                &mut data.healths,
231                data.positions.maybe(),
232                data.uids.maybe(),
233                (&mut data.heads).maybe(),
234            )
235                .lend_join()
236                .get(ev.entity, &data.entities)
237            {
238                // If the change amount was not zero
239                let changed = health.change_by(ev.change);
240                if let Some(mut heads) = heads {
241                    // We want some hp to be left for a headless body, so we divide by (max amount
242                    // of heads + 2)
243                    let hp_per_head = health.maximum() / (heads.capacity() as f32 + 2.0);
244                    let target_heads = (health.current() / hp_per_head) as usize;
245                    if heads.amount() > 0 && ev.change.amount < 0.0 && heads.amount() > target_heads
246                    {
247                        for _ in target_heads..heads.amount() {
248                            if let Some(head) = heads.remove_one(&mut rng, *data.time) {
249                                if let Some(uid) = uid {
250                                    emitters.emit(Outcome::HeadLost { uid: *uid, head });
251                                }
252                            } else {
253                                break;
254                            }
255                        }
256                    }
257                }
258
259                #[cfg(feature = "worldgen")]
260                if changed {
261                    let entity_as_actor =
262                        |entity| entity_as_actor(entity, &data.rtsim_entities, &data.presences);
263                    if let Some(actor) = entity_as_actor(ev.entity) {
264                        let cause = ev
265                            .change
266                            .damage_by()
267                            .map(|by| by.uid())
268                            .and_then(|uid| data.id_maps.uid_entity(uid))
269                            .and_then(entity_as_actor);
270                        data.rtsim.hook_rtsim_actor_hp_change(
271                            &data.world,
272                            data.index.as_index_ref(),
273                            actor,
274                            cause,
275                            health.fraction(),
276                            ev.change.amount,
277                        );
278                    }
279                }
280
281                if let (Some(pos), Some(uid)) = (pos, uid) {
282                    if changed {
283                        emitters.emit(Outcome::HealthChange {
284                            pos: pos.0,
285                            info: HealthChangeInfo {
286                                amount: ev.change.amount,
287                                by: ev.change.by,
288                                target: *uid,
289                                cause: ev.change.cause,
290                                precise: ev.change.precise,
291                                instance: ev.change.instance,
292                            },
293                        });
294                    }
295                }
296
297                if !health.is_dead && health.should_die() {
298                    if health.death_protection {
299                        emitters.emit(DownedEvent { entity: ev.entity });
300                    } else {
301                        emitters.emit(DestroyEvent {
302                            entity: ev.entity,
303                            cause: ev.change,
304                        });
305                    }
306                }
307            }
308
309            // This if statement filters out anything under 5 damage, for DOT ticks
310            // TODO: Find a better way to separate direct damage from DOT here
311            let damage = -ev.change.amount;
312            if damage > 5.0 {
313                if let Some(agent) = data.agents.get_mut(ev.entity) {
314                    agent.inbox.push_back(AgentEvent::Hurt);
315                }
316            }
317        }
318    }
319}
320
321impl ServerEvent for KillEvent {
322    type SystemData<'a> = WriteStorage<'a, comp::Health>;
323
324    fn handle(events: impl ExactSizeIterator<Item = Self>, mut healths: Self::SystemData<'_>) {
325        for ev in events {
326            if let Some(mut health) = healths.get_mut(ev.entity) {
327                health.kill();
328            }
329        }
330    }
331}
332
333#[derive(SystemData)]
334pub struct HelpDownedEventData<'a> {
335    id_maps: Read<'a, IdMaps>,
336    #[cfg(feature = "worldgen")]
337    rtsim: WriteExpect<'a, RtSim>,
338    #[cfg(feature = "worldgen")]
339    world: ReadExpect<'a, Arc<World>>,
340    #[cfg(feature = "worldgen")]
341    index: ReadExpect<'a, IndexOwned>,
342    #[cfg(feature = "worldgen")]
343    rtsim_entities: ReadStorage<'a, RtSimEntity>,
344    #[cfg(feature = "worldgen")]
345    presences: ReadStorage<'a, Presence>,
346    character_states: WriteStorage<'a, comp::CharacterState>,
347    healths: WriteStorage<'a, comp::Health>,
348}
349
350impl ServerEvent for HelpDownedEvent {
351    type SystemData<'a> = HelpDownedEventData<'a>;
352
353    fn handle(events: impl ExactSizeIterator<Item = Self>, mut data: Self::SystemData<'_>) {
354        for ev in events {
355            if let Some(entity) = data.id_maps.uid_entity(ev.target) {
356                if let Some(mut health) = data.healths.get_mut(entity) {
357                    health.refresh_death_protection();
358                }
359                if let Some(mut character_state) = data.character_states.get_mut(entity)
360                    && matches!(*character_state, comp::CharacterState::Crawl)
361                {
362                    *character_state = CharacterState::Idle(Default::default());
363                }
364
365                #[cfg(feature = "worldgen")]
366                let entity_as_actor =
367                    |entity| entity_as_actor(entity, &data.rtsim_entities, &data.presences);
368                #[cfg(feature = "worldgen")]
369                if let Some(actor) = entity_as_actor(entity) {
370                    let saver = ev
371                        .helper
372                        .and_then(|uid| data.id_maps.uid_entity(uid))
373                        .and_then(entity_as_actor);
374                    data.rtsim.hook_rtsim_actor_helped(
375                        &data.world,
376                        data.index.as_index_ref(),
377                        actor,
378                        saver,
379                    );
380                }
381            }
382        }
383    }
384}
385
386impl ServerEvent for DownedEvent {
387    type SystemData<'a> = (
388        Read<'a, EventBus<BuffEvent>>,
389        WriteStorage<'a, comp::CharacterState>,
390        WriteStorage<'a, comp::Health>,
391    );
392
393    fn handle(
394        events: impl ExactSizeIterator<Item = Self>,
395        (buff_event, mut character_states, mut healths): Self::SystemData<'_>,
396    ) {
397        let mut buff_emitter = buff_event.emitter();
398        for ev in events {
399            if let Some(mut health) = healths.get_mut(ev.entity) {
400                health.consume_death_protection()
401            }
402
403            if let Some(mut character_state) = character_states.get_mut(ev.entity) {
404                *character_state = CharacterState::Crawl;
405            }
406
407            // Remove buffs that don't persist when downed.
408            buff_emitter.emit(BuffEvent {
409                entity: ev.entity,
410                buff_change: comp::BuffChange::RemoveByCategory {
411                    all_required: vec![],
412                    any_required: vec![],
413                    none_required: vec![BuffCategory::PersistOnDowned],
414                },
415            });
416        }
417    }
418}
419
420impl ServerEvent for KnockbackEvent {
421    type SystemData<'a> = (
422        Entities<'a>,
423        ReadStorage<'a, Client>,
424        ReadStorage<'a, PhysicsState>,
425        ReadStorage<'a, comp::Mass>,
426        WriteStorage<'a, comp::Vel>,
427    );
428
429    fn handle(
430        events: impl ExactSizeIterator<Item = Self>,
431        (entities, clients, physic_states, mass, mut velocities): Self::SystemData<'_>,
432    ) {
433        for ev in events {
434            if let Some((physics, mass, vel, client)) = (
435                &physic_states,
436                mass.maybe(),
437                &mut velocities,
438                clients.maybe(),
439            )
440                .lend_join()
441                .get(ev.entity, &entities)
442            {
443                //Check if the entity is on a surface. If it is not, reduce knockback.
444                let mut impulse = ev.impulse
445                    * if physics.on_surface().is_some() {
446                        1.0
447                    } else {
448                        0.4
449                    };
450
451                // we go easy on the little ones (because they fly so far)
452                impulse /= mass.map_or(0.0, |m| m.0).max(40.0);
453
454                vel.0 += impulse;
455                if let Some(client) = client {
456                    client.send_fallible(ServerGeneral::Knockback(impulse));
457                }
458            }
459        }
460    }
461}
462
463fn handle_exp_gain(
464    exp_reward: f32,
465    inventory: &Inventory,
466    skill_set: &mut SkillSet,
467    uid: &Uid,
468    outcomes_emitter: &mut Emitter<Outcome>,
469) {
470    use comp::inventory::{item::ItemKind, slot::EquipSlot};
471
472    // Create hash set of xp pools to consider splitting xp amongst
473    let mut xp_pools = HashSet::<SkillGroupKind>::new();
474    // Insert general pool since it is always accessible
475    xp_pools.insert(SkillGroupKind::General);
476    // Closure to add xp pool corresponding to weapon type equipped in a particular
477    // EquipSlot
478    let mut add_tool_from_slot = |equip_slot| {
479        let tool_kind = inventory
480            .equipped(equip_slot)
481            .and_then(|i| match &*i.kind() {
482                ItemKind::Tool(tool) if tool.kind.gains_combat_xp() => Some(tool.kind),
483                _ => None,
484            });
485        if let Some(weapon) = tool_kind {
486            // Only adds to xp pools if entity has that skill group available
487            if skill_set.skill_group_accessible(SkillGroupKind::Weapon(weapon)) {
488                xp_pools.insert(SkillGroupKind::Weapon(weapon));
489            }
490        }
491    };
492    // Add weapons to xp pools considered
493    add_tool_from_slot(EquipSlot::ActiveMainhand);
494    add_tool_from_slot(EquipSlot::ActiveOffhand);
495    add_tool_from_slot(EquipSlot::InactiveMainhand);
496    add_tool_from_slot(EquipSlot::InactiveOffhand);
497    let num_pools = xp_pools.len() as f32;
498    for pool in xp_pools.iter() {
499        if let Some(level_outcome) =
500            skill_set.add_experience(*pool, (exp_reward / num_pools).ceil() as u32)
501        {
502            outcomes_emitter.emit(Outcome::SkillPointGain {
503                uid: *uid,
504                skill_tree: *pool,
505                total_points: level_outcome,
506            });
507        }
508    }
509    outcomes_emitter.emit(Outcome::ExpChange {
510        uid: *uid,
511        exp: exp_reward as u32,
512        xp_pools,
513    });
514}
515
516#[derive(SystemData)]
517pub struct DestroyEventData<'a> {
518    entities: Entities<'a>,
519    #[cfg(feature = "worldgen")]
520    rtsim: WriteExpect<'a, RtSim>,
521    id_maps: Read<'a, IdMaps>,
522    msm: ReadExpect<'a, MaterialStatManifest>,
523    ability_map: ReadExpect<'a, AbilityMap>,
524    time: Read<'a, Time>,
525    program_time: ReadExpect<'a, ProgramTime>,
526    #[cfg(feature = "worldgen")]
527    world: ReadExpect<'a, Arc<World>>,
528    #[cfg(feature = "worldgen")]
529    index: ReadExpect<'a, IndexOwned>,
530    areas_container: Read<'a, AreasContainer<NoDurabilityArea>>,
531    outcomes: Read<'a, EventBus<Outcome>>,
532    create_item_drop: Read<'a, EventBus<CreateItemDropEvent>>,
533    delete_event: Read<'a, EventBus<DeleteEvent>>,
534    transform_events: Read<'a, EventBus<TransformEvent>>,
535    chat_events: Read<'a, EventBus<ChatEvent>>,
536    entities_died_last_tick: Write<'a, EntitiesDiedLastTick>,
537    melees: WriteStorage<'a, comp::Melee>,
538    beams: WriteStorage<'a, comp::Beam>,
539    skill_sets: WriteStorage<'a, SkillSet>,
540    inventories: WriteStorage<'a, Inventory>,
541    item_drops: WriteStorage<'a, comp::ItemDrops>,
542    velocities: WriteStorage<'a, comp::Vel>,
543    force_updates: WriteStorage<'a, comp::ForceUpdate>,
544    energies: WriteStorage<'a, Energy>,
545    character_states: WriteStorage<'a, CharacterState>,
546    death_effects: WriteStorage<'a, DeathEffects>,
547    players: ReadStorage<'a, Player>,
548    clients: ReadStorage<'a, Client>,
549    uids: ReadStorage<'a, Uid>,
550    positions: ReadStorage<'a, Pos>,
551    healths: WriteStorage<'a, Health>,
552    bodies: ReadStorage<'a, Body>,
553    poises: ReadStorage<'a, Poise>,
554    groups: ReadStorage<'a, Group>,
555    alignments: ReadStorage<'a, Alignment>,
556    stats: ReadStorage<'a, Stats>,
557    agents: ReadStorage<'a, Agent>,
558    #[cfg(feature = "worldgen")]
559    rtsim_entities: ReadStorage<'a, RtSimEntity>,
560    #[cfg(feature = "worldgen")]
561    presences: ReadStorage<'a, Presence>,
562    buff_events: Read<'a, EventBus<BuffEvent>>,
563    masses: ReadStorage<'a, comp::Mass>,
564}
565
566/// Handle an entity dying. If it is a player, it will send a message to all
567/// other players. If the entity that killed it had stats, then give it exp for
568/// the kill. Experience given is equal to the level of the entity that was
569/// killed times 10.
570impl ServerEvent for DestroyEvent {
571    type SystemData<'a> = DestroyEventData<'a>;
572
573    fn handle(events: impl ExactSizeIterator<Item = Self>, mut data: Self::SystemData<'_>) {
574        let mut chat_emitter = data.chat_events.emitter();
575        let mut create_item_drop = data.create_item_drop.emitter();
576        let mut delete_emitter = data.delete_event.emitter();
577        let mut outcomes_emitter = data.outcomes.emitter();
578        let mut buff_emitter = data.buff_events.emitter();
579        let mut transform_emitter = data.transform_events.emitter();
580        data.entities_died_last_tick.0.clear();
581
582        for ev in events {
583            // TODO: Investigate duplicate `Destroy` events (but don't remove this).
584            // If the entity was already deleted, it can't be destroyed again.
585            if !data.entities.is_alive(ev.entity) {
586                continue;
587            }
588            let mut outcomes = data.outcomes.emitter();
589            if let Some(mut health) = data.healths.get_mut(ev.entity) {
590                if !health.is_dead {
591                    health.is_dead = true;
592
593                    if let Some(pos) = data.positions.get(ev.entity).copied() {
594                        data.entities_died_last_tick.0.push((ev.entity, pos));
595                    }
596                } else {
597                    // Skip for entities that have already died
598                    continue;
599                }
600            }
601
602            // Remove components that should not persist across death
603            data.melees.remove(ev.entity);
604            data.beams.remove(ev.entity);
605
606            let get_attacker_name = |cause_of_death: KillType, by: Uid| -> KillSource {
607                // Get attacker entity
608                if let Some(char_entity) = data.id_maps.uid_entity(by) {
609                    // Check if attacker is another player or entity with stats (npc)
610                    if data.players.contains(char_entity) {
611                        KillSource::Player(by, cause_of_death)
612                    } else if let Some(stats) = data.stats.get(char_entity) {
613                        KillSource::NonPlayer(stats.name.clone(), cause_of_death)
614                    } else {
615                        KillSource::NonExistent(cause_of_death)
616                    }
617                } else {
618                    KillSource::NonExistent(cause_of_death)
619                }
620            };
621
622            // Push an outcome if entity is has a character state (entities that don't have
623            // one, we probably don't care about emitting death outcome)
624            if let Some((pos, _)) = (&data.positions, &data.character_states)
625                .lend_join()
626                .get(ev.entity, &data.entities)
627            {
628                outcomes_emitter.emit(Outcome::Death { pos: pos.0 });
629            }
630
631            let mut should_delete = true;
632
633            // Handle any effects on death
634            if let Some(killed_stats) = data.stats.get(ev.entity) {
635                let attacker_entity = ev.cause.by.and_then(|x| data.id_maps.uid_entity(x.uid()));
636                let killed_uid = data.uids.get(ev.entity);
637                let attacker_stats = attacker_entity.and_then(|e| data.stats.get(e));
638                let attacker_mass = attacker_entity.and_then(|e| data.masses.get(e));
639                let mut death_effects = data
640                    .death_effects
641                    .remove(ev.entity)
642                    .map(|ef| ef.0.into_iter().map(Cow::Owned));
643
644                for effect in killed_stats
645                    .effects_on_death
646                    .iter()
647                    .map(Cow::Borrowed)
648                    .chain(death_effects.as_mut().map_or(
649                        &mut core::iter::empty() as &mut dyn Iterator<Item = Cow<DeathEffect>>,
650                        |death_effects| death_effects as &mut dyn Iterator<Item = Cow<DeathEffect>>,
651                    ))
652                {
653                    match effect.as_ref() {
654                        DeathEffect::AttackerBuff {
655                            kind,
656                            strength,
657                            duration,
658                        } => {
659                            if let Some(attacker) = attacker_entity {
660                                let dest_info = buff::DestInfo {
661                                    stats: attacker_stats,
662                                    mass: attacker_mass,
663                                };
664                                buff_emitter.emit(BuffEvent {
665                                    entity: attacker,
666                                    buff_change: buff::BuffChange::Add(buff::Buff::new(
667                                        *kind,
668                                        buff::BuffData::new(*strength, *duration),
669                                        vec![],
670                                        if let Some(uid) = killed_uid {
671                                            BuffSource::Character { by: *uid }
672                                        } else {
673                                            BuffSource::World
674                                        },
675                                        *data.time,
676                                        dest_info,
677                                        data.masses.get(ev.entity),
678                                    )),
679                                });
680                            }
681                        },
682                        DeathEffect::Transform {
683                            entity_spec,
684                            allow_players,
685                        } => {
686                            if data.clients.contains(ev.entity) && !allow_players {
687                                continue;
688                            }
689
690                            let Some(killed_uid) = killed_uid.copied() else {
691                                warn!(
692                                    "Could not handle transform death effect for entity without \
693                                     Uid"
694                                );
695
696                                continue;
697                            };
698
699                            transform_emitter.emit(TransformEvent {
700                                target_entity: killed_uid,
701                                entity_info: {
702                                    let Ok(entity_config) = EntityConfig::load(entity_spec)
703                                        .inspect_err(|error| {
704                                            error!(
705                                                ?entity_spec,
706                                                ?error,
707                                                "Could not load entity configuration for death \
708                                                 effect"
709                                            )
710                                        })
711                                    else {
712                                        continue;
713                                    };
714
715                                    EntityInfo::at(
716                                        data.positions
717                                            .get(ev.entity)
718                                            .map(|pos| pos.0)
719                                            .unwrap_or_default(),
720                                    )
721                                    .with_entity_config(
722                                        entity_config.read().clone(),
723                                        Some(entity_spec),
724                                        &mut rand::thread_rng(),
725                                        None,
726                                    )
727                                },
728                                allow_players: *allow_players,
729                                delete_on_failure: true,
730                            });
731
732                            should_delete = false;
733                        },
734                    }
735                }
736            }
737
738            // Chat message
739            // If it was a player that died
740            if let Some((uid, _player)) = (&data.uids, &data.players)
741                .lend_join()
742                .get(ev.entity, &data.entities)
743            {
744                let kill_source = match (ev.cause.cause, ev.cause.by.map(|x| x.uid())) {
745                    (Some(DamageSource::Melee), Some(by)) => get_attacker_name(KillType::Melee, by),
746                    (Some(DamageSource::Projectile), Some(by)) => {
747                        get_attacker_name(KillType::Projectile, by)
748                    },
749                    (Some(DamageSource::Explosion), Some(by)) => {
750                        get_attacker_name(KillType::Explosion, by)
751                    },
752                    (Some(DamageSource::Energy), Some(by)) => {
753                        get_attacker_name(KillType::Energy, by)
754                    },
755                    (Some(DamageSource::Buff(buff_kind)), by) => {
756                        if let Some(by) = by {
757                            get_attacker_name(KillType::Buff(buff_kind), by)
758                        } else {
759                            KillSource::NonExistent(KillType::Buff(buff_kind))
760                        }
761                    },
762                    (Some(DamageSource::Other), Some(by)) => get_attacker_name(KillType::Other, by),
763                    (Some(DamageSource::Falling), _) => KillSource::FallDamage,
764                    // HealthSource::Suicide => KillSource::Suicide,
765                    _ => KillSource::Other,
766                };
767
768                chat_emitter.emit(ChatEvent {
769                    msg: comp::UnresolvedChatMsg::death(kill_source, *uid),
770                    from_client: false,
771                });
772            }
773
774            let mut exp_awards = Vec::<(Entity, f32, Option<Group>)>::new();
775            // Award EXP to damage contributors
776            //
777            // NOTE: Debug logging is disabled by default for this module - to enable it add
778            // veloren_server::events::entity_manipulation=debug to RUST_LOG
779            'xp: {
780                let Some((
781                    entity_skill_set,
782                    entity_health,
783                    entity_energy,
784                    entity_inventory,
785                    entity_body,
786                    entity_poise,
787                    entity_pos,
788                )) = (
789                    &data.skill_sets,
790                    &data.healths,
791                    &data.energies,
792                    &data.inventories,
793                    &data.bodies,
794                    &data.poises,
795                    &data.positions,
796                )
797                    .lend_join()
798                    .get(ev.entity, &data.entities)
799                else {
800                    break 'xp;
801                };
802
803                // Calculate the total EXP award for the kill
804                let exp_reward = combat::combat_rating(
805                    entity_inventory,
806                    entity_health,
807                    entity_energy,
808                    entity_poise,
809                    entity_skill_set,
810                    *entity_body,
811                    &data.msm,
812                ) * 20.0;
813
814                let mut damage_contributors = HashMap::<DamageContrib, (u64, f32)>::new();
815                for (damage_contributor, damage) in entity_health.damage_contributions() {
816                    match damage_contributor {
817                        DamageContributor::Solo(uid) => {
818                            if let Some(attacker) = data.id_maps.uid_entity(*uid) {
819                                damage_contributors
820                                    .insert(DamageContrib::Solo(attacker), (*damage, 0.0));
821                            } else {
822                                // An entity who was not in a group contributed damage but is now
823                                // either dead or offline. Add a
824                                // placeholder to ensure that the contributor's
825                                // exp is discarded, not distributed between
826                                // the other contributors
827                                damage_contributors.insert(DamageContrib::NotFound, (*damage, 0.0));
828                            }
829                        },
830                        DamageContributor::Group {
831                            entity_uid: _,
832                            group,
833                        } => {
834                            // Damage made by entities who were in a group at the time of attack is
835                            // attributed to their group rather than themselves. This allows for all
836                            // members of a group to receive EXP, not just the damage dealers.
837                            let entry = damage_contributors
838                                .entry(DamageContrib::Group(*group))
839                                .or_insert((0, 0.0));
840                            entry.0 += damage;
841                        },
842                    }
843                }
844
845                // Calculate the percentage of total damage that each DamageContributor
846                // contributed
847                let total_damage: f64 = damage_contributors
848                    .values()
849                    .map(|(damage, _)| *damage as f64)
850                    .sum();
851                damage_contributors
852                    .iter_mut()
853                    .for_each(|(_, (damage, percentage))| {
854                        *percentage = (*damage as f64 / total_damage) as f32
855                    });
856
857                let destroyed_group = data.groups.get(ev.entity);
858
859                let within_range = |attacker_pos: &Pos| {
860                    // Maximum distance that an attacker must be from an entity at the time of its
861                    // death to receive EXP for the kill
862                    const MAX_EXP_DIST: f32 = 150.0;
863                    entity_pos.0.distance_squared(attacker_pos.0) < MAX_EXP_DIST.powi(2)
864                };
865
866                let is_pvp_kill = |attacker: Entity| {
867                    data.players.contains(ev.entity) && data.players.contains(attacker)
868                };
869
870                // Iterate through all contributors of damage for the killed entity, calculating
871                // how much EXP each contributor should be awarded based on their
872                // percentage of damage contribution
873                exp_awards = damage_contributors.iter().filter_map(|(damage_contributor, (_, damage_percent))| {
874                let contributor_exp = exp_reward * damage_percent;
875                match damage_contributor {
876                    DamageContrib::Solo(attacker) => {
877                        // No exp for self kills or PvP
878                        if *attacker == ev.entity || is_pvp_kill(*attacker) { return None; }
879
880                        // Only give EXP to the attacker if they are within EXP range of the killed entity
881                        data.positions.get(*attacker).and_then(|attacker_pos| {
882                            if within_range(attacker_pos) {
883                                debug!("Awarding {} exp to individual {:?} who contributed {}% damage to the kill of {:?}", contributor_exp, attacker, *damage_percent * 100.0, ev.entity);
884                                Some(iter::once((*attacker, contributor_exp, None)).collect())
885                            } else {
886                                None
887                            }
888                        })
889                    },
890                    DamageContrib::Group(group) => {
891                        // Don't give EXP to members in the destroyed entity's group
892                        if destroyed_group == Some(group) { return None; }
893
894                        // Only give EXP to members of the group that are within EXP range of the killed entity and aren't a pet
895                        let members_in_range = (
896                            &data.entities,
897                            &data.groups,
898                            &data.positions,
899                            data.alignments.maybe(),
900                            &data.uids,
901                        )
902                            .join()
903                            .filter_map(|(member_entity, member_group, member_pos, alignment, uid)| {
904                                if *member_group == *group && within_range(member_pos) && !is_pvp_kill(member_entity) && !matches!(alignment, Some(Alignment::Owned(owner)) if owner != uid) {
905                                    Some(member_entity)
906                                } else {
907                                    None
908                                }
909                            })
910                            .collect::<Vec<_>>();
911
912                        if members_in_range.is_empty() { return None; }
913
914                        // Divide EXP reward by square root of number of people in group for group EXP scaling
915                        let exp_per_member = contributor_exp / (members_in_range.len() as f32).sqrt();
916
917                        debug!("Awarding {} exp per member of group ID {:?} with {} members which contributed {}% damage to the kill of {:?}", exp_per_member, group, members_in_range.len(), *damage_percent * 100.0, ev.entity);
918                        Some(members_in_range.into_iter().map(|entity| (entity, exp_per_member, Some(*group))).collect::<Vec<(Entity, f32, Option<Group>)>>())
919                    },
920                    DamageContrib::NotFound => {
921                        // Discard exp for dead/offline individual damage contributors
922                        None
923                    }
924                }
925            }).flatten().collect::<Vec<(Entity, f32, Option<Group>)>>();
926
927                exp_awards.iter().for_each(|(attacker, exp_reward, _)| {
928                    // Process the calculated EXP rewards
929                    if let Some((mut attacker_skill_set, attacker_uid, attacker_inventory)) =
930                        (&mut data.skill_sets, &data.uids, &data.inventories)
931                            .lend_join()
932                            .get(*attacker, &data.entities)
933                    {
934                        handle_exp_gain(
935                            *exp_reward,
936                            attacker_inventory,
937                            &mut attacker_skill_set,
938                            attacker_uid,
939                            &mut outcomes,
940                        );
941                    }
942                });
943            };
944
945            should_delete &= if data.clients.contains(ev.entity) {
946                if let Some(vel) = data.velocities.get_mut(ev.entity) {
947                    vel.0 = Vec3::zero();
948                }
949                if let Some(force_update) = data.force_updates.get_mut(ev.entity) {
950                    force_update.update();
951                }
952                if let Some(mut energy) = data.energies.get_mut(ev.entity) {
953                    energy.refresh();
954                }
955                if let Some(mut character_state) = data.character_states.get_mut(ev.entity) {
956                    *character_state = CharacterState::default();
957                }
958
959                false
960            } else {
961                if let Some((_agent, pos, alignment, vel)) = (
962                    &data.agents,
963                    &data.positions,
964                    data.alignments.maybe(),
965                    data.velocities.maybe(),
966                )
967                    .lend_join()
968                    .get(ev.entity, &data.entities)
969                {
970                    // Only drop loot if entity has agency (not a player),
971                    // and if it is not owned by another entity (not a pet)
972                    if !matches!(alignment, Some(Alignment::Owned(_)))
973                        && let Some(items) = data
974                            .item_drops
975                            .remove(ev.entity)
976                            .map(|comp::ItemDrops(item)| item)
977                    {
978                        // Remove entries where zero exp was awarded - this happens because some
979                        // entities like Object bodies don't give EXP.
980                        let mut item_receivers = HashMap::new();
981                        for (entity, exp, group) in exp_awards {
982                            if exp >= f32::EPSILON {
983                                let loot_owner = if let Some(group) = group {
984                                    Some(LootOwnerKind::Group(group))
985                                } else {
986                                    let uid = data.bodies.get(entity).and_then(|body| {
987                                        // Only humanoids are awarded loot ownership - if the winner
988                                        // was a non-humanoid NPC the loot will be free-for-all
989                                        if matches!(body, Body::Humanoid(_)) {
990                                            data.uids.get(entity).copied()
991                                        } else {
992                                            None
993                                        }
994                                    });
995
996                                    uid.map(LootOwnerKind::Player)
997                                };
998
999                                *item_receivers.entry(loot_owner).or_insert(0.0) += exp;
1000                            }
1001                        }
1002
1003                        let mut item_offset_spiral =
1004                            Spiral2d::new().map(|offset| offset.as_::<f32>() * 0.5);
1005
1006                        let mut rng = rand::thread_rng();
1007                        let mut spawn_item = |item, loot_owner| {
1008                            let offset = item_offset_spiral.next().unwrap_or_default();
1009                            create_item_drop.emit(CreateItemDropEvent {
1010                                pos: Pos(pos.0 + Vec3::unit_z() * 0.25 + offset),
1011                                vel: vel.copied().unwrap_or(comp::Vel(Vec3::zero())),
1012                                ori: comp::Ori::from(Dir::random_2d(&mut rng)),
1013                                item: PickupItem::new(item, *data.program_time, false),
1014                                loot_owner: if let Some(loot_owner) = loot_owner {
1015                                    debug!(
1016                                        "Assigned UID {loot_owner:?} as the winner for the loot \
1017                                         drop"
1018                                    );
1019                                    Some(LootOwner::new(loot_owner, false))
1020                                } else {
1021                                    debug!("No loot owner");
1022                                    None
1023                                },
1024                            })
1025                        };
1026
1027                        if item_receivers.is_empty() {
1028                            debug!("No item receivers");
1029                            for item in flatten_counted_items(&items, &data.ability_map, &data.msm)
1030                            {
1031                                spawn_item(item, None)
1032                            }
1033                        } else {
1034                            let mut rng = rand::thread_rng();
1035                            distribute_many(
1036                                item_receivers
1037                                    .iter()
1038                                    .map(|(loot_owner, weight)| (*weight, *loot_owner)),
1039                                &mut rng,
1040                                &items,
1041                                |(amount, _)| *amount,
1042                                |(_, item), loot_owner, count| {
1043                                    for item in
1044                                        item.stacked_duplicates(&data.ability_map, &data.msm, count)
1045                                    {
1046                                        spawn_item(item, loot_owner)
1047                                    }
1048                                },
1049                            );
1050                        }
1051                    }
1052                }
1053                true
1054            };
1055            if !should_delete {
1056                let resists_durability =
1057                    data.positions
1058                        .get(ev.entity)
1059                        .cloned()
1060                        .is_some_and(|our_pos| {
1061                            let our_pos = our_pos.0.map(|i| i as i32);
1062
1063                            let is_in_area = data
1064                                .areas_container
1065                                .areas()
1066                                .iter()
1067                                .any(|(_, area)| area.contains_point(our_pos));
1068
1069                            is_in_area
1070                        });
1071
1072                // Modify durability on all equipped items
1073                if !resists_durability
1074                    && let Some(mut inventory) = data.inventories.get_mut(ev.entity)
1075                {
1076                    inventory.damage_items(&data.ability_map, &data.msm, *data.time);
1077                }
1078            }
1079
1080            #[cfg(feature = "worldgen")]
1081            let entity_as_actor =
1082                |entity| entity_as_actor(entity, &data.rtsim_entities, &data.presences);
1083
1084            #[cfg(feature = "worldgen")]
1085            if let Some(actor) = entity_as_actor(ev.entity)
1086                // Skip the death hook for rtsim entities if they aren't deleted, otherwise
1087                // we'll end up with rtsim respawning an entity that wasn't actually
1088                // removed, producing 2 entities having the same RtsimEntityId.
1089                && should_delete
1090            {
1091                data.rtsim.hook_rtsim_actor_death(
1092                    &data.world,
1093                    data.index.as_index_ref(),
1094                    actor,
1095                    data.positions.get(ev.entity).map(|p| p.0),
1096                    ev.cause
1097                        .by
1098                        .as_ref()
1099                        .and_then(
1100                            |(DamageContributor::Solo(entity_uid)
1101                             | DamageContributor::Group { entity_uid, .. })| {
1102                                data.id_maps.uid_entity(*entity_uid)
1103                            },
1104                        )
1105                        .and_then(entity_as_actor),
1106                );
1107            }
1108
1109            if should_delete {
1110                delete_emitter.emit(DeleteEvent(ev.entity));
1111            }
1112        }
1113    }
1114}
1115
1116impl ServerEvent for LandOnGroundEvent {
1117    type SystemData<'a> = (
1118        Read<'a, Time>,
1119        ReadExpect<'a, MaterialStatManifest>,
1120        Read<'a, EventBus<HealthChangeEvent>>,
1121        Read<'a, EventBus<PoiseChangeEvent>>,
1122        ReadStorage<'a, PhysicsState>,
1123        ReadStorage<'a, CharacterState>,
1124        ReadStorage<'a, comp::Mass>,
1125        ReadStorage<'a, Inventory>,
1126        ReadStorage<'a, Stats>,
1127    );
1128
1129    fn handle(
1130        events: impl ExactSizeIterator<Item = Self>,
1131        (
1132            time,
1133            msm,
1134            health_change_events,
1135            poise_change_events,
1136            physic_states,
1137            character_states,
1138            masses,
1139            inventories,
1140            stats,
1141        ): Self::SystemData<'_>,
1142    ) {
1143        let mut health_change_emitter = health_change_events.emitter();
1144        let mut poise_change_emitter = poise_change_events.emitter();
1145        for ev in events {
1146            // HACK: Certain ability movements currently take us above the fall damage
1147            // threshold in the horizontal axis. This factor dampens velocity in the
1148            // horizontal axis when applying fall damage.
1149            let horizontal_damp = 0.5
1150                + ev.vel
1151                    .try_normalized()
1152                    .unwrap_or_default()
1153                    .dot(Vec3::unit_z())
1154                    .abs()
1155                    * 0.5;
1156
1157            let relative_vel = ev.vel.dot(-ev.surface_normal) * horizontal_damp;
1158            // The second part of this if statement disables all fall damage when in the
1159            // water. This was added as a *temporary* fix a bug that causes you to take
1160            // fall damage while swimming downwards. FIXME: Fix the actual bug and
1161            // remove the following relevant part of the if statement.
1162            if relative_vel >= 30.0
1163                && physic_states
1164                    .get(ev.entity)
1165                    .is_none_or(|ps| ps.in_liquid().is_none())
1166            {
1167                let reduced_vel =
1168                    if let Some(CharacterState::DiveMelee(c)) = character_states.get(ev.entity) {
1169                        (relative_vel + c.static_data.vertical_speed).min(0.0)
1170                    } else {
1171                        relative_vel
1172                    };
1173
1174                let mass = masses.get(ev.entity).copied().unwrap_or_default();
1175                let impact_energy = mass.0 * reduced_vel.powi(2) / 2.0;
1176                let falldmg = impact_energy / 1000.0;
1177
1178                // Emit health change
1179                let damage = Damage {
1180                    source: DamageSource::Falling,
1181                    kind: DamageKind::Crushing,
1182                    value: falldmg,
1183                };
1184                let damage_reduction = Damage::compute_damage_reduction(
1185                    Some(damage),
1186                    inventories.get(ev.entity),
1187                    stats.get(ev.entity),
1188                    &msm,
1189                );
1190                let change = damage.calculate_health_change(
1191                    damage_reduction,
1192                    0.0,
1193                    None,
1194                    None,
1195                    0.0,
1196                    1.0,
1197                    *time,
1198                    rand::random(),
1199                );
1200
1201                health_change_emitter.emit(HealthChangeEvent {
1202                    entity: ev.entity,
1203                    change,
1204                });
1205
1206                // Emit poise change
1207                let poise_damage = -(mass.0 * reduced_vel.powi(2) / 1500.0);
1208                let poise_change = Poise::apply_poise_reduction(
1209                    poise_damage,
1210                    inventories.get(ev.entity),
1211                    &msm,
1212                    character_states.get(ev.entity),
1213                    stats.get(ev.entity),
1214                );
1215                let poise_change = comp::PoiseChange {
1216                    amount: poise_change,
1217                    impulse: Vec3::unit_z(),
1218                    by: None,
1219                    cause: None,
1220                    time: *time,
1221                };
1222                poise_change_emitter.emit(PoiseChangeEvent {
1223                    entity: ev.entity,
1224                    change: poise_change,
1225                });
1226            }
1227        }
1228    }
1229}
1230
1231impl ServerEvent for RespawnEvent {
1232    type SystemData<'a> = (
1233        Read<'a, SpawnPoint>,
1234        WriteStorage<'a, Health>,
1235        WriteStorage<'a, comp::Combo>,
1236        WriteStorage<'a, Pos>,
1237        WriteStorage<'a, comp::PhysicsState>,
1238        WriteStorage<'a, comp::ForceUpdate>,
1239        WriteStorage<'a, Heads>,
1240        ReadStorage<'a, Client>,
1241        ReadStorage<'a, Hardcore>,
1242        ReadStorage<'a, comp::Waypoint>,
1243    );
1244
1245    fn handle(
1246        events: impl ExactSizeIterator<Item = Self>,
1247        (
1248            spawn_point,
1249            mut healths,
1250            mut combos,
1251            mut positions,
1252            mut physic_states,
1253            mut force_updates,
1254            mut heads,
1255            clients,
1256            hardcore,
1257            waypoints,
1258        ): Self::SystemData<'_>,
1259    ) {
1260        for RespawnEvent(entity) in events {
1261            // Hardcore characters cannot respawn
1262            if !hardcore.contains(entity) && clients.contains(entity) {
1263                let respawn_point = waypoints
1264                    .get(entity)
1265                    .map(|wp| wp.get_pos())
1266                    .unwrap_or(spawn_point.0);
1267
1268                healths.get_mut(entity).map(|mut health| health.revive());
1269                combos.get_mut(entity).map(|mut combo| combo.reset());
1270                positions.get_mut(entity).map(|pos| pos.0 = respawn_point);
1271                heads.get_mut(entity).map(|mut heads| heads.reset());
1272                physic_states
1273                    .get_mut(entity)
1274                    .map(|phys_state| phys_state.reset());
1275                force_updates
1276                    .get_mut(entity)
1277                    .map(|force_update| force_update.update());
1278            }
1279        }
1280    }
1281}
1282
1283#[derive(SystemData)]
1284pub struct ExplosionData<'a> {
1285    entities: Entities<'a>,
1286    block_change: Write<'a, BlockChange>,
1287    settings: Read<'a, Settings>,
1288    time: Read<'a, Time>,
1289    id_maps: Read<'a, IdMaps>,
1290    spatial_grid: Read<'a, CachedSpatialGrid>,
1291    terrain: ReadExpect<'a, TerrainGrid>,
1292    msm: ReadExpect<'a, MaterialStatManifest>,
1293    event_busses: ReadExplosionEvents<'a>,
1294    outcomes: Read<'a, EventBus<Outcome>>,
1295    groups: ReadStorage<'a, Group>,
1296    auras: ReadStorage<'a, Auras>,
1297    positions: ReadStorage<'a, Pos>,
1298    players: ReadStorage<'a, Player>,
1299    energies: ReadStorage<'a, Energy>,
1300    combos: ReadStorage<'a, comp::Combo>,
1301    inventories: ReadStorage<'a, Inventory>,
1302    alignments: ReadStorage<'a, Alignment>,
1303    entered_auras: ReadStorage<'a, EnteredAuras>,
1304    buffs: ReadStorage<'a, comp::Buffs>,
1305    stats: ReadStorage<'a, comp::Stats>,
1306    healths: ReadStorage<'a, Health>,
1307    bodies: ReadStorage<'a, Body>,
1308    orientations: ReadStorage<'a, comp::Ori>,
1309    character_states: ReadStorage<'a, CharacterState>,
1310    uids: ReadStorage<'a, Uid>,
1311    masses: ReadStorage<'a, comp::Mass>,
1312}
1313
1314impl ServerEvent for ExplosionEvent {
1315    type SystemData<'a> = ExplosionData<'a>;
1316
1317    fn handle(events: impl ExactSizeIterator<Item = Self>, mut data: Self::SystemData<'_>) {
1318        let mut emitters = data.event_busses.get_emitters();
1319        let mut outcome_emitter = data.outcomes.emitter();
1320
1321        // TODO: Faster RNG?
1322        let mut rng = rand::thread_rng();
1323
1324        for ev in events {
1325            let owner_entity = ev.owner.and_then(|uid| data.id_maps.uid_entity(uid));
1326
1327            let explosion_volume = 6.25 * ev.explosion.radius;
1328
1329            emitters.emit(SoundEvent {
1330                sound: Sound::new(SoundKind::Explosion, ev.pos, explosion_volume, data.time.0),
1331            });
1332
1333            let outcome_power = ev.explosion.radius;
1334            outcome_emitter.emit(Outcome::Explosion {
1335                pos: ev.pos,
1336                power: outcome_power,
1337                radius: ev.explosion.radius,
1338                is_attack: ev
1339                    .explosion
1340                    .effects
1341                    .iter()
1342                    .any(|e| matches!(e, RadiusEffect::Attack(_))),
1343                reagent: ev.explosion.reagent,
1344            });
1345
1346            /// Used to get strength of explosion effects as they falloff over
1347            /// distance
1348            fn cylinder_sphere_strength(
1349                sphere_pos: Vec3<f32>,
1350                radius: f32,
1351                min_falloff: f32,
1352                cyl_pos: Vec3<f32>,
1353                cyl_body: Body,
1354            ) -> f32 {
1355                // 2d check
1356                let horiz_dist = Vec2::<f32>::from(sphere_pos - cyl_pos).distance(Vec2::default())
1357                    - cyl_body.max_radius();
1358                // z check
1359                let half_body_height = cyl_body.height() / 2.0;
1360                let vert_distance =
1361                    (sphere_pos.z - (cyl_pos.z + half_body_height)).abs() - half_body_height;
1362
1363                // Use whichever gives maximum distance as that closer to real value. Sets
1364                // minimum to 0 as negative values would indicate inside entity.
1365                let distance = horiz_dist.max(vert_distance).max(0.0);
1366
1367                if distance > radius {
1368                    // If further than exploion radius, no strength
1369                    0.0
1370                } else {
1371                    // Falloff inversely proportional to radius
1372                    let fall_off = ((distance / radius).min(1.0) - 1.0).abs();
1373                    let min_falloff = min_falloff.clamp(0.0, 1.0);
1374                    min_falloff + fall_off * (1.0 - min_falloff)
1375                }
1376            }
1377
1378            // TODO: Process terrain destruction first so that entities don't get protected
1379            // by terrain that gets destroyed?
1380            'effects: for effect in ev.explosion.effects {
1381                match effect {
1382                    RadiusEffect::TerrainDestruction(power, new_color) => {
1383                        const RAYS: usize = 500;
1384
1385                        // Prevent block colour changes within the radius of a safe zone aura
1386                        if data
1387                            .spatial_grid
1388                            .0
1389                            .in_circle_aabr(ev.pos.xy(), SAFE_ZONE_RADIUS)
1390                            .filter_map(|entity| {
1391                                data.auras
1392                                    .get(entity)
1393                                    .and_then(|entity_auras| {
1394                                        data.positions.get(entity).map(|pos| (entity_auras, pos))
1395                                    })
1396                                    .and_then(|(entity_auras, pos)| {
1397                                        entity_auras
1398                                            .auras
1399                                            .iter()
1400                                            .find(|(_, aura)| {
1401                                                matches!(aura.aura_kind, aura::AuraKind::Buff {
1402                                                    kind: BuffKind::Invulnerability,
1403                                                    source: BuffSource::World,
1404                                                    ..
1405                                                })
1406                                            })
1407                                            .map(|(_, aura)| (*pos, aura.radius))
1408                                    })
1409                            })
1410                            .any(|(aura_pos, aura_radius)| {
1411                                ev.pos.distance_squared(aura_pos.0) < aura_radius.powi(2)
1412                            })
1413                        {
1414                            continue 'effects;
1415                        }
1416
1417                        // Color terrain
1418                        let mut touched_blocks = Vec::new();
1419                        let color_range = power * 2.7;
1420                        for _ in 0..RAYS {
1421                            let dir = Vec3::new(
1422                                rng.gen::<f32>() - 0.5,
1423                                rng.gen::<f32>() - 0.5,
1424                                rng.gen::<f32>() - 0.5,
1425                            )
1426                            .normalized();
1427
1428                            let _ = data
1429                                .terrain
1430                                .ray(ev.pos, ev.pos + dir * color_range)
1431                                .until(|_| rng.gen::<f32>() < 0.05)
1432                                .for_each(|_: &Block, pos| touched_blocks.push(pos))
1433                                .cast();
1434                        }
1435
1436                        for block_pos in touched_blocks {
1437                            if let Ok(block) = data.terrain.get(block_pos) {
1438                                if !matches!(block.kind(), BlockKind::Lava | BlockKind::GlowingRock)
1439                                    && (
1440                                        // Check that owner is not player or explosion_burn_marks by
1441                                        // players
1442                                        // is enabled
1443                                        owner_entity.is_none_or(|e| data.players.get(e).is_none())
1444                                            || data.settings.gameplay.explosion_burn_marks
1445                                    )
1446                                {
1447                                    let diff2 =
1448                                        block_pos.map(|b| b as f32).distance_squared(ev.pos);
1449                                    let fade = (1.0 - diff2 / color_range.powi(2)).max(0.0);
1450                                    if let Some(mut color) = block.get_color() {
1451                                        let r = color[0] as f32
1452                                            + (fade
1453                                                * (color[0] as f32 * 0.5 - color[0] as f32
1454                                                    + new_color[0]));
1455                                        let g = color[1] as f32
1456                                            + (fade
1457                                                * (color[1] as f32 * 0.3 - color[1] as f32
1458                                                    + new_color[1]));
1459                                        let b = color[2] as f32
1460                                            + (fade
1461                                                * (color[2] as f32 * 0.3 - color[2] as f32
1462                                                    + new_color[2]));
1463                                        // Darken blocks, but not too much
1464                                        color[0] = (r as u8).max(30);
1465                                        color[1] = (g as u8).max(30);
1466                                        color[2] = (b as u8).max(30);
1467                                        data.block_change
1468                                            .set(block_pos, Block::new(block.kind(), color));
1469                                    }
1470                                }
1471
1472                                if block.is_bonkable() {
1473                                    emitters.emit(BonkEvent {
1474                                        pos: block_pos.map(|e| e as f32 + 0.5),
1475                                        owner: ev.owner,
1476                                        target: None,
1477                                    });
1478                                }
1479                            }
1480                        }
1481
1482                        // Destroy terrain
1483                        for _ in 0..RAYS {
1484                            let dir = Vec3::new(
1485                                rng.gen::<f32>() - 0.5,
1486                                rng.gen::<f32>() - 0.5,
1487                                rng.gen::<f32>() - 0.15,
1488                            )
1489                            .normalized();
1490
1491                            let mut ray_energy = power;
1492
1493                            let from = ev.pos;
1494                            let to = ev.pos + dir * power;
1495                            let _ = data
1496                                .terrain
1497                                .ray(from, to)
1498                                .while_(|block: &Block| {
1499                                    ray_energy -= block.explode_power().unwrap_or(0.0)
1500                                        + rng.gen::<f32>() * 0.1;
1501
1502                                    // Stop if:
1503                                    // 1) Block is liquid
1504                                    // 2) Consumed all energy
1505                                    // 3) Can't explode block (for example we hit stone wall)
1506                                    block.is_liquid()
1507                                        || block.explode_power().is_none()
1508                                        || ray_energy <= 0.0
1509                                })
1510                                .for_each(|block: &Block, pos| {
1511                                    if block.explode_power().is_some() {
1512                                        data.block_change.set(pos, block.into_vacant());
1513                                    }
1514                                })
1515                                .cast();
1516                        }
1517                    },
1518                    RadiusEffect::Attack(attack) => {
1519                        for (
1520                            entity_b,
1521                            pos_b,
1522                            health_b,
1523                            (body_b_maybe, ori_b_maybe, char_state_b_maybe, uid_b),
1524                        ) in (
1525                            &data.entities,
1526                            &data.positions,
1527                            &data.healths,
1528                            (
1529                                data.bodies.maybe(),
1530                                data.orientations.maybe(),
1531                                data.character_states.maybe(),
1532                                &data.uids,
1533                            ),
1534                        )
1535                            .join()
1536                            .filter(|(_, _, h, _)| !h.is_dead)
1537                        {
1538                            let dist_sqrd = ev.pos.distance_squared(pos_b.0);
1539
1540                            // Check if it is a hit
1541                            let strength = if let Some(body) = body_b_maybe {
1542                                cylinder_sphere_strength(
1543                                    ev.pos,
1544                                    ev.explosion.radius,
1545                                    ev.explosion.min_falloff,
1546                                    pos_b.0,
1547                                    *body,
1548                                )
1549                            } else {
1550                                1.0 - dist_sqrd / ev.explosion.radius.powi(2)
1551                            };
1552
1553                            // Cast a ray from the explosion to the entity to check visibility
1554                            if strength > 0.0
1555                                && (data
1556                                    .terrain
1557                                    .ray(ev.pos, pos_b.0)
1558                                    .until(Block::is_opaque)
1559                                    .cast()
1560                                    .0
1561                                    + 0.1)
1562                                    .powi(2)
1563                                    >= dist_sqrd
1564                            {
1565                                // See if entities are in the same group
1566                                let same_group = owner_entity
1567                                    .and_then(|e| data.groups.get(e))
1568                                    .map(|group_a| Some(group_a) == data.groups.get(entity_b))
1569                                    .unwrap_or(Some(entity_b) == owner_entity);
1570
1571                                let target_group = if same_group {
1572                                    GroupTarget::InGroup
1573                                } else {
1574                                    GroupTarget::OutOfGroup
1575                                };
1576
1577                                let dir = Dir::new(
1578                                    (pos_b.0 - ev.pos)
1579                                        .try_normalized()
1580                                        .unwrap_or_else(Vec3::unit_z),
1581                                );
1582
1583                                let attacker_info =
1584                                    owner_entity.zip(ev.owner).map(|(entity, uid)| {
1585                                        combat::AttackerInfo {
1586                                            entity,
1587                                            uid,
1588                                            group: data.groups.get(entity),
1589                                            energy: data.energies.get(entity),
1590                                            combo: data.combos.get(entity),
1591                                            inventory: data.inventories.get(entity),
1592                                            stats: data.stats.get(entity),
1593                                            mass: data.masses.get(entity),
1594                                        }
1595                                    });
1596
1597                                let target_info = combat::TargetInfo {
1598                                    entity: entity_b,
1599                                    uid: *uid_b,
1600                                    inventory: data.inventories.get(entity_b),
1601                                    stats: data.stats.get(entity_b),
1602                                    health: Some(health_b),
1603                                    pos: pos_b.0,
1604                                    ori: ori_b_maybe,
1605                                    char_state: char_state_b_maybe,
1606                                    energy: data.energies.get(entity_b),
1607                                    buffs: data.buffs.get(entity_b),
1608                                    mass: data.masses.get(entity_b),
1609                                };
1610
1611                                let target_dodging = char_state_b_maybe
1612                                    .and_then(|cs| cs.roll_attack_immunities())
1613                                    .is_some_and(|i| i.explosions);
1614                                let allow_friendly_fire =
1615                                    owner_entity.is_some_and(|owner_entity| {
1616                                        combat::allow_friendly_fire(
1617                                            &data.entered_auras,
1618                                            owner_entity,
1619                                            entity_b,
1620                                        )
1621                                    });
1622                                // PvP check
1623                                let permit_pvp = combat::permit_pvp(
1624                                    &data.alignments,
1625                                    &data.players,
1626                                    &data.entered_auras,
1627                                    &data.id_maps,
1628                                    owner_entity,
1629                                    entity_b,
1630                                );
1631                                let attack_options = combat::AttackOptions {
1632                                    target_dodging,
1633                                    permit_pvp,
1634                                    allow_friendly_fire,
1635                                    target_group,
1636                                    precision_mult: None,
1637                                };
1638
1639                                attack.apply_attack(
1640                                    attacker_info,
1641                                    &target_info,
1642                                    dir,
1643                                    attack_options,
1644                                    strength,
1645                                    combat::AttackSource::Explosion,
1646                                    *data.time,
1647                                    &mut emitters,
1648                                    |o| outcome_emitter.emit(o),
1649                                    &mut rng,
1650                                    0,
1651                                );
1652                            }
1653                        }
1654                    },
1655                    RadiusEffect::Entity(mut effect) => {
1656                        for (entity_b, pos_b, body_b_maybe) in
1657                            (&data.entities, &data.positions, data.bodies.maybe()).join()
1658                        {
1659                            let strength = if let Some(body) = body_b_maybe {
1660                                cylinder_sphere_strength(
1661                                    ev.pos,
1662                                    ev.explosion.radius,
1663                                    ev.explosion.min_falloff,
1664                                    pos_b.0,
1665                                    *body,
1666                                )
1667                            } else {
1668                                let distance_squared = ev.pos.distance_squared(pos_b.0);
1669                                1.0 - distance_squared / ev.explosion.radius.powi(2)
1670                            };
1671
1672                            // Player check only accounts for PvP/PvE flag (unless in a friendly
1673                            // fire aura), but bombs are intented to do
1674                            // friendly fire.
1675                            //
1676                            // What exactly is friendly fire is subject to discussion.
1677                            // As we probably want to minimize possibility of being dick
1678                            // even to your group members, the only exception is when
1679                            // you want to harm yourself.
1680                            //
1681                            // This can be changed later.
1682                            let permit_pvp = || {
1683                                combat::permit_pvp(
1684                                    &data.alignments,
1685                                    &data.players,
1686                                    &data.entered_auras,
1687                                    &data.id_maps,
1688                                    owner_entity,
1689                                    entity_b,
1690                                ) || owner_entity.is_none_or(|entity_a| entity_a == entity_b)
1691                            };
1692                            if strength > 0.0 {
1693                                let is_alive =
1694                                    data.healths.get(entity_b).is_none_or(|h| !h.is_dead);
1695
1696                                if is_alive {
1697                                    effect.modify_strength(strength);
1698                                    if !effect.is_harm() || permit_pvp() {
1699                                        emit_effect_events(
1700                                            &mut emitters,
1701                                            *data.time,
1702                                            entity_b,
1703                                            effect.clone(),
1704                                            ev.owner.map(|owner| {
1705                                                (
1706                                                    owner,
1707                                                    data.id_maps
1708                                                        .uid_entity(owner)
1709                                                        .and_then(|e| data.groups.get(e))
1710                                                        .copied(),
1711                                                )
1712                                            }),
1713                                            data.inventories.get(entity_b),
1714                                            &data.msm,
1715                                            data.character_states.get(entity_b),
1716                                            data.stats.get(entity_b),
1717                                            data.masses.get(entity_b),
1718                                            owner_entity.and_then(|e| data.masses.get(e)),
1719                                            data.bodies.get(entity_b),
1720                                            data.positions.get(entity_b),
1721                                        );
1722                                    }
1723                                }
1724                            }
1725                        }
1726                    },
1727                }
1728            }
1729        }
1730    }
1731}
1732
1733pub fn emit_effect_events(
1734    emitters: &mut (
1735             impl EmitExt<HealthChangeEvent>
1736             + EmitExt<PoiseChangeEvent>
1737             + EmitExt<BuffEvent>
1738             + EmitExt<ChangeBodyEvent>
1739             + EmitExt<Outcome>
1740         ),
1741    time: Time,
1742    entity: EcsEntity,
1743    effect: common::effect::Effect,
1744    source: Option<(Uid, Option<Group>)>,
1745    inventory: Option<&Inventory>,
1746    msm: &MaterialStatManifest,
1747    char_state: Option<&CharacterState>,
1748    stats: Option<&Stats>,
1749    tgt_mass: Option<&comp::Mass>,
1750    source_mass: Option<&comp::Mass>,
1751    tgt_body: Option<&Body>,
1752    tgt_pos: Option<&Pos>,
1753) {
1754    let damage_contributor = source.map(|(uid, group)| DamageContributor::new(uid, group));
1755    match effect {
1756        common::effect::Effect::Health(change) => {
1757            emitters.emit(HealthChangeEvent { entity, change })
1758        },
1759        common::effect::Effect::Poise(amount) => {
1760            let amount = Poise::apply_poise_reduction(amount, inventory, msm, char_state, stats);
1761            emitters.emit(PoiseChangeEvent {
1762                entity,
1763                change: comp::PoiseChange {
1764                    amount,
1765                    impulse: Vec3::zero(),
1766                    by: damage_contributor,
1767                    cause: None,
1768                    time,
1769                },
1770            })
1771        },
1772        common::effect::Effect::Damage(damage) => {
1773            let change = damage.calculate_health_change(
1774                combat::Damage::compute_damage_reduction(Some(damage), inventory, stats, msm),
1775                0.0,
1776                damage_contributor,
1777                None,
1778                0.0,
1779                1.0,
1780                time,
1781                rand::random(),
1782            );
1783            emitters.emit(HealthChangeEvent { entity, change })
1784        },
1785        common::effect::Effect::Buff(buff) => {
1786            let dest_info = buff::DestInfo {
1787                stats,
1788                mass: tgt_mass,
1789            };
1790            emitters.emit(BuffEvent {
1791                entity,
1792                buff_change: comp::BuffChange::Add(comp::Buff::new(
1793                    buff.kind,
1794                    buff.data,
1795                    buff.cat_ids,
1796                    comp::BuffSource::Item,
1797                    time,
1798                    dest_info,
1799                    source_mass,
1800                )),
1801            });
1802        },
1803        common::effect::Effect::Permanent(permanent_effect) => match permanent_effect {
1804            common::effect::PermanentEffect::CycleBodyType => {
1805                if let Some(body) = tgt_body
1806                    && let Some(new_body) = match body {
1807                        Body::Humanoid(body) => Some(Body::Humanoid(comp::humanoid::Body {
1808                            body_type: match body.body_type {
1809                                comp::humanoid::BodyType::Female => comp::humanoid::BodyType::Male,
1810                                comp::humanoid::BodyType::Male => comp::humanoid::BodyType::Female,
1811                            },
1812                            ..*body
1813                        })),
1814                        // Only allow humanoids for now.
1815                        _ => None,
1816                    }
1817                {
1818                    // TODO: Change only the body from the character list?
1819                    emitters.emit(ChangeBodyEvent {
1820                        entity,
1821                        new_body,
1822                        permanent_change: Some(PermanentChange {
1823                            expected_old_body: *body,
1824                        }),
1825                    });
1826                    if let Some(pos) = tgt_pos {
1827                        emitters.emit(Outcome::Transformation { pos: pos.0 });
1828                    }
1829                }
1830            },
1831        },
1832    }
1833}
1834
1835impl ServerEvent for BonkEvent {
1836    type SystemData<'a> = (
1837        Write<'a, BlockChange>,
1838        ReadExpect<'a, TerrainGrid>,
1839        ReadExpect<'a, ProgramTime>,
1840        Read<'a, EventBus<CreateObjectEvent>>,
1841        Read<'a, EventBus<ShootEvent>>,
1842    );
1843
1844    fn handle(
1845        events: impl ExactSizeIterator<Item = Self>,
1846        (mut block_change, terrain, program_time, create_object_events, shoot_events): Self::SystemData<'_>,
1847    ) {
1848        let mut create_object_emitter = create_object_events.emitter();
1849        let mut shoot_emitter = shoot_events.emitter();
1850        for ev in events {
1851            if let Some(_target) = ev.target {
1852                // TODO: bonk entities but do no damage?
1853            } else {
1854                use common::terrain::SpriteKind;
1855                let pos = ev.pos.map(|e| e.floor() as i32);
1856                if let Some(block) = terrain.get(pos).ok().copied().filter(|b| b.is_bonkable()) {
1857                    if block_change
1858                        .try_set(pos, block.with_sprite(SpriteKind::Empty))
1859                        .is_some()
1860                    {
1861                        let sprite_cfg = terrain.sprite_cfg_at(pos);
1862                        if let Some(items) = comp::Item::try_reclaim_from_block(block, sprite_cfg) {
1863                            let msm = &MaterialStatManifest::load().read();
1864                            let ability_map = &AbilityMap::load().read();
1865                            for item in flatten_counted_items(&items, ability_map, msm) {
1866                                let pos = Pos(pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0));
1867                                let vel = comp::Vel::default();
1868                                // TODO: Use the `ItemDrop` body for this.
1869                                let body = match block.get_sprite() {
1870                                    // Create different containers depending on the original
1871                                    // sprite
1872                                    Some(SpriteKind::Apple) => comp::object::Body::Apple,
1873                                    Some(SpriteKind::Beehive) => comp::object::Body::Hive,
1874                                    Some(SpriteKind::Coconut) => comp::object::Body::Coconut,
1875                                    Some(SpriteKind::Bomb) => comp::object::Body::Bomb,
1876                                    _ => comp::object::Body::Pebble,
1877                                };
1878
1879                                if matches!(block.get_sprite(), Some(SpriteKind::Bomb)) {
1880                                    shoot_emitter.emit(ShootEvent {
1881                                        entity: None,
1882                                        pos,
1883                                        dir: Dir::from_unnormalized(vel.0).unwrap_or_default(),
1884                                        body: Body::Object(body),
1885                                        light: None,
1886                                        projectile: ProjectileConstructor {
1887                                            kind: ProjectileConstructorKind::Explosive {
1888                                                radius: 12.0,
1889                                                min_falloff: 0.75,
1890                                                reagent: None,
1891                                                terrain: Some((4.0, ColorPreset::Black)),
1892                                            },
1893                                            attack: Some(ProjectileAttack {
1894                                                damage: 40.0,
1895                                                poise: Some(100.0),
1896                                                knockback: None,
1897                                                energy: None,
1898                                                buff: None,
1899                                                friendly_fire: true,
1900                                            }),
1901                                            scaled: None,
1902                                        }
1903                                        .create_projectile(None, 1.0, None),
1904                                        speed: vel.0.magnitude(),
1905                                        object: None,
1906                                    });
1907                                } else {
1908                                    create_object_emitter.emit(CreateObjectEvent {
1909                                        pos,
1910                                        vel,
1911                                        body,
1912                                        object: None,
1913                                        item: Some(comp::PickupItem::new(
1914                                            item,
1915                                            *program_time,
1916                                            false,
1917                                        )),
1918                                        light_emitter: None,
1919                                        stats: None,
1920                                    });
1921                                }
1922                            }
1923                        }
1924                    }
1925                }
1926            }
1927        }
1928    }
1929}
1930
1931impl ServerEvent for AuraEvent {
1932    type SystemData<'a> = (WriteStorage<'a, Auras>, WriteStorage<'a, EnteredAuras>);
1933
1934    fn handle(
1935        events: impl ExactSizeIterator<Item = Self>,
1936        (mut auras, mut entered_auras): Self::SystemData<'_>,
1937    ) {
1938        for ev in events {
1939            use aura::AuraChange;
1940            match ev.aura_change {
1941                AuraChange::Add(new_aura) => {
1942                    if let Some(mut auras) = auras.get_mut(ev.entity) {
1943                        auras.insert(new_aura);
1944                    }
1945                },
1946                AuraChange::RemoveByKey(keys) => {
1947                    if let Some(mut auras) = auras.get_mut(ev.entity) {
1948                        for key in keys {
1949                            auras.remove(key);
1950                        }
1951                    }
1952                },
1953                AuraChange::EnterAura(uid, key, variant) => {
1954                    if let Some(mut entered_auras) = entered_auras.get_mut(ev.entity) {
1955                        entered_auras
1956                            .auras
1957                            .entry(variant)
1958                            .and_modify(|entered_auras| {
1959                                entered_auras.insert((uid, key));
1960                            })
1961                            .or_insert_with(|| <_ as Into<_>>::into([(uid, key)]));
1962                    }
1963                },
1964                AuraChange::ExitAura(uid, key, variant) => {
1965                    if let Some(mut entered_auras) = entered_auras.get_mut(ev.entity) {
1966                        if let Some(entered_auras_variant) = entered_auras.auras.get_mut(&variant) {
1967                            entered_auras_variant.remove(&(uid, key));
1968
1969                            if entered_auras_variant.is_empty() {
1970                                entered_auras.auras.remove(&variant);
1971                            }
1972                        }
1973                    }
1974                },
1975            }
1976        }
1977    }
1978}
1979
1980impl ServerEvent for BuffEvent {
1981    type SystemData<'a> = (
1982        Read<'a, Time>,
1983        WriteStorage<'a, comp::Buffs>,
1984        ReadStorage<'a, Body>,
1985        ReadStorage<'a, Health>,
1986        ReadStorage<'a, Stats>,
1987        ReadStorage<'a, comp::Mass>,
1988    );
1989
1990    fn handle(
1991        events: impl ExactSizeIterator<Item = Self>,
1992        (time, mut buffs, bodies, healths, stats, masses): Self::SystemData<'_>,
1993    ) {
1994        for ev in events {
1995            if let Some(mut buffs) = buffs.get_mut(ev.entity) {
1996                use buff::BuffChange;
1997                match ev.buff_change {
1998                    BuffChange::Add(new_buff) => {
1999                        let immunity_by_buff = buffs
2000                            .buffs
2001                            .values_mut()
2002                            .flat_map(|b| b.kind.effects(&b.data))
2003                            .find(|b| match b {
2004                                BuffEffect::BuffImmunity(kind) => new_buff.kind == *kind,
2005                                _ => false,
2006                            });
2007
2008                        if !bodies
2009                            .get(ev.entity)
2010                            .is_some_and(|body| body.immune_to(new_buff.kind))
2011                            && immunity_by_buff.is_none()
2012                            && healths.get(ev.entity).is_none_or(|h| !h.is_dead)
2013                        {
2014                            if let Some(strength) =
2015                                new_buff.kind.resilience_ccr_strength(new_buff.data)
2016                            {
2017                                let resilience_buff = buff::Buff::new(
2018                                    BuffKind::Resilience,
2019                                    buff::BuffData::new(
2020                                        strength,
2021                                        Some(
2022                                            new_buff
2023                                                .data
2024                                                .duration
2025                                                .map_or(Secs(30.0), |dur| dur * 5.0),
2026                                        ),
2027                                    ),
2028                                    Vec::new(),
2029                                    BuffSource::Buff,
2030                                    *time,
2031                                    buff::DestInfo {
2032                                        stats: stats.get(ev.entity),
2033                                        mass: masses.get(ev.entity),
2034                                    },
2035                                    // There is no source entity
2036                                    None,
2037                                );
2038                                buffs.insert(resilience_buff, *time);
2039                            }
2040                            buffs.insert(new_buff, *time);
2041                        }
2042                    },
2043                    BuffChange::RemoveByKey(keys) => {
2044                        for key in keys {
2045                            buffs.remove(key);
2046                        }
2047                    },
2048                    BuffChange::RemoveByKind(kind) => {
2049                        buffs.remove_kind(kind);
2050                    },
2051                    BuffChange::RemoveFromController(kind) => {
2052                        if kind.is_buff() {
2053                            buffs.remove_kind(kind);
2054                        }
2055                    },
2056                    BuffChange::RemoveByCategory {
2057                        all_required,
2058                        any_required,
2059                        none_required,
2060                    } => {
2061                        let mut keys_to_remove = Vec::new();
2062                        for (key, buff) in buffs.buffs.iter() {
2063                            let mut required_met = true;
2064                            for required in &all_required {
2065                                if !buff.cat_ids.iter().any(|cat| cat == required) {
2066                                    required_met = false;
2067                                    break;
2068                                }
2069                            }
2070                            let mut any_met = any_required.is_empty();
2071                            for any in &any_required {
2072                                if buff.cat_ids.iter().any(|cat| cat == any) {
2073                                    any_met = true;
2074                                    break;
2075                                }
2076                            }
2077                            let mut none_met = true;
2078                            for none in &none_required {
2079                                if buff.cat_ids.iter().any(|cat| cat == none) {
2080                                    none_met = false;
2081                                    break;
2082                                }
2083                            }
2084                            if required_met && any_met && none_met {
2085                                keys_to_remove.push(key);
2086                            }
2087                        }
2088                        for key in keys_to_remove {
2089                            buffs.remove(key);
2090                        }
2091                    },
2092                    BuffChange::Refresh(kind) => {
2093                        buffs
2094                            .buffs
2095                            .values_mut()
2096                            .filter(|b| b.kind == kind)
2097                            .for_each(|buff| {
2098                                // Resets buff so that its remaining duration is equal to its
2099                                // original duration
2100                                buff.start_time = *time;
2101                                buff.end_time = buff.data.duration.map(|dur| Time(time.0 + dur.0));
2102                            })
2103                    },
2104                }
2105            }
2106        }
2107    }
2108}
2109
2110impl ServerEvent for EnergyChangeEvent {
2111    type SystemData<'a> = WriteStorage<'a, Energy>;
2112
2113    fn handle(events: impl ExactSizeIterator<Item = Self>, mut energies: Self::SystemData<'_>) {
2114        for ev in events {
2115            if let Some(mut energy) = energies.get_mut(ev.entity) {
2116                energy.change_by(ev.change);
2117                if ev.reset_rate {
2118                    energy.reset_regen_rate();
2119                }
2120            }
2121        }
2122    }
2123}
2124
2125impl ServerEvent for ComboChangeEvent {
2126    type SystemData<'a> = (
2127        Read<'a, Time>,
2128        Read<'a, EventBus<Outcome>>,
2129        WriteStorage<'a, comp::Combo>,
2130        ReadStorage<'a, Uid>,
2131    );
2132
2133    fn handle(
2134        events: impl ExactSizeIterator<Item = Self>,
2135        (time, outcomes, mut combos, uids): Self::SystemData<'_>,
2136    ) {
2137        let mut outcome_emitter = outcomes.emitter();
2138        for ev in events {
2139            if let Some(mut combo) = combos.get_mut(ev.entity) {
2140                combo.change_by(ev.change, time.0);
2141                if let Some(uid) = uids.get(ev.entity) {
2142                    outcome_emitter.emit(Outcome::ComboChange {
2143                        uid: *uid,
2144                        combo: combo.counter(),
2145                    });
2146                }
2147            }
2148        }
2149    }
2150}
2151
2152impl ServerEvent for ParryHookEvent {
2153    type SystemData<'a> = (
2154        Read<'a, Time>,
2155        Read<'a, EventBus<EnergyChangeEvent>>,
2156        Read<'a, EventBus<PoiseChangeEvent>>,
2157        Read<'a, EventBus<BuffEvent>>,
2158        WriteStorage<'a, CharacterState>,
2159        ReadStorage<'a, Uid>,
2160        ReadStorage<'a, Stats>,
2161        ReadStorage<'a, comp::Mass>,
2162        ReadStorage<'a, Inventory>,
2163    );
2164
2165    fn handle(
2166        events: impl ExactSizeIterator<Item = Self>,
2167        (
2168            time,
2169            energy_change_events,
2170            poise_change_events,
2171            buff_events,
2172            mut character_states,
2173            uids,
2174            stats,
2175            masses,
2176            inventories,
2177        ): Self::SystemData<'_>,
2178    ) {
2179        let mut energy_change_emitter = energy_change_events.emitter();
2180        let mut poise_change_emitter = poise_change_events.emitter();
2181        let mut buff_emitter = buff_events.emitter();
2182        for ev in events {
2183            if let Some(mut char_state) = character_states.get_mut(ev.defender) {
2184                let return_to_wield = match &mut *char_state {
2185                    CharacterState::RiposteMelee(c) => {
2186                        c.stage_section = StageSection::Action;
2187                        c.timer = Duration::default();
2188                        c.whiffed = false;
2189                        false
2190                    },
2191                    CharacterState::BasicBlock(c) => {
2192                        // Refund half the energy of entering the block for a successful parry
2193                        energy_change_emitter.emit(EnergyChangeEvent {
2194                            entity: ev.defender,
2195                            change: c.static_data.energy_regen,
2196                            reset_rate: false,
2197                        });
2198                        c.is_parry = true;
2199                        false
2200                    },
2201                    _ => false,
2202                };
2203                if return_to_wield {
2204                    *char_state = CharacterState::Wielding(common::states::wielding::Data {
2205                        is_sneaking: false,
2206                    });
2207                }
2208            };
2209
2210            if let Some(attacker) = ev.attacker
2211                && matches!(ev.source, AttackSource::Melee)
2212            {
2213                // When attacker is parried, the debuff lasts 2 seconds, the attacker takes
2214                // poise damage, get precision vulnerability and get slower recovery speed
2215                let data = buff::BuffData::new(1.0, Some(Secs(2.0)));
2216                let source = if let Some(uid) = uids.get(ev.defender) {
2217                    BuffSource::Character { by: *uid }
2218                } else {
2219                    BuffSource::World
2220                };
2221                let dest_info = buff::DestInfo {
2222                    stats: stats.get(attacker),
2223                    mass: masses.get(attacker),
2224                };
2225                let buff = buff::Buff::new(
2226                    BuffKind::Parried,
2227                    data,
2228                    vec![buff::BuffCategory::Physical],
2229                    source,
2230                    *time,
2231                    dest_info,
2232                    masses.get(ev.defender),
2233                );
2234                buff_emitter.emit(BuffEvent {
2235                    entity: attacker,
2236                    buff_change: buff::BuffChange::Add(buff),
2237                });
2238
2239                let attacker_poise_change = Poise::apply_poise_reduction(
2240                    ev.poise_multiplier.clamp(1.0, 2.0) * BASE_PARRIED_POISE_PUNISHMENT,
2241                    inventories.get(attacker),
2242                    &MaterialStatManifest::load().read(),
2243                    character_states.get(attacker),
2244                    stats.get(attacker),
2245                );
2246
2247                poise_change_emitter.emit(PoiseChangeEvent {
2248                    entity: attacker,
2249                    change: PoiseChange {
2250                        amount: -attacker_poise_change,
2251                        impulse: Vec3::zero(),
2252                        by: uids
2253                            .get(ev.defender)
2254                            .map(|d| DamageContributor::new(*d, None)),
2255                        cause: Some(DamageSource::Melee),
2256                        time: *time,
2257                    },
2258                });
2259            }
2260        }
2261    }
2262}
2263
2264impl ServerEvent for TeleportToEvent {
2265    type SystemData<'a> = (
2266        Read<'a, IdMaps>,
2267        WriteStorage<'a, Pos>,
2268        WriteStorage<'a, comp::ForceUpdate>,
2269    );
2270
2271    fn handle(
2272        events: impl ExactSizeIterator<Item = Self>,
2273        (id_maps, mut positions, mut force_updates): Self::SystemData<'_>,
2274    ) {
2275        for ev in events {
2276            let target_pos = id_maps
2277                .uid_entity(ev.target)
2278                .and_then(|e| positions.get(e))
2279                .copied();
2280
2281            if let (Some(pos), Some(target_pos)) = (positions.get_mut(ev.entity), target_pos) {
2282                if ev
2283                    .max_range
2284                    .is_none_or(|r| pos.0.distance_squared(target_pos.0) < r.powi(2))
2285                {
2286                    *pos = target_pos;
2287                    force_updates
2288                        .get_mut(ev.entity)
2289                        .map(|force_update| force_update.update());
2290                }
2291            }
2292        }
2293    }
2294}
2295
2296#[derive(SystemData)]
2297pub struct EntityAttackedHookData<'a> {
2298    entities: Entities<'a>,
2299    trades: Write<'a, Trades>,
2300    id_maps: Read<'a, IdMaps>,
2301    time: Read<'a, Time>,
2302    event_busses: ReadEntityAttackedHookEvents<'a>,
2303    outcomes: Read<'a, EventBus<Outcome>>,
2304    character_states: WriteStorage<'a, CharacterState>,
2305    poises: WriteStorage<'a, Poise>,
2306    agents: WriteStorage<'a, Agent>,
2307    positions: ReadStorage<'a, Pos>,
2308    uids: ReadStorage<'a, Uid>,
2309    clients: ReadStorage<'a, Client>,
2310    stats: ReadStorage<'a, Stats>,
2311    healths: ReadStorage<'a, Health>,
2312}
2313
2314impl ServerEvent for EntityAttackedHookEvent {
2315    type SystemData<'a> = EntityAttackedHookData<'a>;
2316
2317    /// Intended to handle things that should happen for any successful attack,
2318    /// regardless of the damages and effects specific to that attack
2319    fn handle(events: impl ExactSizeIterator<Item = Self>, mut data: Self::SystemData<'_>) {
2320        let mut emitters = data.event_busses.get_emitters();
2321        let mut outcomes = data.outcomes.emitter();
2322
2323        for ev in events {
2324            if let Some(attacker) = ev.attacker {
2325                emitters.emit(BuffEvent {
2326                    entity: attacker,
2327                    buff_change: buff::BuffChange::RemoveByCategory {
2328                        all_required: vec![buff::BuffCategory::RemoveOnAttack],
2329                        any_required: vec![],
2330                        none_required: vec![],
2331                    },
2332                });
2333            }
2334
2335            if let Some((mut char_state, mut poise, pos)) = (
2336                &mut data.character_states,
2337                &mut data.poises,
2338                &data.positions,
2339            )
2340                .lend_join()
2341                .get(ev.entity, &data.entities)
2342            {
2343                // Interrupt sprite interaction and item use if any attack is applied to entity
2344                if matches!(
2345                    *char_state,
2346                    CharacterState::Interact(_) | CharacterState::UseItem(_)
2347                ) {
2348                    let poise_state = comp::poise::PoiseState::Interrupted;
2349                    let was_wielded = char_state.is_wield();
2350                    if let (Some((stunned_state, stunned_duration)), impulse_strength) =
2351                        poise_state.poise_effect(was_wielded)
2352                    {
2353                        // Reset poise if there is some stunned state to apply
2354                        poise.reset(*data.time, stunned_duration);
2355                        if !comp::is_downed(data.healths.get(ev.entity), Some(&char_state)) {
2356                            *char_state = stunned_state;
2357                        }
2358                        outcomes.emit(Outcome::PoiseChange {
2359                            pos: pos.0,
2360                            state: poise_state,
2361                        });
2362                        if let Some(impulse_strength) = impulse_strength {
2363                            emitters.emit(KnockbackEvent {
2364                                entity: ev.entity,
2365                                impulse: impulse_strength * *poise.knockback(),
2366                            });
2367                        }
2368                    }
2369                }
2370            }
2371
2372            // Remove potion/saturation buff if attacked
2373            emitters.emit(BuffEvent {
2374                entity: ev.entity,
2375                buff_change: buff::BuffChange::RemoveByKind(BuffKind::Potion),
2376            });
2377            emitters.emit(BuffEvent {
2378                entity: ev.entity,
2379                buff_change: buff::BuffChange::RemoveByKind(BuffKind::Saturation),
2380            });
2381
2382            // If entity was in an active trade, cancel it
2383            if let Some(uid) = data.uids.get(ev.entity) {
2384                if let Some(trade) = data.trades.entity_trades.get(uid).copied() {
2385                    data.trades
2386                        .decline_trade(trade, *uid)
2387                        .and_then(|uid| data.id_maps.uid_entity(uid))
2388                        .map(|entity_b| {
2389                            // Notify both parties that the trade ended
2390                            let mut notify_trade_party = |entity| {
2391                                // TODO: Can probably improve UX here for the user that sent the
2392                                // trade invite, since right now it
2393                                // may seems like their request was
2394                                // purposefully declined, rather than e.g. being interrupted.
2395                                if let Some(client) = data.clients.get(entity) {
2396                                    client.send_fallible(ServerGeneral::FinishedTrade(
2397                                        TradeResult::Declined,
2398                                    ));
2399                                }
2400                                if let Some(agent) = data.agents.get_mut(entity) {
2401                                    agent.inbox.push_back(AgentEvent::FinishedTrade(
2402                                        TradeResult::Declined,
2403                                    ));
2404                                }
2405                            };
2406                            notify_trade_party(ev.entity);
2407                            notify_trade_party(entity_b);
2408                        });
2409                }
2410            }
2411
2412            if let Some(stats) = data.stats.get(ev.entity) {
2413                for effect in &stats.effects_on_damaged {
2414                    use combat::DamagedEffect;
2415                    match effect {
2416                        DamagedEffect::Combo(c) => {
2417                            emitters.emit(ComboChangeEvent {
2418                                entity: ev.entity,
2419                                change: *c,
2420                            });
2421                        },
2422                        DamagedEffect::Energy(e) => {
2423                            emitters.emit(EnergyChangeEvent {
2424                                entity: ev.entity,
2425                                change: *e,
2426                                reset_rate: false,
2427                            });
2428                        },
2429                    }
2430                }
2431            }
2432        }
2433    }
2434}
2435
2436impl ServerEvent for ChangeAbilityEvent {
2437    type SystemData<'a> = (
2438        WriteStorage<'a, comp::ActiveAbilities>,
2439        ReadStorage<'a, Inventory>,
2440        ReadStorage<'a, SkillSet>,
2441    );
2442
2443    fn handle(
2444        events: impl ExactSizeIterator<Item = Self>,
2445        (mut active_abilities, inventories, skill_sets): Self::SystemData<'_>,
2446    ) {
2447        for ev in events {
2448            if let Some(mut active_abilities) = active_abilities.get_mut(ev.entity) {
2449                active_abilities.change_ability(
2450                    ev.slot,
2451                    ev.auxiliary_key,
2452                    ev.new_ability,
2453                    inventories.get(ev.entity),
2454                    skill_sets.get(ev.entity),
2455                );
2456            }
2457        }
2458    }
2459}
2460
2461impl ServerEvent for UpdateMapMarkerEvent {
2462    type SystemData<'a> = (
2463        Entities<'a>,
2464        WriteStorage<'a, comp::MapMarker>,
2465        ReadStorage<'a, Group>,
2466        ReadStorage<'a, Uid>,
2467        ReadStorage<'a, Client>,
2468        ReadStorage<'a, Alignment>,
2469    );
2470
2471    fn handle(
2472        events: impl ExactSizeIterator<Item = Self>,
2473        (entities, mut map_markers, groups, uids, clients, alignments): Self::SystemData<'_>,
2474    ) {
2475        for ev in events {
2476            match ev.update {
2477                comp::MapMarkerChange::Update(waypoint) => {
2478                    let _ = map_markers.insert(ev.entity, comp::MapMarker(waypoint));
2479                },
2480                comp::MapMarkerChange::Remove => {
2481                    map_markers.remove(ev.entity);
2482                },
2483            }
2484            // Send updated waypoint to group members
2485            if let Some((group_id, uid)) = (&groups, &uids).lend_join().get(ev.entity, &entities) {
2486                for client in
2487                    comp::group::members(*group_id, &groups, &entities, &alignments, &uids)
2488                        .filter_map(|(e, _)| if e != ev.entity { clients.get(e) } else { None })
2489                {
2490                    client.send_fallible(ServerGeneral::MapMarker(
2491                        comp::MapMarkerUpdate::GroupMember(*uid, ev.update),
2492                    ));
2493                }
2494            }
2495        }
2496    }
2497}
2498
2499impl ServerEvent for MakeAdminEvent {
2500    type SystemData<'a> = (WriteStorage<'a, comp::Admin>, ReadStorage<'a, Player>);
2501
2502    fn handle(
2503        events: impl ExactSizeIterator<Item = Self>,
2504        (mut admins, players): Self::SystemData<'_>,
2505    ) {
2506        for ev in events {
2507            if players
2508                .get(ev.entity)
2509                .is_some_and(|player| player.uuid() == ev.uuid)
2510            {
2511                let _ = admins.insert(ev.entity, ev.admin);
2512            }
2513        }
2514    }
2515}
2516
2517impl ServerEvent for ChangeStanceEvent {
2518    type SystemData<'a> = WriteStorage<'a, comp::Stance>;
2519
2520    fn handle(events: impl ExactSizeIterator<Item = Self>, mut stances: Self::SystemData<'_>) {
2521        for ev in events {
2522            if let Some(mut stance) = stances.get_mut(ev.entity) {
2523                *stance = ev.stance;
2524            }
2525        }
2526    }
2527}
2528
2529impl ServerEvent for ChangeBodyEvent {
2530    type SystemData<'a> = (
2531        WriteExpect<'a, CharacterUpdater>,
2532        WriteStorage<'a, comp::Body>,
2533        WriteStorage<'a, comp::Mass>,
2534        WriteStorage<'a, comp::Density>,
2535        WriteStorage<'a, comp::Collider>,
2536        WriteStorage<'a, comp::Stats>,
2537        ReadStorage<'a, comp::Player>,
2538        ReadStorage<'a, comp::Presence>,
2539    );
2540
2541    fn handle(
2542        events: impl ExactSizeIterator<Item = Self>,
2543        (
2544            mut character_updater,
2545            mut bodies,
2546            mut masses,
2547            mut densities,
2548            mut colliders,
2549            mut stats,
2550            players,
2551            presences,
2552        ): Self::SystemData<'_>,
2553    ) {
2554        for ev in events {
2555            if let Some(mut body) = bodies.get_mut(ev.entity) {
2556                if let Some(permanent_change) = ev.permanent_change {
2557                    // If we aren't changing the right body, skip this change.
2558                    if permanent_change.expected_old_body != *body {
2559                        continue;
2560                    }
2561
2562                    if let Some(mut stats) = stats.get_mut(ev.entity)
2563                        && stats.original_body == permanent_change.expected_old_body
2564                    {
2565                        stats.original_body = ev.new_body;
2566                    }
2567
2568                    if let Some(player) = players.get(ev.entity)
2569                        && let Some(comp::Presence {
2570                            kind: comp::PresenceKind::Character(character_id),
2571                            ..
2572                        }) = presences.get(ev.entity)
2573                    {
2574                        character_updater.edit_character(
2575                            ev.entity,
2576                            player.uuid().to_string(),
2577                            *character_id,
2578                            None,
2579                            (ev.new_body,),
2580                            Some(permanent_change),
2581                        );
2582                    }
2583                }
2584
2585                *body = ev.new_body;
2586                masses
2587                    .insert(ev.entity, ev.new_body.mass())
2588                    .expect("We just got this entities body");
2589                densities
2590                    .insert(ev.entity, ev.new_body.density())
2591                    .expect("We just got this entities body");
2592                colliders
2593                    .insert(ev.entity, ev.new_body.collider())
2594                    .expect("We just got this entities body");
2595            }
2596        }
2597    }
2598}
2599
2600impl ServerEvent for RemoveLightEmitterEvent {
2601    type SystemData<'a> = WriteStorage<'a, comp::LightEmitter>;
2602
2603    fn handle(
2604        events: impl ExactSizeIterator<Item = Self>,
2605        mut light_emitters: Self::SystemData<'_>,
2606    ) {
2607        for ev in events {
2608            light_emitters.remove(ev.entity);
2609        }
2610    }
2611}
2612
2613impl ServerEvent for TeleportToPositionEvent {
2614    type SystemData<'a> = (
2615        Read<'a, IdMaps>,
2616        WriteStorage<'a, Is<VolumeRider>>,
2617        WriteStorage<'a, Pos>,
2618        WriteStorage<'a, comp::ForceUpdate>,
2619        ReadStorage<'a, Is<Rider>>,
2620        ReadStorage<'a, Presence>,
2621        ReadStorage<'a, Client>,
2622    );
2623
2624    fn handle(
2625        events: impl ExactSizeIterator<Item = Self>,
2626        (
2627            id_maps,
2628            mut is_volume_riders,
2629            mut positions,
2630            mut force_updates,
2631            is_riders,
2632            presences,
2633            clients,
2634        ): Self::SystemData<'_>,
2635    ) {
2636        for ev in events {
2637            if let Err(error) = crate::state_ext::position_mut(
2638                ev.entity,
2639                true,
2640                |pos| pos.0 = ev.position,
2641                &id_maps,
2642                &mut is_volume_riders,
2643                &mut positions,
2644                &mut force_updates,
2645                &is_riders,
2646                &presences,
2647                &clients,
2648            ) {
2649                warn!(?error, "Failed to teleport entity");
2650            }
2651        }
2652    }
2653}
2654
2655impl ServerEvent for StartTeleportingEvent {
2656    type SystemData<'a> = (
2657        Read<'a, Time>,
2658        WriteStorage<'a, comp::Teleporting>,
2659        ReadStorage<'a, Pos>,
2660        ReadStorage<'a, comp::Object>,
2661    );
2662
2663    fn handle(
2664        events: impl ExactSizeIterator<Item = Self>,
2665        (time, mut teleportings, positions, objects): Self::SystemData<'_>,
2666    ) {
2667        for ev in events {
2668            if let Some(end_time) = (!teleportings.contains(ev.entity))
2669                .then(|| positions.get(ev.entity))
2670                .flatten()
2671                .zip(positions.get(ev.portal))
2672                .filter(|(entity_pos, portal_pos)| {
2673                    entity_pos.0.distance_squared(portal_pos.0) <= TELEPORTER_RADIUS.powi(2)
2674                })
2675                .and_then(|(_, _)| {
2676                    Some(
2677                        time.0
2678                            + objects.get(ev.portal).and_then(|object| {
2679                                if let Object::Portal { buildup_time, .. } = object {
2680                                    Some(buildup_time.0)
2681                                } else {
2682                                    None
2683                                }
2684                            })?,
2685                    )
2686                })
2687            {
2688                let _ = teleportings.insert(ev.entity, comp::Teleporting {
2689                    portal: ev.portal,
2690                    end_time: Time(end_time),
2691                });
2692            }
2693        }
2694    }
2695}
2696
2697impl ServerEvent for RegrowHeadEvent {
2698    type SystemData<'a> = (
2699        Read<'a, EventBus<HealthChangeEvent>>,
2700        Read<'a, Time>,
2701        WriteStorage<'a, Heads>,
2702        ReadStorage<'a, Health>,
2703    );
2704
2705    fn handle(
2706        events: impl ExactSizeIterator<Item = Self>,
2707        (health_change_events, time, mut heads, healths): Self::SystemData<'_>,
2708    ) {
2709        let mut health_change_emitter = health_change_events.emitter();
2710        for ev in events {
2711            if let Some(mut heads) = heads.get_mut(ev.entity)
2712                && heads.regrow_oldest()
2713                && let Some(health) = healths.get(ev.entity)
2714            {
2715                let amount = 1.0 / (heads.capacity() as f32) * health.maximum();
2716                health_change_emitter.emit(HealthChangeEvent {
2717                    entity: ev.entity,
2718                    change: comp::HealthChange {
2719                        amount,
2720                        by: None,
2721                        cause: Some(DamageSource::Other),
2722                        time: *time,
2723                        precise: false,
2724                        instance: rand::random(),
2725                    },
2726                })
2727            }
2728        }
2729    }
2730}
2731
2732pub fn handle_transform(
2733    server: &mut Server,
2734    TransformEvent {
2735        target_entity,
2736        entity_info,
2737        allow_players,
2738        delete_on_failure,
2739    }: TransformEvent,
2740) {
2741    let Some(entity) = server.state().ecs().entity_from_uid(target_entity) else {
2742        return;
2743    };
2744
2745    if let Err(error) = transform_entity(server, entity, entity_info, allow_players) {
2746        if delete_on_failure
2747            && !server
2748                .state()
2749                .ecs()
2750                .read_storage::<Client>()
2751                .contains(entity)
2752        {
2753            _ = server.state.delete_entity_recorded(entity);
2754        }
2755
2756        error!(?error, ?target_entity, "Failed transform entity");
2757    }
2758}
2759
2760#[derive(Debug)]
2761pub enum TransformEntityError {
2762    EntityDead,
2763    UnexpectedSpecialEntity,
2764    LoadingCharacter,
2765    EntityIsPlayer,
2766}
2767
2768pub fn transform_entity(
2769    server: &mut Server,
2770    entity: Entity,
2771    entity_info: EntityInfo,
2772    allow_players: bool,
2773) -> Result<(), TransformEntityError> {
2774    let is_player = server
2775        .state()
2776        .read_storage::<comp::Player>()
2777        .contains(entity);
2778
2779    match SpawnEntityData::from_entity_info(entity_info) {
2780        SpawnEntityData::Npc(NpcData {
2781            inventory,
2782            stats,
2783            skill_set,
2784            poise,
2785            health,
2786            body,
2787            scale,
2788            agent,
2789            loot,
2790            alignment: _,
2791            pos: _,
2792            pets,
2793            rider,
2794            death_effects,
2795            rider_effects,
2796        }) => {
2797            fn set_or_remove_component<C: specs::Component>(
2798                server: &mut Server,
2799                entity: EcsEntity,
2800                component: Option<C>,
2801                with: Option<fn(&mut C, Option<C>)>,
2802            ) -> Result<(), TransformEntityError> {
2803                let mut storage = server.state.ecs_mut().write_storage::<C>();
2804
2805                if let Some(mut component) = component {
2806                    if let Some(with) = with {
2807                        let prev = storage.remove(entity);
2808                        with(&mut component, prev);
2809                    }
2810
2811                    storage
2812                        .insert(entity, component)
2813                        .and(Ok(()))
2814                        .map_err(|_| TransformEntityError::EntityDead)
2815                } else {
2816                    storage.remove(entity);
2817                    Ok(())
2818                }
2819            }
2820
2821            // Disable persistence
2822            'persist: {
2823                match server
2824                    .state
2825                    .ecs()
2826                    .read_storage::<Presence>()
2827                    .get(entity)
2828                    .map(|presence| presence.kind)
2829                {
2830                    // Transforming while the character is being loaded or is spectating is invalid!
2831                    Some(PresenceKind::Spectator | PresenceKind::LoadingCharacter(_)) => {
2832                        return Err(TransformEntityError::LoadingCharacter);
2833                    },
2834                    Some(PresenceKind::Character(_)) if !allow_players => {
2835                        return Err(TransformEntityError::EntityIsPlayer);
2836                    },
2837                    Some(PresenceKind::Possessor | PresenceKind::Character(_)) => {},
2838                    None => break 'persist,
2839                }
2840
2841                // Run persistence once before disabling it
2842                //
2843                // We must NOT early return between persist_entity() being called and
2844                // persistence being set to Possessor
2845                super::player::persist_entity(server.state_mut(), entity);
2846
2847                // We re-fetch presence here as mutable, because checking for a valid
2848                // [`PresenceKind`] must be done BEFORE persist_entity but persist_entity needs
2849                // exclusive mutable access to the server's state
2850                let mut presences = server.state.ecs().write_storage::<Presence>();
2851                let Some(presence) = presences.get_mut(entity) else {
2852                    // Checked above
2853                    unreachable!("We already know this entity has a Presence");
2854                };
2855
2856                if let PresenceKind::Character(id) = presence.kind {
2857                    server.state.ecs().write_resource::<IdMaps>().remove_entity(
2858                        Some(entity),
2859                        None,
2860                        Some(id),
2861                        None,
2862                    );
2863
2864                    presence.kind = PresenceKind::Possessor;
2865                }
2866            }
2867
2868            // Should do basically what StateExt::create_npc does
2869            set_or_remove_component(server, entity, Some(inventory), None)?;
2870            set_or_remove_component(server, entity, Some(stats), None)?;
2871            set_or_remove_component(server, entity, Some(skill_set), None)?;
2872            set_or_remove_component(server, entity, Some(poise), None)?;
2873            set_or_remove_component(server, entity, health, None)?;
2874            set_or_remove_component(server, entity, Some(comp::Energy::new(body)), None)?;
2875            set_or_remove_component(server, entity, Some(body), None)?;
2876            set_or_remove_component(server, entity, Some(body.mass()), None)?;
2877            set_or_remove_component(server, entity, Some(body.density()), None)?;
2878            set_or_remove_component(server, entity, Some(body.collider()), None)?;
2879            set_or_remove_component(server, entity, Some(scale), None)?;
2880            set_or_remove_component(server, entity, death_effects, None)?;
2881            set_or_remove_component(server, entity, rider_effects, None)?;
2882            // Reset active abilities
2883            set_or_remove_component(
2884                server,
2885                entity,
2886                Some(if body.is_humanoid() {
2887                    comp::ActiveAbilities::default_limited(BASE_ABILITY_LIMIT)
2888                } else {
2889                    comp::ActiveAbilities::default()
2890                }),
2891                None,
2892            )?;
2893            set_or_remove_component(server, entity, body.heads().map(Heads::new), None)?;
2894
2895            // Don't add Agent or ItemDrops to players
2896            if !is_player {
2897                set_or_remove_component(
2898                    server,
2899                    entity,
2900                    agent,
2901                    Some(|new_agent, old_agent| {
2902                        if let Some(old_agent) = old_agent {
2903                            new_agent.target = old_agent.target;
2904                            new_agent.awareness = old_agent.awareness;
2905                        }
2906                    }),
2907                )?;
2908                set_or_remove_component(
2909                    server,
2910                    entity,
2911                    loot.to_items().map(comp::ItemDrops),
2912                    None,
2913                )?;
2914            }
2915
2916            // Spawn pets
2917            let position = server.state.read_component_copied::<comp::Pos>(entity);
2918            if let Some(pos) = position {
2919                for (pet, offset) in pets
2920                    .into_iter()
2921                    .map(|(pet, offset)| (pet.to_npc_builder().0, offset))
2922                {
2923                    let pet_entity = handle_create_npc(server, CreateNpcEvent {
2924                        pos: comp::Pos(pos.0 + offset),
2925                        ori: comp::Ori::from_unnormalized_vec(offset).unwrap_or_default(),
2926                        npc: pet,
2927                    });
2928
2929                    tame_pet(server.state.ecs(), pet_entity, entity);
2930                }
2931
2932                // Spawn rider
2933                if let Some(rider) = rider {
2934                    let rider_entity = handle_create_npc(server, CreateNpcEvent {
2935                        pos,
2936                        ori: comp::Ori::default(),
2937                        npc: rider.to_npc_builder().0,
2938                    });
2939                    let uids = server.state().ecs().read_storage::<Uid>();
2940                    let link = Mounting {
2941                        mount: *uids
2942                            .get(entity)
2943                            .expect("We just got the position of this entity"),
2944                        rider: *uids.get(rider_entity).expect("We just created this entity"),
2945                    };
2946                    drop(uids);
2947                    server
2948                        .state
2949                        .link(link)
2950                        .expect("We know these entities exist");
2951                }
2952            }
2953        },
2954        SpawnEntityData::Special(_, _) => {
2955            return Err(TransformEntityError::UnexpectedSpecialEntity);
2956        },
2957    }
2958
2959    Ok(())
2960}
2961
2962pub fn handle_start_interaction(
2963    server: &mut Server,
2964    StartInteractionEvent(interaction): StartInteractionEvent,
2965) {
2966    let i = interaction.interactor;
2967    let t = interaction.target;
2968    if let Err(e) = server.state.link(interaction) {
2969        debug!("Error trying to start interaction between {i:?} and {t:?}: {e:?}");
2970    }
2971}