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