veloren_server/events/
entity_manipulation.rs

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