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