1use crate::{
2 CharacterUpdater, Server, StateExt, client::Client, events::player::handle_exit_ingame,
3 persistence::PersistedComponents, pet::tame_pet, presence::RepositionToFreeSpace, sys,
4};
5use common::{
6 CachedSpatialGrid,
7 combat::AttackTarget,
8 comp::{
9 self, Alignment, BehaviorCapability, Body, Group, Inventory, ItemDrops, LightEmitter,
10 Object, Ori, Pos, ThrownItem, TradingBehavior, Vel, WaypointArea,
11 aura::{Aura, AuraKind, AuraTarget},
12 body,
13 buff::{BuffCategory, BuffData, BuffKind, BuffSource},
14 item::MaterialStatManifest,
15 ship::figuredata::VOXEL_COLLIDER_MANIFEST,
16 tool::AbilityMap,
17 },
18 consts::MAX_CAMPFIRE_RANGE,
19 event::{
20 ArcingEvent, CreateAuraEntityEvent, CreateItemDropEvent, CreateNpcEvent,
21 CreateNpcGroupEvent, CreateObjectEvent, CreatePoolEvent, CreateShipEvent,
22 CreateSpecialEntityEvent, EventBus, InitializeCharacterEvent, InitializeSpectatorEvent,
23 NpcBuilder, ShockwaveEvent, ShootEvent, SummonBeamPillarsEvent, ThrowEvent,
24 UpdateCharacterDataEvent,
25 },
26 generation::SpecialEntity,
27 mounting::{Mounting, Volume, VolumeMounting, VolumePos},
28 outcome::Outcome,
29 resources::{Secs, Time},
30 terrain::TerrainGrid,
31 uid::{IdMaps, Uid},
32 util::Dir,
33 vol::IntoFullVolIterator,
34};
35use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
36use specs::{Builder, Entity as EcsEntity, WorldExt};
37use std::time::Duration;
38use vek::{Rgb, Vec3};
39
40use super::group_manip::update_map_markers;
41
42pub fn handle_initialize_character(server: &mut Server, ev: InitializeCharacterEvent) {
43 let updater = server.state.ecs().fetch::<CharacterUpdater>();
44 let pending_database_action = updater.has_pending_database_action(ev.character_id);
45 drop(updater);
46
47 if !pending_database_action {
48 let clamped_vds = ev
49 .requested_view_distances
50 .clamp(server.settings().max_view_distance);
51 server
52 .state
53 .initialize_character_data(ev.entity, ev.character_id, clamped_vds);
54 if ev.requested_view_distances.terrain != clamped_vds.terrain {
56 server.notify_client(
57 ev.entity,
58 ServerGeneral::SetViewDistance(clamped_vds.terrain),
59 );
60 }
61 } else {
62 handle_exit_ingame(server, ev.entity, true);
66 }
67}
68
69pub fn handle_initialize_spectator(server: &mut Server, ev: InitializeSpectatorEvent) {
70 let clamped_vds = ev.1.clamp(server.settings().max_view_distance);
71 server.state.initialize_spectator_data(ev.0, clamped_vds);
72 if ev.1.terrain != clamped_vds.terrain {
74 server.notify_client(ev.0, ServerGeneral::SetViewDistance(clamped_vds.terrain));
75 }
76 sys::subscription::initialize_region_subscription(server.state.ecs(), ev.0);
77}
78
79pub fn handle_loaded_character_data(server: &mut Server, ev: UpdateCharacterDataEvent) {
80 let loaded_components = PersistedComponents {
81 body: ev.components.0,
82 hardcore: ev.components.1,
83 stats: ev.components.2,
84 skill_set: ev.components.3,
85 inventory: ev.components.4,
86 waypoint: ev.components.5,
87 pets: ev.components.6,
88 active_abilities: ev.components.7,
89 map_marker: ev.components.8,
90 };
91 if let Some(marker) = loaded_components.map_marker {
92 server.notify_client(
93 ev.entity,
94 ServerGeneral::MapMarker(comp::MapMarkerUpdate::Owned(comp::MapMarkerChange::Update(
95 marker.0,
96 ))),
97 );
98 }
99
100 let result_msg = if let Err(err) = server
101 .state
102 .update_character_data(ev.entity, loaded_components)
103 {
104 handle_exit_ingame(server, ev.entity, false); ServerGeneral::CharacterDataLoadResult(Err(err))
106 } else {
107 sys::subscription::initialize_region_subscription(server.state.ecs(), ev.entity);
108 ServerGeneral::CharacterDataLoadResult(Ok(ev.metadata))
110 };
111 server.notify_client(ev.entity, result_msg);
112}
113
114pub fn handle_create_npc(server: &mut Server, ev: CreateNpcEvent) -> EcsEntity {
115 let NpcBuilder {
117 stats,
118 skill_set,
119 health,
120 poise,
121 inventory,
122 body,
123 mut agent,
124 alignment,
125 scale,
126 anchor,
127 loot,
128 pets,
129 rtsim_entity,
130 projectile,
131 heads,
132 death_effects,
133 rider_effects,
134 rider,
135 } = ev.npc;
136 let entity = server
137 .state
138 .create_npc(
139 ev.pos, ev.ori, stats, skill_set, health, poise, inventory, body, scale,
140 )
141 .maybe_with(heads)
142 .maybe_with(death_effects)
143 .maybe_with(rider_effects);
144
145 if let Some(agent) = &mut agent
146 && let Alignment::Owned(_) = &alignment
147 {
148 agent.behavior.allow(BehaviorCapability::TRADE);
149 agent.behavior.trading_behavior = TradingBehavior::AcceptFood;
150 }
151
152 let entity = entity.with(alignment);
153
154 let entity = if let Some(agent) = agent {
155 entity.with(agent)
156 } else {
157 entity
158 };
159
160 let entity = if let Some(drop_items) = loot.to_items() {
161 entity.with(ItemDrops(drop_items))
162 } else {
163 entity
164 };
165
166 let entity = if let Some(home_chunk) = anchor {
167 entity.with(home_chunk)
168 } else {
169 entity
170 };
171
172 let entity = if let Some(rtsim_entity) = rtsim_entity {
174 entity.with(rtsim_entity).with(RepositionToFreeSpace {
175 needs_ground: false,
176 modify_waypoints: true,
177 })
178 } else {
179 entity
180 };
181
182 let entity = if let Some(projectile) = projectile {
183 entity.with(projectile)
184 } else {
185 entity
186 };
187
188 let new_entity = entity.build();
189
190 if let Some(rtsim_entity) = rtsim_entity {
191 server
192 .state()
193 .ecs()
194 .write_resource::<IdMaps>()
195 .add_rtsim(rtsim_entity, new_entity);
196 }
197
198 if let comp::Alignment::Owned(owner_uid) = alignment {
200 let state = server.state();
201 let uids = state.ecs().read_storage::<Uid>();
202 let clients = state.ecs().read_storage::<Client>();
203 let mut group_manager = state.ecs().write_resource::<comp::group::GroupManager>();
204 if let Some(owner) = state.ecs().entity_from_uid(owner_uid) {
205 let map_markers = state.ecs().read_storage::<comp::MapMarker>();
206 group_manager.new_pet(
207 new_entity,
208 owner,
209 &mut state.ecs().write_storage(),
210 &state.ecs().entities(),
211 &state.ecs().read_storage(),
212 &uids,
213 &mut |entity, group_change| {
214 group_change
215 .try_map_ref(|e| uids.get(*e).copied())
216 .zip(clients.get(entity))
217 .map(|(g, c)| {
218 update_map_markers(&map_markers, &uids, c, &group_change);
221 c.send_fallible(ServerGeneral::GroupUpdate(g));
222 });
223 },
224 );
225 }
226 } else if let Some(group) = alignment.group() {
227 let _ = server.state.ecs().write_storage().insert(new_entity, group);
228 }
229
230 if let Some(rider) = rider {
231 let rider_entity = handle_create_npc(server, CreateNpcEvent {
232 pos: ev.pos,
233 ori: Ori::default(),
234 npc: *rider,
235 });
236 let uids = server.state().ecs().read_storage::<Uid>();
237 let link = Mounting {
238 mount: *uids.get(new_entity).expect("We just created this entity"),
239 rider: *uids.get(rider_entity).expect("We just created this entity"),
240 };
241 drop(uids);
242 server
243 .state
244 .link(link)
245 .expect("We just created these entities");
246 }
247
248 for (pet, offset) in pets {
249 let pet_entity = handle_create_npc(server, CreateNpcEvent {
250 pos: comp::Pos(ev.pos.0 + offset),
251 ori: Ori::from_unnormalized_vec(offset).unwrap_or_default(),
252 npc: pet,
253 });
254
255 tame_pet(server.state.ecs(), pet_entity, new_entity);
256 }
257
258 new_entity
259}
260
261pub fn handle_create_npc_group(server: &mut Server, ev: CreateNpcGroupEvent) {
262 let mut npcs = ev
263 .npcs
264 .into_iter()
265 .map(|ev| handle_create_npc(server, ev))
266 .collect::<Vec<_>>()
267 .into_iter();
268 let Some(leader) = npcs.next() else {
269 return;
270 };
271
272 let ecs = server.state().ecs();
273 let entities = ecs.entities();
274 let uids = ecs.read_storage::<Uid>();
275 let alignments = ecs.read_storage::<Alignment>();
276 let mut groups = ecs.write_storage::<Group>();
277 let mut group_manager = ecs.write_resource::<comp::group::GroupManager>();
278
279 if groups.get(leader).is_some() {
280 return;
281 }
282
283 for entity in npcs {
284 group_manager.add_group_member(
285 leader,
286 entity,
287 &entities,
288 &mut groups,
289 &alignments,
290 &uids,
291 |_, _| {},
292 );
293 }
294}
295
296pub fn handle_create_ship(server: &mut Server, ev: CreateShipEvent) {
297 let collider = ev.ship.make_collider();
298 let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
299
300 let (mut steering, mut _seats) = {
303 let mut steering = Vec::new();
304 let mut seats = Vec::new();
305
306 for (pos, block) in collider
307 .get_vol(&voxel_colliders_manifest)
308 .iter()
309 .flat_map(|voxel_collider| voxel_collider.volume().full_vol_iter())
310 {
311 match (block.is_controller(), block.is_mountable()) {
312 (true, true) => steering.push((pos, *block)),
313 (false, true) => seats.push((pos, *block)),
314 _ => {},
315 }
316 }
317 (steering.into_iter(), seats.into_iter())
318 };
319
320 let mut entity = server
321 .state
322 .create_ship(ev.pos, ev.ori, ev.ship, |_| collider);
323 if let Some(rtsim_vehicle) = ev.rtsim_entity {
333 entity = entity.with(rtsim_vehicle);
334 }
335 let entity = entity.build();
336
337 if let Some(rtsim_entity) = ev.rtsim_entity {
338 server
339 .state()
340 .ecs()
341 .write_resource::<IdMaps>()
342 .add_rtsim(rtsim_entity, entity);
343 }
344
345 if let Some(driver) = ev.driver {
346 let npc_entity = handle_create_npc(server, CreateNpcEvent {
347 pos: ev.pos,
348 ori: ev.ori,
349 npc: driver,
350 });
351
352 let uids = server.state.ecs().read_storage::<Uid>();
353 let (rider_uid, mount_uid) = uids
354 .get(npc_entity)
355 .copied()
356 .zip(uids.get(entity).copied())
357 .expect("Couldn't get Uid from newly created ship and npc");
358 drop(uids);
359
360 if let Some((steering_pos, steering_block)) = steering.next() {
361 server
362 .state
363 .link(VolumeMounting {
364 pos: VolumePos {
365 kind: Volume::Entity(mount_uid),
366 pos: steering_pos,
367 },
368 block: steering_block,
369 rider: rider_uid,
370 })
371 .expect("Failed to link driver to ship");
372 } else {
373 server
374 .state
375 .link(Mounting {
376 mount: mount_uid,
377 rider: rider_uid,
378 })
379 .expect("Failed to link driver to ship");
380 }
381 }
382
383 }
415
416pub fn handle_shoot(server: &mut Server, ev: ShootEvent) {
417 let state = server.state_mut();
418
419 let pos = ev.pos.0;
420
421 let vel = *ev.dir * ev.speed + ev.source_vel.map_or(Vec3::zero(), |v| v.0);
422
423 state
425 .ecs()
426 .read_resource::<EventBus<Outcome>>()
427 .emit_now(Outcome::ProjectileShot {
428 pos,
429 body: ev.body,
430 vel,
431 });
432
433 state
434 .create_projectile(Pos(pos), Vel(vel), ev.body, ev.projectile)
435 .maybe_with(ev.light)
436 .maybe_with(ev.object)
437 .maybe_with(ev.marker)
438 .build();
439}
440
441pub fn handle_throw(server: &mut Server, ev: ThrowEvent) {
442 let state = server.state_mut();
443
444 let thrown_item = state
445 .ecs()
446 .write_storage::<Inventory>()
447 .get_mut(ev.entity)
448 .and_then(|mut inv| {
449 if let Some(thrown_item) = inv.equipped(ev.equip_slot) {
450 let ability_map = state.ecs().read_resource::<AbilityMap>();
451 let msm = state.ecs().read_resource::<MaterialStatManifest>();
452 let time = state.ecs().read_resource::<Time>();
453
454 if let Some(inv_slot) = inv.get_slot_of_item(thrown_item)
457 && thrown_item.is_stackable()
458 {
459 inv.take(inv_slot, &ability_map, &msm)
460 } else {
461 inv.replace_loadout_item(ev.equip_slot, None, *time)
462 }
463 } else {
464 None
465 }
466 })
467 .map(|mut thrown_item| {
468 thrown_item.put_in_world();
469 ThrownItem(thrown_item)
470 });
471
472 if let Some(thrown_item) = thrown_item {
473 let body = Body::Item(body::item::Body::from(&thrown_item));
474
475 let pos = ev.pos.0;
476
477 let vel = *ev.dir * ev.speed
478 + state
479 .ecs()
480 .read_storage::<Vel>()
481 .get(ev.entity)
482 .map_or(Vec3::zero(), |v| v.0);
483
484 state
486 .ecs()
487 .read_resource::<EventBus<Outcome>>()
488 .emit_now(Outcome::ProjectileShot { pos, body, vel });
489
490 state
491 .create_projectile(Pos(pos), Vel(vel), body, ev.projectile)
492 .with(thrown_item)
493 .maybe_with(ev.light)
494 .maybe_with(ev.object)
495 .build();
496 }
497}
498
499pub fn handle_shockwave(server: &mut Server, ev: ShockwaveEvent) {
500 let state = server.state_mut();
501 state
502 .create_shockwave(ev.properties, ev.pos, ev.ori)
503 .build();
504}
505
506pub fn handle_arc(server: &mut Server, ev: ArcingEvent) {
507 let state = server.state_mut();
508 state
509 .create_arcing(ev.arc, ev.target, ev.owner, ev.pos)
510 .build();
511}
512
513pub fn handle_create_pool(server: &mut Server, ev: CreatePoolEvent) {
514 let state = server.state_mut();
515 let flat_ori =
518 comp::Ori::from_unnormalized_vec(ev.ori.look_vec().xy().with_z(0.0)).unwrap_or_default();
519 let pos = comp::Pos(ev.pos.0 + vek::Vec3::unit_z() * 0.05);
520 state
521 .create_pool(ev.properties, ev.owner, pos, flat_ori)
522 .build();
523}
524
525pub fn handle_create_special_entity(server: &mut Server, ev: CreateSpecialEntityEvent) {
526 let time = server.state.get_time();
527
528 match ev.entity {
529 SpecialEntity::Waypoint => {
530 server
531 .state
532 .create_object(Pos(ev.pos), comp::object::Body::CampfireLit)
533 .with(LightEmitter {
534 col: Rgb::new(1.0, 0.3, 0.1),
535 strength: 5.0,
536 flicker: 1.0,
537 animated: true,
538 dir: None,
539 })
540 .with(WaypointArea::default())
541 .with(comp::Immovable)
542 .with(comp::EnteredAuras::default())
543 .with(comp::Auras::new(vec![
544 Aura::new(
545 AuraKind::Buff {
546 kind: BuffKind::RestingHeal,
547 data: BuffData::new(0.02, Some(Secs(1.0))),
548 category: BuffCategory::Natural,
549 source: BuffSource::World,
550 },
551 MAX_CAMPFIRE_RANGE,
552 None,
553 AuraTarget::All,
554 Time(time),
555 None,
556 ),
557 Aura::new(
558 AuraKind::Buff {
559 kind: BuffKind::Burning,
560 data: BuffData::new(2.0, Some(Secs(10.0))),
561 category: BuffCategory::Natural,
562 source: BuffSource::World,
563 },
564 0.7,
565 None,
566 AuraTarget::All,
567 Time(time),
568 None,
569 ),
570 ]))
571 .build();
572 },
573 SpecialEntity::Teleporter(portal) => {
574 server
575 .state
576 .create_teleporter(comp::Pos(ev.pos), portal)
577 .build();
578 },
579 SpecialEntity::ArenaTotem { range } => {
580 server
581 .state
582 .create_object(Pos(ev.pos), comp::object::Body::GnarlingTotemGreen)
583 .with(comp::Immovable)
584 .with(comp::EnteredAuras::default())
585 .with(comp::Auras::new(vec![
586 Aura::new(
587 AuraKind::FriendlyFire,
588 range,
589 None,
590 AuraTarget::All,
591 Time(time),
592 None,
593 ),
594 Aura::new(
595 AuraKind::ForcePvP,
596 range,
597 None,
598 AuraTarget::All,
599 Time(time),
600 None,
601 ),
602 ]))
603 .build();
604 },
605 }
606}
607
608pub fn handle_create_item_drop(server: &mut Server, ev: CreateItemDropEvent) {
609 server
610 .state
611 .create_item_drop(ev.pos, ev.ori, ev.vel, ev.item, ev.loot_owner);
612}
613
614pub fn handle_create_object(
615 server: &mut Server,
616 CreateObjectEvent {
617 pos,
618 vel,
619 body,
620 object,
621 item,
622 light_emitter,
623 stats,
624 }: CreateObjectEvent,
625) {
626 match object {
627 Some(
628 object @ Object::Crux {
629 owner,
630 scale,
631 range,
632 strength,
633 duration,
634 ..
635 },
636 ) => {
637 let state = server.state_mut();
638 let time = *state.ecs().read_resource::<Time>();
639
640 let mut health = comp::Health::new(Body::Object(body));
643 health.set_fraction(0.99996);
644
645 let crux = state
646 .create_object(pos, body)
647 .with(object)
648 .maybe_with(light_emitter)
649 .maybe_with(stats)
650 .with(comp::Scale(scale))
651 .with(health)
652 .with(comp::Energy::new(Body::Object(body)))
653 .with(comp::Poise::new(Body::Object(body)))
654 .with(comp::SkillSet::default())
655 .with(comp::Buffs::default())
656 .with(comp::Inventory::with_empty())
657 .with(comp::Immovable)
658 .with(comp::Auras::new(vec![Aura::new(
659 AuraKind::Buff {
660 kind: BuffKind::Heatstroke,
661 data: BuffData {
662 strength,
663 duration: Some(duration),
664 delay: None,
665 secondary_duration: None,
666 misc_data: None,
667 },
668 category: BuffCategory::Magical,
669 source: BuffSource::World,
670 },
671 range,
672 None,
673 AuraTarget::NotGroupOf(owner),
674 time,
675 None,
676 )]))
677 .with(comp::projectile::ProjectileHitEntities::default())
678 .build();
679
680 if let Some(owner) = state.ecs().read_resource::<IdMaps>().uid_entity(owner) {
681 let mut group_manager = state.ecs().write_resource::<comp::group::GroupManager>();
682 group_manager.new_pet(
683 crux,
684 owner,
685 &mut state.ecs().write_storage(),
686 &state.ecs().entities(),
687 &state.ecs().read_storage(),
688 &state.ecs().read_storage::<Uid>(),
689 &mut |_, _| {},
690 );
691 }
692 },
693 _ => {
694 server
695 .state
696 .create_object(pos, body)
697 .with(vel)
698 .maybe_with(object)
699 .maybe_with(item)
700 .maybe_with(light_emitter)
701 .maybe_with(stats)
702 .build();
703 },
704 }
705}
706
707pub fn handle_create_aura_entity(server: &mut Server, ev: CreateAuraEntityEvent) {
708 let time = *server.state.ecs().read_resource::<Time>();
709 let mut entity = server
710 .state
711 .ecs_mut()
712 .create_entity_synced()
713 .with(ev.pos)
714 .with(comp::Vel(Vec3::zero()))
715 .with(comp::Ori::default())
716 .with(ev.auras)
717 .with(comp::Alignment::Owned(ev.creator_uid));
718
719 if let Some(dur) = ev.duration {
721 let object = comp::Object::DeleteAfter {
722 spawned_at: time,
723 timeout: Duration::from_secs_f64(dur.0),
724 };
725 entity = entity.with(object);
726 }
727 entity.build();
728}
729
730pub fn handle_summon_beam_pillars(server: &mut Server, ev: SummonBeamPillarsEvent) {
731 let ecs = server.state().ecs();
732
733 let Some((&Pos(center), &summoner_alignment)) = ecs
734 .read_storage::<Pos>()
735 .get(ev.summoner)
736 .zip(ecs.read_storage::<Alignment>().get(ev.summoner))
737 else {
738 return;
739 };
740
741 let summon_pillar = |server: &mut Server, pos: Vec3<f32>, spawned_at| {
742 let integer_pos = pos.map(|x| x as i32);
743 let ground_height = server
744 .state()
745 .ecs()
746 .read_resource::<TerrainGrid>()
747 .find_ground(integer_pos)
748 .z as f32;
749
750 if (ground_height - pos.z).abs() <= 16.0 {
755 let ecs = server.state_mut().ecs_mut();
756
757 let pillar = ecs
758 .create_entity_synced()
759 .with(Pos(pos.with_z(ground_height)))
760 .with(Ori::from(Dir::up()))
761 .with(comp::Object::BeamPillar {
762 spawned_at,
763 buildup_duration: ev.buildup_duration,
764 attack_duration: ev.attack_duration,
765 beam_duration: ev.beam_duration,
766 radius: ev.radius,
767 height: ev.height,
768 damage: ev.damage,
769 damage_effect: ev.damage_effect.clone(),
770 dodgeable: ev.dodgeable,
771 tick_rate: ev.tick_rate,
772 specifier: ev.specifier,
773 indicator_specifier: ev.indicator_specifier,
774 })
775 .build();
776
777 let mut group_manager = ecs.write_resource::<comp::group::GroupManager>();
778 group_manager.new_pet(
779 pillar,
780 ev.summoner,
781 &mut ecs.write_storage(),
782 &ecs.entities(),
783 &ecs.read_storage(),
784 &ecs.read_storage::<Uid>(),
785 &mut |_, _| {},
786 );
787 }
788 };
789
790 let spawned_at = *ecs.read_resource::<Time>();
791 match ev.target {
792 AttackTarget::AllInRange(range) => {
793 let enemy_positions = ecs
794 .read_resource::<CachedSpatialGrid>()
795 .0
796 .in_circle_aabr(center.xy(), range)
797 .filter(|entity| {
798 ecs.read_storage::<Alignment>()
799 .get(*entity)
800 .is_some_and(|alignment| summoner_alignment.hostile_towards(*alignment))
801 })
802 .filter(|entity| {
803 ecs.read_storage::<comp::Group>()
804 .get(ev.summoner)
805 .is_none_or(|summoner_group| {
806 ecs.read_storage::<comp::Group>()
807 .get(*entity)
808 .is_none_or(|entity_group| summoner_group != entity_group)
809 })
810 })
811 .filter_map(|nearby_enemy| {
812 ecs.read_storage::<Pos>()
813 .get(nearby_enemy)
814 .map(|Pos(pos)| *pos)
815 })
816 .collect::<Vec<_>>();
817
818 for enemy_pos in enemy_positions.into_iter() {
819 summon_pillar(server, enemy_pos, spawned_at);
820 }
821 },
822 AttackTarget::Pos(pos) => {
823 summon_pillar(server, pos, spawned_at);
824 },
825 AttackTarget::Entity(entity) => {
826 let pos = ecs.read_storage::<Pos>().get(entity).map(|pos| pos.0);
827 if let Some(pos) = pos {
828 summon_pillar(server, pos, spawned_at);
829 }
830 },
831 }
832}