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