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