Skip to main content

veloren_server/
state_ext.rs

1#[cfg(feature = "worldgen")]
2use crate::rtsim::RtSim;
3use crate::{
4    BattleModeBuffer, SpawnPoint,
5    automod::AutoMod,
6    chat::ChatExporter,
7    client::Client,
8    events::{self, shared::update_map_markers},
9    persistence::PersistedComponents,
10    pet::restore_pet,
11    presence::RepositionToFreeSpace,
12    settings::Settings,
13    sys::sentinel::DeletedEntities,
14    wiring,
15};
16use common::{
17    LoadoutBuilder, ViewDistances,
18    character::CharacterId,
19    comp::{
20        self, BASE_ABILITY_LIMIT, CapsulePrism, ChatType, Content, Group, Inventory, LootOwner,
21        Object, Player, Poise, Presence, PresenceKind, item::ItemKind, misc::PortalData, object,
22    },
23    interaction::Interaction,
24    link::{Is, Link, LinkHandle},
25    mounting::{Mounting, Rider, VolumeMounting, VolumeRider},
26    resources::{Secs, Time},
27    rtsim,
28    tether::Tethered,
29    uid::{IdMaps, Uid},
30    util::Dir,
31};
32#[cfg(feature = "worldgen")]
33use common::{calendar::Calendar, resources::TimeOfDay, slowjob::SlowJobPool};
34use common_net::{
35    msg::{CharacterInfo, PlayerListUpdate, ServerGeneral},
36    sync::WorldSyncExt,
37};
38use common_state::State;
39use specs::{
40    Builder, Entity as EcsEntity, EntityBuilder as EcsEntityBuilder, Join, WorldExt, WriteStorage,
41    storage::{GenericReadStorage, GenericWriteStorage},
42};
43use std::time::{Duration, Instant};
44use tracing::{error, trace, warn};
45use vek::*;
46
47pub trait StateExt {
48    /// Build a non-player character
49    fn create_npc(
50        &mut self,
51        pos: comp::Pos,
52        ori: comp::Ori,
53        stats: comp::Stats,
54        skill_set: comp::SkillSet,
55        health: Option<comp::Health>,
56        poise: Poise,
57        inventory: Inventory,
58        body: comp::Body,
59        scale: comp::Scale,
60    ) -> EcsEntityBuilder<'_>;
61    /// Create an entity with only a position
62    fn create_empty(&mut self, pos: comp::Pos) -> EcsEntityBuilder<'_>;
63    /// Build a static object entity
64    fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body)
65    -> EcsEntityBuilder<'_>;
66    /// Create an item drop or merge the item with an existing drop, if a
67    /// suitable candidate exists.
68    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    /// Build a projectile
84    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    /// Build a shockwave entity
92    fn create_shockwave(
93        &mut self,
94        properties: comp::shockwave::Properties,
95        pos: comp::Pos,
96        ori: comp::Ori,
97    ) -> EcsEntityBuilder<'_>;
98    fn create_arcing(
99        &mut self,
100        arc: comp::ArcProperties,
101        target: Uid,
102        owner: Option<Uid>,
103        pos: comp::Pos,
104    ) -> EcsEntityBuilder<'_>;
105    fn create_pool(
106        &mut self,
107        properties: comp::pool::PoolProperties,
108        owner: Option<Uid>,
109        pos: comp::Pos,
110        ori: comp::Ori,
111    ) -> EcsEntityBuilder<'_>;
112    /// Creates a safezone
113    fn create_safezone(&mut self, range: Option<f32>, pos: comp::Pos) -> EcsEntityBuilder<'_>;
114    fn create_wiring(
115        &mut self,
116        pos: comp::Pos,
117        object: comp::object::Body,
118        wiring_element: wiring::WiringElement,
119    ) -> EcsEntityBuilder<'_>;
120    // NOTE: currently only used for testing
121    /// Queues chunk generation in the view distance of the persister, this
122    /// entity must be built before those chunks are received (the builder
123    /// borrows the ecs world so that is kind of impossible in practice)
124    #[cfg(feature = "worldgen")]
125    fn create_persister(
126        &mut self,
127        pos: comp::Pos,
128        view_distance: u32,
129        world: &std::sync::Arc<world::World>,
130        index: &world::IndexOwned,
131    ) -> EcsEntityBuilder<'_>;
132    /// Creates a teleporter entity, which allows players to teleport to the
133    /// `target` position. You might want to require the teleporting entity
134    /// to not have agro for teleporting.
135    fn create_teleporter(&mut self, pos: comp::Pos, portal: PortalData) -> EcsEntityBuilder<'_>;
136    /// Insert common/default components for a new character joining the server
137    fn initialize_character_data(
138        &mut self,
139        entity: EcsEntity,
140        character_id: CharacterId,
141        view_distances: ViewDistances,
142    );
143    /// Insert common/default components for a new spectator joining the server
144    fn initialize_spectator_data(&mut self, entity: EcsEntity, view_distances: ViewDistances);
145    /// Update the components associated with the entity's current character.
146    /// Performed after loading component data from the database
147    fn update_character_data(
148        &mut self,
149        entity: EcsEntity,
150        components: PersistedComponents,
151    ) -> Result<(), String>;
152    /// Iterates over registered clients and send each `ServerMsg`
153    fn validate_chat_msg(
154        &self,
155        player: EcsEntity,
156        chat_type: &comp::ChatType<comp::Group>,
157        msg: &Content,
158        // Whether the message is directly from a client or was generated on the server.
159        //
160        // Note, clients can influence the content of messages generated on the server (e.g. via
161        // chat commands like `/tell`), this is just used for logging purposes.
162        from_client: bool,
163    ) -> bool;
164    fn send_chat(&self, msg: comp::UnresolvedChatMsg, from_client: bool);
165    fn notify_players(&self, msg: ServerGeneral);
166    fn notify_in_game_clients(&self, msg: ServerGeneral);
167    /// Create a new link between entities (see [`common::mounting`] for an
168    /// example).
169    fn link<L: Link>(&mut self, link: L) -> Result<(), L::Error>;
170    /// Maintain active links between entities
171    fn maintain_links(&mut self);
172    /// Delete an entity, recording the deletion in [`DeletedEntities`]
173    fn delete_entity_recorded(
174        &mut self,
175        entity: EcsEntity,
176    ) -> Result<(), specs::error::WrongGeneration>;
177    /// Mutate the position of an entity or, if the entity is mounted, the
178    /// mount.
179    ///
180    /// If `dismount_volume` is `true`, an entity mounted on a volume entity
181    /// (such as an airship) will be dismounted to avoid teleporting the volume
182    /// entity.
183    fn position_mut<T>(
184        &mut self,
185        entity: EcsEntity,
186        dismount_volume: bool,
187        f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
188    ) -> Result<T, Content>;
189
190    /// Mutate the position of an entity or, if the entity is mounted, the
191    /// mount.
192    ///
193    /// If `needs_ground` is set to false, the entity will strictly be mutated
194    /// to the nearest available space on the ground
195    /// otherwise, the entity will be mutated to the nearest available space
196    /// regardless of it being the ground or the sky.
197    ///
198    /// If `dismount_volume` is `true`, an entity mounted on a volume entity
199    /// (such as an airship) will be dismounted to avoid teleporting the volume
200    /// entity.
201    fn position_mut_reposition<T>(
202        &mut self,
203        entity: EcsEntity,
204        dismount_volume: bool,
205        f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
206        needs_ground: bool,
207        modify_waypoints: bool,
208    ) -> Result<T, Content>;
209}
210
211impl StateExt for State {
212    fn create_npc(
213        &mut self,
214        pos: comp::Pos,
215        ori: comp::Ori,
216        stats: comp::Stats,
217        skill_set: comp::SkillSet,
218        health: Option<comp::Health>,
219        poise: Poise,
220        inventory: Inventory,
221        body: comp::Body,
222        scale: comp::Scale,
223    ) -> EcsEntityBuilder<'_> {
224        self.ecs_mut()
225            .create_entity_synced()
226            .with(pos)
227            .with(comp::Vel(Vec3::zero()))
228            .with(ori)
229            .with(comp::Mass(body.mass().0 * scale.0.powi(3)))
230            .with(body.density())
231            .with(body.collider())
232            .with(scale)
233            .with(comp::Controller::default())
234            .with(body)
235            .with(comp::Energy::new(body))
236            .with(stats)
237            .with(if body.is_humanoid() {
238                comp::ActiveAbilities::default_limited(BASE_ABILITY_LIMIT)
239            } else {
240                comp::ActiveAbilities::default()
241            })
242            .with(skill_set)
243            .maybe_with(health)
244            .with(poise)
245            .with(comp::Alignment::Npc)
246            .with(comp::CharacterState::default())
247            .with(comp::CharacterActivity::default())
248            .with(inventory)
249            .with(comp::Buffs::default())
250            .with(comp::Combo::default())
251            .with(comp::Auras::default())
252            .with(comp::EnteredAuras::default())
253            .with(comp::Stance::default())
254            .with(comp::projectile::ProjectileHitEntities::default())
255            .maybe_with(body.heads().map(comp::body::parts::Heads::new))
256    }
257
258    fn create_empty(&mut self, pos: comp::Pos) -> EcsEntityBuilder<'_> {
259        self.ecs_mut()
260            .create_entity_synced()
261            .with(pos)
262            .with(comp::Vel(Vec3::zero()))
263            .with(comp::Ori::default())
264    }
265
266    fn create_object(
267        &mut self,
268        pos: comp::Pos,
269        object: comp::object::Body,
270    ) -> EcsEntityBuilder<'_> {
271        let body = comp::Body::Object(object);
272        self.create_empty(pos)
273            .with(body.mass())
274            .with(body.density())
275            .with(body.collider())
276            .with(body)
277    }
278
279    fn create_item_drop(
280        &mut self,
281        pos: comp::Pos,
282        ori: comp::Ori,
283        vel: comp::Vel,
284        world_item: comp::PickupItem,
285        loot_owner: Option<LootOwner>,
286    ) -> Option<EcsEntity> {
287        // Attempt merging with any nearby entities if possible
288        {
289            use crate::sys::item::get_nearby_mergeable_items;
290
291            let positions = self.ecs().read_storage::<comp::Pos>();
292            let loot_owners = self.ecs().read_storage::<LootOwner>();
293            let mut items = self.ecs().write_storage::<comp::PickupItem>();
294            let entities = self.ecs().entities();
295            let spatial_grid = self.ecs().read_resource();
296
297            let nearby_items = get_nearby_mergeable_items(
298                &world_item,
299                &pos,
300                loot_owner.as_ref(),
301                (&entities, &items, &positions, &loot_owners, &spatial_grid),
302            );
303
304            // Merge the nearest item if possible, skip to creating a drop otherwise
305            if let Some((mergeable_item, _)) =
306                nearby_items.min_by_key(|(_, dist)| (dist * 1000.0) as i32)
307            {
308                items
309                    .get_mut(mergeable_item)
310                    .expect("we know that the item exists")
311                    .try_merge(world_item)
312                    .expect("`try_merge` should succeed because `can_merge` returned `true`");
313                return None;
314            }
315        }
316
317        let spawned_at = *self.ecs().read_resource::<Time>();
318
319        let item_body = comp::body::item::Body::from(world_item.item());
320        let body = comp::Body::Item(item_body);
321        let light_emitter = match &*world_item.item().kind() {
322            ItemKind::Lantern(lantern) => Some(comp::LightEmitter {
323                col: lantern.color(),
324                strength: lantern.strength(),
325                flicker: lantern.flicker(),
326                animated: true,
327                dir: lantern.dir,
328            }),
329            _ => None,
330        };
331        Some(
332            self.ecs_mut()
333                .create_entity_synced()
334                .with(world_item)
335                .with(pos)
336                .with(ori)
337                .with(vel)
338                .with(item_body.orientation(&mut rand::rng()))
339                .with(item_body.mass())
340                .with(item_body.density())
341                .with(body.collider())
342                .with(body)
343                .with(Object::DeleteAfter {
344                    spawned_at,
345                    // Delete the item drop after 5 minutes
346                    timeout: Duration::from_secs(300),
347                })
348                .maybe_with(loot_owner)
349                .maybe_with(light_emitter)
350                .build(),
351        )
352    }
353
354    fn create_ship<F: FnOnce(comp::ship::Body) -> comp::Collider>(
355        &mut self,
356        pos: comp::Pos,
357        ori: comp::Ori,
358        ship: comp::ship::Body,
359        make_collider: F,
360    ) -> EcsEntityBuilder<'_> {
361        let body = comp::Body::Ship(ship);
362
363        self
364            .ecs_mut()
365            .create_entity_synced()
366            .with(pos)
367            .with(comp::Vel(Vec3::zero()))
368            .with(ori)
369            .with(body.mass())
370            .with(body.density())
371            .with(make_collider(ship))
372            .with(body)
373            .with(comp::Controller::default())
374            .with(Inventory::with_empty())
375            .with(comp::CharacterState::default())
376            .with(comp::CharacterActivity::default())
377            // TODO: some of these are required in order for the
378            // character_behavior system to recognize a possesed airship;
379            // that system should be refactored to use `.maybe()`
380            .with(comp::Energy::new(ship.into()))
381            .with(comp::Stats::new({
382                // TODO: I hope it is possible to localize it safely, but
383                // at the same time it's not visible anyway, so let's postpone
384                // this.
385                Content::Plain("Airship".to_string())
386            }, body))
387            .with(comp::SkillSet::default())
388            .with(comp::ActiveAbilities::default())
389            .with(comp::Combo::default())
390    }
391
392    fn create_projectile(
393        &mut self,
394        pos: comp::Pos,
395        vel: comp::Vel,
396        body: comp::Body,
397        projectile: comp::Projectile,
398    ) -> EcsEntityBuilder<'_> {
399        let mut projectile_base = self
400            .ecs_mut()
401            .create_entity_synced()
402            .with(pos)
403            .with(vel)
404            .with(comp::Ori::from_unnormalized_vec(vel.0).unwrap_or_default())
405            .with(body.mass())
406            .with(body.density());
407
408        if projectile.is_sticky {
409            projectile_base = projectile_base.with(comp::Sticky);
410        }
411        if projectile.is_point {
412            projectile_base = projectile_base.with(comp::Collider::Point);
413        } else {
414            projectile_base = projectile_base.with(body.collider());
415        }
416
417        projectile_base.with(projectile).with(body)
418    }
419
420    fn create_shockwave(
421        &mut self,
422        properties: comp::shockwave::Properties,
423        pos: comp::Pos,
424        ori: comp::Ori,
425    ) -> EcsEntityBuilder<'_> {
426        self.ecs_mut()
427            .create_entity_synced()
428            .with(pos)
429            .with(ori)
430            .with(comp::Shockwave {
431                properties,
432                creation: None,
433            })
434            .with(comp::ShockwaveHitEntities {
435                hit_entities: Vec::<Uid>::new(),
436            })
437    }
438
439    fn create_arcing(
440        &mut self,
441        arc: comp::ArcProperties,
442        target: Uid,
443        owner: Option<Uid>,
444        pos: comp::Pos,
445    ) -> EcsEntityBuilder<'_> {
446        let time = self.get_time();
447
448        self.ecs_mut()
449            .create_entity_synced()
450            .with(pos)
451            .with(comp::Arcing {
452                properties: arc,
453                last_arc_time: Time(time),
454                hit_entities: vec![target],
455                owner,
456            })
457    }
458
459    fn create_pool(
460        &mut self,
461        properties: comp::pool::PoolProperties,
462        owner: Option<Uid>,
463        pos: comp::Pos,
464        ori: comp::Ori,
465    ) -> EcsEntityBuilder<'_> {
466        let time = self.get_time();
467        let tick_dur = properties.tick_dur;
468
469        let body = comp::Body::Object(comp::object::Body::NapalmPool);
470        self.ecs_mut()
471            .create_entity_synced()
472            .with(pos)
473            .with(ori)
474            .with(comp::Vel(Vec3::zero()))
475            .with(body.mass())
476            .with(body.density())
477            .with(body.collider())
478            .with(body)
479            .with(comp::LightEmitter {
480                col: Rgb::new(1.0, 0.35, 0.05),
481                strength: 3.5,
482                flicker: 1.0,
483                animated: true,
484                dir: None,
485            })
486            .with(comp::pool::Pool {
487                properties,
488                start_time: Time(time),
489                last_tick: Time(time - tick_dur.0),
490                owner,
491            })
492    }
493
494    fn create_safezone(&mut self, range: Option<f32>, pos: comp::Pos) -> EcsEntityBuilder<'_> {
495        use comp::{
496            aura::{Aura, AuraKind, AuraTarget, Auras},
497            buff::{BuffCategory, BuffData, BuffKind, BuffSource},
498        };
499        let time = self.get_time();
500        // TODO: Consider using the area system for this
501        self.ecs_mut()
502            .create_entity_synced()
503            .with(pos)
504            .with(Auras::new(vec![Aura::new(
505                AuraKind::Buff {
506                    kind: BuffKind::Invulnerability,
507                    data: BuffData::new(1.0, Some(Secs(1.0))),
508                    category: BuffCategory::Natural,
509                    source: BuffSource::World,
510                },
511                range.unwrap_or(100.0),
512                None,
513                AuraTarget::All,
514                Time(time),
515                None,
516            )]))
517    }
518
519    fn create_wiring(
520        &mut self,
521        pos: comp::Pos,
522        object: comp::object::Body,
523        wiring_element: wiring::WiringElement,
524    ) -> EcsEntityBuilder<'_> {
525        self.ecs_mut()
526            .create_entity_synced()
527            .with(pos)
528            .with(comp::Vel(Vec3::zero()))
529            .with(comp::Ori::default())
530            .with({
531                let body: comp::Body = object.into();
532                body.collider()
533            })
534            .with(comp::Body::Object(object))
535            .with(comp::Mass(100.0))
536            // .with(comp::Sticky)
537            .with(wiring_element)
538            .with(comp::LightEmitter {
539                col: Rgb::new(0.0, 0.0, 0.0),
540                strength: 2.0,
541                flicker: 1.0,
542                animated: true,
543                dir: None,
544            })
545    }
546
547    // NOTE: currently only used for testing
548    /// Queues chunk generation in the view distance of the persister, this
549    /// entity must be built before those chunks are received (the builder
550    /// borrows the ecs world so that is kind of impossible in practice)
551    #[cfg(feature = "worldgen")]
552    fn create_persister(
553        &mut self,
554        pos: comp::Pos,
555        view_distance: u32,
556        world: &std::sync::Arc<world::World>,
557        index: &world::IndexOwned,
558    ) -> EcsEntityBuilder<'_> {
559        use common::{terrain::TerrainChunkSize, vol::RectVolSize};
560        use std::sync::Arc;
561        // Request chunks
562        {
563            let ecs = self.ecs();
564            let slow_jobs = ecs.write_resource::<SlowJobPool>();
565            let rtsim = ecs.read_resource::<RtSim>();
566            let mut chunk_generator =
567                ecs.write_resource::<crate::chunk_generator::ChunkGenerator>();
568            let chunk_pos = self.terrain().pos_key(pos.0.map(|e| e as i32));
569            (-(view_distance as i32)..view_distance as i32 + 1)
570            .flat_map(|x| {
571                (-(view_distance as i32)..view_distance as i32 + 1).map(move |y| Vec2::new(x, y))
572            })
573            .map(|offset| offset + chunk_pos)
574            // Filter chunks outside the view distance
575            // Note: calculation from client chunk request filtering
576            .filter(|chunk_key| {
577                pos.0.xy().map(|e| e as f64).distance(
578                    chunk_key.map(|e| e as f64 + 0.5) * TerrainChunkSize::RECT_SIZE.map(|e| e as f64),
579                ) < (view_distance as f64 - 1.0 + 2.5 * 2.0_f64.sqrt())
580                    * TerrainChunkSize::RECT_SIZE.x as f64
581            })
582            .for_each(|chunk_key| {
583                {
584                    let time = (*ecs.read_resource::<TimeOfDay>(), (*ecs.read_resource::<Calendar>()).clone());
585                    chunk_generator.generate_chunk(None, chunk_key, &slow_jobs, Arc::clone(world), &rtsim, index.clone(), time);
586                }
587            });
588        }
589
590        self.ecs_mut()
591            .create_entity_synced()
592            .with(pos)
593            .with(Presence::new(
594                ViewDistances {
595                    terrain: view_distance,
596                    entity: view_distance,
597                },
598                PresenceKind::Spectator,
599            ))
600    }
601
602    fn create_teleporter(&mut self, pos: comp::Pos, portal: PortalData) -> EcsEntityBuilder<'_> {
603        self.create_object(pos, object::Body::Portal)
604            .with(comp::Immovable)
605            .with(comp::Object::from(portal))
606    }
607
608    fn initialize_character_data(
609        &mut self,
610        entity: EcsEntity,
611        character_id: CharacterId,
612        view_distances: ViewDistances,
613    ) {
614        let spawn_point = self.ecs().read_resource::<SpawnPoint>().0;
615
616        if let Some(player_uid) = self.read_component_copied::<Uid>(entity) {
617            // NOTE: By fetching the player_uid, we validated that the entity exists, and we
618            // call nothing that can delete it in any of the subsequent
619            // commands, so we can assume that all of these calls succeed,
620            // justifying ignoring the result of insertion.
621            self.write_component_ignore_entity_dead(entity, comp::Controller::default());
622            self.write_component_ignore_entity_dead(entity, comp::Pos(spawn_point));
623            self.write_component_ignore_entity_dead(entity, comp::Vel(Vec3::zero()));
624            self.write_component_ignore_entity_dead(entity, comp::Ori::default());
625            self.write_component_ignore_entity_dead(
626                entity,
627                comp::Collider::CapsulePrism(CapsulePrism {
628                    p0: Vec2::zero(),
629                    p1: Vec2::zero(),
630                    radius: 0.4,
631                    z_min: 0.0,
632                    z_max: 1.75,
633                }),
634            );
635            self.write_component_ignore_entity_dead(entity, comp::CharacterState::default());
636            self.write_component_ignore_entity_dead(entity, comp::CharacterActivity::default());
637            self.write_component_ignore_entity_dead(entity, comp::Alignment::Owned(player_uid));
638            self.write_component_ignore_entity_dead(entity, comp::Buffs::default());
639            self.write_component_ignore_entity_dead(entity, comp::Auras::default());
640            self.write_component_ignore_entity_dead(entity, comp::EnteredAuras::default());
641            self.write_component_ignore_entity_dead(entity, comp::Combo::default());
642            self.write_component_ignore_entity_dead(entity, comp::Stance::default());
643            self.write_component_ignore_entity_dead(
644                entity,
645                comp::projectile::ProjectileHitEntities::default(),
646            );
647
648            // Make sure physics components are updated
649            self.write_component_ignore_entity_dead(entity, comp::ForceUpdate::forced());
650
651            self.write_component_ignore_entity_dead(
652                entity,
653                Presence::new(view_distances, PresenceKind::LoadingCharacter(character_id)),
654            );
655
656            // Tell the client its request was successful.
657            if let Some(client) = self.ecs().read_storage::<Client>().get(entity) {
658                client.send_fallible(ServerGeneral::CharacterSuccess);
659            }
660        }
661    }
662
663    fn initialize_spectator_data(&mut self, entity: EcsEntity, view_distances: ViewDistances) {
664        let spawn_point = self.ecs().read_resource::<SpawnPoint>().0;
665
666        if self.read_component_copied::<Uid>(entity).is_some() {
667            // NOTE: By fetching the player_uid, we validated that the entity exists, and we
668            // call nothing that can delete it in any of the subsequent
669            // commands, so we can assume that all of these calls succeed,
670            // justifying ignoring the result of insertion.
671            self.write_component_ignore_entity_dead(entity, comp::Pos(spawn_point));
672
673            // Make sure physics components are updated
674            self.write_component_ignore_entity_dead(entity, comp::ForceUpdate::forced());
675
676            self.write_component_ignore_entity_dead(
677                entity,
678                Presence::new(view_distances, PresenceKind::Spectator),
679            );
680
681            // Tell the client its request was successful.
682            if let Some(client) = self.ecs().read_storage::<Client>().get(entity) {
683                client.send_fallible(ServerGeneral::SpectatorSuccess(spawn_point));
684            }
685        }
686    }
687
688    /// Returned error intended to be sent to the client.
689    fn update_character_data(
690        &mut self,
691        entity: EcsEntity,
692        components: PersistedComponents,
693    ) -> Result<(), String> {
694        let PersistedComponents {
695            body,
696            hardcore,
697            stats,
698            skill_set,
699            inventory,
700            waypoint,
701            pets,
702            active_abilities,
703            map_marker,
704        } = components;
705
706        if let Some(player_uid) = self.read_component_copied::<Uid>(entity) {
707            let result =
708                if let Some(presence) = self.ecs().write_storage::<Presence>().get_mut(entity) {
709                    if let PresenceKind::LoadingCharacter(id) = presence.kind {
710                        presence.kind = PresenceKind::Character(id);
711                        self.ecs()
712                            .write_resource::<IdMaps>()
713                            .add_character(id, entity);
714                        Ok(())
715                    } else {
716                        Err("PresenceKind is not LoadingCharacter")
717                    }
718                } else {
719                    Err("Presence component missing")
720                };
721            if let Err(err) = result {
722                let err = format!("Unexpected state when applying loaded character info: {err}");
723                error!("{err}");
724                // TODO: we could produce a `comp::Content` for this to allow localization.
725                return Err(err);
726            }
727
728            let name = stats.name.clone();
729            // NOTE: hack, read docs on body::Gender for more
730            let gender = stats.original_body.humanoid_gender();
731
732            // NOTE: By fetching the player_uid, we validated that the entity exists,
733            // and we call nothing that can delete it in any of the subsequent
734            // commands, so we can assume that all of these calls succeed,
735            // justifying ignoring the result of insertion.
736            self.write_component_ignore_entity_dead(entity, body.collider());
737            self.write_component_ignore_entity_dead(entity, body);
738            self.write_component_ignore_entity_dead(entity, body.mass());
739            self.write_component_ignore_entity_dead(entity, body.density());
740            self.write_component_ignore_entity_dead(entity, comp::Health::new(body));
741            self.write_component_ignore_entity_dead(entity, comp::Energy::new(body));
742            self.write_component_ignore_entity_dead(entity, Poise::new(body));
743            self.write_component_ignore_entity_dead(entity, stats);
744            self.write_component_ignore_entity_dead(entity, active_abilities);
745            self.write_component_ignore_entity_dead(entity, skill_set);
746            self.write_component_ignore_entity_dead(entity, inventory);
747            self.write_component_ignore_entity_dead(
748                entity,
749                comp::InventoryUpdateBuffer::new(comp::InventoryUpdateEvent::Init),
750            );
751
752            if let Some(hardcore) = hardcore {
753                self.write_component_ignore_entity_dead(entity, hardcore);
754            }
755
756            if let Some(waypoint) = waypoint {
757                self.write_component_ignore_entity_dead(entity, RepositionToFreeSpace {
758                    needs_ground: true,
759                    modify_waypoints: true,
760                });
761                self.write_component_ignore_entity_dead(entity, waypoint);
762                self.write_component_ignore_entity_dead(entity, comp::Pos(waypoint.get_pos()));
763                self.write_component_ignore_entity_dead(entity, comp::Vel(Vec3::zero()));
764                // TODO: We probably want to increment the existing force update counter since
765                // it is added in initialized_character (to be robust we can also insert it if
766                // it doesn't exist)
767                self.write_component_ignore_entity_dead(entity, comp::ForceUpdate::forced());
768            }
769
770            if let Some(map_marker) = map_marker {
771                self.write_component_ignore_entity_dead(entity, map_marker);
772            }
773
774            let player_pos = self.ecs().read_storage::<comp::Pos>().get(entity).copied();
775            if let Some(player_pos) = player_pos {
776                trace!(
777                    "Loading {} pets for player at pos {:?}",
778                    pets.len(),
779                    player_pos
780                );
781                let mut rng = rand::rng();
782
783                for (pet, body, stats) in pets {
784                    let ori = comp::Ori::from(Dir::random_2d(&mut rng));
785                    let pet_entity = self
786                        .create_npc(
787                            player_pos,
788                            ori,
789                            stats,
790                            comp::SkillSet::default(),
791                            Some(comp::Health::new(body)),
792                            Poise::new(body),
793                            Inventory::with_loadout(
794                                LoadoutBuilder::from_default(&body).build(),
795                                body,
796                            ),
797                            body,
798                            comp::Scale(1.0),
799                        )
800                        .with(comp::Vel(Vec3::new(0.0, 0.0, 0.0)))
801                        .build();
802
803                    restore_pet(self.ecs(), pet_entity, entity, pet);
804                }
805            } else {
806                error!("Player has no pos, cannot load {} pets", pets.len());
807            }
808
809            let mut char_battle_mode = self
810                .ecs()
811                .read_resource::<Settings>()
812                .gameplay
813                .battle_mode
814                .default_mode();
815            let presence_kind = self
816                .ecs()
817                .read_storage::<Presence>()
818                .get(entity)
819                .map(|p| p.kind);
820            if let Some(PresenceKind::Character(char_id)) = presence_kind {
821                if let Some(mut player_info) =
822                    self.ecs().write_storage::<comp::Player>().get_mut(entity)
823                {
824                    if let Some((mode, change)) =
825                        self.ecs().fetch::<BattleModeBuffer>().get(&char_id)
826                    {
827                        char_battle_mode = *mode;
828                        player_info.last_battlemode_change = Some(*change);
829                    } else {
830                        // TODO: this sounds related to handle_exit_ingame? Actually, sounds like
831                        // trying to place character specific info on the `Player` component. TODO
832                        // document component better.
833                        // FIXME:
834                        // ???
835                        //
836                        // This probably shouldn't exist,
837                        // but without this code, character gets battle_mode from
838                        // another character on this account.
839                        player_info.last_battlemode_change = None;
840                    }
841
842                    player_info.battle_mode = char_battle_mode;
843                }
844
845                #[cfg(feature = "worldgen")]
846                {
847                    use ::rtsim::data::{Actor, actor::SimulationMode};
848                    let actor_id = {
849                        let rtsim = self.ecs().write_resource::<RtSim>();
850                        let mut data = rtsim.state().data_mut();
851                        data
852                            .actors
853                            .iter()
854                            .find(|(_, a)| a.character().map_or(false, |c| c.id == char_id))
855                            .map(|(id, _)| id)
856                            // Create actors for characters that don't have one
857                            .unwrap_or_else(|| data.actors.create_actor(Actor::new_character(
858                                char_id,
859                                !(char_id.0 as u32), // TODO: This is a rubbish seed
860                                player_pos.map_or(Vec3::zero(), |p| p.0),
861                                // This sucks. Because the character body is not retrieved from
862                                // storage immedaitely, we have to give players a 'default body' for
863                                // a brief moment until it arrives. Don't worry, it gets replaced
864                                // very soon afterwards as part of server <-> rtsim sync.
865                                comp::Body::default(),
866                                SimulationMode::Loaded,
867                            )))
868                    };
869                    self.write_component_ignore_entity_dead(entity, actor_id);
870                    self.ecs()
871                        .write_resource::<IdMaps>()
872                        .add_rtsim(actor_id, entity);
873                }
874            }
875
876            if self
877                .ecs()
878                .read_component::<Client>()
879                .get(entity)
880                .is_some_and(|client| client.client_type.emit_login_events())
881            {
882                // Notify clients of a player list update
883                self.notify_players(ServerGeneral::PlayerListUpdate(
884                    PlayerListUpdate::SelectedCharacter(player_uid, CharacterInfo {
885                        name,
886                        gender,
887                        battle_mode: char_battle_mode,
888                    }),
889                ));
890            }
891        }
892
893        Ok(())
894    }
895
896    fn validate_chat_msg(
897        &self,
898        entity: EcsEntity,
899        chat_type: &comp::ChatType<comp::Group>,
900        msg: &Content,
901        from_client: bool,
902    ) -> bool {
903        let mut automod = self.ecs().write_resource::<AutoMod>();
904        let client = self.ecs().read_storage::<Client>();
905        let player = self.ecs().read_storage::<Player>();
906        let Some(client) = client.get(entity) else {
907            return true;
908        };
909        let Some(player) = player.get(entity) else {
910            return true;
911        };
912
913        // Don't permit players to send non-plain content sent from their clients...
914        // yet. TODO: Eventually, it would be nice for players to be able to
915        // send messages that get localised on their client!
916        let Some(msg) = msg.as_plain() else {
917            return !from_client;
918        };
919
920        match automod.validate_chat_msg(
921            player.uuid(),
922            self.ecs()
923                .read_storage::<comp::Admin>()
924                .get(entity)
925                .map(|a| a.0),
926            Instant::now(),
927            chat_type,
928            msg,
929        ) {
930            Ok(note) => {
931                if let Some(note) = note {
932                    let _ = client.send(ServerGeneral::server_msg(
933                        ChatType::CommandInfo,
934                        Content::Plain(format!("{}", note)),
935                    ));
936                }
937                true
938            },
939            Err(err) => {
940                let _ = client.send(ServerGeneral::server_msg(
941                    ChatType::CommandError,
942                    Content::Plain(format!("{}", err)),
943                ));
944                false
945            },
946        }
947    }
948
949    /// Send the chat message to the proper players. Say and region are limited
950    /// by location. Faction and group are limited by component.
951    fn send_chat(&self, msg: comp::UnresolvedChatMsg, from_client: bool) {
952        let ecs = self.ecs();
953        let is_within =
954            |target, a: &comp::Pos, b: &comp::Pos| a.0.distance_squared(b.0) < target * target;
955
956        let group_manager = ecs.read_resource::<comp::group::GroupManager>();
957        let chat_exporter = ecs.read_resource::<ChatExporter>();
958
959        let group_info = msg.get_group().and_then(|g| group_manager.group_info(*g));
960
961        if let Some(exported_message) = ChatExporter::generate(&msg, ecs) {
962            chat_exporter.send(exported_message);
963        }
964
965        let resolved_msg = msg
966            .clone()
967            .map_group(|_| group_info.map_or_else(|| "???".to_string(), |i| i.name.clone()));
968
969        let id_maps = ecs.read_resource::<IdMaps>();
970        let entity_from_uid = |uid| id_maps.uid_entity(uid);
971
972        if msg.chat_type.uid().is_none_or(|sender| {
973            entity_from_uid(sender).is_some_and(|e| {
974                self.validate_chat_msg(e, &msg.chat_type, msg.content(), from_client)
975            })
976        }) {
977            match &msg.chat_type {
978                comp::ChatType::Offline(_)
979                | comp::ChatType::CommandInfo
980                | comp::ChatType::CommandError
981                | comp::ChatType::Meta
982                | comp::ChatType::World(_) => {
983                    self.notify_players(ServerGeneral::ChatMsg(resolved_msg))
984                },
985                comp::ChatType::Online(u) => {
986                    for (client, uid) in
987                        (&ecs.read_storage::<Client>(), &ecs.read_storage::<Uid>()).join()
988                    {
989                        if uid != u {
990                            client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
991                        }
992                    }
993                },
994                &comp::ChatType::Tell(from, to) => {
995                    let clients = ecs.read_storage::<Client>();
996                    if let Some(from_client) = entity_from_uid(from).and_then(|e| clients.get(e)) {
997                        from_client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
998                    }
999                    if let Some(to_client) = entity_from_uid(to).and_then(|e| clients.get(e)) {
1000                        to_client.send_fallible(ServerGeneral::ChatMsg(resolved_msg));
1001                    }
1002                },
1003                comp::ChatType::Kill(kill_source, uid) => {
1004                    let clients = ecs.read_storage::<Client>();
1005                    let clients_count = clients.count();
1006                    // Avoid chat spam, send kill message only to group or nearby players if a
1007                    // certain amount of clients are online
1008                    if clients_count
1009                        > ecs
1010                            .fetch::<Settings>()
1011                            .max_player_for_kill_broadcast
1012                            .unwrap_or_default()
1013                    {
1014                        // Send kill message to the dead player's group
1015                        let killed_entity = entity_from_uid(*uid);
1016                        let groups = ecs.read_storage::<Group>();
1017                        let killed_group = killed_entity.and_then(|e| groups.get(e));
1018                        if let Some(g) = &killed_group {
1019                            send_to_group(g, ecs, &resolved_msg);
1020                        }
1021
1022                        // Send kill message to nearby players that aren't part of the deceased's
1023                        // group
1024                        let positions = ecs.read_storage::<comp::Pos>();
1025                        if let Some(died_player_pos) = killed_entity.and_then(|e| positions.get(e))
1026                        {
1027                            for (ent, client, pos) in
1028                                (&*ecs.entities(), &clients, &positions).join()
1029                            {
1030                                let client_group = groups.get(ent);
1031                                let is_different_group =
1032                                    !(killed_group == client_group && client_group.is_some());
1033                                if is_within(comp::ChatMsg::SAY_DISTANCE, pos, died_player_pos)
1034                                    && is_different_group
1035                                {
1036                                    client.send_fallible(ServerGeneral::ChatMsg(
1037                                        resolved_msg.clone(),
1038                                    ));
1039                                }
1040                            }
1041                        }
1042                    } else {
1043                        self.notify_players(ServerGeneral::server_msg(
1044                            comp::ChatType::Kill(kill_source.clone(), *uid),
1045                            msg.into_content(),
1046                        ))
1047                    }
1048                },
1049                comp::ChatType::Say(uid) => {
1050                    let entity_opt = entity_from_uid(*uid);
1051
1052                    let positions = ecs.read_storage::<comp::Pos>();
1053                    if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
1054                        for (client, pos) in (&ecs.read_storage::<Client>(), &positions).join() {
1055                            if is_within(comp::ChatMsg::SAY_DISTANCE, pos, speaker_pos) {
1056                                client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
1057                            }
1058                        }
1059                    }
1060                },
1061                comp::ChatType::Region(uid) => {
1062                    let entity_opt = entity_from_uid(*uid);
1063
1064                    let positions = ecs.read_storage::<comp::Pos>();
1065                    if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
1066                        for (client, pos) in (&ecs.read_storage::<Client>(), &positions).join() {
1067                            if is_within(comp::ChatMsg::REGION_DISTANCE, pos, speaker_pos) {
1068                                client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
1069                            }
1070                        }
1071                    }
1072                },
1073                comp::ChatType::Npc(uid) => {
1074                    let entity_opt = entity_from_uid(*uid);
1075
1076                    let positions = ecs.read_storage::<comp::Pos>();
1077                    if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
1078                        for (client, pos) in (&ecs.read_storage::<Client>(), &positions).join() {
1079                            if is_within(comp::ChatMsg::NPC_DISTANCE, pos, speaker_pos) {
1080                                client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
1081                            }
1082                        }
1083                    }
1084                },
1085                comp::ChatType::NpcSay(uid) => {
1086                    let entity_opt = entity_from_uid(*uid);
1087
1088                    let positions = ecs.read_storage::<comp::Pos>();
1089                    if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
1090                        for (client, pos) in (&ecs.read_storage::<Client>(), &positions).join() {
1091                            if is_within(comp::ChatMsg::NPC_SAY_DISTANCE, pos, speaker_pos) {
1092                                client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
1093                            }
1094                        }
1095                    }
1096                },
1097                &comp::ChatType::NpcTell(from, to) => {
1098                    let clients = ecs.read_storage::<Client>();
1099                    if let Some(from_client) = entity_from_uid(from).and_then(|e| clients.get(e)) {
1100                        from_client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
1101                    }
1102                    if let Some(to_client) = entity_from_uid(to).and_then(|e| clients.get(e)) {
1103                        to_client.send_fallible(ServerGeneral::ChatMsg(resolved_msg));
1104                    }
1105                },
1106                comp::ChatType::FactionMeta(s) | comp::ChatType::Faction(_, s) => {
1107                    for (client, faction) in (
1108                        &ecs.read_storage::<Client>(),
1109                        &ecs.read_storage::<comp::Faction>(),
1110                    )
1111                        .join()
1112                    {
1113                        if s == &faction.0 {
1114                            client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
1115                        }
1116                    }
1117                },
1118                comp::ChatType::Group(from, g) => {
1119                    if group_info.is_none() {
1120                        // Group not found, reply with command error
1121                        // This should usually NEVER happen since now it is checked whether the
1122                        // sender is still in the group upon emitting the message (TODO: Can this be
1123                        // triggered if the message is sent in the same tick as the sender is
1124                        // removed from the group?)
1125
1126                        let reply = comp::ChatType::CommandError
1127                            .into_msg(Content::localized("command-message-group-missing"));
1128
1129                        let clients = ecs.read_storage::<Client>();
1130                        if let Some(client) =
1131                            entity_from_uid(*from).and_then(|entity| clients.get(entity))
1132                        {
1133                            client.send_fallible(ServerGeneral::ChatMsg(reply));
1134                        }
1135                    } else {
1136                        send_to_group(g, ecs, &resolved_msg);
1137                    }
1138                },
1139                comp::ChatType::GroupMeta(g) => {
1140                    send_to_group(g, ecs, &resolved_msg);
1141                },
1142            }
1143        }
1144    }
1145
1146    /// Sends the message to all connected clients
1147    fn notify_players(&self, msg: ServerGeneral) {
1148        let mut msg = Some(msg);
1149        let mut lazy_msg = None;
1150        for (client, _) in (
1151            &self.ecs().read_storage::<Client>(),
1152            &self.ecs().read_storage::<comp::Player>(),
1153        )
1154            .join()
1155        {
1156            if let Some(msg) = msg.take() {
1157                lazy_msg = Some(client.prepare(msg));
1158            }
1159            lazy_msg.as_ref().map(|msg| client.send_prepared(msg));
1160        }
1161    }
1162
1163    /// Sends the message to all clients playing in game
1164    fn notify_in_game_clients(&self, msg: ServerGeneral) {
1165        let mut msg = Some(msg);
1166        let mut lazy_msg = None;
1167        for (client, _) in (
1168            &mut self.ecs().write_storage::<Client>(),
1169            &self.ecs().read_storage::<Presence>(),
1170        )
1171            .join()
1172        {
1173            if let Some(msg) = msg.take() {
1174                lazy_msg = Some(client.prepare(msg));
1175            }
1176            lazy_msg.as_ref().map(|msg| client.send_prepared(msg));
1177        }
1178    }
1179
1180    fn link<L: Link>(&mut self, link: L) -> Result<(), L::Error> {
1181        let linker = LinkHandle::from_link(link);
1182
1183        L::create(&linker, &mut self.ecs().system_data())?;
1184
1185        self.ecs_mut()
1186            .entry::<Vec<LinkHandle<L>>>()
1187            .or_insert_with(Vec::new)
1188            .push(linker);
1189
1190        Ok(())
1191    }
1192
1193    fn maintain_links(&mut self) {
1194        fn maintain_link<L: Link>(state: &State) {
1195            if let Some(mut handles) = state.ecs().try_fetch_mut::<Vec<LinkHandle<L>>>() {
1196                let mut persist_data = None;
1197                handles.retain(|link| {
1198                    if L::persist(
1199                        link,
1200                        persist_data.get_or_insert_with(|| state.ecs().system_data()),
1201                    ) {
1202                        true
1203                    } else {
1204                        // Make sure to drop persist data before running deletion to avoid potential
1205                        // access violations
1206                        persist_data.take();
1207                        L::delete(link, &mut state.ecs().system_data());
1208                        false
1209                    }
1210                });
1211            }
1212        }
1213
1214        maintain_link::<Mounting>(self);
1215        maintain_link::<VolumeMounting>(self);
1216        maintain_link::<Tethered>(self);
1217        maintain_link::<Interaction>(self);
1218    }
1219
1220    fn delete_entity_recorded(
1221        &mut self,
1222        entity: EcsEntity,
1223    ) -> Result<(), specs::error::WrongGeneration> {
1224        // NOTE: both this and handle_exit_ingame call delete_entity_common, so cleanup
1225        // added here may need to be duplicated in handle_exit_ingame (depending
1226        // on its nature).
1227
1228        // Remove entity from a group if they are in one.
1229        {
1230            let clients = self.ecs().read_storage::<Client>();
1231            let uids = self.ecs().read_storage::<Uid>();
1232            let mut group_manager = self.ecs().write_resource::<comp::group::GroupManager>();
1233            let map_markers = self.ecs().read_storage::<comp::MapMarker>();
1234            group_manager.entity_deleted(
1235                entity,
1236                &mut self.ecs().write_storage(),
1237                &self.ecs().read_storage(),
1238                &uids,
1239                &self.ecs().entities(),
1240                &mut |entity, group_change| {
1241                    group_change
1242                        .try_map_ref(|e| uids.get(*e).copied())
1243                        .zip(clients.get(entity))
1244                        .map(|(g, c)| {
1245                            update_map_markers(&map_markers, &uids, c, &group_change);
1246                            c.send_fallible(ServerGeneral::GroupUpdate(g));
1247                        });
1248                },
1249            );
1250        }
1251
1252        // Cancel extant trades
1253        events::shared::cancel_trades_for(self, entity);
1254
1255        // NOTE: We expect that these 3 components are never removed from an entity (nor
1256        // mutated) (at least not without updating the relevant mappings)!
1257        let maybe_uid = self.read_component_copied::<Uid>(entity);
1258        let (maybe_character, sync_me) = self
1259            .read_storage::<Presence>()
1260            .get(entity)
1261            .map(|p| (p.kind.character_id(), p.kind.sync_me()))
1262            .unzip();
1263        let maybe_rtsim = self.read_component_copied::<rtsim::ActorId>(entity);
1264
1265        self.mut_resource::<IdMaps>().remove_entity(
1266            Some(entity),
1267            maybe_uid,
1268            maybe_character.flatten(),
1269            maybe_rtsim,
1270        );
1271
1272        delete_entity_common(self, entity, maybe_uid, sync_me.unwrap_or(true))
1273    }
1274
1275    fn position_mut<T>(
1276        &mut self,
1277        entity: EcsEntity,
1278        dismount_volume: bool,
1279        f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
1280    ) -> Result<T, Content> {
1281        let ecs = self.ecs_mut();
1282        position_mut(
1283            entity,
1284            dismount_volume,
1285            f,
1286            &ecs.read_resource(),
1287            &mut ecs.write_storage(),
1288            ecs.write_storage(),
1289            ecs.write_storage(),
1290            ecs.read_storage(),
1291            ecs.read_storage(),
1292            ecs.read_storage(),
1293        )
1294    }
1295
1296    fn position_mut_reposition<T>(
1297        &mut self,
1298        entity: EcsEntity,
1299        dismount_volume: bool,
1300        f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
1301        needs_ground: bool,
1302        modify_waypoints: bool,
1303    ) -> Result<T, Content> {
1304        self.position_mut(entity, dismount_volume, f).inspect(|_| {
1305            self.write_component_ignore_entity_dead(entity, RepositionToFreeSpace {
1306                needs_ground,
1307                modify_waypoints,
1308            });
1309        })
1310    }
1311}
1312
1313pub fn position_mut<T>(
1314    entity: EcsEntity,
1315    dismount_volume: bool,
1316    f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
1317    id_maps: &IdMaps,
1318    is_volume_riders: &mut WriteStorage<Is<VolumeRider>>,
1319    mut positions: impl GenericWriteStorage<Component = comp::Pos>,
1320    mut force_updates: impl GenericWriteStorage<Component = comp::ForceUpdate>,
1321    is_riders: impl GenericReadStorage<Component = Is<Rider>>,
1322    presences: impl GenericReadStorage<Component = Presence>,
1323    clients: impl GenericReadStorage<Component = Client>,
1324) -> Result<T, Content> {
1325    if dismount_volume {
1326        is_volume_riders.remove(entity);
1327    }
1328
1329    let entity = is_riders
1330        .get(entity)
1331        .and_then(|is_rider| id_maps.uid_entity(is_rider.mount))
1332        .map(Ok)
1333        .or_else(|| {
1334            is_volume_riders.get(entity).and_then(|volume_rider| {
1335                Some(match volume_rider.pos.kind {
1336                    common::mounting::Volume::Terrain => {
1337                        Err(Content::Plain("Tried to move the world.".to_string()))
1338                    },
1339                    common::mounting::Volume::Entity(uid) => Ok(id_maps.uid_entity(uid)?),
1340                })
1341            })
1342        })
1343        .unwrap_or(Ok(entity))?;
1344
1345    let mut maybe_pos = None;
1346
1347    let res = positions
1348        .get_mut(entity)
1349        .map(|pos| {
1350            let res = f(pos);
1351            maybe_pos = Some(pos.0);
1352            res
1353        })
1354        .ok_or(Content::localized_with_args(
1355            "command-position-unavailable",
1356            [("target", "entity")],
1357        ));
1358
1359    if let Some(pos) = maybe_pos {
1360        if presences
1361            .get(entity)
1362            .map(|presence| presence.kind == PresenceKind::Spectator)
1363            .unwrap_or(false)
1364        {
1365            clients.get(entity).map(|client| {
1366                client.send_fallible(ServerGeneral::SpectatePosition(pos));
1367            });
1368        } else {
1369            force_updates
1370                .get_mut(entity)
1371                .map(|force_update| force_update.update());
1372        }
1373    }
1374
1375    res
1376}
1377
1378fn send_to_group(g: &Group, ecs: &specs::World, msg: &comp::ChatMsg) {
1379    for (client, group) in (&ecs.read_storage::<Client>(), &ecs.read_storage::<Group>()).join() {
1380        if g == group {
1381            client.send_fallible(ServerGeneral::ChatMsg(msg.clone()));
1382        }
1383    }
1384}
1385
1386/// This should only be called from `handle_exit_ingame` and
1387/// `delete_entity_recorded`!!!!!!!
1388pub(crate) fn delete_entity_common(
1389    state: &mut State,
1390    entity: EcsEntity,
1391    maybe_uid: Option<Uid>,
1392    sync_me: bool,
1393) -> Result<(), specs::error::WrongGeneration> {
1394    let maybe_pos = state.read_component_copied::<comp::Pos>(entity);
1395
1396    // Delete entity
1397    let result = state.ecs_mut().delete_entity(entity);
1398
1399    if result.is_ok() {
1400        let region_map = state.mut_resource::<common::region::RegionMap>();
1401        let region_key = region_map.entity_deleted(entity);
1402        // Note: Adding the `Uid` to the deleted list when exiting "in-game" relies on
1403        // the client not being able to immediately re-enter the game in the
1404        // same tick (since we could then mix up the ordering of things and
1405        // tell other clients to forget the new entity).
1406        //
1407        // The client will ignore requests to delete its own entity that are triggered
1408        // by this.
1409        if let Some(uid) = maybe_uid {
1410            if let Some(region_key) = region_key {
1411                state
1412                    .mut_resource::<DeletedEntities>()
1413                    .record_deleted_entity(uid, region_key);
1414            // If there is a position and sync_me is true, but the entity is not
1415            // in a region, something might be wrong.
1416            } else if sync_me && let Some(pos) = maybe_pos {
1417                // Don't panic if the entity wasn't found in a region, maybe it was just created
1418                // and then deleted before the region manager had a chance to assign it a region
1419                warn!(
1420                    ?uid,
1421                    ?pos,
1422                    "Failed to find region containing entity during entity deletion, assuming it \
1423                     wasn't sent to any clients and so deletion doesn't need to be recorded for \
1424                     sync purposes"
1425                );
1426            }
1427        } else {
1428            // For now we expect all entities have a Uid component. If this is changed, the
1429            // RegionMap needs to account for presence of Uid when deciding what should be
1430            // tracked.
1431            error!("Deleting entity without Uid component");
1432        }
1433    }
1434    result
1435}