1#[cfg(feature = "worldgen")]
2use crate::rtsim::RtSim;
3use crate::{
4 BattleModeBuffer, SpawnPoint,
5 automod::AutoMod,
6 chat::ChatExporter,
7 client::Client,
8 events::{self, shared::update_map_markers},
9 persistence::PersistedComponents,
10 pet::restore_pet,
11 presence::RepositionToFreeSpace,
12 settings::Settings,
13 sys::sentinel::DeletedEntities,
14 wiring,
15};
16use common::{
17 LoadoutBuilder, ViewDistances,
18 character::CharacterId,
19 comp::{
20 self, BASE_ABILITY_LIMIT, CapsulePrism, ChatType, Content, Group, Inventory, LootOwner,
21 Object, Player, Poise, Presence, PresenceKind, item::ItemKind, misc::PortalData, object,
22 },
23 interaction::Interaction,
24 link::{Is, Link, LinkHandle},
25 mounting::{Mounting, Rider, VolumeMounting, VolumeRider},
26 resources::{Secs, Time},
27 rtsim::{Actor, RtSimEntity},
28 tether::Tethered,
29 uid::{IdMaps, Uid},
30 util::Dir,
31};
32#[cfg(feature = "worldgen")]
33use common::{calendar::Calendar, resources::TimeOfDay, slowjob::SlowJobPool};
34use common_net::{
35 msg::{CharacterInfo, PlayerListUpdate, ServerGeneral},
36 sync::WorldSyncExt,
37};
38use common_state::State;
39use specs::{
40 Builder, Entity as EcsEntity, EntityBuilder as EcsEntityBuilder, Join, WorldExt, WriteStorage,
41 storage::{GenericReadStorage, GenericWriteStorage},
42};
43use std::time::{Duration, Instant};
44use tracing::{error, trace, warn};
45use vek::*;
46
47pub trait StateExt {
48 fn create_npc(
50 &mut self,
51 pos: comp::Pos,
52 ori: comp::Ori,
53 stats: comp::Stats,
54 skill_set: comp::SkillSet,
55 health: Option<comp::Health>,
56 poise: Poise,
57 inventory: Inventory,
58 body: comp::Body,
59 scale: comp::Scale,
60 ) -> EcsEntityBuilder<'_>;
61 fn create_empty(&mut self, pos: comp::Pos) -> EcsEntityBuilder<'_>;
63 fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body)
65 -> EcsEntityBuilder<'_>;
66 fn create_item_drop(
69 &mut self,
70 pos: comp::Pos,
71 ori: comp::Ori,
72 vel: comp::Vel,
73 item: comp::PickupItem,
74 loot_owner: Option<LootOwner>,
75 ) -> Option<EcsEntity>;
76 fn create_ship<F: FnOnce(comp::ship::Body) -> comp::Collider>(
77 &mut self,
78 pos: comp::Pos,
79 ori: comp::Ori,
80 ship: comp::ship::Body,
81 make_collider: F,
82 ) -> EcsEntityBuilder<'_>;
83 fn create_projectile(
85 &mut self,
86 pos: comp::Pos,
87 vel: comp::Vel,
88 body: comp::Body,
89 projectile: comp::Projectile,
90 ) -> EcsEntityBuilder<'_>;
91 fn create_shockwave(
93 &mut self,
94 properties: comp::shockwave::Properties,
95 pos: comp::Pos,
96 ori: comp::Ori,
97 ) -> EcsEntityBuilder<'_>;
98 fn create_arcing(
99 &mut self,
100 arc: comp::ArcProperties,
101 target: Uid,
102 owner: Option<Uid>,
103 pos: comp::Pos,
104 ) -> EcsEntityBuilder<'_>;
105 fn create_pool(
106 &mut self,
107 properties: comp::pool::PoolProperties,
108 owner: Option<Uid>,
109 pos: comp::Pos,
110 ori: comp::Ori,
111 ) -> EcsEntityBuilder<'_>;
112 fn create_safezone(&mut self, range: Option<f32>, pos: comp::Pos) -> EcsEntityBuilder<'_>;
114 fn create_wiring(
115 &mut self,
116 pos: comp::Pos,
117 object: comp::object::Body,
118 wiring_element: wiring::WiringElement,
119 ) -> EcsEntityBuilder<'_>;
120 #[cfg(feature = "worldgen")]
125 fn create_persister(
126 &mut self,
127 pos: comp::Pos,
128 view_distance: u32,
129 world: &std::sync::Arc<world::World>,
130 index: &world::IndexOwned,
131 ) -> EcsEntityBuilder<'_>;
132 fn create_teleporter(&mut self, pos: comp::Pos, portal: PortalData) -> EcsEntityBuilder<'_>;
136 fn initialize_character_data(
138 &mut self,
139 entity: EcsEntity,
140 character_id: CharacterId,
141 view_distances: ViewDistances,
142 );
143 fn initialize_spectator_data(&mut self, entity: EcsEntity, view_distances: ViewDistances);
145 fn update_character_data(
148 &mut self,
149 entity: EcsEntity,
150 components: PersistedComponents,
151 ) -> Result<(), String>;
152 fn validate_chat_msg(
154 &self,
155 player: EcsEntity,
156 chat_type: &comp::ChatType<comp::Group>,
157 msg: &Content,
158 from_client: bool,
163 ) -> bool;
164 fn send_chat(&self, msg: comp::UnresolvedChatMsg, from_client: bool);
165 fn notify_players(&self, msg: ServerGeneral);
166 fn notify_in_game_clients(&self, msg: ServerGeneral);
167 fn link<L: Link>(&mut self, link: L) -> Result<(), L::Error>;
170 fn maintain_links(&mut self);
172 fn delete_entity_recorded(
174 &mut self,
175 entity: EcsEntity,
176 ) -> Result<(), specs::error::WrongGeneration>;
177 fn entity_as_actor(&self, entity: EcsEntity) -> Option<Actor>;
179 fn position_mut<T>(
186 &mut self,
187 entity: EcsEntity,
188 dismount_volume: bool,
189 f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
190 ) -> Result<T, Content>;
191
192 fn position_mut_reposition<T>(
204 &mut self,
205 entity: EcsEntity,
206 dismount_volume: bool,
207 f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
208 needs_ground: bool,
209 modify_waypoints: bool,
210 ) -> Result<T, Content>;
211}
212
213impl StateExt for State {
214 fn create_npc(
215 &mut self,
216 pos: comp::Pos,
217 ori: comp::Ori,
218 stats: comp::Stats,
219 skill_set: comp::SkillSet,
220 health: Option<comp::Health>,
221 poise: Poise,
222 inventory: Inventory,
223 body: comp::Body,
224 scale: comp::Scale,
225 ) -> EcsEntityBuilder<'_> {
226 self.ecs_mut()
227 .create_entity_synced()
228 .with(pos)
229 .with(comp::Vel(Vec3::zero()))
230 .with(ori)
231 .with(comp::Mass(body.mass().0 * scale.0.powi(3)))
232 .with(body.density())
233 .with(body.collider())
234 .with(scale)
235 .with(comp::Controller::default())
236 .with(body)
237 .with(comp::Energy::new(body))
238 .with(stats)
239 .with(if body.is_humanoid() {
240 comp::ActiveAbilities::default_limited(BASE_ABILITY_LIMIT)
241 } else {
242 comp::ActiveAbilities::default()
243 })
244 .with(skill_set)
245 .maybe_with(health)
246 .with(poise)
247 .with(comp::Alignment::Npc)
248 .with(comp::CharacterState::default())
249 .with(comp::CharacterActivity::default())
250 .with(inventory)
251 .with(comp::Buffs::default())
252 .with(comp::Combo::default())
253 .with(comp::Auras::default())
254 .with(comp::EnteredAuras::default())
255 .with(comp::Stance::default())
256 .with(comp::projectile::ProjectileHitEntities::default())
257 .maybe_with(body.heads().map(comp::body::parts::Heads::new))
258 }
259
260 fn create_empty(&mut self, pos: comp::Pos) -> EcsEntityBuilder<'_> {
261 self.ecs_mut()
262 .create_entity_synced()
263 .with(pos)
264 .with(comp::Vel(Vec3::zero()))
265 .with(comp::Ori::default())
266 }
267
268 fn create_object(
269 &mut self,
270 pos: comp::Pos,
271 object: comp::object::Body,
272 ) -> EcsEntityBuilder<'_> {
273 let body = comp::Body::Object(object);
274 self.create_empty(pos)
275 .with(body.mass())
276 .with(body.density())
277 .with(body.collider())
278 .with(body)
279 }
280
281 fn create_item_drop(
282 &mut self,
283 pos: comp::Pos,
284 ori: comp::Ori,
285 vel: comp::Vel,
286 world_item: comp::PickupItem,
287 loot_owner: Option<LootOwner>,
288 ) -> Option<EcsEntity> {
289 {
291 use crate::sys::item::get_nearby_mergeable_items;
292
293 let positions = self.ecs().read_storage::<comp::Pos>();
294 let loot_owners = self.ecs().read_storage::<LootOwner>();
295 let mut items = self.ecs().write_storage::<comp::PickupItem>();
296 let entities = self.ecs().entities();
297 let spatial_grid = self.ecs().read_resource();
298
299 let nearby_items = get_nearby_mergeable_items(
300 &world_item,
301 &pos,
302 loot_owner.as_ref(),
303 (&entities, &items, &positions, &loot_owners, &spatial_grid),
304 );
305
306 if let Some((mergeable_item, _)) =
308 nearby_items.min_by_key(|(_, dist)| (dist * 1000.0) as i32)
309 {
310 items
311 .get_mut(mergeable_item)
312 .expect("we know that the item exists")
313 .try_merge(world_item)
314 .expect("`try_merge` should succeed because `can_merge` returned `true`");
315 return None;
316 }
317 }
318
319 let spawned_at = *self.ecs().read_resource::<Time>();
320
321 let item_body = comp::body::item::Body::from(world_item.item());
322 let body = comp::Body::Item(item_body);
323 let light_emitter = match &*world_item.item().kind() {
324 ItemKind::Lantern(lantern) => Some(comp::LightEmitter {
325 col: lantern.color(),
326 strength: lantern.strength(),
327 flicker: lantern.flicker(),
328 animated: true,
329 dir: lantern.dir,
330 }),
331 _ => None,
332 };
333 Some(
334 self.ecs_mut()
335 .create_entity_synced()
336 .with(world_item)
337 .with(pos)
338 .with(ori)
339 .with(vel)
340 .with(item_body.orientation(&mut rand::rng()))
341 .with(item_body.mass())
342 .with(item_body.density())
343 .with(body.collider())
344 .with(body)
345 .with(Object::DeleteAfter {
346 spawned_at,
347 timeout: Duration::from_secs(300),
349 })
350 .maybe_with(loot_owner)
351 .maybe_with(light_emitter)
352 .build(),
353 )
354 }
355
356 fn create_ship<F: FnOnce(comp::ship::Body) -> comp::Collider>(
357 &mut self,
358 pos: comp::Pos,
359 ori: comp::Ori,
360 ship: comp::ship::Body,
361 make_collider: F,
362 ) -> EcsEntityBuilder<'_> {
363 let body = comp::Body::Ship(ship);
364
365 self
366 .ecs_mut()
367 .create_entity_synced()
368 .with(pos)
369 .with(comp::Vel(Vec3::zero()))
370 .with(ori)
371 .with(body.mass())
372 .with(body.density())
373 .with(make_collider(ship))
374 .with(body)
375 .with(comp::Controller::default())
376 .with(Inventory::with_empty())
377 .with(comp::CharacterState::default())
378 .with(comp::CharacterActivity::default())
379 .with(comp::Energy::new(ship.into()))
383 .with(comp::Stats::new({
384 Content::Plain("Airship".to_string())
388 }, body))
389 .with(comp::SkillSet::default())
390 .with(comp::ActiveAbilities::default())
391 .with(comp::Combo::default())
392 }
393
394 fn create_projectile(
395 &mut self,
396 pos: comp::Pos,
397 vel: comp::Vel,
398 body: comp::Body,
399 projectile: comp::Projectile,
400 ) -> EcsEntityBuilder<'_> {
401 let mut projectile_base = self
402 .ecs_mut()
403 .create_entity_synced()
404 .with(pos)
405 .with(vel)
406 .with(comp::Ori::from_unnormalized_vec(vel.0).unwrap_or_default())
407 .with(body.mass())
408 .with(body.density());
409
410 if projectile.is_sticky {
411 projectile_base = projectile_base.with(comp::Sticky);
412 }
413 if projectile.is_point {
414 projectile_base = projectile_base.with(comp::Collider::Point);
415 } else {
416 projectile_base = projectile_base.with(body.collider());
417 }
418
419 projectile_base.with(projectile).with(body)
420 }
421
422 fn create_shockwave(
423 &mut self,
424 properties: comp::shockwave::Properties,
425 pos: comp::Pos,
426 ori: comp::Ori,
427 ) -> EcsEntityBuilder<'_> {
428 self.ecs_mut()
429 .create_entity_synced()
430 .with(pos)
431 .with(ori)
432 .with(comp::Shockwave {
433 properties,
434 creation: None,
435 })
436 .with(comp::ShockwaveHitEntities {
437 hit_entities: Vec::<Uid>::new(),
438 })
439 }
440
441 fn create_arcing(
442 &mut self,
443 arc: comp::ArcProperties,
444 target: Uid,
445 owner: Option<Uid>,
446 pos: comp::Pos,
447 ) -> EcsEntityBuilder<'_> {
448 let time = self.get_time();
449
450 self.ecs_mut()
451 .create_entity_synced()
452 .with(pos)
453 .with(comp::Arcing {
454 properties: arc,
455 last_arc_time: Time(time),
456 hit_entities: vec![target],
457 owner,
458 })
459 }
460
461 fn create_pool(
462 &mut self,
463 properties: comp::pool::PoolProperties,
464 owner: Option<Uid>,
465 pos: comp::Pos,
466 ori: comp::Ori,
467 ) -> EcsEntityBuilder<'_> {
468 let time = self.get_time();
469 let tick_dur = properties.tick_dur;
470
471 let body = comp::Body::Object(comp::object::Body::NapalmPool);
472 self.ecs_mut()
473 .create_entity_synced()
474 .with(pos)
475 .with(ori)
476 .with(comp::Vel(Vec3::zero()))
477 .with(body.mass())
478 .with(body.density())
479 .with(body.collider())
480 .with(body)
481 .with(comp::LightEmitter {
482 col: Rgb::new(1.0, 0.35, 0.05),
483 strength: 3.5,
484 flicker: 1.0,
485 animated: true,
486 dir: None,
487 })
488 .with(comp::pool::Pool {
489 properties,
490 start_time: Time(time),
491 last_tick: Time(time - tick_dur.0),
492 owner,
493 })
494 }
495
496 fn create_safezone(&mut self, range: Option<f32>, pos: comp::Pos) -> EcsEntityBuilder<'_> {
497 use comp::{
498 aura::{Aura, AuraKind, AuraTarget, Auras},
499 buff::{BuffCategory, BuffData, BuffKind, BuffSource},
500 };
501 let time = self.get_time();
502 self.ecs_mut()
504 .create_entity_synced()
505 .with(pos)
506 .with(Auras::new(vec![Aura::new(
507 AuraKind::Buff {
508 kind: BuffKind::Invulnerability,
509 data: BuffData::new(1.0, Some(Secs(1.0))),
510 category: BuffCategory::Natural,
511 source: BuffSource::World,
512 },
513 range.unwrap_or(100.0),
514 None,
515 AuraTarget::All,
516 Time(time),
517 None,
518 )]))
519 }
520
521 fn create_wiring(
522 &mut self,
523 pos: comp::Pos,
524 object: comp::object::Body,
525 wiring_element: wiring::WiringElement,
526 ) -> EcsEntityBuilder<'_> {
527 self.ecs_mut()
528 .create_entity_synced()
529 .with(pos)
530 .with(comp::Vel(Vec3::zero()))
531 .with(comp::Ori::default())
532 .with({
533 let body: comp::Body = object.into();
534 body.collider()
535 })
536 .with(comp::Body::Object(object))
537 .with(comp::Mass(100.0))
538 .with(wiring_element)
540 .with(comp::LightEmitter {
541 col: Rgb::new(0.0, 0.0, 0.0),
542 strength: 2.0,
543 flicker: 1.0,
544 animated: true,
545 dir: None,
546 })
547 }
548
549 #[cfg(feature = "worldgen")]
554 fn create_persister(
555 &mut self,
556 pos: comp::Pos,
557 view_distance: u32,
558 world: &std::sync::Arc<world::World>,
559 index: &world::IndexOwned,
560 ) -> EcsEntityBuilder<'_> {
561 use common::{terrain::TerrainChunkSize, vol::RectVolSize};
562 use std::sync::Arc;
563 {
565 let ecs = self.ecs();
566 let slow_jobs = ecs.write_resource::<SlowJobPool>();
567 let rtsim = ecs.read_resource::<RtSim>();
568 let mut chunk_generator =
569 ecs.write_resource::<crate::chunk_generator::ChunkGenerator>();
570 let chunk_pos = self.terrain().pos_key(pos.0.map(|e| e as i32));
571 (-(view_distance as i32)..view_distance as i32 + 1)
572 .flat_map(|x| {
573 (-(view_distance as i32)..view_distance as i32 + 1).map(move |y| Vec2::new(x, y))
574 })
575 .map(|offset| offset + chunk_pos)
576 .filter(|chunk_key| {
579 pos.0.xy().map(|e| e as f64).distance(
580 chunk_key.map(|e| e as f64 + 0.5) * TerrainChunkSize::RECT_SIZE.map(|e| e as f64),
581 ) < (view_distance as f64 - 1.0 + 2.5 * 2.0_f64.sqrt())
582 * TerrainChunkSize::RECT_SIZE.x as f64
583 })
584 .for_each(|chunk_key| {
585 {
586 let time = (*ecs.read_resource::<TimeOfDay>(), (*ecs.read_resource::<Calendar>()).clone());
587 chunk_generator.generate_chunk(None, chunk_key, &slow_jobs, Arc::clone(world), &rtsim, index.clone(), time);
588 }
589 });
590 }
591
592 self.ecs_mut()
593 .create_entity_synced()
594 .with(pos)
595 .with(Presence::new(
596 ViewDistances {
597 terrain: view_distance,
598 entity: view_distance,
599 },
600 PresenceKind::Spectator,
601 ))
602 }
603
604 fn create_teleporter(&mut self, pos: comp::Pos, portal: PortalData) -> EcsEntityBuilder<'_> {
605 self.create_object(pos, object::Body::Portal)
606 .with(comp::Immovable)
607 .with(comp::Object::from(portal))
608 }
609
610 fn initialize_character_data(
611 &mut self,
612 entity: EcsEntity,
613 character_id: CharacterId,
614 view_distances: ViewDistances,
615 ) {
616 let spawn_point = self.ecs().read_resource::<SpawnPoint>().0;
617
618 if let Some(player_uid) = self.read_component_copied::<Uid>(entity) {
619 self.write_component_ignore_entity_dead(entity, comp::Controller::default());
624 self.write_component_ignore_entity_dead(entity, comp::Pos(spawn_point));
625 self.write_component_ignore_entity_dead(entity, comp::Vel(Vec3::zero()));
626 self.write_component_ignore_entity_dead(entity, comp::Ori::default());
627 self.write_component_ignore_entity_dead(
628 entity,
629 comp::Collider::CapsulePrism(CapsulePrism {
630 p0: Vec2::zero(),
631 p1: Vec2::zero(),
632 radius: 0.4,
633 z_min: 0.0,
634 z_max: 1.75,
635 }),
636 );
637 self.write_component_ignore_entity_dead(entity, comp::CharacterState::default());
638 self.write_component_ignore_entity_dead(entity, comp::CharacterActivity::default());
639 self.write_component_ignore_entity_dead(entity, comp::Alignment::Owned(player_uid));
640 self.write_component_ignore_entity_dead(entity, comp::Buffs::default());
641 self.write_component_ignore_entity_dead(entity, comp::Auras::default());
642 self.write_component_ignore_entity_dead(entity, comp::EnteredAuras::default());
643 self.write_component_ignore_entity_dead(entity, comp::Combo::default());
644 self.write_component_ignore_entity_dead(entity, comp::Stance::default());
645 self.write_component_ignore_entity_dead(
646 entity,
647 comp::projectile::ProjectileHitEntities::default(),
648 );
649
650 self.write_component_ignore_entity_dead(entity, comp::ForceUpdate::forced());
652
653 self.write_component_ignore_entity_dead(
654 entity,
655 Presence::new(view_distances, PresenceKind::LoadingCharacter(character_id)),
656 );
657
658 if let Some(client) = self.ecs().read_storage::<Client>().get(entity) {
660 client.send_fallible(ServerGeneral::CharacterSuccess);
661 }
662 }
663 }
664
665 fn initialize_spectator_data(&mut self, entity: EcsEntity, view_distances: ViewDistances) {
666 let spawn_point = self.ecs().read_resource::<SpawnPoint>().0;
667
668 if self.read_component_copied::<Uid>(entity).is_some() {
669 self.write_component_ignore_entity_dead(entity, comp::Pos(spawn_point));
674
675 self.write_component_ignore_entity_dead(entity, comp::ForceUpdate::forced());
677
678 self.write_component_ignore_entity_dead(
679 entity,
680 Presence::new(view_distances, PresenceKind::Spectator),
681 );
682
683 if let Some(client) = self.ecs().read_storage::<Client>().get(entity) {
685 client.send_fallible(ServerGeneral::SpectatorSuccess(spawn_point));
686 }
687 }
688 }
689
690 fn update_character_data(
692 &mut self,
693 entity: EcsEntity,
694 components: PersistedComponents,
695 ) -> Result<(), String> {
696 let PersistedComponents {
697 body,
698 hardcore,
699 stats,
700 skill_set,
701 inventory,
702 waypoint,
703 pets,
704 active_abilities,
705 map_marker,
706 } = components;
707
708 if let Some(player_uid) = self.read_component_copied::<Uid>(entity) {
709 let result =
710 if let Some(presence) = self.ecs().write_storage::<Presence>().get_mut(entity) {
711 if let PresenceKind::LoadingCharacter(id) = presence.kind {
712 presence.kind = PresenceKind::Character(id);
713 self.ecs()
714 .write_resource::<IdMaps>()
715 .add_character(id, entity);
716 Ok(())
717 } else {
718 Err("PresenceKind is not LoadingCharacter")
719 }
720 } else {
721 Err("Presence component missing")
722 };
723 if let Err(err) = result {
724 let err = format!("Unexpected state when applying loaded character info: {err}");
725 error!("{err}");
726 return Err(err);
728 }
729
730 let name = stats.name.clone();
731 let gender = stats.original_body.humanoid_gender();
733
734 self.write_component_ignore_entity_dead(entity, body.collider());
739 self.write_component_ignore_entity_dead(entity, body);
740 self.write_component_ignore_entity_dead(entity, body.mass());
741 self.write_component_ignore_entity_dead(entity, body.density());
742 self.write_component_ignore_entity_dead(entity, comp::Health::new(body));
743 self.write_component_ignore_entity_dead(entity, comp::Energy::new(body));
744 self.write_component_ignore_entity_dead(entity, Poise::new(body));
745 self.write_component_ignore_entity_dead(entity, stats);
746 self.write_component_ignore_entity_dead(entity, active_abilities);
747 self.write_component_ignore_entity_dead(entity, skill_set);
748 self.write_component_ignore_entity_dead(entity, inventory);
749 self.write_component_ignore_entity_dead(
750 entity,
751 comp::InventoryUpdateBuffer::new(comp::InventoryUpdateEvent::Init),
752 );
753
754 if let Some(hardcore) = hardcore {
755 self.write_component_ignore_entity_dead(entity, hardcore);
756 }
757
758 if let Some(waypoint) = waypoint {
759 self.write_component_ignore_entity_dead(entity, RepositionToFreeSpace {
760 needs_ground: true,
761 modify_waypoints: true,
762 });
763 self.write_component_ignore_entity_dead(entity, waypoint);
764 self.write_component_ignore_entity_dead(entity, comp::Pos(waypoint.get_pos()));
765 self.write_component_ignore_entity_dead(entity, comp::Vel(Vec3::zero()));
766 self.write_component_ignore_entity_dead(entity, comp::ForceUpdate::forced());
770 }
771
772 if let Some(map_marker) = map_marker {
773 self.write_component_ignore_entity_dead(entity, map_marker);
774 }
775
776 let player_pos = self.ecs().read_storage::<comp::Pos>().get(entity).copied();
777 if let Some(player_pos) = player_pos {
778 trace!(
779 "Loading {} pets for player at pos {:?}",
780 pets.len(),
781 player_pos
782 );
783 let mut rng = rand::rng();
784
785 for (pet, body, stats) in pets {
786 let ori = comp::Ori::from(Dir::random_2d(&mut rng));
787 let pet_entity = self
788 .create_npc(
789 player_pos,
790 ori,
791 stats,
792 comp::SkillSet::default(),
793 Some(comp::Health::new(body)),
794 Poise::new(body),
795 Inventory::with_loadout(
796 LoadoutBuilder::from_default(&body).build(),
797 body,
798 ),
799 body,
800 comp::Scale(1.0),
801 )
802 .with(comp::Vel(Vec3::new(0.0, 0.0, 0.0)))
803 .build();
804
805 restore_pet(self.ecs(), pet_entity, entity, pet);
806 }
807 } else {
808 error!("Player has no pos, cannot load {} pets", pets.len());
809 }
810
811 let settings = self.ecs().read_resource::<Settings>();
812 let mut char_battle_mode = settings.gameplay.battle_mode.default_mode();
813 let presences = self.ecs().read_storage::<Presence>();
814 let presence = presences.get(entity);
815 if let Some(Presence {
816 kind: PresenceKind::Character(char_id),
817 ..
818 }) = presence
819 {
820 let battlemode_buffer = self.ecs().fetch::<BattleModeBuffer>();
821 let mut players = self.ecs().write_storage::<comp::Player>();
822 if let Some(mut player_info) = players.get_mut(entity) {
823 if let Some((mode, change)) = battlemode_buffer.get(char_id) {
824 char_battle_mode = *mode;
825 player_info.last_battlemode_change = Some(*change);
826 } else {
827 player_info.last_battlemode_change = None;
837 }
838
839 player_info.battle_mode = char_battle_mode;
840 }
841 }
842
843 if self
844 .ecs()
845 .read_component::<Client>()
846 .get(entity)
847 .is_some_and(|client| client.client_type.emit_login_events())
848 {
849 self.notify_players(ServerGeneral::PlayerListUpdate(
851 PlayerListUpdate::SelectedCharacter(player_uid, CharacterInfo {
852 name,
853 gender,
854 battle_mode: char_battle_mode,
855 }),
856 ));
857 }
858 }
859
860 Ok(())
861 }
862
863 fn validate_chat_msg(
864 &self,
865 entity: EcsEntity,
866 chat_type: &comp::ChatType<comp::Group>,
867 msg: &Content,
868 from_client: bool,
869 ) -> bool {
870 let mut automod = self.ecs().write_resource::<AutoMod>();
871 let client = self.ecs().read_storage::<Client>();
872 let player = self.ecs().read_storage::<Player>();
873 let Some(client) = client.get(entity) else {
874 return true;
875 };
876 let Some(player) = player.get(entity) else {
877 return true;
878 };
879
880 let Some(msg) = msg.as_plain() else {
884 return !from_client;
885 };
886
887 match automod.validate_chat_msg(
888 player.uuid(),
889 self.ecs()
890 .read_storage::<comp::Admin>()
891 .get(entity)
892 .map(|a| a.0),
893 Instant::now(),
894 chat_type,
895 msg,
896 ) {
897 Ok(note) => {
898 if let Some(note) = note {
899 let _ = client.send(ServerGeneral::server_msg(
900 ChatType::CommandInfo,
901 Content::Plain(format!("{}", note)),
902 ));
903 }
904 true
905 },
906 Err(err) => {
907 let _ = client.send(ServerGeneral::server_msg(
908 ChatType::CommandError,
909 Content::Plain(format!("{}", err)),
910 ));
911 false
912 },
913 }
914 }
915
916 fn send_chat(&self, msg: comp::UnresolvedChatMsg, from_client: bool) {
919 let ecs = self.ecs();
920 let is_within =
921 |target, a: &comp::Pos, b: &comp::Pos| a.0.distance_squared(b.0) < target * target;
922
923 let group_manager = ecs.read_resource::<comp::group::GroupManager>();
924 let chat_exporter = ecs.read_resource::<ChatExporter>();
925
926 let group_info = msg.get_group().and_then(|g| group_manager.group_info(*g));
927
928 if let Some(exported_message) = ChatExporter::generate(&msg, ecs) {
929 chat_exporter.send(exported_message);
930 }
931
932 let resolved_msg = msg
933 .clone()
934 .map_group(|_| group_info.map_or_else(|| "???".to_string(), |i| i.name.clone()));
935
936 let id_maps = ecs.read_resource::<IdMaps>();
937 let entity_from_uid = |uid| id_maps.uid_entity(uid);
938
939 if msg.chat_type.uid().is_none_or(|sender| {
940 entity_from_uid(sender).is_some_and(|e| {
941 self.validate_chat_msg(e, &msg.chat_type, msg.content(), from_client)
942 })
943 }) {
944 match &msg.chat_type {
945 comp::ChatType::Offline(_)
946 | comp::ChatType::CommandInfo
947 | comp::ChatType::CommandError
948 | comp::ChatType::Meta
949 | comp::ChatType::World(_) => {
950 self.notify_players(ServerGeneral::ChatMsg(resolved_msg))
951 },
952 comp::ChatType::Online(u) => {
953 for (client, uid) in
954 (&ecs.read_storage::<Client>(), &ecs.read_storage::<Uid>()).join()
955 {
956 if uid != u {
957 client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
958 }
959 }
960 },
961 &comp::ChatType::Tell(from, to) => {
962 let clients = ecs.read_storage::<Client>();
963 if let Some(from_client) = entity_from_uid(from).and_then(|e| clients.get(e)) {
964 from_client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
965 }
966 if let Some(to_client) = entity_from_uid(to).and_then(|e| clients.get(e)) {
967 to_client.send_fallible(ServerGeneral::ChatMsg(resolved_msg));
968 }
969 },
970 comp::ChatType::Kill(kill_source, uid) => {
971 let clients = ecs.read_storage::<Client>();
972 let clients_count = clients.count();
973 if clients_count
976 > ecs
977 .fetch::<Settings>()
978 .max_player_for_kill_broadcast
979 .unwrap_or_default()
980 {
981 let killed_entity = entity_from_uid(*uid);
983 let groups = ecs.read_storage::<Group>();
984 let killed_group = killed_entity.and_then(|e| groups.get(e));
985 if let Some(g) = &killed_group {
986 send_to_group(g, ecs, &resolved_msg);
987 }
988
989 let positions = ecs.read_storage::<comp::Pos>();
992 if let Some(died_player_pos) = killed_entity.and_then(|e| positions.get(e))
993 {
994 for (ent, client, pos) in
995 (&*ecs.entities(), &clients, &positions).join()
996 {
997 let client_group = groups.get(ent);
998 let is_different_group =
999 !(killed_group == client_group && client_group.is_some());
1000 if is_within(comp::ChatMsg::SAY_DISTANCE, pos, died_player_pos)
1001 && is_different_group
1002 {
1003 client.send_fallible(ServerGeneral::ChatMsg(
1004 resolved_msg.clone(),
1005 ));
1006 }
1007 }
1008 }
1009 } else {
1010 self.notify_players(ServerGeneral::server_msg(
1011 comp::ChatType::Kill(kill_source.clone(), *uid),
1012 msg.into_content(),
1013 ))
1014 }
1015 },
1016 comp::ChatType::Say(uid) => {
1017 let entity_opt = entity_from_uid(*uid);
1018
1019 let positions = ecs.read_storage::<comp::Pos>();
1020 if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
1021 for (client, pos) in (&ecs.read_storage::<Client>(), &positions).join() {
1022 if is_within(comp::ChatMsg::SAY_DISTANCE, pos, speaker_pos) {
1023 client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
1024 }
1025 }
1026 }
1027 },
1028 comp::ChatType::Region(uid) => {
1029 let entity_opt = entity_from_uid(*uid);
1030
1031 let positions = ecs.read_storage::<comp::Pos>();
1032 if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
1033 for (client, pos) in (&ecs.read_storage::<Client>(), &positions).join() {
1034 if is_within(comp::ChatMsg::REGION_DISTANCE, pos, speaker_pos) {
1035 client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
1036 }
1037 }
1038 }
1039 },
1040 comp::ChatType::Npc(uid) => {
1041 let entity_opt = entity_from_uid(*uid);
1042
1043 let positions = ecs.read_storage::<comp::Pos>();
1044 if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
1045 for (client, pos) in (&ecs.read_storage::<Client>(), &positions).join() {
1046 if is_within(comp::ChatMsg::NPC_DISTANCE, pos, speaker_pos) {
1047 client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
1048 }
1049 }
1050 }
1051 },
1052 comp::ChatType::NpcSay(uid) => {
1053 let entity_opt = entity_from_uid(*uid);
1054
1055 let positions = ecs.read_storage::<comp::Pos>();
1056 if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
1057 for (client, pos) in (&ecs.read_storage::<Client>(), &positions).join() {
1058 if is_within(comp::ChatMsg::NPC_SAY_DISTANCE, pos, speaker_pos) {
1059 client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
1060 }
1061 }
1062 }
1063 },
1064 &comp::ChatType::NpcTell(from, to) => {
1065 let clients = ecs.read_storage::<Client>();
1066 if let Some(from_client) = entity_from_uid(from).and_then(|e| clients.get(e)) {
1067 from_client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
1068 }
1069 if let Some(to_client) = entity_from_uid(to).and_then(|e| clients.get(e)) {
1070 to_client.send_fallible(ServerGeneral::ChatMsg(resolved_msg));
1071 }
1072 },
1073 comp::ChatType::FactionMeta(s) | comp::ChatType::Faction(_, s) => {
1074 for (client, faction) in (
1075 &ecs.read_storage::<Client>(),
1076 &ecs.read_storage::<comp::Faction>(),
1077 )
1078 .join()
1079 {
1080 if s == &faction.0 {
1081 client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
1082 }
1083 }
1084 },
1085 comp::ChatType::Group(from, g) => {
1086 if group_info.is_none() {
1087 let reply = comp::ChatType::CommandError
1094 .into_msg(Content::localized("command-message-group-missing"));
1095
1096 let clients = ecs.read_storage::<Client>();
1097 if let Some(client) =
1098 entity_from_uid(*from).and_then(|entity| clients.get(entity))
1099 {
1100 client.send_fallible(ServerGeneral::ChatMsg(reply));
1101 }
1102 } else {
1103 send_to_group(g, ecs, &resolved_msg);
1104 }
1105 },
1106 comp::ChatType::GroupMeta(g) => {
1107 send_to_group(g, ecs, &resolved_msg);
1108 },
1109 }
1110 }
1111 }
1112
1113 fn notify_players(&self, msg: ServerGeneral) {
1115 let mut msg = Some(msg);
1116 let mut lazy_msg = None;
1117 for (client, _) in (
1118 &self.ecs().read_storage::<Client>(),
1119 &self.ecs().read_storage::<comp::Player>(),
1120 )
1121 .join()
1122 {
1123 if let Some(msg) = msg.take() {
1124 lazy_msg = Some(client.prepare(msg));
1125 }
1126 lazy_msg.as_ref().map(|msg| client.send_prepared(msg));
1127 }
1128 }
1129
1130 fn notify_in_game_clients(&self, msg: ServerGeneral) {
1132 let mut msg = Some(msg);
1133 let mut lazy_msg = None;
1134 for (client, _) in (
1135 &mut self.ecs().write_storage::<Client>(),
1136 &self.ecs().read_storage::<Presence>(),
1137 )
1138 .join()
1139 {
1140 if let Some(msg) = msg.take() {
1141 lazy_msg = Some(client.prepare(msg));
1142 }
1143 lazy_msg.as_ref().map(|msg| client.send_prepared(msg));
1144 }
1145 }
1146
1147 fn link<L: Link>(&mut self, link: L) -> Result<(), L::Error> {
1148 let linker = LinkHandle::from_link(link);
1149
1150 L::create(&linker, &mut self.ecs().system_data())?;
1151
1152 self.ecs_mut()
1153 .entry::<Vec<LinkHandle<L>>>()
1154 .or_insert_with(Vec::new)
1155 .push(linker);
1156
1157 Ok(())
1158 }
1159
1160 fn maintain_links(&mut self) {
1161 fn maintain_link<L: Link>(state: &State) {
1162 if let Some(mut handles) = state.ecs().try_fetch_mut::<Vec<LinkHandle<L>>>() {
1163 let mut persist_data = None;
1164 handles.retain(|link| {
1165 if L::persist(
1166 link,
1167 persist_data.get_or_insert_with(|| state.ecs().system_data()),
1168 ) {
1169 true
1170 } else {
1171 persist_data.take();
1174 L::delete(link, &mut state.ecs().system_data());
1175 false
1176 }
1177 });
1178 }
1179 }
1180
1181 maintain_link::<Mounting>(self);
1182 maintain_link::<VolumeMounting>(self);
1183 maintain_link::<Tethered>(self);
1184 maintain_link::<Interaction>(self);
1185 }
1186
1187 fn delete_entity_recorded(
1188 &mut self,
1189 entity: EcsEntity,
1190 ) -> Result<(), specs::error::WrongGeneration> {
1191 {
1197 let clients = self.ecs().read_storage::<Client>();
1198 let uids = self.ecs().read_storage::<Uid>();
1199 let mut group_manager = self.ecs().write_resource::<comp::group::GroupManager>();
1200 let map_markers = self.ecs().read_storage::<comp::MapMarker>();
1201 group_manager.entity_deleted(
1202 entity,
1203 &mut self.ecs().write_storage(),
1204 &self.ecs().read_storage(),
1205 &uids,
1206 &self.ecs().entities(),
1207 &mut |entity, group_change| {
1208 clients
1209 .get(entity)
1210 .and_then(|c| {
1211 group_change
1212 .try_map_ref(|e| uids.get(*e).copied())
1213 .map(|g| (g, c))
1214 })
1215 .map(|(g, c)| {
1216 update_map_markers(&map_markers, &uids, c, &group_change);
1217 c.send_fallible(ServerGeneral::GroupUpdate(g));
1218 });
1219 },
1220 );
1221 }
1222
1223 events::shared::cancel_trades_for(self, entity);
1225
1226 let maybe_uid = self.read_component_copied::<Uid>(entity);
1229 let (maybe_character, sync_me) = self
1230 .read_storage::<Presence>()
1231 .get(entity)
1232 .map(|p| (p.kind.character_id(), p.kind.sync_me()))
1233 .unzip();
1234 let maybe_rtsim = self.read_component_copied::<RtSimEntity>(entity);
1235
1236 self.mut_resource::<IdMaps>().remove_entity(
1237 Some(entity),
1238 maybe_uid,
1239 maybe_character.flatten(),
1240 maybe_rtsim,
1241 );
1242
1243 delete_entity_common(self, entity, maybe_uid, sync_me.unwrap_or(true))
1244 }
1245
1246 fn entity_as_actor(&self, entity: EcsEntity) -> Option<Actor> {
1247 if let Some(rtsim_entity) = self
1248 .ecs()
1249 .read_storage::<RtSimEntity>()
1250 .get(entity)
1251 .copied()
1252 {
1253 Some(Actor::Npc(rtsim_entity))
1254 } else if let Some(PresenceKind::Character(character)) = self
1255 .ecs()
1256 .read_storage::<Presence>()
1257 .get(entity)
1258 .map(|p| p.kind)
1259 {
1260 Some(Actor::Character(character))
1261 } else {
1262 None
1263 }
1264 }
1265
1266 fn position_mut<T>(
1267 &mut self,
1268 entity: EcsEntity,
1269 dismount_volume: bool,
1270 f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
1271 ) -> Result<T, Content> {
1272 let ecs = self.ecs_mut();
1273 position_mut(
1274 entity,
1275 dismount_volume,
1276 f,
1277 &ecs.read_resource(),
1278 &mut ecs.write_storage(),
1279 ecs.write_storage(),
1280 ecs.write_storage(),
1281 ecs.read_storage(),
1282 ecs.read_storage(),
1283 ecs.read_storage(),
1284 )
1285 }
1286
1287 fn position_mut_reposition<T>(
1288 &mut self,
1289 entity: EcsEntity,
1290 dismount_volume: bool,
1291 f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
1292 needs_ground: bool,
1293 modify_waypoints: bool,
1294 ) -> Result<T, Content> {
1295 self.position_mut(entity, dismount_volume, f).inspect(|_| {
1296 self.write_component_ignore_entity_dead(entity, RepositionToFreeSpace {
1297 needs_ground,
1298 modify_waypoints,
1299 });
1300 })
1301 }
1302}
1303
1304pub fn position_mut<T>(
1305 entity: EcsEntity,
1306 dismount_volume: bool,
1307 f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
1308 id_maps: &IdMaps,
1309 is_volume_riders: &mut WriteStorage<Is<VolumeRider>>,
1310 mut positions: impl GenericWriteStorage<Component = comp::Pos>,
1311 mut force_updates: impl GenericWriteStorage<Component = comp::ForceUpdate>,
1312 is_riders: impl GenericReadStorage<Component = Is<Rider>>,
1313 presences: impl GenericReadStorage<Component = Presence>,
1314 clients: impl GenericReadStorage<Component = Client>,
1315) -> Result<T, Content> {
1316 if dismount_volume {
1317 is_volume_riders.remove(entity);
1318 }
1319
1320 let entity = is_riders
1321 .get(entity)
1322 .and_then(|is_rider| id_maps.uid_entity(is_rider.mount))
1323 .map(Ok)
1324 .or_else(|| {
1325 is_volume_riders.get(entity).and_then(|volume_rider| {
1326 Some(match volume_rider.pos.kind {
1327 common::mounting::Volume::Terrain => {
1328 Err(Content::Plain("Tried to move the world.".to_string()))
1329 },
1330 common::mounting::Volume::Entity(uid) => Ok(id_maps.uid_entity(uid)?),
1331 })
1332 })
1333 })
1334 .unwrap_or(Ok(entity))?;
1335
1336 let mut maybe_pos = None;
1337
1338 let res = positions
1339 .get_mut(entity)
1340 .map(|pos| {
1341 let res = f(pos);
1342 maybe_pos = Some(pos.0);
1343 res
1344 })
1345 .ok_or(Content::localized_with_args(
1346 "command-position-unavailable",
1347 [("target", "entity")],
1348 ));
1349
1350 if let Some(pos) = maybe_pos {
1351 if presences
1352 .get(entity)
1353 .map(|presence| presence.kind == PresenceKind::Spectator)
1354 .unwrap_or(false)
1355 {
1356 clients.get(entity).map(|client| {
1357 client.send_fallible(ServerGeneral::SpectatePosition(pos));
1358 });
1359 } else {
1360 force_updates
1361 .get_mut(entity)
1362 .map(|force_update| force_update.update());
1363 }
1364 }
1365
1366 res
1367}
1368
1369fn send_to_group(g: &Group, ecs: &specs::World, msg: &comp::ChatMsg) {
1370 for (client, group) in (&ecs.read_storage::<Client>(), &ecs.read_storage::<Group>()).join() {
1371 if g == group {
1372 client.send_fallible(ServerGeneral::ChatMsg(msg.clone()));
1373 }
1374 }
1375}
1376
1377pub(crate) fn delete_entity_common(
1380 state: &mut State,
1381 entity: EcsEntity,
1382 maybe_uid: Option<Uid>,
1383 sync_me: bool,
1384) -> Result<(), specs::error::WrongGeneration> {
1385 let maybe_pos = state.read_component_copied::<comp::Pos>(entity);
1386
1387 let result = state.ecs_mut().delete_entity(entity);
1389
1390 if result.is_ok() {
1391 let region_map = state.mut_resource::<common::region::RegionMap>();
1392 let region_key = region_map.entity_deleted(entity);
1393 if let Some(uid) = maybe_uid {
1401 if let Some(region_key) = region_key {
1402 state
1403 .mut_resource::<DeletedEntities>()
1404 .record_deleted_entity(uid, region_key);
1405 } else if sync_me && let Some(pos) = maybe_pos {
1408 warn!(
1411 ?uid,
1412 ?pos,
1413 "Failed to find region containing entity during entity deletion, assuming it \
1414 wasn't sent to any clients and so deletion doesn't need to be recorded for \
1415 sync purposes"
1416 );
1417 }
1418 } else {
1419 error!("Deleting entity without Uid component");
1423 }
1424 }
1425 result
1426}