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