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