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