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 clients
215 .get(entity)
216 .and_then(|c| {
217 group_change
218 .try_map_ref(|e| uids.get(*e).copied())
219 .map(|g| (g, c))
220 })
221 .map(|(g, c)| {
222 update_map_markers(&map_markers, &uids, c, &group_change);
225 c.send_fallible(ServerGeneral::GroupUpdate(g));
226 });
227 },
228 );
229 }
230 } else if let Some(group) = alignment.group() {
231 let _ = server.state.ecs().write_storage().insert(new_entity, group);
232 }
233
234 if let Some(rider) = rider {
235 let rider_entity = handle_create_npc(server, CreateNpcEvent {
236 pos: ev.pos,
237 ori: Ori::default(),
238 npc: *rider,
239 });
240 let uids = server.state().ecs().read_storage::<Uid>();
241 let link = Mounting {
242 mount: *uids.get(new_entity).expect("We just created this entity"),
243 rider: *uids.get(rider_entity).expect("We just created this entity"),
244 };
245 drop(uids);
246 server
247 .state
248 .link(link)
249 .expect("We just created these entities");
250 }
251
252 for (pet, offset) in pets {
253 let pet_entity = handle_create_npc(server, CreateNpcEvent {
254 pos: comp::Pos(ev.pos.0 + offset),
255 ori: Ori::from_unnormalized_vec(offset).unwrap_or_default(),
256 npc: pet,
257 });
258
259 tame_pet(server.state.ecs(), pet_entity, new_entity);
260 }
261
262 new_entity
263}
264
265pub fn handle_create_npc_group(server: &mut Server, ev: CreateNpcGroupEvent) {
266 let mut npcs = ev
267 .npcs
268 .into_iter()
269 .map(|ev| handle_create_npc(server, ev))
270 .collect::<Vec<_>>()
271 .into_iter();
272 let Some(leader) = npcs.next() else {
273 return;
274 };
275
276 let ecs = server.state().ecs();
277 let entities = ecs.entities();
278 let uids = ecs.read_storage::<Uid>();
279 let alignments = ecs.read_storage::<Alignment>();
280 let mut groups = ecs.write_storage::<Group>();
281 let mut group_manager = ecs.write_resource::<comp::group::GroupManager>();
282
283 if groups.get(leader).is_some() {
284 return;
285 }
286
287 for entity in npcs {
288 group_manager.add_group_member(
289 leader,
290 entity,
291 &entities,
292 &mut groups,
293 &alignments,
294 &uids,
295 |_, _| {},
296 );
297 }
298}
299
300pub fn handle_create_ship(server: &mut Server, ev: CreateShipEvent) {
301 let collider = ev.ship.make_collider();
302 let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
303
304 let (mut steering, mut _seats) = {
307 let mut steering = Vec::new();
308 let mut seats = Vec::new();
309
310 for (pos, block) in collider
311 .get_vol(&voxel_colliders_manifest)
312 .iter()
313 .flat_map(|voxel_collider| voxel_collider.volume().full_vol_iter())
314 {
315 match (block.is_controller(), block.is_mountable()) {
316 (true, true) => steering.push((pos, *block)),
317 (false, true) => seats.push((pos, *block)),
318 _ => {},
319 }
320 }
321 (steering.into_iter(), seats.into_iter())
322 };
323
324 let mut entity = server
325 .state
326 .create_ship(ev.pos, ev.ori, ev.ship, |_| collider);
327 if let Some(rtsim_vehicle) = ev.rtsim_entity {
337 entity = entity.with(rtsim_vehicle);
338 }
339 let entity = entity.build();
340
341 if let Some(rtsim_entity) = ev.rtsim_entity {
342 server
343 .state()
344 .ecs()
345 .write_resource::<IdMaps>()
346 .add_rtsim(rtsim_entity, entity);
347 }
348
349 if let Some(driver) = ev.driver {
350 let npc_entity = handle_create_npc(server, CreateNpcEvent {
351 pos: ev.pos,
352 ori: ev.ori,
353 npc: driver,
354 });
355
356 let uids = server.state.ecs().read_storage::<Uid>();
357 let (rider_uid, mount_uid) = uids
358 .get(npc_entity)
359 .copied()
360 .zip(uids.get(entity).copied())
361 .expect("Couldn't get Uid from newly created ship and npc");
362 drop(uids);
363
364 if let Some((steering_pos, steering_block)) = steering.next() {
365 server
366 .state
367 .link(VolumeMounting {
368 pos: VolumePos {
369 kind: Volume::Entity(mount_uid),
370 pos: steering_pos,
371 },
372 block: steering_block,
373 rider: rider_uid,
374 })
375 .expect("Failed to link driver to ship");
376 } else {
377 server
378 .state
379 .link(Mounting {
380 mount: mount_uid,
381 rider: rider_uid,
382 })
383 .expect("Failed to link driver to ship");
384 }
385 }
386
387 }
419
420pub fn handle_shoot(server: &mut Server, ev: ShootEvent) {
421 let state = server.state_mut();
422
423 let pos = ev.pos.0;
424
425 let vel = *ev.dir * ev.speed + ev.source_vel.map_or(Vec3::zero(), |v| v.0);
426
427 state
429 .ecs()
430 .read_resource::<EventBus<Outcome>>()
431 .emit_now(Outcome::ProjectileShot {
432 pos,
433 body: ev.body,
434 vel,
435 });
436
437 state
438 .create_projectile(Pos(pos), Vel(vel), ev.body, ev.projectile)
439 .maybe_with(ev.light)
440 .maybe_with(ev.object)
441 .maybe_with(ev.marker)
442 .build();
443}
444
445pub fn handle_throw(server: &mut Server, ev: ThrowEvent) {
446 let state = server.state_mut();
447
448 let thrown_item = state
449 .ecs()
450 .write_storage::<Inventory>()
451 .get_mut(ev.entity)
452 .and_then(|mut inv| {
453 if let Some(thrown_item) = inv.equipped(ev.equip_slot) {
454 let ability_map = state.ecs().read_resource::<AbilityMap>();
455 let msm = state.ecs().read_resource::<MaterialStatManifest>();
456 let time = state.ecs().read_resource::<Time>();
457
458 if let Some(inv_slot) = inv.get_slot_of_item(thrown_item)
461 && thrown_item.is_stackable()
462 {
463 inv.take(inv_slot, &ability_map, &msm)
464 } else {
465 inv.replace_loadout_item(ev.equip_slot, None, *time)
466 }
467 } else {
468 None
469 }
470 })
471 .map(|mut thrown_item| {
472 thrown_item.put_in_world();
473 ThrownItem(thrown_item)
474 });
475
476 if let Some(thrown_item) = thrown_item {
477 let body = Body::Item(body::item::Body::from(&thrown_item));
478
479 let pos = ev.pos.0;
480
481 let vel = *ev.dir * ev.speed
482 + state
483 .ecs()
484 .read_storage::<Vel>()
485 .get(ev.entity)
486 .map_or(Vec3::zero(), |v| v.0);
487
488 state
490 .ecs()
491 .read_resource::<EventBus<Outcome>>()
492 .emit_now(Outcome::ProjectileShot { pos, body, vel });
493
494 state
495 .create_projectile(Pos(pos), Vel(vel), body, ev.projectile)
496 .with(thrown_item)
497 .maybe_with(ev.light)
498 .maybe_with(ev.object)
499 .build();
500 }
501}
502
503pub fn handle_shockwave(server: &mut Server, ev: ShockwaveEvent) {
504 let state = server.state_mut();
505 state
506 .create_shockwave(ev.properties, ev.pos, ev.ori)
507 .build();
508}
509
510pub fn handle_arc(server: &mut Server, ev: ArcingEvent) {
511 let state = server.state_mut();
512 state
513 .create_arcing(ev.arc, ev.target, ev.owner, ev.pos)
514 .build();
515}
516
517pub fn handle_create_pool(server: &mut Server, ev: CreatePoolEvent) {
518 let state = server.state_mut();
519 let flat_ori =
522 comp::Ori::from_unnormalized_vec(ev.ori.look_vec().xy().with_z(0.0)).unwrap_or_default();
523 let pos = comp::Pos(ev.pos.0 + vek::Vec3::unit_z() * 0.05);
524 state
525 .create_pool(ev.properties, ev.owner, pos, flat_ori)
526 .build();
527}
528
529pub fn handle_create_special_entity(server: &mut Server, ev: CreateSpecialEntityEvent) {
530 let time = server.state.get_time();
531
532 match ev.entity {
533 SpecialEntity::Waypoint => {
534 server
535 .state
536 .create_object(Pos(ev.pos), comp::object::Body::CampfireLit)
537 .with(LightEmitter {
538 col: Rgb::new(1.0, 0.3, 0.1),
539 strength: 5.0,
540 flicker: 1.0,
541 animated: true,
542 dir: None,
543 })
544 .with(WaypointArea::default())
545 .with(comp::Immovable)
546 .with(comp::EnteredAuras::default())
547 .with(comp::Auras::new(vec![
548 Aura::new(
549 AuraKind::Buff {
550 kind: BuffKind::RestingHeal,
551 data: BuffData::new(0.02, Some(Secs(1.0))),
552 category: BuffCategory::Natural,
553 source: BuffSource::World,
554 },
555 MAX_CAMPFIRE_RANGE,
556 None,
557 AuraTarget::All,
558 Time(time),
559 None,
560 ),
561 Aura::new(
562 AuraKind::Buff {
563 kind: BuffKind::Burning,
564 data: BuffData::new(2.0, Some(Secs(10.0))),
565 category: BuffCategory::Natural,
566 source: BuffSource::World,
567 },
568 0.7,
569 None,
570 AuraTarget::All,
571 Time(time),
572 None,
573 ),
574 ]))
575 .build();
576 },
577 SpecialEntity::Teleporter(portal) => {
578 server
579 .state
580 .create_teleporter(comp::Pos(ev.pos), portal)
581 .build();
582 },
583 SpecialEntity::ArenaTotem { range } => {
584 server
585 .state
586 .create_object(Pos(ev.pos), comp::object::Body::GnarlingTotemGreen)
587 .with(comp::Immovable)
588 .with(comp::EnteredAuras::default())
589 .with(comp::Auras::new(vec![
590 Aura::new(
591 AuraKind::FriendlyFire,
592 range,
593 None,
594 AuraTarget::All,
595 Time(time),
596 None,
597 ),
598 Aura::new(
599 AuraKind::ForcePvP,
600 range,
601 None,
602 AuraTarget::All,
603 Time(time),
604 None,
605 ),
606 ]))
607 .build();
608 },
609 }
610}
611
612pub fn handle_create_item_drop(server: &mut Server, ev: CreateItemDropEvent) {
613 server
614 .state
615 .create_item_drop(ev.pos, ev.ori, ev.vel, ev.item, ev.loot_owner);
616}
617
618pub fn handle_create_object(
619 server: &mut Server,
620 CreateObjectEvent {
621 pos,
622 vel,
623 body,
624 object,
625 item,
626 light_emitter,
627 stats,
628 }: CreateObjectEvent,
629) {
630 match object {
631 Some(
632 object @ Object::Crux {
633 owner,
634 scale,
635 range,
636 strength,
637 duration,
638 ..
639 },
640 ) => {
641 let state = server.state_mut();
642 let time = *state.ecs().read_resource::<Time>();
643
644 let mut health = comp::Health::new(Body::Object(body));
647 health.set_fraction(0.99996);
648
649 let crux = state
650 .create_object(pos, body)
651 .with(object)
652 .maybe_with(light_emitter)
653 .maybe_with(stats)
654 .with(comp::Scale(scale))
655 .with(health)
656 .with(comp::Energy::new(Body::Object(body)))
657 .with(comp::Poise::new(Body::Object(body)))
658 .with(comp::SkillSet::default())
659 .with(comp::Buffs::default())
660 .with(comp::Inventory::with_empty())
661 .with(comp::Immovable)
662 .with(comp::Auras::new(vec![Aura::new(
663 AuraKind::Buff {
664 kind: BuffKind::Heatstroke,
665 data: BuffData {
666 strength,
667 duration: Some(duration),
668 delay: None,
669 secondary_duration: None,
670 misc_data: None,
671 },
672 category: BuffCategory::Magical,
673 source: BuffSource::World,
674 },
675 range,
676 None,
677 AuraTarget::NotGroupOf(owner),
678 time,
679 None,
680 )]))
681 .with(comp::projectile::ProjectileHitEntities::default())
682 .build();
683
684 if let Some(owner) = state.ecs().read_resource::<IdMaps>().uid_entity(owner) {
685 let mut group_manager = state.ecs().write_resource::<comp::group::GroupManager>();
686 group_manager.new_pet(
687 crux,
688 owner,
689 &mut state.ecs().write_storage(),
690 &state.ecs().entities(),
691 &state.ecs().read_storage(),
692 &state.ecs().read_storage::<Uid>(),
693 &mut |_, _| {},
694 );
695 }
696 },
697 _ => {
698 server
699 .state
700 .create_object(pos, body)
701 .with(vel)
702 .maybe_with(object)
703 .maybe_with(item)
704 .maybe_with(light_emitter)
705 .maybe_with(stats)
706 .build();
707 },
708 }
709}
710
711pub fn handle_create_aura_entity(server: &mut Server, ev: CreateAuraEntityEvent) {
712 let time = *server.state.ecs().read_resource::<Time>();
713 let mut entity = server
714 .state
715 .ecs_mut()
716 .create_entity_synced()
717 .with(ev.pos)
718 .with(comp::Vel(Vec3::zero()))
719 .with(comp::Ori::default())
720 .with(ev.auras)
721 .with(comp::Alignment::Owned(ev.creator_uid));
722
723 if let Some(dur) = ev.duration {
725 let object = comp::Object::DeleteAfter {
726 spawned_at: time,
727 timeout: Duration::from_secs_f64(dur.0),
728 };
729 entity = entity.with(object);
730 }
731 entity.build();
732}
733
734pub fn handle_summon_beam_pillars(server: &mut Server, ev: SummonBeamPillarsEvent) {
735 let ecs = server.state().ecs();
736
737 let Some((&Pos(center), &summoner_alignment)) = ecs
738 .read_storage::<Pos>()
739 .get(ev.summoner)
740 .zip(ecs.read_storage::<Alignment>().get(ev.summoner))
741 else {
742 return;
743 };
744
745 let summon_pillar = |server: &mut Server, pos: Vec3<f32>, spawned_at| {
746 let integer_pos = pos.map(|x| x as i32);
747 let ground_height = server
748 .state()
749 .ecs()
750 .read_resource::<TerrainGrid>()
751 .find_ground(integer_pos)
752 .z as f32;
753
754 if (ground_height - pos.z).abs() <= 16.0 {
759 let ecs = server.state_mut().ecs_mut();
760
761 let pillar = ecs
762 .create_entity_synced()
763 .with(Pos(pos.with_z(ground_height)))
764 .with(Ori::from(Dir::up()))
765 .with(comp::Object::BeamPillar {
766 spawned_at,
767 buildup_duration: ev.buildup_duration,
768 attack_duration: ev.attack_duration,
769 beam_duration: ev.beam_duration,
770 radius: ev.radius,
771 height: ev.height,
772 damage: ev.damage,
773 damage_effect: ev.damage_effect.clone(),
774 dodgeable: ev.dodgeable,
775 tick_rate: ev.tick_rate,
776 specifier: ev.specifier,
777 indicator_specifier: ev.indicator_specifier,
778 })
779 .build();
780
781 let mut group_manager = ecs.write_resource::<comp::group::GroupManager>();
782 group_manager.new_pet(
783 pillar,
784 ev.summoner,
785 &mut ecs.write_storage(),
786 &ecs.entities(),
787 &ecs.read_storage(),
788 &ecs.read_storage::<Uid>(),
789 &mut |_, _| {},
790 );
791 }
792 };
793
794 let spawned_at = *ecs.read_resource::<Time>();
795 match ev.target {
796 AttackTarget::AllInRange(range) => {
797 let enemy_positions = ecs
798 .read_resource::<CachedSpatialGrid>()
799 .0
800 .in_circle_aabr(center.xy(), range)
801 .filter(|entity| {
802 ecs.read_storage::<Alignment>()
803 .get(*entity)
804 .is_some_and(|alignment| summoner_alignment.hostile_towards(*alignment))
805 })
806 .filter(|entity| {
807 ecs.read_storage::<comp::Group>()
808 .get(ev.summoner)
809 .is_none_or(|summoner_group| {
810 ecs.read_storage::<comp::Group>()
811 .get(*entity)
812 .is_none_or(|entity_group| summoner_group != entity_group)
813 })
814 })
815 .filter_map(|nearby_enemy| {
816 ecs.read_storage::<Pos>()
817 .get(nearby_enemy)
818 .map(|Pos(pos)| *pos)
819 })
820 .collect::<Vec<_>>();
821
822 for enemy_pos in enemy_positions.into_iter() {
823 summon_pillar(server, enemy_pos, spawned_at);
824 }
825 },
826 AttackTarget::Pos(pos) => {
827 summon_pillar(server, pos, spawned_at);
828 },
829 AttackTarget::Entity(entity) => {
830 let pos = ecs.read_storage::<Pos>().get(entity).map(|pos| pos.0);
831 if let Some(pos) = pos {
832 summon_pillar(server, pos, spawned_at);
833 }
834 },
835 }
836}