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 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 let entity = if !skip_persistence {
49 persist_entity(state, entity)
50 } else {
51 entity
52 };
53
54 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 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 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 .maybe_with(maybe_group)
96 .maybe_with(maybe_admin)
98 .with(uid)
99 .build();
100
101 state.mut_resource::<IdMaps>().remap_entity(uid, new_entity);
103
104 let ecs = state.ecs();
105 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 |_, _| {},
122 );
123 }
124 }
125
126 } 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, maybe_character.flatten(),
142 maybe_rtsim,
143 );
144
145 #[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 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 let already_disconnected = !already_disconnected_clients.insert(entity);
206
207 let mut emit_logoff_event = true;
208 let mut disconnected_event = None;
209
210 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 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 if !skip_persistence {
283 entity = persist_entity(state, entity);
284 }
285
286 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
296pub(super) fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity {
308 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 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 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 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 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 => { },
398 PresenceKind::Possessor => { },
399 };
400 }
401
402 entity
403}
404
405pub 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 let new_presence = {
429 let ecs = state.ecs();
430 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 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 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 kind: PresenceKind::Possessor,
490 lossy_terrain_compression: presence.lossy_terrain_compression,
491 })
492 } else {
493 None
494 }
495
496 };
498
499 let possessor = persist_entity(state, possessor);
513 let ecs = state.ecs();
514
515 let mut clients = ecs.write_storage::<Client>();
516
517 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 clients
526 .insert(possessee, client)
527 .expect("Checked entity was alive!");
528
529 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 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 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 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 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)); state.notify_players(remove_player_msg);
608 state.notify_players(add_player_msg);
609 }
610 drop(admins);
611
612 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 ecs.write_storage::<comp::Agent>().remove(possessee);
637 if let Some(c) = ecs.write_storage::<comp::Controller>().get_mut(possessor) {
639 *c = Default::default();
640 }
641
642 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 if let Some(entity) = delete_entity {
665 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}