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()))
344 .with(comp::Stats::new("Airship".to_string(), body))
345 .with(comp::SkillSet::default())
346 .with(comp::ActiveAbilities::default())
347 .with(comp::Combo::default());
348
349 builder
350 }
351
352 fn create_projectile(
353 &mut self,
354 pos: comp::Pos,
355 vel: comp::Vel,
356 body: comp::Body,
357 projectile: comp::Projectile,
358 ) -> EcsEntityBuilder {
359 let mut projectile_base = self
360 .ecs_mut()
361 .create_entity_synced()
362 .with(pos)
363 .with(vel)
364 .with(comp::Ori::from_unnormalized_vec(vel.0).unwrap_or_default())
365 .with(body.mass())
366 .with(body.density());
367
368 if projectile.is_sticky {
369 projectile_base = projectile_base.with(comp::Sticky)
370 }
371 if projectile.is_point {
372 projectile_base = projectile_base.with(comp::Collider::Point)
373 } else {
374 projectile_base = projectile_base.with(body.collider())
375 }
376
377 projectile_base.with(projectile).with(body)
378 }
379
380 fn create_shockwave(
381 &mut self,
382 properties: comp::shockwave::Properties,
383 pos: comp::Pos,
384 ori: comp::Ori,
385 ) -> EcsEntityBuilder {
386 self.ecs_mut()
387 .create_entity_synced()
388 .with(pos)
389 .with(ori)
390 .with(comp::Shockwave {
391 properties,
392 creation: None,
393 })
394 .with(comp::ShockwaveHitEntities {
395 hit_entities: Vec::<Uid>::new(),
396 })
397 }
398
399 fn create_safezone(&mut self, range: Option<f32>, pos: comp::Pos) -> EcsEntityBuilder {
400 use comp::{
401 aura::{Aura, AuraKind, AuraTarget, Auras},
402 buff::{BuffCategory, BuffData, BuffKind, BuffSource},
403 };
404 let time = self.get_time();
405 self.ecs_mut()
407 .create_entity_synced()
408 .with(pos)
409 .with(Auras::new(vec![Aura::new(
410 AuraKind::Buff {
411 kind: BuffKind::Invulnerability,
412 data: BuffData::new(1.0, Some(Secs(1.0))),
413 category: BuffCategory::Natural,
414 source: BuffSource::World,
415 },
416 range.unwrap_or(100.0),
417 None,
418 AuraTarget::All,
419 Time(time),
420 )]))
421 }
422
423 fn create_wiring(
424 &mut self,
425 pos: comp::Pos,
426 object: comp::object::Body,
427 wiring_element: wiring::WiringElement,
428 ) -> EcsEntityBuilder {
429 self.ecs_mut()
430 .create_entity_synced()
431 .with(pos)
432 .with(comp::Vel(Vec3::zero()))
433 .with(comp::Ori::default())
434 .with({
435 let body: comp::Body = object.into();
436 body.collider()
437 })
438 .with(comp::Body::Object(object))
439 .with(comp::Mass(100.0))
440 .with(wiring_element)
442 .with(comp::LightEmitter {
443 col: Rgb::new(0.0, 0.0, 0.0),
444 strength: 2.0,
445 flicker: 1.0,
446 animated: true,
447 })
448 }
449
450 #[cfg(feature = "worldgen")]
455 fn create_persister(
456 &mut self,
457 pos: comp::Pos,
458 view_distance: u32,
459 world: &std::sync::Arc<world::World>,
460 index: &world::IndexOwned,
461 ) -> EcsEntityBuilder {
462 use common::{terrain::TerrainChunkSize, vol::RectVolSize};
463 use std::sync::Arc;
464 {
466 let ecs = self.ecs();
467 let slow_jobs = ecs.write_resource::<SlowJobPool>();
468 let rtsim = ecs.read_resource::<RtSim>();
469 let mut chunk_generator =
470 ecs.write_resource::<crate::chunk_generator::ChunkGenerator>();
471 let chunk_pos = self.terrain().pos_key(pos.0.map(|e| e as i32));
472 (-(view_distance as i32)..view_distance as i32 + 1)
473 .flat_map(|x| {
474 (-(view_distance as i32)..view_distance as i32 + 1).map(move |y| Vec2::new(x, y))
475 })
476 .map(|offset| offset + chunk_pos)
477 .filter(|chunk_key| {
480 pos.0.xy().map(|e| e as f64).distance(
481 chunk_key.map(|e| e as f64 + 0.5) * TerrainChunkSize::RECT_SIZE.map(|e| e as f64),
482 ) < (view_distance as f64 - 1.0 + 2.5 * 2.0_f64.sqrt())
483 * TerrainChunkSize::RECT_SIZE.x as f64
484 })
485 .for_each(|chunk_key| {
486 {
487 let time = (*ecs.read_resource::<TimeOfDay>(), (*ecs.read_resource::<Calendar>()).clone());
488 chunk_generator.generate_chunk(None, chunk_key, &slow_jobs, Arc::clone(world), &rtsim, index.clone(), time);
489 }
490 });
491 }
492
493 self.ecs_mut()
494 .create_entity_synced()
495 .with(pos)
496 .with(Presence::new(
497 ViewDistances {
498 terrain: view_distance,
499 entity: view_distance,
500 },
501 PresenceKind::Spectator,
502 ))
503 }
504
505 fn create_teleporter(&mut self, pos: comp::Pos, portal: PortalData) -> EcsEntityBuilder {
506 self.create_object(pos, object::Body::Portal)
507 .with(comp::Immovable)
508 .with(comp::Object::from(portal))
509 }
510
511 fn initialize_character_data(
512 &mut self,
513 entity: EcsEntity,
514 character_id: CharacterId,
515 view_distances: ViewDistances,
516 ) {
517 let spawn_point = self.ecs().read_resource::<SpawnPoint>().0;
518
519 if let Some(player_uid) = self.read_component_copied::<Uid>(entity) {
520 self.write_component_ignore_entity_dead(entity, comp::Controller::default());
525 self.write_component_ignore_entity_dead(entity, comp::Pos(spawn_point));
526 self.write_component_ignore_entity_dead(entity, comp::Vel(Vec3::zero()));
527 self.write_component_ignore_entity_dead(entity, comp::Ori::default());
528 self.write_component_ignore_entity_dead(entity, comp::Collider::CapsulePrism {
529 p0: Vec2::zero(),
530 p1: Vec2::zero(),
531 radius: 0.4,
532 z_min: 0.0,
533 z_max: 1.75,
534 });
535 self.write_component_ignore_entity_dead(entity, comp::CharacterState::default());
536 self.write_component_ignore_entity_dead(entity, comp::CharacterActivity::default());
537 self.write_component_ignore_entity_dead(entity, comp::Alignment::Owned(player_uid));
538 self.write_component_ignore_entity_dead(entity, comp::Buffs::default());
539 self.write_component_ignore_entity_dead(entity, comp::Auras::default());
540 self.write_component_ignore_entity_dead(entity, comp::EnteredAuras::default());
541 self.write_component_ignore_entity_dead(entity, comp::Combo::default());
542 self.write_component_ignore_entity_dead(entity, comp::Stance::default());
543
544 self.write_component_ignore_entity_dead(entity, comp::ForceUpdate::forced());
546
547 self.write_component_ignore_entity_dead(
548 entity,
549 Presence::new(view_distances, PresenceKind::LoadingCharacter(character_id)),
550 );
551
552 if let Some(client) = self.ecs().read_storage::<Client>().get(entity) {
554 client.send_fallible(ServerGeneral::CharacterSuccess);
555 }
556 }
557 }
558
559 fn initialize_spectator_data(&mut self, entity: EcsEntity, view_distances: ViewDistances) {
560 let spawn_point = self.ecs().read_resource::<SpawnPoint>().0;
561
562 if self.read_component_copied::<Uid>(entity).is_some() {
563 self.write_component_ignore_entity_dead(entity, comp::Pos(spawn_point));
568
569 self.write_component_ignore_entity_dead(entity, comp::ForceUpdate::forced());
571
572 self.write_component_ignore_entity_dead(
573 entity,
574 Presence::new(view_distances, PresenceKind::Spectator),
575 );
576
577 if let Some(client) = self.ecs().read_storage::<Client>().get(entity) {
579 client.send_fallible(ServerGeneral::SpectatorSuccess(spawn_point));
580 }
581 }
582 }
583
584 fn update_character_data(
586 &mut self,
587 entity: EcsEntity,
588 components: PersistedComponents,
589 ) -> Result<(), String> {
590 let PersistedComponents {
591 body,
592 hardcore,
593 stats,
594 skill_set,
595 inventory,
596 waypoint,
597 pets,
598 active_abilities,
599 map_marker,
600 } = components;
601
602 if let Some(player_uid) = self.read_component_copied::<Uid>(entity) {
603 let result =
604 if let Some(presence) = self.ecs().write_storage::<Presence>().get_mut(entity) {
605 if let PresenceKind::LoadingCharacter(id) = presence.kind {
606 presence.kind = PresenceKind::Character(id);
607 self.ecs()
608 .write_resource::<IdMaps>()
609 .add_character(id, entity);
610 Ok(())
611 } else {
612 Err("PresenceKind is not LoadingCharacter")
613 }
614 } else {
615 Err("Presence component missing")
616 };
617 if let Err(err) = result {
618 let err = format!("Unexpected state when applying loaded character info: {err}");
619 error!("{err}");
620 return Err(err);
622 }
623
624 if self
625 .ecs()
626 .read_component::<Client>()
627 .get(entity)
628 .is_some_and(|client| client.client_type.emit_login_events())
629 {
630 self.notify_players(ServerGeneral::PlayerListUpdate(
632 PlayerListUpdate::SelectedCharacter(player_uid, CharacterInfo {
633 name: String::from(&stats.name),
634 gender: stats.original_body.humanoid_gender(),
636 }),
637 ));
638 }
639
640 self.write_component_ignore_entity_dead(entity, body.collider());
645 self.write_component_ignore_entity_dead(entity, body);
646 self.write_component_ignore_entity_dead(entity, body.mass());
647 self.write_component_ignore_entity_dead(entity, body.density());
648 self.write_component_ignore_entity_dead(entity, comp::Health::new(body));
649 self.write_component_ignore_entity_dead(entity, comp::Energy::new(body));
650 self.write_component_ignore_entity_dead(entity, Poise::new(body));
651 self.write_component_ignore_entity_dead(entity, stats);
652 self.write_component_ignore_entity_dead(entity, active_abilities);
653 self.write_component_ignore_entity_dead(entity, skill_set);
654 self.write_component_ignore_entity_dead(entity, inventory);
655 self.write_component_ignore_entity_dead(
656 entity,
657 comp::InventoryUpdate::new(comp::InventoryUpdateEvent::default()),
658 );
659
660 if let Some(hardcore) = hardcore {
661 self.write_component_ignore_entity_dead(entity, hardcore);
662 }
663
664 if let Some(waypoint) = waypoint {
665 self.write_component_ignore_entity_dead(entity, RepositionOnChunkLoad {
666 needs_ground: true,
667 });
668 self.write_component_ignore_entity_dead(entity, waypoint);
669 self.write_component_ignore_entity_dead(entity, comp::Pos(waypoint.get_pos()));
670 self.write_component_ignore_entity_dead(entity, comp::Vel(Vec3::zero()));
671 self.write_component_ignore_entity_dead(entity, comp::ForceUpdate::forced());
675 }
676
677 if let Some(map_marker) = map_marker {
678 self.write_component_ignore_entity_dead(entity, map_marker);
679 }
680
681 let player_pos = self.ecs().read_storage::<comp::Pos>().get(entity).copied();
682 if let Some(player_pos) = player_pos {
683 trace!(
684 "Loading {} pets for player at pos {:?}",
685 pets.len(),
686 player_pos
687 );
688 let mut rng = rand::thread_rng();
689
690 for (pet, body, stats) in pets {
691 let ori = comp::Ori::from(Dir::random_2d(&mut rng));
692 let pet_entity = self
693 .create_npc(
694 player_pos,
695 ori,
696 stats,
697 comp::SkillSet::default(),
698 Some(comp::Health::new(body)),
699 Poise::new(body),
700 Inventory::with_loadout(
701 LoadoutBuilder::from_default(&body).build(),
702 body,
703 ),
704 body,
705 comp::Scale(1.0),
706 )
707 .with(comp::Vel(Vec3::new(0.0, 0.0, 0.0)))
708 .build();
709
710 restore_pet(self.ecs(), pet_entity, entity, pet);
711 }
712 } else {
713 error!("Player has no pos, cannot load {} pets", pets.len());
714 }
715
716 let presences = self.ecs().read_storage::<Presence>();
717 let presence = presences.get(entity);
718 if let Some(Presence {
719 kind: PresenceKind::Character(char_id),
720 ..
721 }) = presence
722 {
723 let battlemode_buffer = self.ecs().fetch::<BattleModeBuffer>();
724 let mut players = self.ecs().write_storage::<comp::Player>();
725 if let Some((mode, change)) = battlemode_buffer.get(char_id) {
726 if let Some(mut player_info) = players.get_mut(entity) {
727 player_info.battle_mode = *mode;
728 player_info.last_battlemode_change = Some(*change);
729 }
730 } else {
731 let settings = self.ecs().read_resource::<Settings>();
741 let mode = settings.gameplay.battle_mode.default_mode();
742 if let Some(mut player_info) = players.get_mut(entity) {
743 player_info.battle_mode = mode;
744 player_info.last_battlemode_change = None;
745 }
746 }
747 }
748 }
749
750 Ok(())
751 }
752
753 fn validate_chat_msg(
754 &self,
755 entity: EcsEntity,
756 chat_type: &comp::ChatType<comp::Group>,
757 msg: &Content,
758 from_client: bool,
759 ) -> bool {
760 let mut automod = self.ecs().write_resource::<AutoMod>();
761 let client = self.ecs().read_storage::<Client>();
762 let player = self.ecs().read_storage::<Player>();
763 let Some(client) = client.get(entity) else {
764 return true;
765 };
766 let Some(player) = player.get(entity) else {
767 return true;
768 };
769
770 let Some(msg) = msg.as_plain() else {
774 if !from_client {
775 warn!(
776 "Non-plain chat message with a player as the sender was filtered out. This \
777 message did not come directly from the client so this is probably a bug in \
778 the server as these message types are not allowed currently."
779 )
780 }
781 return false;
782 };
783
784 match automod.validate_chat_msg(
785 player.uuid(),
786 self.ecs()
787 .read_storage::<comp::Admin>()
788 .get(entity)
789 .map(|a| a.0),
790 Instant::now(),
791 chat_type,
792 msg,
793 ) {
794 Ok(note) => {
795 if let Some(note) = note {
796 let _ = client.send(ServerGeneral::server_msg(
797 ChatType::CommandInfo,
798 Content::Plain(format!("{}", note)),
799 ));
800 }
801 true
802 },
803 Err(err) => {
804 let _ = client.send(ServerGeneral::server_msg(
805 ChatType::CommandError,
806 Content::Plain(format!("{}", err)),
807 ));
808 false
809 },
810 }
811 }
812
813 fn send_chat(&self, msg: comp::UnresolvedChatMsg, from_client: bool) {
816 let ecs = self.ecs();
817 let is_within =
818 |target, a: &comp::Pos, b: &comp::Pos| a.0.distance_squared(b.0) < target * target;
819
820 let group_manager = ecs.read_resource::<comp::group::GroupManager>();
821 let chat_exporter = ecs.read_resource::<ChatExporter>();
822
823 let group_info = msg.get_group().and_then(|g| group_manager.group_info(*g));
824
825 if let Some(exported_message) = ChatExporter::generate(&msg, ecs) {
826 chat_exporter.send(exported_message);
827 }
828
829 let resolved_msg = msg
830 .clone()
831 .map_group(|_| group_info.map_or_else(|| "???".to_string(), |i| i.name.clone()));
832
833 let id_maps = ecs.read_resource::<IdMaps>();
834 let entity_from_uid = |uid| id_maps.uid_entity(uid);
835
836 if msg.chat_type.uid().is_none_or(|sender| {
837 entity_from_uid(sender).is_some_and(|e| {
838 self.validate_chat_msg(e, &msg.chat_type, msg.content(), from_client)
839 })
840 }) {
841 match &msg.chat_type {
842 comp::ChatType::Offline(_)
843 | comp::ChatType::CommandInfo
844 | comp::ChatType::CommandError
845 | comp::ChatType::Meta
846 | comp::ChatType::World(_) => {
847 self.notify_players(ServerGeneral::ChatMsg(resolved_msg))
848 },
849 comp::ChatType::Online(u) => {
850 for (client, uid) in
851 (&ecs.read_storage::<Client>(), &ecs.read_storage::<Uid>()).join()
852 {
853 if uid != u {
854 client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
855 }
856 }
857 },
858 &comp::ChatType::Tell(from, to) => {
859 let clients = ecs.read_storage::<Client>();
860 if let Some(from_client) = entity_from_uid(from).and_then(|e| clients.get(e)) {
861 from_client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
862 }
863 if let Some(to_client) = entity_from_uid(to).and_then(|e| clients.get(e)) {
864 to_client.send_fallible(ServerGeneral::ChatMsg(resolved_msg));
865 }
866 },
867 comp::ChatType::Kill(kill_source, uid) => {
868 let clients = ecs.read_storage::<Client>();
869 let clients_count = clients.count();
870 if clients_count
873 > ecs
874 .fetch::<Settings>()
875 .max_player_for_kill_broadcast
876 .unwrap_or_default()
877 {
878 let killed_entity = entity_from_uid(*uid);
880 let groups = ecs.read_storage::<Group>();
881 let killed_group = killed_entity.and_then(|e| groups.get(e));
882 if let Some(g) = &killed_group {
883 send_to_group(g, ecs, &resolved_msg);
884 }
885
886 let positions = ecs.read_storage::<comp::Pos>();
889 if let Some(died_player_pos) = killed_entity.and_then(|e| positions.get(e))
890 {
891 for (ent, client, pos) in
892 (&*ecs.entities(), &clients, &positions).join()
893 {
894 let client_group = groups.get(ent);
895 let is_different_group =
896 !(killed_group == client_group && client_group.is_some());
897 if is_within(comp::ChatMsg::SAY_DISTANCE, pos, died_player_pos)
898 && is_different_group
899 {
900 client.send_fallible(ServerGeneral::ChatMsg(
901 resolved_msg.clone(),
902 ));
903 }
904 }
905 }
906 } else {
907 self.notify_players(ServerGeneral::server_msg(
908 comp::ChatType::Kill(kill_source.clone(), *uid),
909 msg.into_content(),
910 ))
911 }
912 },
913 comp::ChatType::Say(uid) => {
914 let entity_opt = entity_from_uid(*uid);
915
916 let positions = ecs.read_storage::<comp::Pos>();
917 if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
918 for (client, pos) in (&ecs.read_storage::<Client>(), &positions).join() {
919 if is_within(comp::ChatMsg::SAY_DISTANCE, pos, speaker_pos) {
920 client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
921 }
922 }
923 }
924 },
925 comp::ChatType::Region(uid) => {
926 let entity_opt = entity_from_uid(*uid);
927
928 let positions = ecs.read_storage::<comp::Pos>();
929 if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
930 for (client, pos) in (&ecs.read_storage::<Client>(), &positions).join() {
931 if is_within(comp::ChatMsg::REGION_DISTANCE, pos, speaker_pos) {
932 client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
933 }
934 }
935 }
936 },
937 comp::ChatType::Npc(uid) => {
938 let entity_opt = entity_from_uid(*uid);
939
940 let positions = ecs.read_storage::<comp::Pos>();
941 if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
942 for (client, pos) in (&ecs.read_storage::<Client>(), &positions).join() {
943 if is_within(comp::ChatMsg::NPC_DISTANCE, pos, speaker_pos) {
944 client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
945 }
946 }
947 }
948 },
949 comp::ChatType::NpcSay(uid) => {
950 let entity_opt = entity_from_uid(*uid);
951
952 let positions = ecs.read_storage::<comp::Pos>();
953 if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
954 for (client, pos) in (&ecs.read_storage::<Client>(), &positions).join() {
955 if is_within(comp::ChatMsg::NPC_SAY_DISTANCE, pos, speaker_pos) {
956 client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
957 }
958 }
959 }
960 },
961 &comp::ChatType::NpcTell(from, to) => {
962 let clients = ecs.read_storage::<Client>();
963 if let Some(from_client) = entity_from_uid(from).and_then(|e| clients.get(e)) {
964 from_client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
965 }
966 if let Some(to_client) = entity_from_uid(to).and_then(|e| clients.get(e)) {
967 to_client.send_fallible(ServerGeneral::ChatMsg(resolved_msg));
968 }
969 },
970 comp::ChatType::FactionMeta(s) | comp::ChatType::Faction(_, s) => {
971 for (client, faction) in (
972 &ecs.read_storage::<Client>(),
973 &ecs.read_storage::<comp::Faction>(),
974 )
975 .join()
976 {
977 if s == &faction.0 {
978 client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
979 }
980 }
981 },
982 comp::ChatType::Group(from, g) => {
983 if group_info.is_none() {
984 let reply = comp::ChatType::CommandError
991 .into_msg(Content::localized("command-message-group-missing"));
992
993 let clients = ecs.read_storage::<Client>();
994 if let Some(client) =
995 entity_from_uid(*from).and_then(|entity| clients.get(entity))
996 {
997 client.send_fallible(ServerGeneral::ChatMsg(reply));
998 }
999 } else {
1000 send_to_group(g, ecs, &resolved_msg);
1001 }
1002 },
1003 comp::ChatType::GroupMeta(g) => {
1004 send_to_group(g, ecs, &resolved_msg);
1005 },
1006 }
1007 }
1008 }
1009
1010 fn notify_players(&self, msg: ServerGeneral) {
1012 let mut msg = Some(msg);
1013 let mut lazy_msg = None;
1014 for (client, _) in (
1015 &self.ecs().read_storage::<Client>(),
1016 &self.ecs().read_storage::<comp::Player>(),
1017 )
1018 .join()
1019 {
1020 if let Some(msg) = msg.take() {
1021 lazy_msg = Some(client.prepare(msg));
1022 }
1023 lazy_msg.as_ref().map(|msg| client.send_prepared(msg));
1024 }
1025 }
1026
1027 fn notify_in_game_clients(&self, msg: ServerGeneral) {
1029 let mut msg = Some(msg);
1030 let mut lazy_msg = None;
1031 for (client, _) in (
1032 &mut self.ecs().write_storage::<Client>(),
1033 &self.ecs().read_storage::<Presence>(),
1034 )
1035 .join()
1036 {
1037 if let Some(msg) = msg.take() {
1038 lazy_msg = Some(client.prepare(msg));
1039 }
1040 lazy_msg.as_ref().map(|msg| client.send_prepared(msg));
1041 }
1042 }
1043
1044 fn link<L: Link>(&mut self, link: L) -> Result<(), L::Error> {
1045 let linker = LinkHandle::from_link(link);
1046
1047 L::create(&linker, &mut self.ecs().system_data())?;
1048
1049 self.ecs_mut()
1050 .entry::<Vec<LinkHandle<L>>>()
1051 .or_insert_with(Vec::new)
1052 .push(linker);
1053
1054 Ok(())
1055 }
1056
1057 fn maintain_links(&mut self) {
1058 fn maintain_link<L: Link>(state: &State) {
1059 if let Some(mut handles) = state.ecs().try_fetch_mut::<Vec<LinkHandle<L>>>() {
1060 let mut persist_data = None;
1061 handles.retain(|link| {
1062 if L::persist(
1063 link,
1064 persist_data.get_or_insert_with(|| state.ecs().system_data()),
1065 ) {
1066 true
1067 } else {
1068 persist_data.take();
1071 L::delete(link, &mut state.ecs().system_data());
1072 false
1073 }
1074 });
1075 }
1076 }
1077
1078 maintain_link::<Mounting>(self);
1079 maintain_link::<VolumeMounting>(self);
1080 maintain_link::<Tethered>(self);
1081 maintain_link::<Interaction>(self);
1082 }
1083
1084 fn delete_entity_recorded(
1085 &mut self,
1086 entity: EcsEntity,
1087 ) -> Result<(), specs::error::WrongGeneration> {
1088 {
1094 let clients = self.ecs().read_storage::<Client>();
1095 let uids = self.ecs().read_storage::<Uid>();
1096 let mut group_manager = self.ecs().write_resource::<comp::group::GroupManager>();
1097 let map_markers = self.ecs().read_storage::<comp::MapMarker>();
1098 group_manager.entity_deleted(
1099 entity,
1100 &mut self.ecs().write_storage(),
1101 &self.ecs().read_storage(),
1102 &uids,
1103 &self.ecs().entities(),
1104 &mut |entity, group_change| {
1105 clients
1106 .get(entity)
1107 .and_then(|c| {
1108 group_change
1109 .try_map_ref(|e| uids.get(*e).copied())
1110 .map(|g| (g, c))
1111 })
1112 .map(|(g, c)| {
1113 update_map_markers(&map_markers, &uids, c, &group_change);
1114 c.send_fallible(ServerGeneral::GroupUpdate(g));
1115 });
1116 },
1117 );
1118 }
1119
1120 events::shared::cancel_trades_for(self, entity);
1122
1123 let maybe_uid = self.read_component_copied::<Uid>(entity);
1126 let (maybe_character, sync_me) = self
1127 .read_storage::<Presence>()
1128 .get(entity)
1129 .map(|p| (p.kind.character_id(), p.kind.sync_me()))
1130 .unzip();
1131 let maybe_rtsim = self.read_component_copied::<RtSimEntity>(entity);
1132
1133 self.mut_resource::<IdMaps>().remove_entity(
1134 Some(entity),
1135 maybe_uid,
1136 maybe_character.flatten(),
1137 maybe_rtsim,
1138 );
1139
1140 delete_entity_common(self, entity, maybe_uid, sync_me.unwrap_or(true))
1141 }
1142
1143 fn entity_as_actor(&self, entity: EcsEntity) -> Option<Actor> {
1144 if let Some(rtsim_entity) = self
1145 .ecs()
1146 .read_storage::<RtSimEntity>()
1147 .get(entity)
1148 .copied()
1149 {
1150 Some(Actor::Npc(rtsim_entity.0))
1151 } else if let Some(PresenceKind::Character(character)) = self
1152 .ecs()
1153 .read_storage::<Presence>()
1154 .get(entity)
1155 .map(|p| p.kind)
1156 {
1157 Some(Actor::Character(character))
1158 } else {
1159 None
1160 }
1161 }
1162
1163 fn position_mut<T>(
1164 &mut self,
1165 entity: EcsEntity,
1166 dismount_volume: bool,
1167 f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
1168 ) -> Result<T, Content> {
1169 let ecs = self.ecs_mut();
1170 position_mut(
1171 entity,
1172 dismount_volume,
1173 f,
1174 &ecs.read_resource(),
1175 &mut ecs.write_storage(),
1176 ecs.write_storage(),
1177 ecs.write_storage(),
1178 ecs.read_storage(),
1179 ecs.read_storage(),
1180 ecs.read_storage(),
1181 )
1182 }
1183}
1184
1185pub fn position_mut<T>(
1186 entity: EcsEntity,
1187 dismount_volume: bool,
1188 f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
1189 id_maps: &IdMaps,
1190 is_volume_riders: &mut WriteStorage<Is<VolumeRider>>,
1191 mut positions: impl GenericWriteStorage<Component = comp::Pos>,
1192 mut force_updates: impl GenericWriteStorage<Component = comp::ForceUpdate>,
1193 is_riders: impl GenericReadStorage<Component = Is<Rider>>,
1194 presences: impl GenericReadStorage<Component = Presence>,
1195 clients: impl GenericReadStorage<Component = Client>,
1196) -> Result<T, Content> {
1197 if dismount_volume {
1198 is_volume_riders.remove(entity);
1199 }
1200
1201 let entity = is_riders
1202 .get(entity)
1203 .and_then(|is_rider| id_maps.uid_entity(is_rider.mount))
1204 .map(Ok)
1205 .or_else(|| {
1206 is_volume_riders.get(entity).and_then(|volume_rider| {
1207 Some(match volume_rider.pos.kind {
1208 common::mounting::Volume::Terrain => {
1209 Err(Content::Plain("Tried to move the world.".to_string()))
1210 },
1211 common::mounting::Volume::Entity(uid) => Ok(id_maps.uid_entity(uid)?),
1212 })
1213 })
1214 })
1215 .unwrap_or(Ok(entity))?;
1216
1217 let mut maybe_pos = None;
1218
1219 let res = positions
1220 .get_mut(entity)
1221 .map(|pos| {
1222 let res = f(pos);
1223 maybe_pos = Some(pos.0);
1224 res
1225 })
1226 .ok_or(Content::localized_with_args(
1227 "command-position-unavailable",
1228 [("target", "entity")],
1229 ));
1230
1231 if let Some(pos) = maybe_pos {
1232 if presences
1233 .get(entity)
1234 .map(|presence| presence.kind == PresenceKind::Spectator)
1235 .unwrap_or(false)
1236 {
1237 clients.get(entity).map(|client| {
1238 client.send_fallible(ServerGeneral::SpectatePosition(pos));
1239 });
1240 } else {
1241 force_updates
1242 .get_mut(entity)
1243 .map(|force_update| force_update.update());
1244 }
1245 }
1246
1247 res
1248}
1249
1250fn send_to_group(g: &Group, ecs: &specs::World, msg: &comp::ChatMsg) {
1251 for (client, group) in (&ecs.read_storage::<Client>(), &ecs.read_storage::<Group>()).join() {
1252 if g == group {
1253 client.send_fallible(ServerGeneral::ChatMsg(msg.clone()));
1254 }
1255 }
1256}
1257
1258pub(crate) fn delete_entity_common(
1261 state: &mut State,
1262 entity: EcsEntity,
1263 maybe_uid: Option<Uid>,
1264 sync_me: bool,
1265) -> Result<(), specs::error::WrongGeneration> {
1266 let maybe_pos = state.read_component_copied::<comp::Pos>(entity);
1267
1268 let result = state.ecs_mut().delete_entity(entity);
1270
1271 if result.is_ok() {
1272 let region_map = state.mut_resource::<common::region::RegionMap>();
1273 let region_key = region_map.entity_deleted(entity);
1274 if let Some(uid) = maybe_uid {
1282 if let Some(region_key) = region_key {
1283 state
1284 .mut_resource::<DeletedEntities>()
1285 .record_deleted_entity(uid, region_key);
1286 } else if sync_me && let Some(pos) = maybe_pos {
1289 warn!(
1292 ?uid,
1293 ?pos,
1294 "Failed to find region containing entity during entity deletion, assuming it \
1295 wasn't sent to any clients and so deletion doesn't need to be recorded for \
1296 sync purposes"
1297 );
1298 }
1299 } else {
1300 error!("Deleting entity without Uid component");
1304 }
1305 }
1306 result
1307}