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, Ron},
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))
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::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 && changed
284 {
285 emitters.emit(Outcome::HealthChange {
286 pos: pos.0,
287 info: HealthChangeInfo {
288 amount: ev.change.amount,
289 by: ev.change.by,
290 target: *uid,
291 cause: ev.change.cause,
292 precise: ev.change.precise,
293 instance: ev.change.instance,
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 && let Some(agent) = data.agents.get_mut(ev.entity)
315 {
316 agent.inbox.push_back(AgentEvent::Hurt);
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) = Ron::<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().into_inner(),
724 Some(entity_spec),
725 &mut rand::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::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::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 data.areas_container
1065 .areas()
1066 .iter()
1067 .any(|(_, area)| area.contains_point(our_pos))
1068 });
1069
1070 if !resists_durability
1072 && let Some(mut inventory) = data.inventories.get_mut(ev.entity)
1073 {
1074 inventory.damage_items(&data.ability_map, &data.msm, *data.time);
1075 }
1076 }
1077
1078 #[cfg(feature = "worldgen")]
1079 let entity_as_actor =
1080 |entity| entity_as_actor(entity, &data.rtsim_entities, &data.presences);
1081
1082 #[cfg(feature = "worldgen")]
1083 if let Some(actor) = entity_as_actor(ev.entity)
1084 && should_delete
1088 {
1089 data.rtsim.hook_rtsim_actor_death(
1090 &data.world,
1091 data.index.as_index_ref(),
1092 actor,
1093 data.positions.get(ev.entity).map(|p| p.0),
1094 ev.cause
1095 .by
1096 .as_ref()
1097 .and_then(
1098 |(DamageContributor::Solo(entity_uid)
1099 | DamageContributor::Group { entity_uid, .. })| {
1100 data.id_maps.uid_entity(*entity_uid)
1101 },
1102 )
1103 .and_then(entity_as_actor),
1104 );
1105 }
1106
1107 if should_delete {
1108 delete_emitter.emit(DeleteEvent(ev.entity));
1109 }
1110 }
1111 }
1112}
1113
1114impl ServerEvent for LandOnGroundEvent {
1115 type SystemData<'a> = (
1116 Read<'a, Time>,
1117 ReadExpect<'a, MaterialStatManifest>,
1118 Read<'a, EventBus<HealthChangeEvent>>,
1119 Read<'a, EventBus<PoiseChangeEvent>>,
1120 ReadStorage<'a, PhysicsState>,
1121 ReadStorage<'a, CharacterState>,
1122 ReadStorage<'a, comp::Mass>,
1123 ReadStorage<'a, Inventory>,
1124 ReadStorage<'a, Stats>,
1125 );
1126
1127 fn handle(
1128 events: impl ExactSizeIterator<Item = Self>,
1129 (
1130 time,
1131 msm,
1132 health_change_events,
1133 poise_change_events,
1134 physic_states,
1135 character_states,
1136 masses,
1137 inventories,
1138 stats,
1139 ): Self::SystemData<'_>,
1140 ) {
1141 let mut health_change_emitter = health_change_events.emitter();
1142 let mut poise_change_emitter = poise_change_events.emitter();
1143 for ev in events {
1144 let horizontal_damp = 0.5
1148 + ev.vel
1149 .try_normalized()
1150 .unwrap_or_default()
1151 .dot(Vec3::unit_z())
1152 .abs()
1153 * 0.5;
1154
1155 let relative_vel = ev.vel.dot(-ev.surface_normal) * horizontal_damp;
1156 if relative_vel >= 30.0
1161 && physic_states
1162 .get(ev.entity)
1163 .is_none_or(|ps| ps.in_liquid().is_none())
1164 {
1165 let reduced_vel =
1166 if let Some(CharacterState::DiveMelee(c)) = character_states.get(ev.entity) {
1167 (relative_vel + c.static_data.vertical_speed).min(0.0)
1168 } else {
1169 relative_vel
1170 };
1171
1172 let mass = masses.get(ev.entity).copied().unwrap_or_default();
1173 let impact_energy = mass.0 * reduced_vel.powi(2) / 2.0;
1174 let falldmg = impact_energy / 1000.0;
1175
1176 let damage = Damage {
1178 source: DamageSource::Falling,
1179 kind: DamageKind::Crushing,
1180 value: falldmg,
1181 };
1182 let damage_reduction = Damage::compute_damage_reduction(
1183 Some(damage),
1184 inventories.get(ev.entity),
1185 stats.get(ev.entity),
1186 &msm,
1187 );
1188 let change = damage.calculate_health_change(
1189 damage_reduction,
1190 0.0,
1191 None,
1192 None,
1193 0.0,
1194 1.0,
1195 *time,
1196 rand::random(),
1197 );
1198
1199 health_change_emitter.emit(HealthChangeEvent {
1200 entity: ev.entity,
1201 change,
1202 });
1203
1204 let poise_damage = -(mass.0 * reduced_vel.powi(2) / 1500.0);
1206 let poise_change = Poise::apply_poise_reduction(
1207 poise_damage,
1208 inventories.get(ev.entity),
1209 &msm,
1210 character_states.get(ev.entity),
1211 stats.get(ev.entity),
1212 );
1213 let poise_change = comp::PoiseChange {
1214 amount: poise_change,
1215 impulse: Vec3::unit_z(),
1216 by: None,
1217 cause: None,
1218 time: *time,
1219 };
1220 poise_change_emitter.emit(PoiseChangeEvent {
1221 entity: ev.entity,
1222 change: poise_change,
1223 });
1224 }
1225 }
1226 }
1227}
1228
1229impl ServerEvent for RespawnEvent {
1230 type SystemData<'a> = (
1231 Read<'a, SpawnPoint>,
1232 WriteStorage<'a, Health>,
1233 WriteStorage<'a, comp::Combo>,
1234 WriteStorage<'a, Pos>,
1235 WriteStorage<'a, comp::PhysicsState>,
1236 WriteStorage<'a, comp::ForceUpdate>,
1237 WriteStorage<'a, Heads>,
1238 ReadStorage<'a, Client>,
1239 ReadStorage<'a, Hardcore>,
1240 ReadStorage<'a, comp::Waypoint>,
1241 );
1242
1243 fn handle(
1244 events: impl ExactSizeIterator<Item = Self>,
1245 (
1246 spawn_point,
1247 mut healths,
1248 mut combos,
1249 mut positions,
1250 mut physic_states,
1251 mut force_updates,
1252 mut heads,
1253 clients,
1254 hardcore,
1255 waypoints,
1256 ): Self::SystemData<'_>,
1257 ) {
1258 for RespawnEvent(entity) in events {
1259 if !hardcore.contains(entity) && clients.contains(entity) {
1261 let respawn_point = waypoints
1262 .get(entity)
1263 .map(|wp| wp.get_pos())
1264 .unwrap_or(spawn_point.0);
1265
1266 healths.get_mut(entity).map(|mut health| health.revive());
1267 combos.get_mut(entity).map(|mut combo| combo.reset());
1268 positions.get_mut(entity).map(|pos| pos.0 = respawn_point);
1269 heads.get_mut(entity).map(|mut heads| heads.reset());
1270 physic_states
1271 .get_mut(entity)
1272 .map(|phys_state| phys_state.reset());
1273 force_updates
1274 .get_mut(entity)
1275 .map(|force_update| force_update.update());
1276 }
1277 }
1278 }
1279}
1280
1281#[derive(SystemData)]
1282pub struct ExplosionData<'a> {
1283 entities: Entities<'a>,
1284 block_change: Write<'a, BlockChange>,
1285 scheduled_block_change: WriteExpect<'a, ScheduledBlockChange>,
1286 settings: Read<'a, Settings>,
1287 time: Read<'a, Time>,
1288 id_maps: Read<'a, IdMaps>,
1289 spatial_grid: Read<'a, CachedSpatialGrid>,
1290 terrain: ReadExpect<'a, TerrainGrid>,
1291 msm: ReadExpect<'a, MaterialStatManifest>,
1292 event_busses: ReadExplosionEvents<'a>,
1293 outcomes: Read<'a, EventBus<Outcome>>,
1294 groups: ReadStorage<'a, Group>,
1295 auras: ReadStorage<'a, Auras>,
1296 positions: ReadStorage<'a, Pos>,
1297 players: ReadStorage<'a, Player>,
1298 energies: ReadStorage<'a, Energy>,
1299 combos: ReadStorage<'a, comp::Combo>,
1300 inventories: ReadStorage<'a, Inventory>,
1301 alignments: ReadStorage<'a, Alignment>,
1302 entered_auras: ReadStorage<'a, EnteredAuras>,
1303 buffs: ReadStorage<'a, comp::Buffs>,
1304 stats: ReadStorage<'a, comp::Stats>,
1305 healths: ReadStorage<'a, Health>,
1306 bodies: ReadStorage<'a, Body>,
1307 orientations: ReadStorage<'a, comp::Ori>,
1308 character_states: ReadStorage<'a, CharacterState>,
1309 physics_states: ReadStorage<'a, PhysicsState>,
1310 uids: ReadStorage<'a, Uid>,
1311 masses: ReadStorage<'a, comp::Mass>,
1312}
1313
1314impl ServerEvent for ExplosionEvent {
1315 type SystemData<'a> = ExplosionData<'a>;
1316
1317 fn handle(events: impl ExactSizeIterator<Item = Self>, mut data: Self::SystemData<'_>) {
1318 let mut emitters = data.event_busses.get_emitters();
1319 let mut outcome_emitter = data.outcomes.emitter();
1320
1321 let mut rng = rand::rng();
1323
1324 for ev in events {
1325 let owner_entity = ev.owner.and_then(|uid| data.id_maps.uid_entity(uid));
1326
1327 let explosion_volume = 6.25 * ev.explosion.radius;
1328
1329 emitters.emit(SoundEvent {
1330 sound: Sound::new(SoundKind::Explosion, ev.pos, explosion_volume, data.time.0),
1331 });
1332
1333 let outcome_power = ev.explosion.radius;
1334 outcome_emitter.emit(Outcome::Explosion {
1335 pos: ev.pos,
1336 power: outcome_power,
1337 radius: ev.explosion.radius,
1338 is_attack: ev
1339 .explosion
1340 .effects
1341 .iter()
1342 .any(|e| matches!(e, RadiusEffect::Attack { .. })),
1343 reagent: ev.explosion.reagent,
1344 });
1345
1346 fn cylinder_sphere_strength(
1349 sphere_pos: Vec3<f32>,
1350 radius: f32,
1351 min_falloff: f32,
1352 cyl_pos: Vec3<f32>,
1353 cyl_body: Body,
1354 ) -> f32 {
1355 let horiz_dist = Vec2::<f32>::from(sphere_pos - cyl_pos).distance(Vec2::default())
1357 - cyl_body.max_radius();
1358 let half_body_height = cyl_body.height() / 2.0;
1360 let vert_distance =
1361 (sphere_pos.z - (cyl_pos.z + half_body_height)).abs() - half_body_height;
1362
1363 let distance = horiz_dist.max(vert_distance).max(0.0);
1366
1367 if distance > radius {
1368 0.0
1370 } else {
1371 let fall_off = ((distance / radius).min(1.0) - 1.0).abs();
1373 let min_falloff = min_falloff.clamp(0.0, 1.0);
1374 min_falloff + fall_off * (1.0 - min_falloff)
1375 }
1376 }
1377
1378 'effects: for effect in ev.explosion.effects {
1381 match effect {
1382 RadiusEffect::TerrainDestruction(power, new_color) => {
1383 const RAYS: usize = 500;
1384
1385 if data
1387 .spatial_grid
1388 .0
1389 .in_circle_aabr(ev.pos.xy(), SAFE_ZONE_RADIUS)
1390 .filter_map(|entity| {
1391 data.auras
1392 .get(entity)
1393 .and_then(|entity_auras| {
1394 data.positions.get(entity).map(|pos| (entity_auras, pos))
1395 })
1396 .and_then(|(entity_auras, pos)| {
1397 entity_auras
1398 .auras
1399 .iter()
1400 .find(|(_, aura)| {
1401 matches!(aura.aura_kind, aura::AuraKind::Buff {
1402 kind: BuffKind::Invulnerability,
1403 source: BuffSource::World,
1404 ..
1405 })
1406 })
1407 .map(|(_, aura)| (*pos, aura.radius))
1408 })
1409 })
1410 .any(|(aura_pos, aura_radius)| {
1411 ev.pos.distance_squared(aura_pos.0) < aura_radius.powi(2)
1412 })
1413 {
1414 continue 'effects;
1415 }
1416
1417 let mut touched_blocks = Vec::new();
1419 let color_range = power * 2.7;
1420 for _ in 0..RAYS {
1421 let dir = Vec3::new(
1422 rng.random::<f32>() - 0.5,
1423 rng.random::<f32>() - 0.5,
1424 rng.random::<f32>() - 0.5,
1425 )
1426 .normalized();
1427
1428 let _ = data
1429 .terrain
1430 .ray(ev.pos, ev.pos + dir * color_range)
1431 .until(|_| rng.random::<f32>() < 0.05)
1432 .for_each(|_: &Block, pos| touched_blocks.push(pos))
1433 .cast();
1434 }
1435
1436 for block_pos in touched_blocks {
1437 if let Ok(block) = data.terrain.get(block_pos) {
1438 if !matches!(block.kind(), BlockKind::Lava | BlockKind::GlowingRock)
1439 && (
1440 owner_entity.is_none_or(|e| data.players.get(e).is_none())
1444 || data.settings.gameplay.explosion_burn_marks
1445 )
1446 {
1447 let diff2 =
1448 block_pos.map(|b| b as f32).distance_squared(ev.pos);
1449 let fade = (1.0 - diff2 / color_range.powi(2)).max(0.0);
1450 if let Some(mut color) = block.get_color() {
1451 let r = color[0] as f32
1452 + (fade
1453 * (color[0] as f32 * 0.5 - color[0] as f32
1454 + new_color[0]));
1455 let g = color[1] as f32
1456 + (fade
1457 * (color[1] as f32 * 0.3 - color[1] as f32
1458 + new_color[1]));
1459 let b = color[2] as f32
1460 + (fade
1461 * (color[2] as f32 * 0.3 - color[2] as f32
1462 + new_color[2]));
1463 color[0] = (r as u8).max(30);
1465 color[1] = (g as u8).max(30);
1466 color[2] = (b as u8).max(30);
1467 data.block_change
1468 .set(block_pos, Block::new(block.kind(), color));
1469 }
1470 }
1471
1472 if block.is_bonkable() {
1473 emitters.emit(BonkEvent {
1474 pos: block_pos.map(|e| e as f32 + 0.5),
1475 owner: ev.owner,
1476 target: None,
1477 });
1478 }
1479 }
1480 }
1481
1482 for _ in 0..RAYS {
1484 let dir = Vec3::new(
1485 rng.random::<f32>() - 0.5,
1486 rng.random::<f32>() - 0.5,
1487 rng.random::<f32>() - 0.15,
1488 )
1489 .normalized();
1490
1491 let mut ray_energy = power;
1492
1493 let from = ev.pos;
1494 let to = ev.pos + dir * power;
1495 let _ = data
1496 .terrain
1497 .ray(from, to)
1498 .while_(|block: &Block| {
1499 ray_energy -= block.explode_power().unwrap_or(0.0)
1500 + rng.random::<f32>() * 0.1;
1501
1502 block.is_liquid()
1507 || block.explode_power().is_none()
1508 || ray_energy <= 0.0
1509 })
1510 .for_each(|block: &Block, pos| {
1511 if block.explode_power().is_some() {
1512 data.block_change.set(pos, block.into_vacant());
1513 }
1514 })
1515 .cast();
1516 }
1517 },
1518 RadiusEffect::ReplaceTerrain(radius, terrain_replacement_preset) => {
1519 const RAY_DENSITY: f32 = 20.0;
1520 const RAY_LENGTH: f32 = 50.0;
1521
1522 if data
1524 .spatial_grid
1525 .0
1526 .in_circle_aabr(ev.pos.xy(), SAFE_ZONE_RADIUS)
1527 .filter_map(|entity| {
1528 data.auras
1529 .get(entity)
1530 .and_then(|entity_auras| {
1531 data.positions.get(entity).map(|pos| (entity_auras, pos))
1532 })
1533 .and_then(|(entity_auras, pos)| {
1534 entity_auras
1535 .auras
1536 .iter()
1537 .find(|(_, aura)| {
1538 matches!(aura.aura_kind, aura::AuraKind::Buff {
1539 kind: BuffKind::Invulnerability,
1540 source: BuffSource::World,
1541 ..
1542 })
1543 })
1544 .map(|(_, aura)| (*pos, aura.radius))
1545 })
1546 })
1547 .any(|(aura_pos, aura_radius)| {
1548 ev.pos.distance_squared(aura_pos.0) < aura_radius.powi(2)
1549 })
1550 {
1551 continue 'effects;
1552 }
1553
1554 let mut touched_blocks = Vec::new();
1556 let height = data
1557 .terrain
1558 .ray(ev.pos, ev.pos - RAY_LENGTH * Vec3::unit_z())
1559 .until(Block::is_solid)
1560 .cast()
1561 .0;
1562 let max_phi = (height / radius).atan();
1563 for _ in 0..(RAY_DENSITY * radius.powi(2)) as usize {
1564 let phi = rng.random_range(-PI / 2.0..-max_phi);
1565 let theta = rng.random_range(0.0..2.0 * PI);
1566 let ray = Vec3::new(
1567 RAY_LENGTH * phi.cos() * theta.cos(),
1568 RAY_LENGTH * phi.cos() * theta.sin(),
1569 RAY_LENGTH * phi.sin(),
1570 );
1571
1572 let _ = data
1573 .terrain
1574 .ray(ev.pos, ev.pos + ray)
1575 .until(Block::is_solid)
1576 .for_each(|_: &Block, pos| touched_blocks.push(pos))
1577 .cast();
1578 }
1579
1580 for block_pos in touched_blocks {
1581 if let Ok(block) = data.terrain.get(block_pos) {
1582 match terrain_replacement_preset {
1583 TerrainReplacementPreset::Lava {
1584 timeout,
1585 timeout_offset,
1586 timeout_chance,
1587 } => {
1588 if !matches!(
1589 block.kind(),
1590 BlockKind::Air
1591 | BlockKind::Water
1592 | BlockKind::Lava
1593 | BlockKind::GlowingRock
1594 ) {
1595 data.block_change.set(
1596 block_pos,
1597 Block::new(BlockKind::Lava, Rgb::new(255, 65, 0)),
1598 );
1599
1600 if rng.random_bool(timeout_chance as f64) {
1601 let current_time: f64 = data.time.0;
1602 let replace_time = current_time
1603 + (timeout
1604 + rng.random_range(0.0..timeout_offset))
1605 as f64;
1606 data.scheduled_block_change.set(
1607 block_pos,
1608 Block::new(
1609 BlockKind::Rock,
1610 Rgb::new(12, 10, 25),
1611 ),
1612 replace_time,
1613 );
1614 }
1615 }
1616 },
1617 }
1618 }
1619 }
1620 },
1621 RadiusEffect::Attack { attack, dodgeable } => {
1622 for (
1623 entity_b,
1624 pos_b,
1625 health_b,
1626 (
1627 body_b_maybe,
1628 ori_b_maybe,
1629 char_state_b_maybe,
1630 physics_state_b_maybe,
1631 uid_b,
1632 ),
1633 ) in (
1634 &data.entities,
1635 &data.positions,
1636 &data.healths,
1637 (
1638 data.bodies.maybe(),
1639 data.orientations.maybe(),
1640 data.character_states.maybe(),
1641 data.physics_states.maybe(),
1642 &data.uids,
1643 ),
1644 )
1645 .join()
1646 .filter(|(_, _, h, _)| !h.is_dead)
1647 {
1648 let dist_sqrd = ev.pos.distance_squared(pos_b.0);
1649
1650 let strength = if let Some(body) = body_b_maybe {
1652 cylinder_sphere_strength(
1653 ev.pos,
1654 ev.explosion.radius,
1655 ev.explosion.min_falloff,
1656 pos_b.0,
1657 *body,
1658 )
1659 } else {
1660 1.0 - dist_sqrd / ev.explosion.radius.powi(2)
1661 };
1662
1663 if strength > 0.0
1665 && (data
1666 .terrain
1667 .ray(ev.pos, pos_b.0)
1668 .until(Block::is_opaque)
1669 .cast()
1670 .0
1671 + 0.1)
1672 .powi(2)
1673 >= dist_sqrd
1674 {
1675 let same_group = owner_entity
1677 .and_then(|e| data.groups.get(e))
1678 .map(|group_a| Some(group_a) == data.groups.get(entity_b))
1679 .unwrap_or(Some(entity_b) == owner_entity);
1680
1681 let target_group = if same_group {
1682 GroupTarget::InGroup
1683 } else {
1684 GroupTarget::OutOfGroup
1685 };
1686
1687 let dir = Dir::new(
1688 (pos_b.0 - ev.pos)
1689 .try_normalized()
1690 .unwrap_or_else(Vec3::unit_z),
1691 );
1692
1693 let attacker_info =
1694 owner_entity.zip(ev.owner).map(|(entity, uid)| {
1695 combat::AttackerInfo {
1696 entity,
1697 uid,
1698 group: data.groups.get(entity),
1699 energy: data.energies.get(entity),
1700 combo: data.combos.get(entity),
1701 inventory: data.inventories.get(entity),
1702 stats: data.stats.get(entity),
1703 mass: data.masses.get(entity),
1704 }
1705 });
1706
1707 let target_info = combat::TargetInfo {
1708 entity: entity_b,
1709 uid: *uid_b,
1710 inventory: data.inventories.get(entity_b),
1711 stats: data.stats.get(entity_b),
1712 health: Some(health_b),
1713 pos: pos_b.0,
1714 ori: ori_b_maybe,
1715 char_state: char_state_b_maybe,
1716 energy: data.energies.get(entity_b),
1717 buffs: data.buffs.get(entity_b),
1718 mass: data.masses.get(entity_b),
1719 };
1720
1721 let target_dodging = match dodgeable {
1723 Dodgeable::Roll => char_state_b_maybe
1724 .and_then(|cs| cs.roll_attack_immunities())
1725 .is_some_and(|i| i.melee),
1726 Dodgeable::Jump => physics_state_b_maybe
1727 .is_some_and(|ps| ps.on_ground.is_none()),
1728 Dodgeable::No => false,
1729 };
1730 let allow_friendly_fire =
1731 owner_entity.is_some_and(|owner_entity| {
1732 combat::allow_friendly_fire(
1733 &data.entered_auras,
1734 owner_entity,
1735 entity_b,
1736 )
1737 });
1738 let permit_pvp = combat::permit_pvp(
1740 &data.alignments,
1741 &data.players,
1742 &data.entered_auras,
1743 &data.id_maps,
1744 owner_entity,
1745 entity_b,
1746 );
1747 let attack_options = combat::AttackOptions {
1748 target_dodging,
1749 permit_pvp,
1750 allow_friendly_fire,
1751 target_group,
1752 precision_mult: None,
1753 };
1754
1755 attack.apply_attack(
1756 attacker_info,
1757 &target_info,
1758 dir,
1759 attack_options,
1760 strength,
1761 combat::AttackSource::Explosion,
1762 *data.time,
1763 &mut emitters,
1764 |o| outcome_emitter.emit(o),
1765 &mut rng,
1766 0,
1767 );
1768 }
1769 }
1770 },
1771 RadiusEffect::Entity(mut effect) => {
1772 for (entity_b, pos_b, body_b_maybe) in
1773 (&data.entities, &data.positions, data.bodies.maybe()).join()
1774 {
1775 let strength = if let Some(body) = body_b_maybe {
1776 cylinder_sphere_strength(
1777 ev.pos,
1778 ev.explosion.radius,
1779 ev.explosion.min_falloff,
1780 pos_b.0,
1781 *body,
1782 )
1783 } else {
1784 let distance_squared = ev.pos.distance_squared(pos_b.0);
1785 1.0 - distance_squared / ev.explosion.radius.powi(2)
1786 };
1787
1788 let permit_pvp = || {
1799 combat::permit_pvp(
1800 &data.alignments,
1801 &data.players,
1802 &data.entered_auras,
1803 &data.id_maps,
1804 owner_entity,
1805 entity_b,
1806 ) || owner_entity.is_none_or(|entity_a| entity_a == entity_b)
1807 };
1808 if strength > 0.0 {
1809 let is_alive =
1810 data.healths.get(entity_b).is_none_or(|h| !h.is_dead);
1811
1812 if is_alive {
1813 effect.modify_strength(strength);
1814 if !effect.is_harm() || permit_pvp() {
1815 emit_effect_events(
1816 &mut emitters,
1817 *data.time,
1818 entity_b,
1819 effect.clone(),
1820 ev.owner.map(|owner| {
1821 (
1822 owner,
1823 data.id_maps
1824 .uid_entity(owner)
1825 .and_then(|e| data.groups.get(e))
1826 .copied(),
1827 )
1828 }),
1829 data.inventories.get(entity_b),
1830 &data.msm,
1831 data.character_states.get(entity_b),
1832 data.stats.get(entity_b),
1833 data.masses.get(entity_b),
1834 owner_entity.and_then(|e| data.masses.get(e)),
1835 data.bodies.get(entity_b),
1836 data.positions.get(entity_b),
1837 );
1838 }
1839 }
1840 }
1841 }
1842 },
1843 }
1844 }
1845 }
1846 }
1847}
1848
1849pub fn emit_effect_events(
1850 emitters: &mut (
1851 impl EmitExt<HealthChangeEvent>
1852 + EmitExt<PoiseChangeEvent>
1853 + EmitExt<BuffEvent>
1854 + EmitExt<ChangeBodyEvent>
1855 + EmitExt<Outcome>
1856 ),
1857 time: Time,
1858 entity: EcsEntity,
1859 effect: common::effect::Effect,
1860 source: Option<(Uid, Option<Group>)>,
1861 inventory: Option<&Inventory>,
1862 msm: &MaterialStatManifest,
1863 char_state: Option<&CharacterState>,
1864 stats: Option<&Stats>,
1865 tgt_mass: Option<&comp::Mass>,
1866 source_mass: Option<&comp::Mass>,
1867 tgt_body: Option<&Body>,
1868 tgt_pos: Option<&Pos>,
1869) {
1870 let damage_contributor = source.map(|(uid, group)| DamageContributor::new(uid, group));
1871 match effect {
1872 common::effect::Effect::Health(change) => {
1873 emitters.emit(HealthChangeEvent { entity, change })
1874 },
1875 common::effect::Effect::Poise(amount) => {
1876 let amount = Poise::apply_poise_reduction(amount, inventory, msm, char_state, stats);
1877 emitters.emit(PoiseChangeEvent {
1878 entity,
1879 change: comp::PoiseChange {
1880 amount,
1881 impulse: Vec3::zero(),
1882 by: damage_contributor,
1883 cause: None,
1884 time,
1885 },
1886 })
1887 },
1888 common::effect::Effect::Damage(damage) => {
1889 let change = damage.calculate_health_change(
1890 combat::Damage::compute_damage_reduction(Some(damage), inventory, stats, msm),
1891 0.0,
1892 damage_contributor,
1893 None,
1894 0.0,
1895 1.0,
1896 time,
1897 rand::random(),
1898 );
1899 emitters.emit(HealthChangeEvent { entity, change })
1900 },
1901 common::effect::Effect::Buff(buff) => {
1902 let dest_info = buff::DestInfo {
1903 stats,
1904 mass: tgt_mass,
1905 };
1906 emitters.emit(BuffEvent {
1907 entity,
1908 buff_change: comp::BuffChange::Add(comp::Buff::new(
1909 buff.kind,
1910 buff.data,
1911 buff.cat_ids,
1912 comp::BuffSource::Item,
1913 time,
1914 dest_info,
1915 source_mass,
1916 )),
1917 });
1918 },
1919 common::effect::Effect::Permanent(permanent_effect) => match permanent_effect {
1920 common::effect::PermanentEffect::CycleBodyType => {
1921 if let Some(body) = tgt_body
1922 && let Some(new_body) = match body {
1923 Body::Humanoid(body) => Some(Body::Humanoid(comp::humanoid::Body {
1924 body_type: match body.body_type {
1925 comp::humanoid::BodyType::Female => comp::humanoid::BodyType::Male,
1926 comp::humanoid::BodyType::Male => comp::humanoid::BodyType::Female,
1927 },
1928 ..*body
1929 })),
1930 _ => None,
1932 }
1933 {
1934 emitters.emit(ChangeBodyEvent {
1936 entity,
1937 new_body,
1938 permanent_change: Some(PermanentChange {
1939 expected_old_body: *body,
1940 }),
1941 });
1942 if let Some(pos) = tgt_pos {
1943 emitters.emit(Outcome::Transformation { pos: pos.0 });
1944 }
1945 }
1946 },
1947 },
1948 }
1949}
1950
1951impl ServerEvent for BonkEvent {
1952 type SystemData<'a> = (
1953 Write<'a, BlockChange>,
1954 ReadExpect<'a, TerrainGrid>,
1955 ReadExpect<'a, ProgramTime>,
1956 Read<'a, EventBus<CreateObjectEvent>>,
1957 Read<'a, EventBus<ShootEvent>>,
1958 );
1959
1960 fn handle(
1961 events: impl ExactSizeIterator<Item = Self>,
1962 (mut block_change, terrain, program_time, create_object_events, shoot_events): Self::SystemData<'_>,
1963 ) {
1964 let mut create_object_emitter = create_object_events.emitter();
1965 let mut shoot_emitter = shoot_events.emitter();
1966 for ev in events {
1967 if let Some(_target) = ev.target {
1968 } else {
1970 use common::terrain::SpriteKind;
1971 let pos = ev.pos.map(|e| e.floor() as i32);
1972 if let Some(block) = terrain.get(pos).ok().copied().filter(|b| b.is_bonkable())
1973 && block_change
1974 .try_set(pos, block.with_sprite(SpriteKind::Empty))
1975 .is_some()
1976 {
1977 let sprite_cfg = terrain.sprite_cfg_at(pos);
1978 if let Some(items) = comp::Item::try_reclaim_from_block(block, sprite_cfg) {
1979 let msm = &MaterialStatManifest::load().read();
1980 let ability_map = &AbilityMap::load().read();
1981 for item in flatten_counted_items(&items, ability_map, msm) {
1982 let pos = Pos(pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0));
1983 let vel = comp::Vel::default();
1984 let body = match block.get_sprite() {
1986 Some(SpriteKind::Apple) => comp::object::Body::Apple,
1989 Some(SpriteKind::Beehive) => comp::object::Body::Hive,
1990 Some(SpriteKind::Coconut) => comp::object::Body::Coconut,
1991 Some(SpriteKind::Bomb) => comp::object::Body::Bomb,
1992 _ => comp::object::Body::Pebble,
1993 };
1994
1995 if matches!(block.get_sprite(), Some(SpriteKind::Bomb)) {
1996 shoot_emitter.emit(ShootEvent {
1997 entity: None,
1998 pos,
1999 dir: Dir::from_unnormalized(vel.0).unwrap_or_default(),
2000 body: Body::Object(body),
2001 light: None,
2002 projectile: ProjectileConstructor {
2003 kind: ProjectileConstructorKind::Explosive {
2004 radius: 12.0,
2005 min_falloff: 0.75,
2006 reagent: None,
2007 terrain: Some((4.0, ColorPreset::Black)),
2008 },
2009 attack: Some(ProjectileAttack {
2010 damage: 40.0,
2011 poise: Some(100.0),
2012 knockback: None,
2013 energy: None,
2014 buff: None,
2015 friendly_fire: true,
2016 }),
2017 scaled: None,
2018 }
2019 .create_projectile(None, 1.0, None),
2020 speed: vel.0.magnitude(),
2021 object: None,
2022 });
2023 } else {
2024 create_object_emitter.emit(CreateObjectEvent {
2025 pos,
2026 vel,
2027 body,
2028 object: None,
2029 item: Some(comp::PickupItem::new(item, *program_time, false)),
2030 light_emitter: None,
2031 stats: None,
2032 });
2033 }
2034 }
2035 }
2036 }
2037 }
2038 }
2039 }
2040}
2041
2042impl ServerEvent for AuraEvent {
2043 type SystemData<'a> = (WriteStorage<'a, Auras>, WriteStorage<'a, EnteredAuras>);
2044
2045 fn handle(
2046 events: impl ExactSizeIterator<Item = Self>,
2047 (mut auras, mut entered_auras): Self::SystemData<'_>,
2048 ) {
2049 for ev in events {
2050 use aura::AuraChange;
2051 match ev.aura_change {
2052 AuraChange::Add(new_aura) => {
2053 if let Some(mut auras) = auras.get_mut(ev.entity) {
2054 auras.insert(new_aura);
2055 }
2056 },
2057 AuraChange::RemoveByKey(keys) => {
2058 if let Some(mut auras) = auras.get_mut(ev.entity) {
2059 for key in keys {
2060 auras.remove(key);
2061 }
2062 }
2063 },
2064 AuraChange::EnterAura(uid, key, variant) => {
2065 if let Some(mut entered_auras) = entered_auras.get_mut(ev.entity) {
2066 entered_auras
2067 .auras
2068 .entry(variant)
2069 .and_modify(|entered_auras| {
2070 entered_auras.insert((uid, key));
2071 })
2072 .or_insert_with(|| <_ as Into<_>>::into([(uid, key)]));
2073 }
2074 },
2075 AuraChange::ExitAura(uid, key, variant) => {
2076 if let Some(mut entered_auras) = entered_auras.get_mut(ev.entity)
2077 && let Some(entered_auras_variant) = entered_auras.auras.get_mut(&variant)
2078 {
2079 entered_auras_variant.remove(&(uid, key));
2080
2081 if entered_auras_variant.is_empty() {
2082 entered_auras.auras.remove(&variant);
2083 }
2084 }
2085 },
2086 }
2087 }
2088 }
2089}
2090
2091impl ServerEvent for BuffEvent {
2092 type SystemData<'a> = (
2093 Read<'a, Time>,
2094 WriteStorage<'a, comp::Buffs>,
2095 ReadStorage<'a, Body>,
2096 ReadStorage<'a, Health>,
2097 ReadStorage<'a, Stats>,
2098 ReadStorage<'a, comp::Mass>,
2099 );
2100
2101 fn handle(
2102 events: impl ExactSizeIterator<Item = Self>,
2103 (time, mut buffs, bodies, healths, stats, masses): Self::SystemData<'_>,
2104 ) {
2105 for ev in events {
2106 if let Some(mut buffs) = buffs.get_mut(ev.entity) {
2107 use buff::BuffChange;
2108 match ev.buff_change {
2109 BuffChange::Add(mut new_buff) => {
2110 let immunity_by_buff = buffs
2111 .buffs
2112 .values_mut()
2113 .flat_map(|b| b.kind.effects(&b.data))
2114 .find(|b| match b {
2115 BuffEffect::BuffImmunity(kind) => new_buff.kind == *kind,
2116 _ => false,
2117 });
2118
2119 if !bodies
2120 .get(ev.entity)
2121 .is_some_and(|body| body.immune_to(new_buff.kind))
2122 && immunity_by_buff.is_none()
2123 && healths.get(ev.entity).is_none_or(|h| !h.is_dead)
2124 {
2125 if let Some(strength) =
2126 new_buff.kind.resilience_ccr_strength(new_buff.data)
2127 {
2128 let resilience_buff = buff::Buff::new(
2129 BuffKind::Resilience,
2130 buff::BuffData::new(
2131 strength,
2132 Some(
2133 new_buff
2134 .data
2135 .duration
2136 .map_or(Secs(30.0), |dur| dur * 5.0),
2137 ),
2138 ),
2139 Vec::new(),
2140 BuffSource::Buff,
2141 *time,
2142 buff::DestInfo {
2143 stats: stats.get(ev.entity),
2144 mass: masses.get(ev.entity),
2145 },
2146 None,
2148 );
2149 buffs.insert(resilience_buff, *time);
2150 }
2151
2152 if bodies
2153 .get(ev.entity)
2154 .is_some_and(|body| body.negates_buff(new_buff.kind))
2155 {
2156 new_buff.effects.clear();
2157 }
2158
2159 buffs.insert(new_buff, *time);
2160 }
2161 },
2162 BuffChange::RemoveByKey(keys) => {
2163 for key in keys {
2164 buffs.remove(key);
2165 }
2166 },
2167 BuffChange::RemoveByKind(kind) => {
2168 buffs.remove_kind(kind);
2169 },
2170 BuffChange::RemoveFromController(kind) => {
2171 if kind.is_buff() {
2172 buffs.remove_kind(kind);
2173 }
2174 },
2175 BuffChange::RemoveByCategory {
2176 all_required,
2177 any_required,
2178 none_required,
2179 } => {
2180 let mut keys_to_remove = Vec::new();
2181 for (key, buff) in buffs.buffs.iter() {
2182 let mut required_met = true;
2183 for required in &all_required {
2184 if !buff.cat_ids.iter().any(|cat| cat == required) {
2185 required_met = false;
2186 break;
2187 }
2188 }
2189 let mut any_met = any_required.is_empty();
2190 for any in &any_required {
2191 if buff.cat_ids.iter().any(|cat| cat == any) {
2192 any_met = true;
2193 break;
2194 }
2195 }
2196 let mut none_met = true;
2197 for none in &none_required {
2198 if buff.cat_ids.iter().any(|cat| cat == none) {
2199 none_met = false;
2200 break;
2201 }
2202 }
2203 if required_met && any_met && none_met {
2204 keys_to_remove.push(key);
2205 }
2206 }
2207 for key in keys_to_remove {
2208 buffs.remove(key);
2209 }
2210 },
2211 BuffChange::Refresh(kind) => {
2212 buffs
2213 .buffs
2214 .values_mut()
2215 .filter(|b| b.kind == kind)
2216 .for_each(|buff| {
2217 buff.start_time = *time;
2220 buff.end_time = buff.data.duration.map(|dur| Time(time.0 + dur.0));
2221 })
2222 },
2223 }
2224 }
2225 }
2226 }
2227}
2228
2229impl ServerEvent for EnergyChangeEvent {
2230 type SystemData<'a> = WriteStorage<'a, Energy>;
2231
2232 fn handle(events: impl ExactSizeIterator<Item = Self>, mut energies: Self::SystemData<'_>) {
2233 for ev in events {
2234 if let Some(mut energy) = energies.get_mut(ev.entity) {
2235 energy.change_by(ev.change);
2236 if ev.reset_rate {
2237 energy.reset_regen_rate();
2238 }
2239 }
2240 }
2241 }
2242}
2243
2244impl ServerEvent for ComboChangeEvent {
2245 type SystemData<'a> = (
2246 Read<'a, Time>,
2247 Read<'a, EventBus<Outcome>>,
2248 WriteStorage<'a, comp::Combo>,
2249 ReadStorage<'a, Uid>,
2250 );
2251
2252 fn handle(
2253 events: impl ExactSizeIterator<Item = Self>,
2254 (time, outcomes, mut combos, uids): Self::SystemData<'_>,
2255 ) {
2256 let mut outcome_emitter = outcomes.emitter();
2257 for ev in events {
2258 if let Some(mut combo) = combos.get_mut(ev.entity) {
2259 combo.change_by(ev.change, time.0);
2260 if let Some(uid) = uids.get(ev.entity) {
2261 outcome_emitter.emit(Outcome::ComboChange {
2262 uid: *uid,
2263 combo: combo.counter(),
2264 });
2265 }
2266 }
2267 }
2268 }
2269}
2270
2271impl ServerEvent for ParryHookEvent {
2272 type SystemData<'a> = (
2273 Read<'a, Time>,
2274 Read<'a, EventBus<EnergyChangeEvent>>,
2275 Read<'a, EventBus<PoiseChangeEvent>>,
2276 Read<'a, EventBus<BuffEvent>>,
2277 WriteStorage<'a, CharacterState>,
2278 ReadStorage<'a, Uid>,
2279 ReadStorage<'a, Stats>,
2280 ReadStorage<'a, comp::Mass>,
2281 ReadStorage<'a, Inventory>,
2282 );
2283
2284 fn handle(
2285 events: impl ExactSizeIterator<Item = Self>,
2286 (
2287 time,
2288 energy_change_events,
2289 poise_change_events,
2290 buff_events,
2291 mut character_states,
2292 uids,
2293 stats,
2294 masses,
2295 inventories,
2296 ): Self::SystemData<'_>,
2297 ) {
2298 let mut energy_change_emitter = energy_change_events.emitter();
2299 let mut poise_change_emitter = poise_change_events.emitter();
2300 let mut buff_emitter = buff_events.emitter();
2301 for ev in events {
2302 if let Some(mut char_state) = character_states.get_mut(ev.defender) {
2303 let return_to_wield = match &mut *char_state {
2304 CharacterState::RiposteMelee(c) => {
2305 c.stage_section = StageSection::Action;
2306 c.timer = Duration::default();
2307 c.whiffed = false;
2308 false
2309 },
2310 CharacterState::BasicBlock(c) => {
2311 energy_change_emitter.emit(EnergyChangeEvent {
2313 entity: ev.defender,
2314 change: c.static_data.energy_regen,
2315 reset_rate: false,
2316 });
2317 c.is_parry = true;
2318 false
2319 },
2320 _ => false,
2321 };
2322 if return_to_wield {
2323 *char_state = CharacterState::Wielding(common::states::wielding::Data {
2324 is_sneaking: false,
2325 });
2326 }
2327 };
2328
2329 if let Some(attacker) = ev.attacker
2330 && matches!(ev.source, AttackSource::Melee)
2331 {
2332 let data = buff::BuffData::new(1.0, Some(Secs(2.0)));
2335 let source = if let Some(uid) = uids.get(ev.defender) {
2336 BuffSource::Character { by: *uid }
2337 } else {
2338 BuffSource::World
2339 };
2340 let dest_info = buff::DestInfo {
2341 stats: stats.get(attacker),
2342 mass: masses.get(attacker),
2343 };
2344 let buff = buff::Buff::new(
2345 BuffKind::Parried,
2346 data,
2347 vec![buff::BuffCategory::Physical],
2348 source,
2349 *time,
2350 dest_info,
2351 masses.get(ev.defender),
2352 );
2353 buff_emitter.emit(BuffEvent {
2354 entity: attacker,
2355 buff_change: buff::BuffChange::Add(buff),
2356 });
2357
2358 let attacker_poise_change = Poise::apply_poise_reduction(
2359 ev.poise_multiplier.clamp(1.0, 2.0) * BASE_PARRIED_POISE_PUNISHMENT,
2360 inventories.get(attacker),
2361 &MaterialStatManifest::load().read(),
2362 character_states.get(attacker),
2363 stats.get(attacker),
2364 );
2365
2366 poise_change_emitter.emit(PoiseChangeEvent {
2367 entity: attacker,
2368 change: PoiseChange {
2369 amount: -attacker_poise_change,
2370 impulse: Vec3::zero(),
2371 by: uids
2372 .get(ev.defender)
2373 .map(|d| DamageContributor::new(*d, None)),
2374 cause: Some(DamageSource::Melee),
2375 time: *time,
2376 },
2377 });
2378 }
2379 }
2380 }
2381}
2382
2383impl ServerEvent for TeleportToEvent {
2384 type SystemData<'a> = (
2385 Read<'a, IdMaps>,
2386 WriteStorage<'a, Pos>,
2387 WriteStorage<'a, comp::ForceUpdate>,
2388 );
2389
2390 fn handle(
2391 events: impl ExactSizeIterator<Item = Self>,
2392 (id_maps, mut positions, mut force_updates): Self::SystemData<'_>,
2393 ) {
2394 for ev in events {
2395 let target_pos = id_maps
2396 .uid_entity(ev.target)
2397 .and_then(|e| positions.get(e))
2398 .copied();
2399
2400 if let (Some(pos), Some(target_pos)) = (positions.get_mut(ev.entity), target_pos)
2401 && ev
2402 .max_range
2403 .is_none_or(|r| pos.0.distance_squared(target_pos.0) < r.powi(2))
2404 {
2405 *pos = target_pos;
2406 force_updates
2407 .get_mut(ev.entity)
2408 .map(|force_update| force_update.update());
2409 }
2410 }
2411 }
2412}
2413
2414#[derive(SystemData)]
2415pub struct EntityAttackedHookData<'a> {
2416 entities: Entities<'a>,
2417 trades: Write<'a, Trades>,
2418 id_maps: Read<'a, IdMaps>,
2419 time: Read<'a, Time>,
2420 event_busses: ReadEntityAttackedHookEvents<'a>,
2421 outcomes: Read<'a, EventBus<Outcome>>,
2422 character_states: WriteStorage<'a, CharacterState>,
2423 poises: WriteStorage<'a, Poise>,
2424 agents: WriteStorage<'a, Agent>,
2425 positions: ReadStorage<'a, Pos>,
2426 uids: ReadStorage<'a, Uid>,
2427 clients: ReadStorage<'a, Client>,
2428 stats: ReadStorage<'a, Stats>,
2429 healths: ReadStorage<'a, Health>,
2430}
2431
2432impl ServerEvent for EntityAttackedHookEvent {
2433 type SystemData<'a> = EntityAttackedHookData<'a>;
2434
2435 fn handle(events: impl ExactSizeIterator<Item = Self>, mut data: Self::SystemData<'_>) {
2438 let mut emitters = data.event_busses.get_emitters();
2439 let mut outcomes = data.outcomes.emitter();
2440
2441 for ev in events {
2442 if let Some(attacker) = ev.attacker {
2443 emitters.emit(BuffEvent {
2444 entity: attacker,
2445 buff_change: buff::BuffChange::RemoveByCategory {
2446 all_required: vec![buff::BuffCategory::RemoveOnAttack],
2447 any_required: vec![],
2448 none_required: vec![],
2449 },
2450 });
2451 }
2452
2453 if let Some((mut char_state, mut poise, pos)) = (
2454 &mut data.character_states,
2455 &mut data.poises,
2456 &data.positions,
2457 )
2458 .lend_join()
2459 .get(ev.entity, &data.entities)
2460 {
2461 if matches!(
2463 *char_state,
2464 CharacterState::Interact(_) | CharacterState::UseItem(_)
2465 ) {
2466 let poise_state = comp::poise::PoiseState::Interrupted;
2467 let was_wielded = char_state.is_wield();
2468 if let (Some((stunned_state, stunned_duration)), impulse_strength) =
2469 poise_state.poise_effect(was_wielded)
2470 {
2471 poise.reset(*data.time, stunned_duration);
2473 if !comp::is_downed(data.healths.get(ev.entity), Some(&char_state)) {
2474 *char_state = stunned_state;
2475 }
2476 outcomes.emit(Outcome::PoiseChange {
2477 pos: pos.0,
2478 state: poise_state,
2479 });
2480 if let Some(impulse_strength) = impulse_strength {
2481 emitters.emit(KnockbackEvent {
2482 entity: ev.entity,
2483 impulse: impulse_strength * *poise.knockback(),
2484 });
2485 }
2486 }
2487 }
2488 }
2489
2490 emitters.emit(BuffEvent {
2492 entity: ev.entity,
2493 buff_change: buff::BuffChange::RemoveByKind(BuffKind::Potion),
2494 });
2495 emitters.emit(BuffEvent {
2496 entity: ev.entity,
2497 buff_change: buff::BuffChange::RemoveByKind(BuffKind::Saturation),
2498 });
2499
2500 if let Some(uid) = data.uids.get(ev.entity)
2502 && let Some(trade) = data.trades.entity_trades.get(uid).copied()
2503 {
2504 data.trades
2505 .decline_trade(trade, *uid)
2506 .and_then(|uid| data.id_maps.uid_entity(uid))
2507 .map(|entity_b| {
2508 let mut notify_trade_party = |entity| {
2510 if let Some(client) = data.clients.get(entity) {
2515 client.send_fallible(ServerGeneral::FinishedTrade(
2516 TradeResult::Declined,
2517 ));
2518 }
2519 if let Some(agent) = data.agents.get_mut(entity) {
2520 agent
2521 .inbox
2522 .push_back(AgentEvent::FinishedTrade(TradeResult::Declined));
2523 }
2524 };
2525 notify_trade_party(ev.entity);
2526 notify_trade_party(entity_b);
2527 });
2528 }
2529
2530 if let Some(stats) = data.stats.get(ev.entity) {
2531 for effect in &stats.effects_on_damaged {
2532 use combat::DamagedEffect;
2533 match effect {
2534 DamagedEffect::Combo(c) => {
2535 emitters.emit(ComboChangeEvent {
2536 entity: ev.entity,
2537 change: *c,
2538 });
2539 },
2540 DamagedEffect::Energy(e) => {
2541 emitters.emit(EnergyChangeEvent {
2542 entity: ev.entity,
2543 change: *e,
2544 reset_rate: false,
2545 });
2546 },
2547 }
2548 }
2549 }
2550 }
2551 }
2552}
2553
2554impl ServerEvent for ChangeAbilityEvent {
2555 type SystemData<'a> = (
2556 WriteStorage<'a, comp::ActiveAbilities>,
2557 ReadStorage<'a, Inventory>,
2558 ReadStorage<'a, SkillSet>,
2559 );
2560
2561 fn handle(
2562 events: impl ExactSizeIterator<Item = Self>,
2563 (mut active_abilities, inventories, skill_sets): Self::SystemData<'_>,
2564 ) {
2565 for ev in events {
2566 if let Some(mut active_abilities) = active_abilities.get_mut(ev.entity) {
2567 active_abilities.change_ability(
2568 ev.slot,
2569 ev.auxiliary_key,
2570 ev.new_ability,
2571 inventories.get(ev.entity),
2572 skill_sets.get(ev.entity),
2573 );
2574 }
2575 }
2576 }
2577}
2578
2579impl ServerEvent for UpdateMapMarkerEvent {
2580 type SystemData<'a> = (
2581 Entities<'a>,
2582 WriteStorage<'a, comp::MapMarker>,
2583 ReadStorage<'a, Group>,
2584 ReadStorage<'a, Uid>,
2585 ReadStorage<'a, Client>,
2586 ReadStorage<'a, Alignment>,
2587 );
2588
2589 fn handle(
2590 events: impl ExactSizeIterator<Item = Self>,
2591 (entities, mut map_markers, groups, uids, clients, alignments): Self::SystemData<'_>,
2592 ) {
2593 for ev in events {
2594 match ev.update {
2595 comp::MapMarkerChange::Update(waypoint) => {
2596 let _ = map_markers.insert(ev.entity, comp::MapMarker(waypoint));
2597 },
2598 comp::MapMarkerChange::Remove => {
2599 map_markers.remove(ev.entity);
2600 },
2601 }
2602 if let Some((group_id, uid)) = (&groups, &uids).lend_join().get(ev.entity, &entities) {
2604 for client in
2605 comp::group::members(*group_id, &groups, &entities, &alignments, &uids)
2606 .filter_map(|(e, _)| if e != ev.entity { clients.get(e) } else { None })
2607 {
2608 client.send_fallible(ServerGeneral::MapMarker(
2609 comp::MapMarkerUpdate::GroupMember(*uid, ev.update),
2610 ));
2611 }
2612 }
2613 }
2614 }
2615}
2616
2617impl ServerEvent for MakeAdminEvent {
2618 type SystemData<'a> = (WriteStorage<'a, comp::Admin>, ReadStorage<'a, Player>);
2619
2620 fn handle(
2621 events: impl ExactSizeIterator<Item = Self>,
2622 (mut admins, players): Self::SystemData<'_>,
2623 ) {
2624 for ev in events {
2625 if players
2626 .get(ev.entity)
2627 .is_some_and(|player| player.uuid() == ev.uuid)
2628 {
2629 let _ = admins.insert(ev.entity, ev.admin);
2630 }
2631 }
2632 }
2633}
2634
2635impl ServerEvent for ChangeStanceEvent {
2636 type SystemData<'a> = WriteStorage<'a, comp::Stance>;
2637
2638 fn handle(events: impl ExactSizeIterator<Item = Self>, mut stances: Self::SystemData<'_>) {
2639 for ev in events {
2640 if let Some(mut stance) = stances.get_mut(ev.entity) {
2641 *stance = ev.stance;
2642 }
2643 }
2644 }
2645}
2646
2647impl ServerEvent for ChangeBodyEvent {
2648 type SystemData<'a> = (
2649 WriteExpect<'a, CharacterUpdater>,
2650 WriteStorage<'a, comp::Body>,
2651 WriteStorage<'a, comp::Mass>,
2652 WriteStorage<'a, comp::Density>,
2653 WriteStorage<'a, comp::Collider>,
2654 WriteStorage<'a, comp::Stats>,
2655 ReadStorage<'a, comp::Player>,
2656 ReadStorage<'a, comp::Presence>,
2657 );
2658
2659 fn handle(
2660 events: impl ExactSizeIterator<Item = Self>,
2661 (
2662 mut character_updater,
2663 mut bodies,
2664 mut masses,
2665 mut densities,
2666 mut colliders,
2667 mut stats,
2668 players,
2669 presences,
2670 ): Self::SystemData<'_>,
2671 ) {
2672 for ev in events {
2673 if let Some(mut body) = bodies.get_mut(ev.entity) {
2674 if let Some(permanent_change) = ev.permanent_change {
2675 if permanent_change.expected_old_body != *body {
2677 continue;
2678 }
2679
2680 if let Some(mut stats) = stats.get_mut(ev.entity)
2681 && stats.original_body == permanent_change.expected_old_body
2682 {
2683 stats.original_body = ev.new_body;
2684 }
2685
2686 if let Some(player) = players.get(ev.entity)
2687 && let Some(comp::Presence {
2688 kind: comp::PresenceKind::Character(character_id),
2689 ..
2690 }) = presences.get(ev.entity)
2691 {
2692 character_updater.edit_character(
2693 ev.entity,
2694 player.uuid().to_string(),
2695 *character_id,
2696 None,
2697 (ev.new_body,),
2698 Some(permanent_change),
2699 );
2700 }
2701 }
2702
2703 *body = ev.new_body;
2704 masses
2705 .insert(ev.entity, ev.new_body.mass())
2706 .expect("We just got this entities body");
2707 densities
2708 .insert(ev.entity, ev.new_body.density())
2709 .expect("We just got this entities body");
2710 colliders
2711 .insert(ev.entity, ev.new_body.collider())
2712 .expect("We just got this entities body");
2713 }
2714 }
2715 }
2716}
2717
2718impl ServerEvent for RemoveLightEmitterEvent {
2719 type SystemData<'a> = WriteStorage<'a, comp::LightEmitter>;
2720
2721 fn handle(
2722 events: impl ExactSizeIterator<Item = Self>,
2723 mut light_emitters: Self::SystemData<'_>,
2724 ) {
2725 for ev in events {
2726 light_emitters.remove(ev.entity);
2727 }
2728 }
2729}
2730
2731impl ServerEvent for TeleportToPositionEvent {
2732 type SystemData<'a> = (
2733 Read<'a, IdMaps>,
2734 WriteStorage<'a, Is<VolumeRider>>,
2735 WriteStorage<'a, Pos>,
2736 WriteStorage<'a, comp::ForceUpdate>,
2737 ReadStorage<'a, Is<Rider>>,
2738 ReadStorage<'a, Presence>,
2739 ReadStorage<'a, Client>,
2740 );
2741
2742 fn handle(
2743 events: impl ExactSizeIterator<Item = Self>,
2744 (
2745 id_maps,
2746 mut is_volume_riders,
2747 mut positions,
2748 mut force_updates,
2749 is_riders,
2750 presences,
2751 clients,
2752 ): Self::SystemData<'_>,
2753 ) {
2754 for ev in events {
2755 if let Err(error) = crate::state_ext::position_mut(
2756 ev.entity,
2757 true,
2758 |pos| pos.0 = ev.position,
2759 &id_maps,
2760 &mut is_volume_riders,
2761 &mut positions,
2762 &mut force_updates,
2763 &is_riders,
2764 &presences,
2765 &clients,
2766 ) {
2767 warn!(?error, "Failed to teleport entity");
2768 }
2769 }
2770 }
2771}
2772
2773impl ServerEvent for StartTeleportingEvent {
2774 type SystemData<'a> = (
2775 Read<'a, Time>,
2776 WriteStorage<'a, comp::Teleporting>,
2777 ReadStorage<'a, Pos>,
2778 ReadStorage<'a, comp::Object>,
2779 );
2780
2781 fn handle(
2782 events: impl ExactSizeIterator<Item = Self>,
2783 (time, mut teleportings, positions, objects): Self::SystemData<'_>,
2784 ) {
2785 for ev in events {
2786 if let Some(end_time) = (!teleportings.contains(ev.entity))
2787 .then(|| positions.get(ev.entity))
2788 .flatten()
2789 .zip(positions.get(ev.portal))
2790 .filter(|(entity_pos, portal_pos)| {
2791 entity_pos.0.distance_squared(portal_pos.0) <= TELEPORTER_RADIUS.powi(2)
2792 })
2793 .and_then(|(_, _)| {
2794 Some(
2795 time.0
2796 + objects.get(ev.portal).and_then(|object| {
2797 if let Object::Portal { buildup_time, .. } = object {
2798 Some(buildup_time.0)
2799 } else {
2800 None
2801 }
2802 })?,
2803 )
2804 })
2805 {
2806 let _ = teleportings.insert(ev.entity, comp::Teleporting {
2807 portal: ev.portal,
2808 end_time: Time(end_time),
2809 });
2810 }
2811 }
2812 }
2813}
2814
2815impl ServerEvent for RegrowHeadEvent {
2816 type SystemData<'a> = (
2817 Read<'a, EventBus<HealthChangeEvent>>,
2818 Read<'a, Time>,
2819 WriteStorage<'a, Heads>,
2820 ReadStorage<'a, Health>,
2821 );
2822
2823 fn handle(
2824 events: impl ExactSizeIterator<Item = Self>,
2825 (health_change_events, time, mut heads, healths): Self::SystemData<'_>,
2826 ) {
2827 let mut health_change_emitter = health_change_events.emitter();
2828 for ev in events {
2829 if let Some(mut heads) = heads.get_mut(ev.entity)
2830 && heads.regrow_oldest()
2831 && let Some(health) = healths.get(ev.entity)
2832 {
2833 let amount = 1.0 / (heads.capacity() as f32) * health.maximum();
2834 health_change_emitter.emit(HealthChangeEvent {
2835 entity: ev.entity,
2836 change: comp::HealthChange {
2837 amount,
2838 by: None,
2839 cause: Some(DamageSource::Other),
2840 time: *time,
2841 precise: false,
2842 instance: rand::random(),
2843 },
2844 })
2845 }
2846 }
2847 }
2848}
2849
2850pub fn handle_transform(
2851 server: &mut Server,
2852 TransformEvent {
2853 target_entity,
2854 entity_info,
2855 allow_players,
2856 delete_on_failure,
2857 }: TransformEvent,
2858) {
2859 let Some(entity) = server.state().ecs().entity_from_uid(target_entity) else {
2860 return;
2861 };
2862
2863 if let Err(error) = transform_entity(server, entity, entity_info, allow_players) {
2864 if delete_on_failure
2865 && !server
2866 .state()
2867 .ecs()
2868 .read_storage::<Client>()
2869 .contains(entity)
2870 {
2871 _ = server.state.delete_entity_recorded(entity);
2872 }
2873
2874 error!(?error, ?target_entity, "Failed transform entity");
2875 }
2876}
2877
2878#[derive(Debug)]
2879pub enum TransformEntityError {
2880 EntityDead,
2881 UnexpectedSpecialEntity,
2882 LoadingCharacter,
2883 EntityIsPlayer,
2884}
2885
2886pub fn transform_entity(
2887 server: &mut Server,
2888 entity: Entity,
2889 entity_info: EntityInfo,
2890 allow_players: bool,
2891) -> Result<(), TransformEntityError> {
2892 let is_player = server
2893 .state()
2894 .read_storage::<comp::Player>()
2895 .contains(entity);
2896
2897 match SpawnEntityData::from_entity_info(entity_info) {
2898 SpawnEntityData::Npc(NpcData {
2899 inventory,
2900 stats,
2901 skill_set,
2902 poise,
2903 health,
2904 body,
2905 scale,
2906 agent,
2907 loot,
2908 alignment: _,
2909 pos: _,
2910 pets,
2911 rider,
2912 death_effects,
2913 rider_effects,
2914 }) => {
2915 fn set_or_remove_component<C: specs::Component>(
2916 server: &mut Server,
2917 entity: EcsEntity,
2918 component: Option<C>,
2919 with: Option<fn(&mut C, Option<C>)>,
2920 ) -> Result<(), TransformEntityError> {
2921 let mut storage = server.state.ecs_mut().write_storage::<C>();
2922
2923 if let Some(mut component) = component {
2924 if let Some(with) = with {
2925 let prev = storage.remove(entity);
2926 with(&mut component, prev);
2927 }
2928
2929 storage
2930 .insert(entity, component)
2931 .and(Ok(()))
2932 .map_err(|_| TransformEntityError::EntityDead)
2933 } else {
2934 storage.remove(entity);
2935 Ok(())
2936 }
2937 }
2938
2939 'persist: {
2941 match server
2942 .state
2943 .ecs()
2944 .read_storage::<Presence>()
2945 .get(entity)
2946 .map(|presence| presence.kind)
2947 {
2948 Some(PresenceKind::Spectator | PresenceKind::LoadingCharacter(_)) => {
2950 return Err(TransformEntityError::LoadingCharacter);
2951 },
2952 Some(PresenceKind::Character(_)) if !allow_players => {
2953 return Err(TransformEntityError::EntityIsPlayer);
2954 },
2955 Some(PresenceKind::Possessor | PresenceKind::Character(_)) => {},
2956 None => break 'persist,
2957 }
2958
2959 super::player::persist_entity(server.state_mut(), entity);
2964
2965 let mut presences = server.state.ecs().write_storage::<Presence>();
2969 let Some(presence) = presences.get_mut(entity) else {
2970 unreachable!("We already know this entity has a Presence");
2972 };
2973
2974 if let PresenceKind::Character(id) = presence.kind {
2975 server.state.ecs().write_resource::<IdMaps>().remove_entity(
2976 Some(entity),
2977 None,
2978 Some(id),
2979 None,
2980 );
2981
2982 presence.kind = PresenceKind::Possessor;
2983 }
2984 }
2985
2986 set_or_remove_component(server, entity, Some(inventory), None)?;
2988 set_or_remove_component(server, entity, Some(stats), None)?;
2989 set_or_remove_component(server, entity, Some(skill_set), None)?;
2990 set_or_remove_component(server, entity, Some(poise), None)?;
2991 set_or_remove_component(server, entity, health, None)?;
2992 set_or_remove_component(server, entity, Some(comp::Energy::new(body)), None)?;
2993 set_or_remove_component(server, entity, Some(body), None)?;
2994 set_or_remove_component(server, entity, Some(body.mass()), None)?;
2995 set_or_remove_component(server, entity, Some(body.density()), None)?;
2996 set_or_remove_component(server, entity, Some(body.collider()), None)?;
2997 set_or_remove_component(server, entity, Some(scale), None)?;
2998 set_or_remove_component(server, entity, death_effects, None)?;
2999 set_or_remove_component(server, entity, rider_effects, None)?;
3000 set_or_remove_component(
3002 server,
3003 entity,
3004 Some(if body.is_humanoid() {
3005 comp::ActiveAbilities::default_limited(BASE_ABILITY_LIMIT)
3006 } else {
3007 comp::ActiveAbilities::default()
3008 }),
3009 None,
3010 )?;
3011 set_or_remove_component(server, entity, body.heads().map(Heads::new), None)?;
3012
3013 if !is_player {
3015 set_or_remove_component(
3016 server,
3017 entity,
3018 agent,
3019 Some(|new_agent, old_agent| {
3020 if let Some(old_agent) = old_agent {
3021 new_agent.target = old_agent.target;
3022 new_agent.awareness = old_agent.awareness;
3023 }
3024 }),
3025 )?;
3026 set_or_remove_component(
3027 server,
3028 entity,
3029 loot.to_items().map(comp::ItemDrops),
3030 None,
3031 )?;
3032 }
3033
3034 let position = server.state.read_component_copied::<comp::Pos>(entity);
3036 if let Some(pos) = position {
3037 for (pet, offset) in pets
3038 .into_iter()
3039 .map(|(pet, offset)| (pet.to_npc_builder().0, offset))
3040 {
3041 let pet_entity = handle_create_npc(server, CreateNpcEvent {
3042 pos: comp::Pos(pos.0 + offset),
3043 ori: comp::Ori::from_unnormalized_vec(offset).unwrap_or_default(),
3044 npc: pet,
3045 });
3046
3047 tame_pet(server.state.ecs(), pet_entity, entity);
3048 }
3049
3050 if let Some(rider) = rider {
3052 let rider_entity = handle_create_npc(server, CreateNpcEvent {
3053 pos,
3054 ori: comp::Ori::default(),
3055 npc: rider.to_npc_builder().0,
3056 });
3057 let uids = server.state().ecs().read_storage::<Uid>();
3058 let link = Mounting {
3059 mount: *uids
3060 .get(entity)
3061 .expect("We just got the position of this entity"),
3062 rider: *uids.get(rider_entity).expect("We just created this entity"),
3063 };
3064 drop(uids);
3065 server
3066 .state
3067 .link(link)
3068 .expect("We know these entities exist");
3069 }
3070 }
3071 },
3072 SpawnEntityData::Special(_, _) => {
3073 return Err(TransformEntityError::UnexpectedSpecialEntity);
3074 },
3075 }
3076
3077 Ok(())
3078}
3079
3080pub fn handle_start_interaction(
3081 server: &mut Server,
3082 StartInteractionEvent(interaction): StartInteractionEvent,
3083) {
3084 let i = interaction.interactor;
3085 let t = interaction.target;
3086 if let Err(e) = server.state.link(interaction) {
3087 debug!("Error trying to start interaction between {i:?} and {t:?}: {e:?}");
3088 }
3089}