1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
use super::Event;
use crate::{
    client::Client, metrics::PlayerMetrics, persistence::character_updater::CharacterUpdater,
    state_ext::StateExt, BattleModeBuffer, Server,
};
use common::{
    comp::{self, group, pet::is_tameable, Content, Presence, PresenceKind},
    event::{DeleteCharacterEvent, PossessEvent},
    resources::Time,
    uid::{IdMaps, Uid},
};
use common_base::span;
use common_net::msg::{PlayerListUpdate, ServerGeneral};
use common_state::State;
use specs::{Builder, Entity as EcsEntity, Join, WorldExt};
use tracing::{debug, error, trace, warn, Instrument};

pub fn handle_character_delete(server: &mut Server, ev: DeleteCharacterEvent) {
    // Can't process a character delete for a player that has an in-game presence,
    // so kick them out before processing the delete.
    // NOTE: This relies on StateExt::handle_initialize_character adding the
    // Presence component when a character is initialized to detect whether a client
    // is in-game.
    let has_presence = {
        let presences = server.state.ecs().read_storage::<Presence>();
        presences.get(ev.entity).is_some()
    };
    if has_presence {
        warn!(
            ?ev.requesting_player_uuid,
            ?ev.character_id,
            "Character delete received while in-game, disconnecting client."
        );
        handle_exit_ingame(server, ev.entity, true);
    }

    let mut updater = server.state.ecs().fetch_mut::<CharacterUpdater>();
    updater.queue_character_deletion(ev.requesting_player_uuid, ev.character_id);
}

pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity, skip_persistence: bool) {
    span!(_guard, "handle_exit_ingame");
    let state = server.state_mut();

    // Sync the player's character data to the database. This must be done before
    // removing any components from the entity
    let entity = if !skip_persistence {
        persist_entity(state, entity)
    } else {
        entity
    };

    // Create new entity with just `Client`, `Uid`, `Player`, `Admin`, `Group`
    // components.
    //
    // Easier than checking and removing all other known components.
    //
    // Also, allows clients to not update their Uid based references to this
    // client (e.g. for this specific client's knowledge of its own Uid and for
    // groups since exiting in-game does not affect group membership)
    //
    // Note: If other `ServerEvent`s are referring to this entity they will be
    // disrupted.

    // Cancel trades here since we don't use `delete_entity_recorded` and we
    // remove `Uid` below.
    super::trade::cancel_trades_for(state, entity);

    let maybe_group = state.read_component_copied::<group::Group>(entity);
    let maybe_admin = state.delete_component::<comp::Admin>(entity);
    // Not sure if we still need to actually remove the Uid or if the group
    // logic below relies on this...
    let maybe_uid = state.delete_component::<Uid>(entity);

    if let Some(client) = state.delete_component::<Client>(entity)
        && let Some(uid) = maybe_uid
        && let Some(player) = state.delete_component::<comp::Player>(entity)
    {
        // Tell client its request was successful
        client.send_fallible(ServerGeneral::ExitInGameSuccess);

        if client.client_type.emit_login_events() {
            state.notify_players(ServerGeneral::PlayerListUpdate(
                PlayerListUpdate::ExitCharacter(uid),
            ));
        }

        let new_entity = state
            .ecs_mut()
            .create_entity()
            .with(client)
            .with(player)
            // Preserve group component if present
            .maybe_with(maybe_group)
            // Preserve admin component if present
            .maybe_with(maybe_admin)
            .with(uid)
            .build();

        // Ensure IdMaps maps this uid to the new entity.
        state.mut_resource::<IdMaps>().remap_entity(uid, new_entity);

        let ecs = state.ecs();
        // Note, we use `delete_entity_common` directly to avoid
        // `delete_entity_recorded` from making any changes to the group.
        if let Some(group) = maybe_group {
            let mut group_manager = ecs.write_resource::<group::GroupManager>();
            if group_manager
                .group_info(group)
                .map(|info| info.leader == entity)
                .unwrap_or(false)
            {
                group_manager.assign_leader(
                    new_entity,
                    &ecs.read_storage(),
                    &ecs.entities(),
                    &ecs.read_storage(),
                    &ecs.read_storage(),
                    // Nothing actually changing since Uid is transferred
                    |_, _| {},
                );
            }
        }

        // delete_entity_recorded` is not used so we don't need to worry aobut
        // group restructuring when deleting this entity.
    } else {
        error!("handle_exit_ingame called with entity that is missing expected components");
    }

    let (maybe_character, sync_me) = state
        .read_storage::<Presence>()
        .get(entity)
        .map(|p| (p.kind.character_id(), p.kind.sync_me()))
        .unzip();
    let maybe_rtsim = state.read_component_copied::<common::rtsim::RtSimEntity>(entity);
    state.mut_resource::<IdMaps>().remove_entity(
        Some(entity),
        None, // Uid re-mapped, we don't want to remove the mapping
        maybe_character.flatten(),
        maybe_rtsim,
    );

    // If the character had a RtSim id (possibly from possesing an rtsim entity),
    // make rtsim aware that this entity can now be respawned.
    #[cfg(feature = "worldgen")]
    if let Some(rtsim_entity) = maybe_rtsim {
        let world = state.ecs().read_resource::<std::sync::Arc<world::World>>();
        let index = state.ecs().read_resource::<world::index::IndexOwned>();
        let pos = state.read_component_copied::<comp::Pos>(entity);
        state
            .ecs()
            .write_resource::<crate::rtsim::RtSim>()
            .hook_rtsim_actor_death(
                &world,
                index.as_index_ref(),
                common::rtsim::Actor::Npc(rtsim_entity.0),
                pos.map(|p| p.0),
                None,
            );
    }

    // We don't want to use delete_entity_recorded since we are transfering the
    // Uid to a new entity (and e.g. don't want it to be unmapped).
    //
    // Delete old entity
    if let Err(e) =
        crate::state_ext::delete_entity_common(state, entity, maybe_uid, sync_me.unwrap_or(true))
    {
        error!(
            ?e,
            ?entity,
            "Failed to delete entity when removing character"
        );
    }
}

fn get_reason_str(reason: &comp::DisconnectReason) -> &str {
    match reason {
        comp::DisconnectReason::Timeout => "timeout",
        comp::DisconnectReason::NetworkError => "network_error",
        comp::DisconnectReason::NewerLogin => "newer_login",
        comp::DisconnectReason::Kicked => "kicked",
        comp::DisconnectReason::ClientRequested => "client_requested",
        comp::DisconnectReason::InvalidClientType => "invalid_client_type",
    }
}

pub fn handle_client_disconnect(
    server: &mut Server,
    mut entity: EcsEntity,
    reason: comp::DisconnectReason,
    skip_persistence: bool,
) -> Event {
    span!(_guard, "handle_client_disconnect");
    let mut emit_logoff_event = true;

    // Entity deleted below and persist_entity doesn't require a `Client` component,
    // so we can just remove the Client component to get ownership of the
    // participant.
    if let Some(client) = server
        .state()
        .ecs()
        .write_storage::<Client>()
        .remove(entity)
    {
        // NOTE: There are not and likely will not be a way to safeguard against
        // receiving multiple `ServerEvent::ClientDisconnect` messages in a tick
        // intended for the same player, so the `None` case here is *not* a bug
        // and we should not log it as a potential issue.
        server
            .state()
            .ecs()
            .read_resource::<PlayerMetrics>()
            .clients_disconnected
            .with_label_values(&[get_reason_str(&reason)])
            .inc();

        if let Some(participant) = client.participant {
            let pid = participant.remote_pid();
            server.runtime.spawn(
                async {
                    let now = std::time::Instant::now();
                    debug!("Start handle disconnect of client");
                    if let Err(e) = participant.disconnect().await {
                        debug!(
                            ?e,
                            "Error when disconnecting client, maybe the pipe already broke"
                        );
                    };
                    trace!("finished disconnect");
                    let elapsed = now.elapsed();
                    if elapsed.as_millis() > 100 {
                        warn!(?elapsed, "disconnecting took quite long");
                    } else {
                        debug!(?elapsed, "disconnecting took");
                    }
                }
                .instrument(tracing::debug_span!(
                    "client_disconnect",
                    ?pid,
                    ?entity,
                    ?reason,
                )),
            );
        }

        emit_logoff_event = client.client_type.emit_login_events();
    }

    let state = server.state_mut();

    // Tell other clients to remove from player list
    // And send a disconnected message
    if let (Some(uid), Some(_)) = (
        state.read_storage::<Uid>().get(entity),
        state.read_storage::<comp::Player>().get(entity),
    ) && emit_logoff_event
    {
        state.notify_players(ServerGeneral::server_msg(
            comp::ChatType::Offline(*uid),
            Content::Plain("".to_string()),
        ));

        state.notify_players(ServerGeneral::PlayerListUpdate(PlayerListUpdate::Remove(
            *uid,
        )));
    }

    // Sync the player's character data to the database
    if !skip_persistence {
        entity = persist_entity(state, entity);
    }

    // Delete client entity
    if let Err(e) = server.state.delete_entity_recorded(entity) {
        error!(?e, ?entity, "Failed to delete disconnected client");
    }

    Event::ClientDisconnected { entity }
}

/// When a player logs out, their data is queued for persistence in the next
/// tick of the persistence batch update unless the character logging out is
/// dead and has hardcore enabled, in which case the character is deleted
/// instead of being persisted. The player will be temporarily unable to log in
/// during this period to avoid the race condition of their login fetching their
/// old data and overwriting the data saved here.
///
/// This function is also used by the Transform event and MUST NOT assume that
/// the persisting entity is deleted afterwards. It is however safe to assume
/// that this function will not be called twice on an entity with the same
/// character id.
pub(super) fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity {
    // NOTE: `Client` component may already be removed by the caller to close the
    // connection. Don't depend on it here!
    if let (
        Some(presence),
        Some(skill_set),
        Some(inventory),
        Some(active_abilities),
        Some(player_uid),
        Some(player_info),
        mut character_updater,
        mut battlemode_buffer,
    ) = (
        state.read_storage::<Presence>().get(entity),
        state.read_storage::<comp::SkillSet>().get(entity),
        state.read_storage::<comp::Inventory>().get(entity),
        state
            .read_storage::<comp::ability::ActiveAbilities>()
            .get(entity),
        state.read_storage::<Uid>().get(entity),
        state.read_storage::<comp::Player>().get(entity),
        state.ecs().fetch_mut::<CharacterUpdater>(),
        state.ecs().fetch_mut::<BattleModeBuffer>(),
    ) {
        match presence.kind {
            PresenceKind::LoadingCharacter(_char_id) => {
                error!(
                    "Unexpected state when persist_entity is called! Some of the components \
                     required above should only be present after a character is loaded!"
                );
            },
            PresenceKind::Character(char_id) => {
                if state.read_storage::<comp::Hardcore>().get(entity).is_some()
                    && state
                        .read_storage::<comp::Health>()
                        .get(entity)
                        .map_or(false, |health| health.is_dead)
                {
                    // Delete dead hardcore characters instead of persisting
                    character_updater
                        .queue_character_deletion(player_info.uuid().to_string(), char_id);
                } else {
                    let waypoint = state
                        .ecs()
                        .read_storage::<comp::Waypoint>()
                        .get(entity)
                        .cloned();
                    let map_marker = state
                        .ecs()
                        .read_storage::<comp::MapMarker>()
                        .get(entity)
                        .cloned();
                    // Store last battle mode change
                    if let Some(change) = player_info.last_battlemode_change {
                        let mode = player_info.battle_mode;
                        let save = (mode, change);
                        battlemode_buffer.push(char_id, save);
                    }

                    // Get player's pets
                    let alignments = state.ecs().read_storage::<comp::Alignment>();
                    let bodies = state.ecs().read_storage::<comp::Body>();
                    let stats = state.ecs().read_storage::<comp::Stats>();
                    let pets = state.ecs().read_storage::<comp::Pet>();
                    let pets = (&alignments, &bodies, &stats, &pets)
                        .join()
                        .filter_map(|(alignment, body, stats, pet)| match alignment {
                            // Don't try to persist non-tameable pets (likely spawned
                            // using /spawn) since there isn't any code to handle
                            // persisting them
                            common::comp::Alignment::Owned(ref pet_owner)
                                if pet_owner == player_uid && is_tameable(body) =>
                            {
                                Some(((*pet).clone(), *body, stats.clone()))
                            },
                            _ => None,
                        })
                        .collect();

                    character_updater.add_pending_logout_update((
                        char_id,
                        skill_set.clone(),
                        inventory.clone(),
                        pets,
                        waypoint,
                        active_abilities.clone(),
                        map_marker,
                    ));
                }
            },
            PresenceKind::Spectator => { /* Do nothing, spectators do not need persisting */ },
            PresenceKind::Possessor => { /* Do nothing, possessor's are not persisted */ },
        };
    }

    entity
}

/// FIXME: This code is dangerous and needs to be refactored.  We can't just
/// comment it out, but it needs to be fixed for a variety of reasons.  Get rid
/// of this ASAP!
pub fn handle_possess(
    server: &mut Server,
    PossessEvent(possessor_uid, possessee_uid): PossessEvent,
) {
    use crate::presence::RegionSubscription;
    use common::{
        comp::{inventory::slot::EquipSlot, item, slot::Slot, Inventory},
        region::RegionMap,
    };
    use common_net::sync::WorldSyncExt;

    let state = server.state_mut();
    let mut delete_entity = None;

    if let (Some(possessor), Some(possessee)) = (
        state.ecs().entity_from_uid(possessor_uid),
        state.ecs().entity_from_uid(possessee_uid),
    ) {
        // In this section we check various invariants and can return early if any of
        // them are not met.
        let new_presence = {
            let ecs = state.ecs();
            // Check that entities still exist
            if !possessor.gen().is_alive()
                || !ecs.is_alive(possessor)
                || !possessee.gen().is_alive()
                || !ecs.is_alive(possessee)
            {
                error!(
                    "Error possessing! either the possessor entity or possessee entity no longer \
                     exists"
                );
                return;
            }

            let clients = ecs.read_storage::<Client>();
            let players = ecs.read_storage::<comp::Player>();
            let presences = ecs.read_storage::<comp::Presence>();

            if clients.contains(possessee) || players.contains(possessee) {
                error!("Can't possess other players!");
                return;
            }

            if !clients.contains(possessor) {
                error!("Error posessing, no `Client` component on the possessor!");
                return;
            }

            // Limit possessible entities to those in the client's subscribed regions (so
            // that the entity already exists on the client, this reduces the
            // amount of syncing edge cases to consider).
            let subscriptions = ecs.read_storage::<RegionSubscription>();
            let region_map = ecs.read_resource::<RegionMap>();
            let possessee_in_subscribed_region = subscriptions
                .get(possessor)
                .iter()
                .flat_map(|s| s.regions.iter())
                .filter_map(|key| region_map.get(*key))
                .any(|region| region.entities().contains(possessee.id()));
            if !possessee_in_subscribed_region {
                return;
            }

            if let Some(presence) = presences.get(possessor) {
                delete_entity = match presence.kind {
                    k @ (PresenceKind::LoadingCharacter(_) | PresenceKind::Spectator) => {
                        error!(?k, "Unexpected presence kind for a possessor.");
                        return;
                    },
                    PresenceKind::Possessor => None,
                    // Since we call `persist_entity` below we will want to delete the entity (to
                    // avoid item duplication).
                    PresenceKind::Character(_) => Some(possessor),
                };

                Some(Presence {
                    terrain_view_distance: presence.terrain_view_distance,
                    entity_view_distance: presence.entity_view_distance,
                    // This kind (rather than copying Character presence) prevents persistence
                    // from overwriting original character info with stuff from the new character.
                    kind: PresenceKind::Possessor,
                    lossy_terrain_compression: presence.lossy_terrain_compression,
                })
            } else {
                None
            }

            // No early returns allowed after this.
        };

        // Sync the player's character data to the database. This must be done before
        // moving any components from the entity.
        //
        // NOTE: Below we delete old entity (if PresenceKind::Character) as if logging
        // out. This is to prevent any potential for item duplication (although
        // it would only be possible if the player could repossess their entity,
        // hand off some items, and then crash the server in a particular time
        // window, and only admins should have access to the item with this ability
        // in the first place (though that isn't foolproof)). We could potentially fix
        // this but it would require some tweaks to the CharacterUpdater code
        // (to be able to deque the pending persistence request issued here if
        // repossesing the original character), and it seems prudent to be more
        // conservative with making changes there to support this feature.
        let possessor = persist_entity(state, possessor);
        let ecs = state.ecs();

        let mut clients = ecs.write_storage::<Client>();

        // Transfer client component. Note: we require this component for possession.
        let client = clients
            .remove(possessor)
            .expect("Checked client component was present above!");
        client.send_fallible(ServerGeneral::SetPlayerEntity(possessee_uid));
        let emit_player_list_events = client.client_type.emit_login_events();
        // Note: we check that the `possessor` and `possessee` entities exist above, so
        // this should never panic.
        clients
            .insert(possessee, client)
            .expect("Checked entity was alive!");

        // Other components to transfer if they exist.
        fn transfer_component<C: specs::Component>(
            storage: &mut specs::WriteStorage<'_, C>,
            possessor: EcsEntity,
            possessee: EcsEntity,
            transform: impl FnOnce(C) -> C,
        ) {
            if let Some(c) = storage.remove(possessor) {
                // Note: we check that the `possessor` and `possessee` entities exist above, so
                // this should never panic.
                storage
                    .insert(possessee, transform(c))
                    .expect("Checked entity was alive!");
            }
        }

        let mut players = ecs.write_storage::<comp::Player>();
        let mut subscriptions = ecs.write_storage::<RegionSubscription>();
        let mut admins = ecs.write_storage::<comp::Admin>();
        let mut waypoints = ecs.write_storage::<comp::Waypoint>();
        let mut force_updates = ecs.write_storage::<comp::ForceUpdate>();

        transfer_component(&mut players, possessor, possessee, |x| x);
        transfer_component(&mut subscriptions, possessor, possessee, |x| x);
        transfer_component(&mut admins, possessor, possessee, |x| x);
        transfer_component(&mut waypoints, possessor, possessee, |x| x);
        let mut update_counter = 0;
        transfer_component(&mut force_updates, possessor, possessee, |mut x| {
            x.update();
            update_counter = x.counter();
            x
        });

        let mut presences = ecs.write_storage::<Presence>();
        // We leave Presence on the old entity for character IDs to be properly removed
        // from the ID mapping if deleting the previous entity.
        //
        // If the entity is not going to be deleted, we remove it so that the entity
        // doesn't keep an area loaded.
        if delete_entity.is_none() {
            presences.remove(possessor);
        }
        if let Some(p) = new_presence {
            presences
                .insert(possessee, p)
                .expect("Checked entity was alive!");
        }

        // If a player is possessing, add possessee to playerlist as player and remove
        // old player.
        // Fetches from possessee entity here since we have transferred over the
        // `Player` component.
        if let Some(player) = players.get(possessee)
            && emit_player_list_events
        {
            use common_net::msg;

            let add_player_msg = ServerGeneral::PlayerListUpdate(PlayerListUpdate::Add(
                possessee_uid,
                msg::server::PlayerInfo {
                    player_alias: player.alias.clone(),
                    is_online: true,
                    is_moderator: admins.contains(possessee),
                    character: ecs.read_storage::<comp::Stats>().get(possessee).map(|s| {
                        msg::CharacterInfo {
                            name: s.name.clone(),
                            // NOTE: hack, read docs on body::Gender for more
                            gender: s.original_body.humanoid_gender(),
                        }
                    }),
                    uuid: player.uuid(),
                },
            ));
            let remove_player_msg =
                ServerGeneral::PlayerListUpdate(PlayerListUpdate::Remove(possessor_uid));

            drop((clients, players)); // need to drop so we can use `notify_players` below
            state.notify_players(remove_player_msg);
            state.notify_players(add_player_msg);
        }
        drop(admins);

        // Put possess item into loadout
        let time = ecs.read_resource::<Time>();
        let mut inventories = ecs.write_storage::<Inventory>();
        let mut inventory = inventories
            .entry(possessee)
            .expect("Nobody has &mut World, so there's no way to delete an entity.")
            .or_insert(Inventory::with_empty());

        let debug_item = comp::Item::new_from_asset_expect("common.items.debug.admin_stick");
        if let item::ItemKind::Tool(_) = &*debug_item.kind() {
            let leftover_items = inventory.swap(
                Slot::Equip(EquipSlot::ActiveMainhand),
                Slot::Equip(EquipSlot::InactiveMainhand),
                *time,
            );
            assert!(
                leftover_items.is_empty(),
                "Swapping active and inactive mainhands never results in leftover items"
            );
            inventory.replace_loadout_item(EquipSlot::ActiveMainhand, Some(debug_item), *time);
        }
        drop(inventories);

        // Remove will of the entity
        ecs.write_storage::<comp::Agent>().remove(possessee);
        // Reset controller of former shell
        if let Some(c) = ecs.write_storage::<comp::Controller>().get_mut(possessor) {
            *c = Default::default();
        }

        // Send client new `SyncFrom::ClientEntity` components and tell it to
        // deletes these on the old entity.
        let clients = ecs.read_storage::<Client>();
        let client = clients
            .get(possessee)
            .expect("We insert this component above and have exclusive access to the world.");
        use crate::sys::sentinel::TrackedStorages;
        use specs::SystemData;
        let tracked_storages = TrackedStorages::fetch(ecs);
        let comp_sync_package = tracked_storages.create_sync_from_client_entity_switch(
            possessor_uid,
            possessee_uid,
            possessee,
        );
        if !comp_sync_package.is_empty() {
            client.send_fallible(ServerGeneral::CompSync(comp_sync_package, update_counter));
        }
    }

    // Outside block above to prevent borrow conflicts (i.e. convenient to let
    // everything drop at the end of the block rather than doing it manually for
    // this). See note on `persist_entity` call above for why we do this.
    if let Some(entity) = delete_entity {
        // Delete old entity
        if let Err(e) = state.delete_entity_recorded(entity) {
            error!(
                ?e,
                ?entity,
                "Failed to delete entity when removing character during possession."
            );
        }
    }
}