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::RtSimEntity>(entity);
139 state.mut_resource::<IdMaps>().remove_entity(
140 Some(entity),
141 None, maybe_character.flatten(),
143 maybe_rtsim,
144 );
145
146 #[cfg(feature = "worldgen")]
149 if let Some(rtsim_entity) = maybe_rtsim {
150 let world = state.ecs().read_resource::<std::sync::Arc<world::World>>();
151 let index = state.ecs().read_resource::<world::index::IndexOwned>();
152 let pos = state.read_component_copied::<comp::Pos>(entity);
153 state
154 .ecs()
155 .write_resource::<crate::rtsim::RtSim>()
156 .hook_rtsim_actor_death(
157 &world,
158 index.as_index_ref(),
159 common::rtsim::Actor::Npc(rtsim_entity.0),
160 pos.map(|p| p.0),
161 None,
162 );
163 }
164
165 if let Err(e) =
170 crate::state_ext::delete_entity_common(state, entity, maybe_uid, sync_me.unwrap_or(true))
171 {
172 error!(
173 ?e,
174 ?entity,
175 "Failed to delete entity when removing character"
176 );
177 }
178}
179
180fn get_reason_str(reason: &comp::DisconnectReason) -> &str {
181 match reason {
182 comp::DisconnectReason::Timeout => "timeout",
183 comp::DisconnectReason::NetworkError => "network_error",
184 comp::DisconnectReason::NewerLogin => "newer_login",
185 comp::DisconnectReason::Kicked => "kicked",
186 comp::DisconnectReason::ClientRequested => "client_requested",
187 comp::DisconnectReason::InvalidClientType => "invalid_client_type",
188 }
189}
190
191#[must_use]
192pub fn handle_client_disconnect(
193 server: &mut Server,
194 mut entity: EcsEntity,
195 reason: comp::DisconnectReason,
196 skip_persistence: bool,
197 already_disconnected_clients: &mut HashSet<EcsEntity>,
198) -> Option<Event> {
199 span!(_guard, "handle_client_disconnect");
200
201 let already_disconnected = !already_disconnected_clients.insert(entity);
207
208 let mut emit_logoff_event = true;
209 let mut disconnected_event = None;
210
211 if let Some(client) = server
215 .state()
216 .ecs()
217 .write_storage::<Client>()
218 .remove(entity)
219 {
220 server
221 .state()
222 .ecs()
223 .read_resource::<PlayerMetrics>()
224 .clients_disconnected
225 .with_label_values(&[get_reason_str(&reason)])
226 .inc();
227
228 if let Some(player) = server
229 .state()
230 .ecs()
231 .read_storage::<comp::Player>()
232 .get(entity)
233 && let Some(connect_addr) = client.connected_from_addr().socket_addr()
234 {
235 server
236 .state()
237 .ecs()
238 .write_resource::<crate::RecentClientIPs>()
239 .last_addrs
240 .insert(player.uuid(), NormalizedIpAddr::from(connect_addr.ip()));
241 }
242
243 if let Some(participant) = client.participant {
244 let pid = participant.remote_pid();
245 server.runtime.spawn(
246 async {
247 let now = std::time::Instant::now();
248 debug!("Start handle disconnect of client");
249 if let Err(e) = participant.disconnect().await {
250 debug!(
251 ?e,
252 "Error when disconnecting client, maybe the pipe already broke"
253 );
254 };
255 trace!("finished disconnect");
256 let elapsed = now.elapsed();
257 if elapsed.as_millis() > 100 {
258 warn!(?elapsed, "disconnecting took quite long");
259 } else {
260 debug!(?elapsed, "disconnecting took");
261 }
262 }
263 .instrument(tracing::debug_span!(
264 "client_disconnect",
265 ?pid,
266 ?entity,
267 ?reason,
268 )),
269 );
270 } else if !already_disconnected {
271 error!("handle_client_disconnect called for entity without client component");
272 }
273
274 emit_logoff_event = client.client_type.emit_login_events();
275 disconnected_event = Some(Event::ClientDisconnected { entity });
276 }
277
278 let state = server.state_mut();
279
280 if let (Some(uid), Some(_)) = (
283 state.read_storage::<Uid>().get(entity),
284 state.read_storage::<comp::Player>().get(entity),
285 ) && emit_logoff_event
286 {
287 state.notify_players(ServerGeneral::server_msg(
288 comp::ChatType::Offline(*uid),
289 Content::Plain("".to_string()),
290 ));
291
292 state.notify_players(ServerGeneral::PlayerListUpdate(PlayerListUpdate::Remove(
293 *uid,
294 )));
295 }
296
297 if !skip_persistence {
299 entity = persist_entity(state, entity);
300 }
301
302 if let Err(e) = server.state.delete_entity_recorded(entity)
304 && !already_disconnected
305 {
306 error!(?e, ?entity, "Failed to delete disconnected client");
307 }
308
309 disconnected_event
310}
311
312pub(super) fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity {
324 if let (
327 Some(presence),
328 Some(skill_set),
329 Some(inventory),
330 Some(active_abilities),
331 Some(player_uid),
332 Some(player_info),
333 mut character_updater,
334 mut battlemode_buffer,
335 ) = (
336 state.read_storage::<Presence>().get(entity),
337 state.read_storage::<comp::SkillSet>().get(entity),
338 state.read_storage::<comp::Inventory>().get(entity),
339 state
340 .read_storage::<comp::ability::ActiveAbilities>()
341 .get(entity),
342 state.read_storage::<Uid>().get(entity),
343 state.read_storage::<comp::Player>().get(entity),
344 state.ecs().fetch_mut::<CharacterUpdater>(),
345 state.ecs().fetch_mut::<BattleModeBuffer>(),
346 ) {
347 match presence.kind {
348 PresenceKind::LoadingCharacter(_char_id) => {
349 error!(
350 "Unexpected state when persist_entity is called! Some of the components \
351 required above should only be present after a character is loaded!"
352 );
353 },
354 PresenceKind::Character(char_id) => {
355 if state.read_storage::<comp::Hardcore>().get(entity).is_some()
356 && state
357 .read_storage::<comp::Health>()
358 .get(entity)
359 .is_some_and(|health| health.is_dead)
360 {
361 character_updater
363 .queue_character_deletion(player_info.uuid().to_string(), char_id);
364 } else {
365 let waypoint = state
366 .ecs()
367 .read_storage::<comp::Waypoint>()
368 .get(entity)
369 .cloned();
370 let map_marker = state
371 .ecs()
372 .read_storage::<comp::MapMarker>()
373 .get(entity)
374 .cloned();
375 if let Some(change) = player_info.last_battlemode_change {
377 let mode = player_info.battle_mode;
378 let save = (mode, change);
379 battlemode_buffer.push(char_id, save);
380 }
381
382 let alignments = state.ecs().read_storage::<comp::Alignment>();
384 let bodies = state.ecs().read_storage::<comp::Body>();
385 let stats = state.ecs().read_storage::<comp::Stats>();
386 let pets = state.ecs().read_storage::<comp::Pet>();
387 let pets = (&alignments, &bodies, &stats, &pets)
388 .join()
389 .filter_map(|(alignment, body, stats, pet)| match alignment {
390 common::comp::Alignment::Owned(pet_owner)
394 if pet_owner == player_uid && is_tameable(body) =>
395 {
396 Some(((*pet).clone(), *body, stats.clone()))
397 },
398 _ => None,
399 })
400 .collect();
401
402 character_updater.add_pending_logout_update((
403 char_id,
404 skill_set.clone(),
405 inventory.clone(),
406 pets,
407 waypoint,
408 active_abilities.clone(),
409 map_marker,
410 ));
411 }
412 },
413 PresenceKind::Spectator => { },
414 PresenceKind::Possessor => { },
415 };
416 }
417
418 entity
419}
420
421pub fn handle_possess(
425 server: &mut Server,
426 PossessEvent(possessor_uid, possessee_uid): PossessEvent,
427) {
428 use crate::presence::RegionSubscription;
429 use common::{
430 comp::{Inventory, inventory::slot::EquipSlot, item, slot::Slot},
431 region::RegionMap,
432 };
433 use common_net::sync::WorldSyncExt;
434
435 let state = server.state_mut();
436 let mut delete_entity = None;
437
438 if let (Some(possessor), Some(possessee)) = (
439 state.ecs().entity_from_uid(possessor_uid),
440 state.ecs().entity_from_uid(possessee_uid),
441 ) {
442 let new_presence = {
445 let ecs = state.ecs();
446 if !possessor.gen().is_alive()
448 || !ecs.is_alive(possessor)
449 || !possessee.gen().is_alive()
450 || !ecs.is_alive(possessee)
451 {
452 error!(
453 "Error possessing! either the possessor entity or possessee entity no longer \
454 exists"
455 );
456 return;
457 }
458
459 let clients = ecs.read_storage::<Client>();
460 let players = ecs.read_storage::<comp::Player>();
461 let presences = ecs.read_storage::<comp::Presence>();
462
463 if clients.contains(possessee) || players.contains(possessee) {
464 error!("Can't possess other players!");
465 return;
466 }
467
468 if !clients.contains(possessor) {
469 error!("Error posessing, no `Client` component on the possessor!");
470 return;
471 }
472
473 let subscriptions = ecs.read_storage::<RegionSubscription>();
477 let region_map = ecs.read_resource::<RegionMap>();
478 let possessee_in_subscribed_region = subscriptions
479 .get(possessor)
480 .iter()
481 .flat_map(|s| s.regions.iter())
482 .filter_map(|key| region_map.get(*key))
483 .any(|region| region.entities().contains(possessee.id()));
484 if !possessee_in_subscribed_region {
485 return;
486 }
487
488 if let Some(presence) = presences.get(possessor) {
489 delete_entity = match presence.kind {
490 k @ (PresenceKind::LoadingCharacter(_) | PresenceKind::Spectator) => {
491 error!(?k, "Unexpected presence kind for a possessor.");
492 return;
493 },
494 PresenceKind::Possessor => None,
495 PresenceKind::Character(_) => Some(possessor),
498 };
499
500 Some(Presence {
501 terrain_view_distance: presence.terrain_view_distance,
502 entity_view_distance: presence.entity_view_distance,
503 kind: PresenceKind::Possessor,
506 lossy_terrain_compression: presence.lossy_terrain_compression,
507 })
508 } else {
509 None
510 }
511
512 };
514
515 let possessor = persist_entity(state, possessor);
529 let ecs = state.ecs();
530
531 let mut clients = ecs.write_storage::<Client>();
532
533 let client = clients
535 .remove(possessor)
536 .expect("Checked client component was present above!");
537 client.send_fallible(ServerGeneral::SetPlayerEntity(possessee_uid));
538 let emit_player_list_events = client.client_type.emit_login_events();
539 clients
542 .insert(possessee, client)
543 .expect("Checked entity was alive!");
544
545 fn transfer_component<C: specs::Component>(
547 storage: &mut specs::WriteStorage<'_, C>,
548 possessor: EcsEntity,
549 possessee: EcsEntity,
550 transform: impl FnOnce(C) -> C,
551 ) {
552 if let Some(c) = storage.remove(possessor) {
553 storage
556 .insert(possessee, transform(c))
557 .expect("Checked entity was alive!");
558 }
559 }
560
561 let mut players = ecs.write_storage::<comp::Player>();
562 let mut subscriptions = ecs.write_storage::<RegionSubscription>();
563 let mut admins = ecs.write_storage::<comp::Admin>();
564 let mut waypoints = ecs.write_storage::<comp::Waypoint>();
565 let mut force_updates = ecs.write_storage::<comp::ForceUpdate>();
566
567 transfer_component(&mut players, possessor, possessee, |x| x);
568 transfer_component(&mut subscriptions, possessor, possessee, |x| x);
569 transfer_component(&mut admins, possessor, possessee, |x| x);
570 transfer_component(&mut waypoints, possessor, possessee, |x| x);
571 let mut update_counter = 0;
572 transfer_component(&mut force_updates, possessor, possessee, |mut x| {
573 x.update();
574 update_counter = x.counter();
575 x
576 });
577
578 let mut presences = ecs.write_storage::<Presence>();
579 if delete_entity.is_none() {
585 presences.remove(possessor);
586 }
587 if let Some(p) = new_presence {
588 presences
589 .insert(possessee, p)
590 .expect("Checked entity was alive!");
591 }
592
593 if let Some(player) = players.get(possessee)
598 && emit_player_list_events
599 {
600 use common_net::msg;
601
602 let add_player_msg = ServerGeneral::PlayerListUpdate(PlayerListUpdate::Add(
603 possessee_uid,
604 msg::server::PlayerInfo {
605 player_alias: player.alias.clone(),
606 is_online: true,
607 is_moderator: admins.contains(possessee),
608 character: ecs.read_storage::<comp::Stats>().get(possessee).map(|s| {
609 msg::CharacterInfo {
610 name: s.name.clone(),
611 gender: s.original_body.humanoid_gender(),
613 battle_mode: player.battle_mode,
614 }
615 }),
616 uuid: player.uuid(),
617 },
618 ));
619 let remove_player_msg =
620 ServerGeneral::PlayerListUpdate(PlayerListUpdate::Remove(possessor_uid));
621
622 drop((clients, players)); state.notify_players(remove_player_msg);
624 state.notify_players(add_player_msg);
625 }
626 drop(admins);
627
628 let time = ecs.read_resource::<Time>();
630 let mut inventories = ecs.write_storage::<Inventory>();
631 let mut inventory = inventories
632 .entry(possessee)
633 .expect("Nobody has &mut World, so there's no way to delete an entity.")
634 .or_insert(Inventory::with_empty());
635
636 let debug_item = comp::Item::new_from_asset_expect("common.items.debug.admin_stick");
637 if let item::ItemKind::Tool(_) = &*debug_item.kind() {
638 let leftover_items = inventory.swap(
639 Slot::Equip(EquipSlot::ActiveMainhand),
640 Slot::Equip(EquipSlot::InactiveMainhand),
641 *time,
642 );
643 assert!(
644 leftover_items.is_empty(),
645 "Swapping active and inactive mainhands never results in leftover items"
646 );
647 inventory.replace_loadout_item(EquipSlot::ActiveMainhand, Some(debug_item), *time);
648 }
649 drop(inventories);
650
651 ecs.write_storage::<comp::Agent>().remove(possessee);
653 if let Some(c) = ecs.write_storage::<comp::Controller>().get_mut(possessor) {
655 *c = Default::default();
656 }
657
658 let clients = ecs.read_storage::<Client>();
661 let client = clients
662 .get(possessee)
663 .expect("We insert this component above and have exclusive access to the world.");
664 use crate::sys::sentinel::TrackedStorages;
665 use specs::SystemData;
666 let tracked_storages = TrackedStorages::fetch(ecs);
667 let comp_sync_package = tracked_storages.create_sync_from_client_entity_switch(
668 possessor_uid,
669 possessee_uid,
670 possessee,
671 );
672 if !comp_sync_package.is_empty() {
673 client.send_fallible(ServerGeneral::CompSync(comp_sync_package, update_counter));
674 }
675 }
676
677 if let Some(entity) = delete_entity {
681 if let Err(e) = state.delete_entity_recorded(entity) {
683 error!(
684 ?e,
685 ?entity,
686 "Failed to delete entity when removing character during possession."
687 );
688 }
689 }
690}
691
692pub fn handle_set_battle_mode(
693 server: &mut Server,
694 SetBattleModeEvent {
695 entity,
696 battle_mode,
697 }: SetBattleModeEvent,
698) {
699 server.set_battle_mode_for(entity, battle_mode);
700}