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