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