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