Skip to main content

veloren_server/events/
player.rs

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