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 if !from_client {
784 warn!(
785 "Non-plain chat message with a player as the sender was filtered out. This \
786 message did not come directly from the client so this is probably a bug in \
787 the server as these message types are not allowed currently."
788 )
789 }
790 return false;
791 };
792
793 match automod.validate_chat_msg(
794 player.uuid(),
795 self.ecs()
796 .read_storage::<comp::Admin>()
797 .get(entity)
798 .map(|a| a.0),
799 Instant::now(),
800 chat_type,
801 msg,
802 ) {
803 Ok(note) => {
804 if let Some(note) = note {
805 let _ = client.send(ServerGeneral::server_msg(
806 ChatType::CommandInfo,
807 Content::Plain(format!("{}", note)),
808 ));
809 }
810 true
811 },
812 Err(err) => {
813 let _ = client.send(ServerGeneral::server_msg(
814 ChatType::CommandError,
815 Content::Plain(format!("{}", err)),
816 ));
817 false
818 },
819 }
820 }
821
822 fn send_chat(&self, msg: comp::UnresolvedChatMsg, from_client: bool) {
825 let ecs = self.ecs();
826 let is_within =
827 |target, a: &comp::Pos, b: &comp::Pos| a.0.distance_squared(b.0) < target * target;
828
829 let group_manager = ecs.read_resource::<comp::group::GroupManager>();
830 let chat_exporter = ecs.read_resource::<ChatExporter>();
831
832 let group_info = msg.get_group().and_then(|g| group_manager.group_info(*g));
833
834 if let Some(exported_message) = ChatExporter::generate(&msg, ecs) {
835 chat_exporter.send(exported_message);
836 }
837
838 let resolved_msg = msg
839 .clone()
840 .map_group(|_| group_info.map_or_else(|| "???".to_string(), |i| i.name.clone()));
841
842 let id_maps = ecs.read_resource::<IdMaps>();
843 let entity_from_uid = |uid| id_maps.uid_entity(uid);
844
845 if msg.chat_type.uid().is_none_or(|sender| {
846 entity_from_uid(sender).is_some_and(|e| {
847 self.validate_chat_msg(e, &msg.chat_type, msg.content(), from_client)
848 })
849 }) {
850 match &msg.chat_type {
851 comp::ChatType::Offline(_)
852 | comp::ChatType::CommandInfo
853 | comp::ChatType::CommandError
854 | comp::ChatType::Meta
855 | comp::ChatType::World(_) => {
856 self.notify_players(ServerGeneral::ChatMsg(resolved_msg))
857 },
858 comp::ChatType::Online(u) => {
859 for (client, uid) in
860 (&ecs.read_storage::<Client>(), &ecs.read_storage::<Uid>()).join()
861 {
862 if uid != u {
863 client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
864 }
865 }
866 },
867 &comp::ChatType::Tell(from, to) => {
868 let clients = ecs.read_storage::<Client>();
869 if let Some(from_client) = entity_from_uid(from).and_then(|e| clients.get(e)) {
870 from_client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
871 }
872 if let Some(to_client) = entity_from_uid(to).and_then(|e| clients.get(e)) {
873 to_client.send_fallible(ServerGeneral::ChatMsg(resolved_msg));
874 }
875 },
876 comp::ChatType::Kill(kill_source, uid) => {
877 let clients = ecs.read_storage::<Client>();
878 let clients_count = clients.count();
879 if clients_count
882 > ecs
883 .fetch::<Settings>()
884 .max_player_for_kill_broadcast
885 .unwrap_or_default()
886 {
887 let killed_entity = entity_from_uid(*uid);
889 let groups = ecs.read_storage::<Group>();
890 let killed_group = killed_entity.and_then(|e| groups.get(e));
891 if let Some(g) = &killed_group {
892 send_to_group(g, ecs, &resolved_msg);
893 }
894
895 let positions = ecs.read_storage::<comp::Pos>();
898 if let Some(died_player_pos) = killed_entity.and_then(|e| positions.get(e))
899 {
900 for (ent, client, pos) in
901 (&*ecs.entities(), &clients, &positions).join()
902 {
903 let client_group = groups.get(ent);
904 let is_different_group =
905 !(killed_group == client_group && client_group.is_some());
906 if is_within(comp::ChatMsg::SAY_DISTANCE, pos, died_player_pos)
907 && is_different_group
908 {
909 client.send_fallible(ServerGeneral::ChatMsg(
910 resolved_msg.clone(),
911 ));
912 }
913 }
914 }
915 } else {
916 self.notify_players(ServerGeneral::server_msg(
917 comp::ChatType::Kill(kill_source.clone(), *uid),
918 msg.into_content(),
919 ))
920 }
921 },
922 comp::ChatType::Say(uid) => {
923 let entity_opt = entity_from_uid(*uid);
924
925 let positions = ecs.read_storage::<comp::Pos>();
926 if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
927 for (client, pos) in (&ecs.read_storage::<Client>(), &positions).join() {
928 if is_within(comp::ChatMsg::SAY_DISTANCE, pos, speaker_pos) {
929 client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
930 }
931 }
932 }
933 },
934 comp::ChatType::Region(uid) => {
935 let entity_opt = entity_from_uid(*uid);
936
937 let positions = ecs.read_storage::<comp::Pos>();
938 if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
939 for (client, pos) in (&ecs.read_storage::<Client>(), &positions).join() {
940 if is_within(comp::ChatMsg::REGION_DISTANCE, pos, speaker_pos) {
941 client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
942 }
943 }
944 }
945 },
946 comp::ChatType::Npc(uid) => {
947 let entity_opt = entity_from_uid(*uid);
948
949 let positions = ecs.read_storage::<comp::Pos>();
950 if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
951 for (client, pos) in (&ecs.read_storage::<Client>(), &positions).join() {
952 if is_within(comp::ChatMsg::NPC_DISTANCE, pos, speaker_pos) {
953 client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
954 }
955 }
956 }
957 },
958 comp::ChatType::NpcSay(uid) => {
959 let entity_opt = entity_from_uid(*uid);
960
961 let positions = ecs.read_storage::<comp::Pos>();
962 if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
963 for (client, pos) in (&ecs.read_storage::<Client>(), &positions).join() {
964 if is_within(comp::ChatMsg::NPC_SAY_DISTANCE, pos, speaker_pos) {
965 client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
966 }
967 }
968 }
969 },
970 &comp::ChatType::NpcTell(from, to) => {
971 let clients = ecs.read_storage::<Client>();
972 if let Some(from_client) = entity_from_uid(from).and_then(|e| clients.get(e)) {
973 from_client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
974 }
975 if let Some(to_client) = entity_from_uid(to).and_then(|e| clients.get(e)) {
976 to_client.send_fallible(ServerGeneral::ChatMsg(resolved_msg));
977 }
978 },
979 comp::ChatType::FactionMeta(s) | comp::ChatType::Faction(_, s) => {
980 for (client, faction) in (
981 &ecs.read_storage::<Client>(),
982 &ecs.read_storage::<comp::Faction>(),
983 )
984 .join()
985 {
986 if s == &faction.0 {
987 client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
988 }
989 }
990 },
991 comp::ChatType::Group(from, g) => {
992 if group_info.is_none() {
993 let reply = comp::ChatType::CommandError
1000 .into_msg(Content::localized("command-message-group-missing"));
1001
1002 let clients = ecs.read_storage::<Client>();
1003 if let Some(client) =
1004 entity_from_uid(*from).and_then(|entity| clients.get(entity))
1005 {
1006 client.send_fallible(ServerGeneral::ChatMsg(reply));
1007 }
1008 } else {
1009 send_to_group(g, ecs, &resolved_msg);
1010 }
1011 },
1012 comp::ChatType::GroupMeta(g) => {
1013 send_to_group(g, ecs, &resolved_msg);
1014 },
1015 }
1016 }
1017 }
1018
1019 fn notify_players(&self, msg: ServerGeneral) {
1021 let mut msg = Some(msg);
1022 let mut lazy_msg = None;
1023 for (client, _) in (
1024 &self.ecs().read_storage::<Client>(),
1025 &self.ecs().read_storage::<comp::Player>(),
1026 )
1027 .join()
1028 {
1029 if let Some(msg) = msg.take() {
1030 lazy_msg = Some(client.prepare(msg));
1031 }
1032 lazy_msg.as_ref().map(|msg| client.send_prepared(msg));
1033 }
1034 }
1035
1036 fn notify_in_game_clients(&self, msg: ServerGeneral) {
1038 let mut msg = Some(msg);
1039 let mut lazy_msg = None;
1040 for (client, _) in (
1041 &mut self.ecs().write_storage::<Client>(),
1042 &self.ecs().read_storage::<Presence>(),
1043 )
1044 .join()
1045 {
1046 if let Some(msg) = msg.take() {
1047 lazy_msg = Some(client.prepare(msg));
1048 }
1049 lazy_msg.as_ref().map(|msg| client.send_prepared(msg));
1050 }
1051 }
1052
1053 fn link<L: Link>(&mut self, link: L) -> Result<(), L::Error> {
1054 let linker = LinkHandle::from_link(link);
1055
1056 L::create(&linker, &mut self.ecs().system_data())?;
1057
1058 self.ecs_mut()
1059 .entry::<Vec<LinkHandle<L>>>()
1060 .or_insert_with(Vec::new)
1061 .push(linker);
1062
1063 Ok(())
1064 }
1065
1066 fn maintain_links(&mut self) {
1067 fn maintain_link<L: Link>(state: &State) {
1068 if let Some(mut handles) = state.ecs().try_fetch_mut::<Vec<LinkHandle<L>>>() {
1069 let mut persist_data = None;
1070 handles.retain(|link| {
1071 if L::persist(
1072 link,
1073 persist_data.get_or_insert_with(|| state.ecs().system_data()),
1074 ) {
1075 true
1076 } else {
1077 persist_data.take();
1080 L::delete(link, &mut state.ecs().system_data());
1081 false
1082 }
1083 });
1084 }
1085 }
1086
1087 maintain_link::<Mounting>(self);
1088 maintain_link::<VolumeMounting>(self);
1089 maintain_link::<Tethered>(self);
1090 maintain_link::<Interaction>(self);
1091 }
1092
1093 fn delete_entity_recorded(
1094 &mut self,
1095 entity: EcsEntity,
1096 ) -> Result<(), specs::error::WrongGeneration> {
1097 {
1103 let clients = self.ecs().read_storage::<Client>();
1104 let uids = self.ecs().read_storage::<Uid>();
1105 let mut group_manager = self.ecs().write_resource::<comp::group::GroupManager>();
1106 let map_markers = self.ecs().read_storage::<comp::MapMarker>();
1107 group_manager.entity_deleted(
1108 entity,
1109 &mut self.ecs().write_storage(),
1110 &self.ecs().read_storage(),
1111 &uids,
1112 &self.ecs().entities(),
1113 &mut |entity, group_change| {
1114 clients
1115 .get(entity)
1116 .and_then(|c| {
1117 group_change
1118 .try_map_ref(|e| uids.get(*e).copied())
1119 .map(|g| (g, c))
1120 })
1121 .map(|(g, c)| {
1122 update_map_markers(&map_markers, &uids, c, &group_change);
1123 c.send_fallible(ServerGeneral::GroupUpdate(g));
1124 });
1125 },
1126 );
1127 }
1128
1129 events::shared::cancel_trades_for(self, entity);
1131
1132 let maybe_uid = self.read_component_copied::<Uid>(entity);
1135 let (maybe_character, sync_me) = self
1136 .read_storage::<Presence>()
1137 .get(entity)
1138 .map(|p| (p.kind.character_id(), p.kind.sync_me()))
1139 .unzip();
1140 let maybe_rtsim = self.read_component_copied::<RtSimEntity>(entity);
1141
1142 self.mut_resource::<IdMaps>().remove_entity(
1143 Some(entity),
1144 maybe_uid,
1145 maybe_character.flatten(),
1146 maybe_rtsim,
1147 );
1148
1149 delete_entity_common(self, entity, maybe_uid, sync_me.unwrap_or(true))
1150 }
1151
1152 fn entity_as_actor(&self, entity: EcsEntity) -> Option<Actor> {
1153 if let Some(rtsim_entity) = self
1154 .ecs()
1155 .read_storage::<RtSimEntity>()
1156 .get(entity)
1157 .copied()
1158 {
1159 Some(Actor::Npc(rtsim_entity.0))
1160 } else if let Some(PresenceKind::Character(character)) = self
1161 .ecs()
1162 .read_storage::<Presence>()
1163 .get(entity)
1164 .map(|p| p.kind)
1165 {
1166 Some(Actor::Character(character))
1167 } else {
1168 None
1169 }
1170 }
1171
1172 fn position_mut<T>(
1173 &mut self,
1174 entity: EcsEntity,
1175 dismount_volume: bool,
1176 f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
1177 ) -> Result<T, Content> {
1178 let ecs = self.ecs_mut();
1179 position_mut(
1180 entity,
1181 dismount_volume,
1182 f,
1183 &ecs.read_resource(),
1184 &mut ecs.write_storage(),
1185 ecs.write_storage(),
1186 ecs.write_storage(),
1187 ecs.read_storage(),
1188 ecs.read_storage(),
1189 ecs.read_storage(),
1190 )
1191 }
1192}
1193
1194pub fn position_mut<T>(
1195 entity: EcsEntity,
1196 dismount_volume: bool,
1197 f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
1198 id_maps: &IdMaps,
1199 is_volume_riders: &mut WriteStorage<Is<VolumeRider>>,
1200 mut positions: impl GenericWriteStorage<Component = comp::Pos>,
1201 mut force_updates: impl GenericWriteStorage<Component = comp::ForceUpdate>,
1202 is_riders: impl GenericReadStorage<Component = Is<Rider>>,
1203 presences: impl GenericReadStorage<Component = Presence>,
1204 clients: impl GenericReadStorage<Component = Client>,
1205) -> Result<T, Content> {
1206 if dismount_volume {
1207 is_volume_riders.remove(entity);
1208 }
1209
1210 let entity = is_riders
1211 .get(entity)
1212 .and_then(|is_rider| id_maps.uid_entity(is_rider.mount))
1213 .map(Ok)
1214 .or_else(|| {
1215 is_volume_riders.get(entity).and_then(|volume_rider| {
1216 Some(match volume_rider.pos.kind {
1217 common::mounting::Volume::Terrain => {
1218 Err(Content::Plain("Tried to move the world.".to_string()))
1219 },
1220 common::mounting::Volume::Entity(uid) => Ok(id_maps.uid_entity(uid)?),
1221 })
1222 })
1223 })
1224 .unwrap_or(Ok(entity))?;
1225
1226 let mut maybe_pos = None;
1227
1228 let res = positions
1229 .get_mut(entity)
1230 .map(|pos| {
1231 let res = f(pos);
1232 maybe_pos = Some(pos.0);
1233 res
1234 })
1235 .ok_or(Content::localized_with_args(
1236 "command-position-unavailable",
1237 [("target", "entity")],
1238 ));
1239
1240 if let Some(pos) = maybe_pos {
1241 if presences
1242 .get(entity)
1243 .map(|presence| presence.kind == PresenceKind::Spectator)
1244 .unwrap_or(false)
1245 {
1246 clients.get(entity).map(|client| {
1247 client.send_fallible(ServerGeneral::SpectatePosition(pos));
1248 });
1249 } else {
1250 force_updates
1251 .get_mut(entity)
1252 .map(|force_update| force_update.update());
1253 }
1254 }
1255
1256 res
1257}
1258
1259fn send_to_group(g: &Group, ecs: &specs::World, msg: &comp::ChatMsg) {
1260 for (client, group) in (&ecs.read_storage::<Client>(), &ecs.read_storage::<Group>()).join() {
1261 if g == group {
1262 client.send_fallible(ServerGeneral::ChatMsg(msg.clone()));
1263 }
1264 }
1265}
1266
1267pub(crate) fn delete_entity_common(
1270 state: &mut State,
1271 entity: EcsEntity,
1272 maybe_uid: Option<Uid>,
1273 sync_me: bool,
1274) -> Result<(), specs::error::WrongGeneration> {
1275 let maybe_pos = state.read_component_copied::<comp::Pos>(entity);
1276
1277 let result = state.ecs_mut().delete_entity(entity);
1279
1280 if result.is_ok() {
1281 let region_map = state.mut_resource::<common::region::RegionMap>();
1282 let region_key = region_map.entity_deleted(entity);
1283 if let Some(uid) = maybe_uid {
1291 if let Some(region_key) = region_key {
1292 state
1293 .mut_resource::<DeletedEntities>()
1294 .record_deleted_entity(uid, region_key);
1295 } else if sync_me && let Some(pos) = maybe_pos {
1298 warn!(
1301 ?uid,
1302 ?pos,
1303 "Failed to find region containing entity during entity deletion, assuming it \
1304 wasn't sent to any clients and so deletion doesn't need to be recorded for \
1305 sync purposes"
1306 );
1307 }
1308 } else {
1309 error!("Deleting entity without Uid component");
1313 }
1314 }
1315 result
1316}