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 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 let entity = if !skip_persistence {
50 persist_entity(state, entity)
51 } else {
52 entity
53 };
54
55 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 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 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 .maybe_with(maybe_group)
97 .maybe_with(maybe_admin)
99 .with(uid)
100 .build();
101
102 state.mut_resource::<IdMaps>().remap_entity(uid, new_entity);
104
105 let ecs = state.ecs();
106 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 |_, _| {},
123 );
124 }
125 }
126
127 } 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, maybe_character.flatten(),
143 maybe_rtsim,
144 );
145
146 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 let already_disconnected = !already_disconnected_clients.insert(entity);
188
189 let mut emit_logoff_event = true;
190 let mut disconnected_event = None;
191
192 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 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 if !skip_persistence {
280 entity = persist_entity(state, entity);
281 }
282
283 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
293pub(super) fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity {
305 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 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 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 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 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 => { },
395 PresenceKind::Possessor => { },
396 };
397 }
398
399 entity
400}
401
402pub 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 let new_presence = {
426 let ecs = state.ecs();
427 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 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 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 kind: PresenceKind::Possessor,
487 lossy_terrain_compression: presence.lossy_terrain_compression,
488 })
489 } else {
490 None
491 }
492
493 };
495
496 let possessor = persist_entity(state, possessor);
510 let ecs = state.ecs();
511
512 let mut clients = ecs.write_storage::<Client>();
513
514 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 clients
523 .insert(possessee, client)
524 .expect("Checked entity was alive!");
525
526 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 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 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 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 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)); state.notify_players(remove_player_msg);
607 state.notify_players(add_player_msg);
608 }
609 drop(admins);
610
611 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 ecs.write_storage::<comp::Agent>().remove(possessee);
636 if let Some(c) = ecs.write_storage::<comp::Controller>().get_mut(possessor) {
638 *c = Default::default();
639 }
640
641 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 if let Some(entity) = delete_entity {
664 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}