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