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