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