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},
9 resources::Time,
10 uid::{IdMaps, Uid},
11};
12use common_base::span;
13use common_net::msg::{PlayerListUpdate, ServerGeneral};
14use common_state::State;
15use specs::{Builder, Entity as EcsEntity, Join, WorldExt};
16use tracing::{Instrument, debug, error, trace, warn};
17
18pub fn handle_character_delete(server: &mut Server, ev: DeleteCharacterEvent) {
19 let has_presence = {
25 let presences = server.state.ecs().read_storage::<Presence>();
26 presences.get(ev.entity).is_some()
27 };
28 if has_presence {
29 warn!(
30 ?ev.requesting_player_uuid,
31 ?ev.character_id,
32 "Character delete received while in-game, disconnecting client."
33 );
34 handle_exit_ingame(server, ev.entity, true);
35 }
36
37 let mut updater = server.state.ecs().fetch_mut::<CharacterUpdater>();
38 updater.queue_character_deletion(ev.requesting_player_uuid, ev.character_id);
39}
40
41pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity, skip_persistence: bool) {
42 span!(_guard, "handle_exit_ingame");
43 let state = server.state_mut();
44
45 let entity = if !skip_persistence {
48 persist_entity(state, entity)
49 } else {
50 entity
51 };
52
53 super::trade::cancel_trades_for(state, entity);
68
69 let maybe_group = state.read_component_copied::<group::Group>(entity);
70 let maybe_admin = state.delete_component::<comp::Admin>(entity);
71 let maybe_uid = state.delete_component::<Uid>(entity);
74
75 if let Some(client) = state.delete_component::<Client>(entity)
76 && let Some(uid) = maybe_uid
77 && let Some(player) = state.delete_component::<comp::Player>(entity)
78 {
79 client.send_fallible(ServerGeneral::ExitInGameSuccess);
81
82 if client.client_type.emit_login_events() {
83 state.notify_players(ServerGeneral::PlayerListUpdate(
84 PlayerListUpdate::ExitCharacter(uid),
85 ));
86 }
87
88 let new_entity = state
89 .ecs_mut()
90 .create_entity()
91 .with(client)
92 .with(player)
93 .maybe_with(maybe_group)
95 .maybe_with(maybe_admin)
97 .with(uid)
98 .build();
99
100 state.mut_resource::<IdMaps>().remap_entity(uid, new_entity);
102
103 let ecs = state.ecs();
104 if let Some(group) = maybe_group {
107 let mut group_manager = ecs.write_resource::<group::GroupManager>();
108 if group_manager
109 .group_info(group)
110 .map(|info| info.leader == entity)
111 .unwrap_or(false)
112 {
113 group_manager.assign_leader(
114 new_entity,
115 &ecs.read_storage(),
116 &ecs.entities(),
117 &ecs.read_storage(),
118 &ecs.read_storage(),
119 |_, _| {},
121 );
122 }
123 }
124
125 } else {
128 error!("handle_exit_ingame called with entity that is missing expected components");
129 }
130
131 let (maybe_character, sync_me) = state
132 .read_storage::<Presence>()
133 .get(entity)
134 .map(|p| (p.kind.character_id(), p.kind.sync_me()))
135 .unzip();
136 let maybe_rtsim = state.read_component_copied::<common::rtsim::RtSimEntity>(entity);
137 state.mut_resource::<IdMaps>().remove_entity(
138 Some(entity),
139 None, maybe_character.flatten(),
141 maybe_rtsim,
142 );
143
144 #[cfg(feature = "worldgen")]
147 if let Some(rtsim_entity) = maybe_rtsim {
148 let world = state.ecs().read_resource::<std::sync::Arc<world::World>>();
149 let index = state.ecs().read_resource::<world::index::IndexOwned>();
150 let pos = state.read_component_copied::<comp::Pos>(entity);
151 state
152 .ecs()
153 .write_resource::<crate::rtsim::RtSim>()
154 .hook_rtsim_actor_death(
155 &world,
156 index.as_index_ref(),
157 common::rtsim::Actor::Npc(rtsim_entity.0),
158 pos.map(|p| p.0),
159 None,
160 );
161 }
162
163 if let Err(e) =
168 crate::state_ext::delete_entity_common(state, entity, maybe_uid, sync_me.unwrap_or(true))
169 {
170 error!(
171 ?e,
172 ?entity,
173 "Failed to delete entity when removing character"
174 );
175 }
176}
177
178fn get_reason_str(reason: &comp::DisconnectReason) -> &str {
179 match reason {
180 comp::DisconnectReason::Timeout => "timeout",
181 comp::DisconnectReason::NetworkError => "network_error",
182 comp::DisconnectReason::NewerLogin => "newer_login",
183 comp::DisconnectReason::Kicked => "kicked",
184 comp::DisconnectReason::ClientRequested => "client_requested",
185 comp::DisconnectReason::InvalidClientType => "invalid_client_type",
186 }
187}
188
189pub fn handle_client_disconnect(
190 server: &mut Server,
191 mut entity: EcsEntity,
192 reason: comp::DisconnectReason,
193 skip_persistence: bool,
194) -> Event {
195 span!(_guard, "handle_client_disconnect");
196 let mut emit_logoff_event = true;
197
198 if let Some(client) = server
202 .state()
203 .ecs()
204 .write_storage::<Client>()
205 .remove(entity)
206 {
207 server
212 .state()
213 .ecs()
214 .read_resource::<PlayerMetrics>()
215 .clients_disconnected
216 .with_label_values(&[get_reason_str(&reason)])
217 .inc();
218
219 if let Some(participant) = client.participant {
220 let pid = participant.remote_pid();
221 server.runtime.spawn(
222 async {
223 let now = std::time::Instant::now();
224 debug!("Start handle disconnect of client");
225 if let Err(e) = participant.disconnect().await {
226 debug!(
227 ?e,
228 "Error when disconnecting client, maybe the pipe already broke"
229 );
230 };
231 trace!("finished disconnect");
232 let elapsed = now.elapsed();
233 if elapsed.as_millis() > 100 {
234 warn!(?elapsed, "disconnecting took quite long");
235 } else {
236 debug!(?elapsed, "disconnecting took");
237 }
238 }
239 .instrument(tracing::debug_span!(
240 "client_disconnect",
241 ?pid,
242 ?entity,
243 ?reason,
244 )),
245 );
246 }
247
248 emit_logoff_event = client.client_type.emit_login_events();
249 }
250
251 let state = server.state_mut();
252
253 if let (Some(uid), Some(_)) = (
256 state.read_storage::<Uid>().get(entity),
257 state.read_storage::<comp::Player>().get(entity),
258 ) && emit_logoff_event
259 {
260 state.notify_players(ServerGeneral::server_msg(
261 comp::ChatType::Offline(*uid),
262 Content::Plain("".to_string()),
263 ));
264
265 state.notify_players(ServerGeneral::PlayerListUpdate(PlayerListUpdate::Remove(
266 *uid,
267 )));
268 }
269
270 if !skip_persistence {
272 entity = persist_entity(state, entity);
273 }
274
275 if let Err(e) = server.state.delete_entity_recorded(entity) {
277 error!(?e, ?entity, "Failed to delete disconnected client");
278 }
279
280 Event::ClientDisconnected { entity }
281}
282
283pub(super) fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity {
295 if let (
298 Some(presence),
299 Some(skill_set),
300 Some(inventory),
301 Some(active_abilities),
302 Some(player_uid),
303 Some(player_info),
304 mut character_updater,
305 mut battlemode_buffer,
306 ) = (
307 state.read_storage::<Presence>().get(entity),
308 state.read_storage::<comp::SkillSet>().get(entity),
309 state.read_storage::<comp::Inventory>().get(entity),
310 state
311 .read_storage::<comp::ability::ActiveAbilities>()
312 .get(entity),
313 state.read_storage::<Uid>().get(entity),
314 state.read_storage::<comp::Player>().get(entity),
315 state.ecs().fetch_mut::<CharacterUpdater>(),
316 state.ecs().fetch_mut::<BattleModeBuffer>(),
317 ) {
318 match presence.kind {
319 PresenceKind::LoadingCharacter(_char_id) => {
320 error!(
321 "Unexpected state when persist_entity is called! Some of the components \
322 required above should only be present after a character is loaded!"
323 );
324 },
325 PresenceKind::Character(char_id) => {
326 if state.read_storage::<comp::Hardcore>().get(entity).is_some()
327 && state
328 .read_storage::<comp::Health>()
329 .get(entity)
330 .is_some_and(|health| health.is_dead)
331 {
332 character_updater
334 .queue_character_deletion(player_info.uuid().to_string(), char_id);
335 } else {
336 let waypoint = state
337 .ecs()
338 .read_storage::<comp::Waypoint>()
339 .get(entity)
340 .cloned();
341 let map_marker = state
342 .ecs()
343 .read_storage::<comp::MapMarker>()
344 .get(entity)
345 .cloned();
346 if let Some(change) = player_info.last_battlemode_change {
348 let mode = player_info.battle_mode;
349 let save = (mode, change);
350 battlemode_buffer.push(char_id, save);
351 }
352
353 let alignments = state.ecs().read_storage::<comp::Alignment>();
355 let bodies = state.ecs().read_storage::<comp::Body>();
356 let stats = state.ecs().read_storage::<comp::Stats>();
357 let pets = state.ecs().read_storage::<comp::Pet>();
358 let pets = (&alignments, &bodies, &stats, &pets)
359 .join()
360 .filter_map(|(alignment, body, stats, pet)| match alignment {
361 common::comp::Alignment::Owned(ref pet_owner)
365 if pet_owner == player_uid && is_tameable(body) =>
366 {
367 Some(((*pet).clone(), *body, stats.clone()))
368 },
369 _ => None,
370 })
371 .collect();
372
373 character_updater.add_pending_logout_update((
374 char_id,
375 skill_set.clone(),
376 inventory.clone(),
377 pets,
378 waypoint,
379 active_abilities.clone(),
380 map_marker,
381 ));
382 }
383 },
384 PresenceKind::Spectator => { },
385 PresenceKind::Possessor => { },
386 };
387 }
388
389 entity
390}
391
392pub fn handle_possess(
396 server: &mut Server,
397 PossessEvent(possessor_uid, possessee_uid): PossessEvent,
398) {
399 use crate::presence::RegionSubscription;
400 use common::{
401 comp::{Inventory, inventory::slot::EquipSlot, item, slot::Slot},
402 region::RegionMap,
403 };
404 use common_net::sync::WorldSyncExt;
405
406 let state = server.state_mut();
407 let mut delete_entity = None;
408
409 if let (Some(possessor), Some(possessee)) = (
410 state.ecs().entity_from_uid(possessor_uid),
411 state.ecs().entity_from_uid(possessee_uid),
412 ) {
413 let new_presence = {
416 let ecs = state.ecs();
417 if !possessor.gen().is_alive()
419 || !ecs.is_alive(possessor)
420 || !possessee.gen().is_alive()
421 || !ecs.is_alive(possessee)
422 {
423 error!(
424 "Error possessing! either the possessor entity or possessee entity no longer \
425 exists"
426 );
427 return;
428 }
429
430 let clients = ecs.read_storage::<Client>();
431 let players = ecs.read_storage::<comp::Player>();
432 let presences = ecs.read_storage::<comp::Presence>();
433
434 if clients.contains(possessee) || players.contains(possessee) {
435 error!("Can't possess other players!");
436 return;
437 }
438
439 if !clients.contains(possessor) {
440 error!("Error posessing, no `Client` component on the possessor!");
441 return;
442 }
443
444 let subscriptions = ecs.read_storage::<RegionSubscription>();
448 let region_map = ecs.read_resource::<RegionMap>();
449 let possessee_in_subscribed_region = subscriptions
450 .get(possessor)
451 .iter()
452 .flat_map(|s| s.regions.iter())
453 .filter_map(|key| region_map.get(*key))
454 .any(|region| region.entities().contains(possessee.id()));
455 if !possessee_in_subscribed_region {
456 return;
457 }
458
459 if let Some(presence) = presences.get(possessor) {
460 delete_entity = match presence.kind {
461 k @ (PresenceKind::LoadingCharacter(_) | PresenceKind::Spectator) => {
462 error!(?k, "Unexpected presence kind for a possessor.");
463 return;
464 },
465 PresenceKind::Possessor => None,
466 PresenceKind::Character(_) => Some(possessor),
469 };
470
471 Some(Presence {
472 terrain_view_distance: presence.terrain_view_distance,
473 entity_view_distance: presence.entity_view_distance,
474 kind: PresenceKind::Possessor,
477 lossy_terrain_compression: presence.lossy_terrain_compression,
478 })
479 } else {
480 None
481 }
482
483 };
485
486 let possessor = persist_entity(state, possessor);
500 let ecs = state.ecs();
501
502 let mut clients = ecs.write_storage::<Client>();
503
504 let client = clients
506 .remove(possessor)
507 .expect("Checked client component was present above!");
508 client.send_fallible(ServerGeneral::SetPlayerEntity(possessee_uid));
509 let emit_player_list_events = client.client_type.emit_login_events();
510 clients
513 .insert(possessee, client)
514 .expect("Checked entity was alive!");
515
516 fn transfer_component<C: specs::Component>(
518 storage: &mut specs::WriteStorage<'_, C>,
519 possessor: EcsEntity,
520 possessee: EcsEntity,
521 transform: impl FnOnce(C) -> C,
522 ) {
523 if let Some(c) = storage.remove(possessor) {
524 storage
527 .insert(possessee, transform(c))
528 .expect("Checked entity was alive!");
529 }
530 }
531
532 let mut players = ecs.write_storage::<comp::Player>();
533 let mut subscriptions = ecs.write_storage::<RegionSubscription>();
534 let mut admins = ecs.write_storage::<comp::Admin>();
535 let mut waypoints = ecs.write_storage::<comp::Waypoint>();
536 let mut force_updates = ecs.write_storage::<comp::ForceUpdate>();
537
538 transfer_component(&mut players, possessor, possessee, |x| x);
539 transfer_component(&mut subscriptions, possessor, possessee, |x| x);
540 transfer_component(&mut admins, possessor, possessee, |x| x);
541 transfer_component(&mut waypoints, possessor, possessee, |x| x);
542 let mut update_counter = 0;
543 transfer_component(&mut force_updates, possessor, possessee, |mut x| {
544 x.update();
545 update_counter = x.counter();
546 x
547 });
548
549 let mut presences = ecs.write_storage::<Presence>();
550 if delete_entity.is_none() {
556 presences.remove(possessor);
557 }
558 if let Some(p) = new_presence {
559 presences
560 .insert(possessee, p)
561 .expect("Checked entity was alive!");
562 }
563
564 if let Some(player) = players.get(possessee)
569 && emit_player_list_events
570 {
571 use common_net::msg;
572
573 let add_player_msg = ServerGeneral::PlayerListUpdate(PlayerListUpdate::Add(
574 possessee_uid,
575 msg::server::PlayerInfo {
576 player_alias: player.alias.clone(),
577 is_online: true,
578 is_moderator: admins.contains(possessee),
579 character: ecs.read_storage::<comp::Stats>().get(possessee).map(|s| {
580 msg::CharacterInfo {
581 name: s.name.clone(),
582 gender: s.original_body.humanoid_gender(),
584 }
585 }),
586 uuid: player.uuid(),
587 },
588 ));
589 let remove_player_msg =
590 ServerGeneral::PlayerListUpdate(PlayerListUpdate::Remove(possessor_uid));
591
592 drop((clients, players)); state.notify_players(remove_player_msg);
594 state.notify_players(add_player_msg);
595 }
596 drop(admins);
597
598 let time = ecs.read_resource::<Time>();
600 let mut inventories = ecs.write_storage::<Inventory>();
601 let mut inventory = inventories
602 .entry(possessee)
603 .expect("Nobody has &mut World, so there's no way to delete an entity.")
604 .or_insert(Inventory::with_empty());
605
606 let debug_item = comp::Item::new_from_asset_expect("common.items.debug.admin_stick");
607 if let item::ItemKind::Tool(_) = &*debug_item.kind() {
608 let leftover_items = inventory.swap(
609 Slot::Equip(EquipSlot::ActiveMainhand),
610 Slot::Equip(EquipSlot::InactiveMainhand),
611 *time,
612 );
613 assert!(
614 leftover_items.is_empty(),
615 "Swapping active and inactive mainhands never results in leftover items"
616 );
617 inventory.replace_loadout_item(EquipSlot::ActiveMainhand, Some(debug_item), *time);
618 }
619 drop(inventories);
620
621 ecs.write_storage::<comp::Agent>().remove(possessee);
623 if let Some(c) = ecs.write_storage::<comp::Controller>().get_mut(possessor) {
625 *c = Default::default();
626 }
627
628 let clients = ecs.read_storage::<Client>();
631 let client = clients
632 .get(possessee)
633 .expect("We insert this component above and have exclusive access to the world.");
634 use crate::sys::sentinel::TrackedStorages;
635 use specs::SystemData;
636 let tracked_storages = TrackedStorages::fetch(ecs);
637 let comp_sync_package = tracked_storages.create_sync_from_client_entity_switch(
638 possessor_uid,
639 possessee_uid,
640 possessee,
641 );
642 if !comp_sync_package.is_empty() {
643 client.send_fallible(ServerGeneral::CompSync(comp_sync_package, update_counter));
644 }
645 }
646
647 if let Some(entity) = delete_entity {
651 if let Err(e) = state.delete_entity_recorded(entity) {
653 error!(
654 ?e,
655 ?entity,
656 "Failed to delete entity when removing character during possession."
657 );
658 }
659 }
660}