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