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