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 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 let changed = health.change_by(ev.change);
241 if let Some(mut heads) = heads {
242 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 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 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 let mut impulse = ev.impulse
446 * if physics.on_surface().is_some() {
447 1.0
448 } else {
449 0.4
450 };
451
452 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 let mut xp_pools = HashSet::<SkillGroupKind>::new();
475 xp_pools.insert(SkillGroupKind::General);
477 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 if skill_set.skill_group_accessible(SkillGroupKind::Weapon(weapon)) {
489 xp_pools.insert(SkillGroupKind::Weapon(weapon));
490 }
491 }
492 };
493 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
567impl 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 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 continue;
600 }
601 }
602
603 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 if let Some(char_entity) = data.id_maps.uid_entity(by) {
610 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 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 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 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 _ => 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 '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 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 damage_contributors.insert(DamageContrib::NotFound, (*damage, 0.0));
829 }
830 },
831 DamageContributor::Group {
832 entity_uid: _,
833 group,
834 } => {
835 let entry = damage_contributors
839 .entry(DamageContrib::Group(*group))
840 .or_insert((0, 0.0));
841 entry.0 += damage;
842 },
843 }
844 }
845
846 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 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 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 if *attacker == ev.entity || is_pvp_kill(*attacker) { return None; }
880
881 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 if destroyed_group == Some(group) { return None; }
894
895 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 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 None
924 }
925 }
926 }).flatten().collect::<Vec<(Entity, f32, Option<Group>)>>();
927
928 exp_awards.iter().for_each(|(attacker, exp_reward, _)| {
929 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 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 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 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 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 && 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 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 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 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 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 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 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 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 let horiz_dist = Vec2::<f32>::from(sphere_pos - cyl_pos).distance(Vec2::default())
1360 - cyl_body.max_radius();
1361 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 let distance = horiz_dist.max(vert_distance).max(0.0);
1369
1370 if distance > radius {
1371 0.0
1373 } else {
1374 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 'effects: for effect in ev.explosion.effects {
1384 match effect {
1385 RadiusEffect::TerrainDestruction(power, new_color) => {
1386 const RAYS: usize = 500;
1387
1388 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 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 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 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 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 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 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 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 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 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 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 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 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 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 _ => None,
1934 }
1935 {
1936 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 } 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 let body = match block.get_sprite() {
1988 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 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 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 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 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 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 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 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 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 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 let mut notify_trade_party = |entity| {
2517 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 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 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 'persist: {
2949 match server
2950 .state
2951 .ecs()
2952 .read_storage::<Presence>()
2953 .get(entity)
2954 .map(|presence| presence.kind)
2955 {
2956 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 super::player::persist_entity(server.state_mut(), entity);
2972
2973 let mut presences = server.state.ecs().write_storage::<Presence>();
2977 let Some(presence) = presences.get_mut(entity) else {
2978 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 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 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 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 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 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}