veloren_server/events/
player.rs

1use super::Event;
2use crate::{
3    BattleModeBuffer, Server, client::Client, metrics::PlayerMetrics,
4    persistence::character_updater::CharacterUpdater, state_ext::StateExt,
5};
6use common::{
7    comp::{self, Content, Presence, PresenceKind, group, pet::is_tameable},
8    event::{DeleteCharacterEvent, PossessEvent},
9    resources::Time,
10    uid::{IdMaps, Uid},
11};
12use common_base::span;
13use common_net::msg::{PlayerListUpdate, ServerGeneral};
14use common_state::State;
15use specs::{Builder, Entity as EcsEntity, Join, WorldExt};
16use tracing::{Instrument, debug, error, trace, warn};
17
18pub fn handle_character_delete(server: &mut Server, ev: DeleteCharacterEvent) {
19    // Can't process a character delete for a player that has an in-game presence,
20    // so kick them out before processing the delete.
21    // NOTE: This relies on StateExt::handle_initialize_character adding the
22    // Presence component when a character is initialized to detect whether a client
23    // is in-game.
24    let has_presence = {
25        let presences = server.state.ecs().read_storage::<Presence>();
26        presences.get(ev.entity).is_some()
27    };
28    if has_presence {
29        warn!(
30            ?ev.requesting_player_uuid,
31            ?ev.character_id,
32            "Character delete received while in-game, disconnecting client."
33        );
34        handle_exit_ingame(server, ev.entity, true);
35    }
36
37    let mut updater = server.state.ecs().fetch_mut::<CharacterUpdater>();
38    updater.queue_character_deletion(ev.requesting_player_uuid, ev.character_id);
39}
40
41pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity, skip_persistence: bool) {
42    span!(_guard, "handle_exit_ingame");
43    let state = server.state_mut();
44
45    // Sync the player's character data to the database. This must be done before
46    // removing any components from the entity
47    let entity = if !skip_persistence {
48        persist_entity(state, entity)
49    } else {
50        entity
51    };
52
53    // Create new entity with just `Client`, `Uid`, `Player`, `Admin`, `Group`
54    // components.
55    //
56    // Easier than checking and removing all other known components.
57    //
58    // Also, allows clients to not update their Uid based references to this
59    // client (e.g. for this specific client's knowledge of its own Uid and for
60    // groups since exiting in-game does not affect group membership)
61    //
62    // Note: If other `ServerEvent`s are referring to this entity they will be
63    // disrupted.
64
65    // Cancel trades here since we don't use `delete_entity_recorded` and we
66    // remove `Uid` below.
67    super::trade::cancel_trades_for(state, entity);
68
69    let maybe_group = state.read_component_copied::<group::Group>(entity);
70    let maybe_admin = state.delete_component::<comp::Admin>(entity);
71    // Not sure if we still need to actually remove the Uid or if the group
72    // logic below relies on this...
73    let maybe_uid = state.delete_component::<Uid>(entity);
74
75    if let Some(client) = state.delete_component::<Client>(entity)
76        && let Some(uid) = maybe_uid
77        && let Some(player) = state.delete_component::<comp::Player>(entity)
78    {
79        // Tell client its request was successful
80        client.send_fallible(ServerGeneral::ExitInGameSuccess);
81
82        if client.client_type.emit_login_events() {
83            state.notify_players(ServerGeneral::PlayerListUpdate(
84                PlayerListUpdate::ExitCharacter(uid),
85            ));
86        }
87
88        let new_entity = state
89            .ecs_mut()
90            .create_entity()
91            .with(client)
92            .with(player)
93            // Preserve group component if present
94            .maybe_with(maybe_group)
95            // Preserve admin component if present
96            .maybe_with(maybe_admin)
97            .with(uid)
98            .build();
99
100        // Ensure IdMaps maps this uid to the new entity.
101        state.mut_resource::<IdMaps>().remap_entity(uid, new_entity);
102
103        let ecs = state.ecs();
104        // Note, we use `delete_entity_common` directly to avoid
105        // `delete_entity_recorded` from making any changes to the group.
106        if let Some(group) = maybe_group {
107            let mut group_manager = ecs.write_resource::<group::GroupManager>();
108            if group_manager
109                .group_info(group)
110                .map(|info| info.leader == entity)
111                .unwrap_or(false)
112            {
113                group_manager.assign_leader(
114                    new_entity,
115                    &ecs.read_storage(),
116                    &ecs.entities(),
117                    &ecs.read_storage(),
118                    &ecs.read_storage(),
119                    // Nothing actually changing since Uid is transferred
120                    |_, _| {},
121                );
122            }
123        }
124
125        // delete_entity_recorded` is not used so we don't need to worry aobut
126        // group restructuring when deleting this entity.
127    } else {
128        error!("handle_exit_ingame called with entity that is missing expected components");
129    }
130
131    let (maybe_character, sync_me) = state
132        .read_storage::<Presence>()
133        .get(entity)
134        .map(|p| (p.kind.character_id(), p.kind.sync_me()))
135        .unzip();
136    let maybe_rtsim = state.read_component_copied::<common::rtsim::RtSimEntity>(entity);
137    state.mut_resource::<IdMaps>().remove_entity(
138        Some(entity),
139        None, // Uid re-mapped, we don't want to remove the mapping
140        maybe_character.flatten(),
141        maybe_rtsim,
142    );
143
144    // If the character had a RtSim id (possibly from possesing an rtsim entity),
145    // make rtsim aware that this entity can now be respawned.
146    #[cfg(feature = "worldgen")]
147    if let Some(rtsim_entity) = maybe_rtsim {
148        let world = state.ecs().read_resource::<std::sync::Arc<world::World>>();
149        let index = state.ecs().read_resource::<world::index::IndexOwned>();
150        let pos = state.read_component_copied::<comp::Pos>(entity);
151        state
152            .ecs()
153            .write_resource::<crate::rtsim::RtSim>()
154            .hook_rtsim_actor_death(
155                &world,
156                index.as_index_ref(),
157                common::rtsim::Actor::Npc(rtsim_entity.0),
158                pos.map(|p| p.0),
159                None,
160            );
161    }
162
163    // We don't want to use delete_entity_recorded since we are transfering the
164    // Uid to a new entity (and e.g. don't want it to be unmapped).
165    //
166    // Delete old entity
167    if let Err(e) =
168        crate::state_ext::delete_entity_common(state, entity, maybe_uid, sync_me.unwrap_or(true))
169    {
170        error!(
171            ?e,
172            ?entity,
173            "Failed to delete entity when removing character"
174        );
175    }
176}
177
178fn get_reason_str(reason: &comp::DisconnectReason) -> &str {
179    match reason {
180        comp::DisconnectReason::Timeout => "timeout",
181        comp::DisconnectReason::NetworkError => "network_error",
182        comp::DisconnectReason::NewerLogin => "newer_login",
183        comp::DisconnectReason::Kicked => "kicked",
184        comp::DisconnectReason::ClientRequested => "client_requested",
185        comp::DisconnectReason::InvalidClientType => "invalid_client_type",
186    }
187}
188
189pub fn handle_client_disconnect(
190    server: &mut Server,
191    mut entity: EcsEntity,
192    reason: comp::DisconnectReason,
193    skip_persistence: bool,
194) -> Event {
195    span!(_guard, "handle_client_disconnect");
196    let mut emit_logoff_event = true;
197
198    // Entity deleted below and persist_entity doesn't require a `Client` component,
199    // so we can just remove the Client component to get ownership of the
200    // participant.
201    if let Some(client) = server
202        .state()
203        .ecs()
204        .write_storage::<Client>()
205        .remove(entity)
206    {
207        // NOTE: There are not and likely will not be a way to safeguard against
208        // receiving multiple `ServerEvent::ClientDisconnect` messages in a tick
209        // intended for the same player, so the `None` case here is *not* a bug
210        // and we should not log it as a potential issue.
211        server
212            .state()
213            .ecs()
214            .read_resource::<PlayerMetrics>()
215            .clients_disconnected
216            .with_label_values(&[get_reason_str(&reason)])
217            .inc();
218
219        if let Some(participant) = client.participant {
220            let pid = participant.remote_pid();
221            server.runtime.spawn(
222                async {
223                    let now = std::time::Instant::now();
224                    debug!("Start handle disconnect of client");
225                    if let Err(e) = participant.disconnect().await {
226                        debug!(
227                            ?e,
228                            "Error when disconnecting client, maybe the pipe already broke"
229                        );
230                    };
231                    trace!("finished disconnect");
232                    let elapsed = now.elapsed();
233                    if elapsed.as_millis() > 100 {
234                        warn!(?elapsed, "disconnecting took quite long");
235                    } else {
236                        debug!(?elapsed, "disconnecting took");
237                    }
238                }
239                .instrument(tracing::debug_span!(
240                    "client_disconnect",
241                    ?pid,
242                    ?entity,
243                    ?reason,
244                )),
245            );
246        }
247
248        emit_logoff_event = client.client_type.emit_login_events();
249    }
250
251    let state = server.state_mut();
252
253    // Tell other clients to remove from player list
254    // And send a disconnected message
255    if let (Some(uid), Some(_)) = (
256        state.read_storage::<Uid>().get(entity),
257        state.read_storage::<comp::Player>().get(entity),
258    ) && emit_logoff_event
259    {
260        state.notify_players(ServerGeneral::server_msg(
261            comp::ChatType::Offline(*uid),
262            Content::Plain("".to_string()),
263        ));
264
265        state.notify_players(ServerGeneral::PlayerListUpdate(PlayerListUpdate::Remove(
266            *uid,
267        )));
268    }
269
270    // Sync the player's character data to the database
271    if !skip_persistence {
272        entity = persist_entity(state, entity);
273    }
274
275    // Delete client entity
276    if let Err(e) = server.state.delete_entity_recorded(entity) {
277        error!(?e, ?entity, "Failed to delete disconnected client");
278    }
279
280    Event::ClientDisconnected { entity }
281}
282
283/// When a player logs out, their data is queued for persistence in the next
284/// tick of the persistence batch update unless the character logging out is
285/// dead and has hardcore enabled, in which case the character is deleted
286/// instead of being persisted. The player will be temporarily unable to log in
287/// during this period to avoid the race condition of their login fetching their
288/// old data and overwriting the data saved here.
289///
290/// This function is also used by the Transform event and MUST NOT assume that
291/// the persisting entity is deleted afterwards. It is however safe to assume
292/// that this function will not be called twice on an entity with the same
293/// character id.
294pub(super) fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity {
295    // NOTE: `Client` component may already be removed by the caller to close the
296    // connection. Don't depend on it here!
297    if let (
298        Some(presence),
299        Some(skill_set),
300        Some(inventory),
301        Some(active_abilities),
302        Some(player_uid),
303        Some(player_info),
304        mut character_updater,
305        mut battlemode_buffer,
306    ) = (
307        state.read_storage::<Presence>().get(entity),
308        state.read_storage::<comp::SkillSet>().get(entity),
309        state.read_storage::<comp::Inventory>().get(entity),
310        state
311            .read_storage::<comp::ability::ActiveAbilities>()
312            .get(entity),
313        state.read_storage::<Uid>().get(entity),
314        state.read_storage::<comp::Player>().get(entity),
315        state.ecs().fetch_mut::<CharacterUpdater>(),
316        state.ecs().fetch_mut::<BattleModeBuffer>(),
317    ) {
318        match presence.kind {
319            PresenceKind::LoadingCharacter(_char_id) => {
320                error!(
321                    "Unexpected state when persist_entity is called! Some of the components \
322                     required above should only be present after a character is loaded!"
323                );
324            },
325            PresenceKind::Character(char_id) => {
326                if state.read_storage::<comp::Hardcore>().get(entity).is_some()
327                    && state
328                        .read_storage::<comp::Health>()
329                        .get(entity)
330                        .is_some_and(|health| health.is_dead)
331                {
332                    // Delete dead hardcore characters instead of persisting
333                    character_updater
334                        .queue_character_deletion(player_info.uuid().to_string(), char_id);
335                } else {
336                    let waypoint = state
337                        .ecs()
338                        .read_storage::<comp::Waypoint>()
339                        .get(entity)
340                        .cloned();
341                    let map_marker = state
342                        .ecs()
343                        .read_storage::<comp::MapMarker>()
344                        .get(entity)
345                        .cloned();
346                    // Store last battle mode change
347                    if let Some(change) = player_info.last_battlemode_change {
348                        let mode = player_info.battle_mode;
349                        let save = (mode, change);
350                        battlemode_buffer.push(char_id, save);
351                    }
352
353                    // Get player's pets
354                    let alignments = state.ecs().read_storage::<comp::Alignment>();
355                    let bodies = state.ecs().read_storage::<comp::Body>();
356                    let stats = state.ecs().read_storage::<comp::Stats>();
357                    let pets = state.ecs().read_storage::<comp::Pet>();
358                    let pets = (&alignments, &bodies, &stats, &pets)
359                        .join()
360                        .filter_map(|(alignment, body, stats, pet)| match alignment {
361                            // Don't try to persist non-tameable pets (likely spawned
362                            // using /spawn) since there isn't any code to handle
363                            // persisting them
364                            common::comp::Alignment::Owned(ref pet_owner)
365                                if pet_owner == player_uid && is_tameable(body) =>
366                            {
367                                Some(((*pet).clone(), *body, stats.clone()))
368                            },
369                            _ => None,
370                        })
371                        .collect();
372
373                    character_updater.add_pending_logout_update((
374                        char_id,
375                        skill_set.clone(),
376                        inventory.clone(),
377                        pets,
378                        waypoint,
379                        active_abilities.clone(),
380                        map_marker,
381                    ));
382                }
383            },
384            PresenceKind::Spectator => { /* Do nothing, spectators do not need persisting */ },
385            PresenceKind::Possessor => { /* Do nothing, possessor's are not persisted */ },
386        };
387    }
388
389    entity
390}
391
392/// FIXME: This code is dangerous and needs to be refactored.  We can't just
393/// comment it out, but it needs to be fixed for a variety of reasons.  Get rid
394/// of this ASAP!
395pub fn handle_possess(
396    server: &mut Server,
397    PossessEvent(possessor_uid, possessee_uid): PossessEvent,
398) {
399    use crate::presence::RegionSubscription;
400    use common::{
401        comp::{Inventory, inventory::slot::EquipSlot, item, slot::Slot},
402        region::RegionMap,
403    };
404    use common_net::sync::WorldSyncExt;
405
406    let state = server.state_mut();
407    let mut delete_entity = None;
408
409    if let (Some(possessor), Some(possessee)) = (
410        state.ecs().entity_from_uid(possessor_uid),
411        state.ecs().entity_from_uid(possessee_uid),
412    ) {
413        // In this section we check various invariants and can return early if any of
414        // them are not met.
415        let new_presence = {
416            let ecs = state.ecs();
417            // Check that entities still exist
418            if !possessor.gen().is_alive()
419                || !ecs.is_alive(possessor)
420                || !possessee.gen().is_alive()
421                || !ecs.is_alive(possessee)
422            {
423                error!(
424                    "Error possessing! either the possessor entity or possessee entity no longer \
425                     exists"
426                );
427                return;
428            }
429
430            let clients = ecs.read_storage::<Client>();
431            let players = ecs.read_storage::<comp::Player>();
432            let presences = ecs.read_storage::<comp::Presence>();
433
434            if clients.contains(possessee) || players.contains(possessee) {
435                error!("Can't possess other players!");
436                return;
437            }
438
439            if !clients.contains(possessor) {
440                error!("Error posessing, no `Client` component on the possessor!");
441                return;
442            }
443
444            // Limit possessible entities to those in the client's subscribed regions (so
445            // that the entity already exists on the client, this reduces the
446            // amount of syncing edge cases to consider).
447            let subscriptions = ecs.read_storage::<RegionSubscription>();
448            let region_map = ecs.read_resource::<RegionMap>();
449            let possessee_in_subscribed_region = subscriptions
450                .get(possessor)
451                .iter()
452                .flat_map(|s| s.regions.iter())
453                .filter_map(|key| region_map.get(*key))
454                .any(|region| region.entities().contains(possessee.id()));
455            if !possessee_in_subscribed_region {
456                return;
457            }
458
459            if let Some(presence) = presences.get(possessor) {
460                delete_entity = match presence.kind {
461                    k @ (PresenceKind::LoadingCharacter(_) | PresenceKind::Spectator) => {
462                        error!(?k, "Unexpected presence kind for a possessor.");
463                        return;
464                    },
465                    PresenceKind::Possessor => None,
466                    // Since we call `persist_entity` below we will want to delete the entity (to
467                    // avoid item duplication).
468                    PresenceKind::Character(_) => Some(possessor),
469                };
470
471                Some(Presence {
472                    terrain_view_distance: presence.terrain_view_distance,
473                    entity_view_distance: presence.entity_view_distance,
474                    // This kind (rather than copying Character presence) prevents persistence
475                    // from overwriting original character info with stuff from the new character.
476                    kind: PresenceKind::Possessor,
477                    lossy_terrain_compression: presence.lossy_terrain_compression,
478                })
479            } else {
480                None
481            }
482
483            // No early returns allowed after this.
484        };
485
486        // Sync the player's character data to the database. This must be done before
487        // moving any components from the entity.
488        //
489        // NOTE: Below we delete old entity (if PresenceKind::Character) as if logging
490        // out. This is to prevent any potential for item duplication (although
491        // it would only be possible if the player could repossess their entity,
492        // hand off some items, and then crash the server in a particular time
493        // window, and only admins should have access to the item with this ability
494        // in the first place (though that isn't foolproof)). We could potentially fix
495        // this but it would require some tweaks to the CharacterUpdater code
496        // (to be able to deque the pending persistence request issued here if
497        // repossesing the original character), and it seems prudent to be more
498        // conservative with making changes there to support this feature.
499        let possessor = persist_entity(state, possessor);
500        let ecs = state.ecs();
501
502        let mut clients = ecs.write_storage::<Client>();
503
504        // Transfer client component. Note: we require this component for possession.
505        let client = clients
506            .remove(possessor)
507            .expect("Checked client component was present above!");
508        client.send_fallible(ServerGeneral::SetPlayerEntity(possessee_uid));
509        let emit_player_list_events = client.client_type.emit_login_events();
510        // Note: we check that the `possessor` and `possessee` entities exist above, so
511        // this should never panic.
512        clients
513            .insert(possessee, client)
514            .expect("Checked entity was alive!");
515
516        // Other components to transfer if they exist.
517        fn transfer_component<C: specs::Component>(
518            storage: &mut specs::WriteStorage<'_, C>,
519            possessor: EcsEntity,
520            possessee: EcsEntity,
521            transform: impl FnOnce(C) -> C,
522        ) {
523            if let Some(c) = storage.remove(possessor) {
524                // Note: we check that the `possessor` and `possessee` entities exist above, so
525                // this should never panic.
526                storage
527                    .insert(possessee, transform(c))
528                    .expect("Checked entity was alive!");
529            }
530        }
531
532        let mut players = ecs.write_storage::<comp::Player>();
533        let mut subscriptions = ecs.write_storage::<RegionSubscription>();
534        let mut admins = ecs.write_storage::<comp::Admin>();
535        let mut waypoints = ecs.write_storage::<comp::Waypoint>();
536        let mut force_updates = ecs.write_storage::<comp::ForceUpdate>();
537
538        transfer_component(&mut players, possessor, possessee, |x| x);
539        transfer_component(&mut subscriptions, possessor, possessee, |x| x);
540        transfer_component(&mut admins, possessor, possessee, |x| x);
541        transfer_component(&mut waypoints, possessor, possessee, |x| x);
542        let mut update_counter = 0;
543        transfer_component(&mut force_updates, possessor, possessee, |mut x| {
544            x.update();
545            update_counter = x.counter();
546            x
547        });
548
549        let mut presences = ecs.write_storage::<Presence>();
550        // We leave Presence on the old entity for character IDs to be properly removed
551        // from the ID mapping if deleting the previous entity.
552        //
553        // If the entity is not going to be deleted, we remove it so that the entity
554        // doesn't keep an area loaded.
555        if delete_entity.is_none() {
556            presences.remove(possessor);
557        }
558        if let Some(p) = new_presence {
559            presences
560                .insert(possessee, p)
561                .expect("Checked entity was alive!");
562        }
563
564        // If a player is possessing, add possessee to playerlist as player and remove
565        // old player.
566        // Fetches from possessee entity here since we have transferred over the
567        // `Player` component.
568        if let Some(player) = players.get(possessee)
569            && emit_player_list_events
570        {
571            use common_net::msg;
572
573            let add_player_msg = ServerGeneral::PlayerListUpdate(PlayerListUpdate::Add(
574                possessee_uid,
575                msg::server::PlayerInfo {
576                    player_alias: player.alias.clone(),
577                    is_online: true,
578                    is_moderator: admins.contains(possessee),
579                    character: ecs.read_storage::<comp::Stats>().get(possessee).map(|s| {
580                        msg::CharacterInfo {
581                            name: s.name.clone(),
582                            // NOTE: hack, read docs on body::Gender for more
583                            gender: s.original_body.humanoid_gender(),
584                        }
585                    }),
586                    uuid: player.uuid(),
587                },
588            ));
589            let remove_player_msg =
590                ServerGeneral::PlayerListUpdate(PlayerListUpdate::Remove(possessor_uid));
591
592            drop((clients, players)); // need to drop so we can use `notify_players` below
593            state.notify_players(remove_player_msg);
594            state.notify_players(add_player_msg);
595        }
596        drop(admins);
597
598        // Put possess item into loadout
599        let time = ecs.read_resource::<Time>();
600        let mut inventories = ecs.write_storage::<Inventory>();
601        let mut inventory = inventories
602            .entry(possessee)
603            .expect("Nobody has &mut World, so there's no way to delete an entity.")
604            .or_insert(Inventory::with_empty());
605
606        let debug_item = comp::Item::new_from_asset_expect("common.items.debug.admin_stick");
607        if let item::ItemKind::Tool(_) = &*debug_item.kind() {
608            let leftover_items = inventory.swap(
609                Slot::Equip(EquipSlot::ActiveMainhand),
610                Slot::Equip(EquipSlot::InactiveMainhand),
611                *time,
612            );
613            assert!(
614                leftover_items.is_empty(),
615                "Swapping active and inactive mainhands never results in leftover items"
616            );
617            inventory.replace_loadout_item(EquipSlot::ActiveMainhand, Some(debug_item), *time);
618        }
619        drop(inventories);
620
621        // Remove will of the entity
622        ecs.write_storage::<comp::Agent>().remove(possessee);
623        // Reset controller of former shell
624        if let Some(c) = ecs.write_storage::<comp::Controller>().get_mut(possessor) {
625            *c = Default::default();
626        }
627
628        // Send client new `SyncFrom::ClientEntity` components and tell it to
629        // deletes these on the old entity.
630        let clients = ecs.read_storage::<Client>();
631        let client = clients
632            .get(possessee)
633            .expect("We insert this component above and have exclusive access to the world.");
634        use crate::sys::sentinel::TrackedStorages;
635        use specs::SystemData;
636        let tracked_storages = TrackedStorages::fetch(ecs);
637        let comp_sync_package = tracked_storages.create_sync_from_client_entity_switch(
638            possessor_uid,
639            possessee_uid,
640            possessee,
641        );
642        if !comp_sync_package.is_empty() {
643            client.send_fallible(ServerGeneral::CompSync(comp_sync_package, update_counter));
644        }
645    }
646
647    // Outside block above to prevent borrow conflicts (i.e. convenient to let
648    // everything drop at the end of the block rather than doing it manually for
649    // this). See note on `persist_entity` call above for why we do this.
650    if let Some(entity) = delete_entity {
651        // Delete old entity
652        if let Err(e) = state.delete_entity_recorded(entity) {
653            error!(
654                ?e,
655                ?entity,
656                "Failed to delete entity when removing character during possession."
657            );
658        }
659    }
660}