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