From d66d441f1d4b2ba082b70570911d1d7340cb3d81 Mon Sep 17 00:00:00 2001 From: TestingPlant <44930139+TestingPlant@users.noreply.github.com> Date: Tue, 8 Jul 2025 23:12:22 +0000 Subject: [PATCH 1/2] fix: make player teleport occur immediately Player positions are updated immediately when TeleportEvent is sent instead of later when the player confirms the teleportation. This allows the new position to be immediately replicated to other clients, which is also useful to allow the anticheat to continue working even if the client does not acknowledge the teleportation quickly. This also fixes a bug where a player's teleport is not synced with other players because last_tick_position is set to the current position every tick, meaning that the server will continue sending position deltas relative to the previous location instead of the full position to the teleport location. --- Cargo.toml | 1 + crates/hyperion/src/egress/player_join/mod.rs | 13 +- .../hyperion/src/egress/sync_entity_state.rs | 199 +++++++++--------- crates/hyperion/src/simulation/handlers.rs | 110 +++++----- crates/hyperion/src/simulation/mod.rs | 104 +++++++-- events/tag/src/plugin/attack.rs | 117 +++++++++- 6 files changed, 355 insertions(+), 189 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 74155a70..1dd993ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -322,6 +322,7 @@ missing_panics_doc = 'allow' module_name_repetitions = 'allow' print_stdout = 'deny' single_match_else = 'allow' +struct_excessive_bools = 'allow' too_long_first_doc_paragraph = 'allow' too_many_lines = 'allow' needless_pass_by_value = 'allow' diff --git a/crates/hyperion/src/egress/player_join/mod.rs b/crates/hyperion/src/egress/player_join/mod.rs index 5333841e..f756cd10 100644 --- a/crates/hyperion/src/egress/player_join/mod.rs +++ b/crates/hyperion/src/egress/player_join/mod.rs @@ -20,7 +20,7 @@ use valence_registry::{BiomeRegistry, RegistryCodec}; use valence_server::entity::EntityKind; use valence_text::IntoText; -use crate::simulation::{MovementTracking, Pitch, packet_state}; +use crate::simulation::{MovementTracking, Pitch, TeleportEvent, packet_state}; mod list; pub use list::*; @@ -29,9 +29,7 @@ use crate::{ config::Config, egress::metadata::show_all, net::{Compose, ConnectionId, DataBundle}, - simulation::{ - PendingTeleportation, Position, Uuid, Yaw, skin::PlayerSkin, util::registry_codec_raw, - }, + simulation::{Position, Uuid, Yaw, skin::PlayerSkin, util::registry_codec_raw}, }; #[derive(Event)] @@ -345,10 +343,15 @@ fn process_player_join( server_velocity: DVec3::ZERO, sprinting: false, was_on_ground: false, + skip_next_check: false, }, - PendingTeleportation::new(position), packet_state::Play(()), )); + commands.send_event(TeleportEvent { + player: entity_id, + destination: position, + reset_velocity: false, + }); }); info!("{name} joined the world"); diff --git a/crates/hyperion/src/egress/sync_entity_state.rs b/crates/hyperion/src/egress/sync_entity_state.rs index ac251a55..f1d2f1fb 100644 --- a/crates/hyperion/src/egress/sync_entity_state.rs +++ b/crates/hyperion/src/egress/sync_entity_state.rs @@ -14,10 +14,11 @@ use crate::{ net::{Compose, ConnectionId, DataBundle}, simulation::{ EntitySize, Flight, MovementTracking, Owner, PendingTeleportation, Pitch, Position, - Velocity, Xp, Yaw, + TeleportEvent, Velocity, Xp, Yaw, animation::ActiveAnimation, event, event::HitGroundEvent, + handle_teleports, handlers::is_grounded, metadata::{MetadataChanges, get_and_clear_metadata}, }, @@ -102,15 +103,16 @@ fn sync_player_entity( &mut Velocity, &Yaw, &Pitch, - Option<&mut PendingTeleportation>, + &mut PendingTeleportation, &mut MovementTracking, &Flight, ), >, - mut event_writer: EventWriter<'_, HitGroundEvent>, - commands: ParallelCommands<'_, '_>, + mut hit_ground_writer: EventWriter<'_, HitGroundEvent>, + mut teleport_writer: EventWriter<'_, TeleportEvent>, ) { - let events = boxcar::Vec::new(); + let hit_ground_events = boxcar::Vec::new(); + let teleport_events = boxcar::Vec::new(); query .par_iter_mut() .batching_strategy(BatchingStrategy { @@ -126,117 +128,100 @@ fn sync_player_entity( mut velocity, yaw, pitch, - pending_teleport, + mut pending_teleport, mut tracking, flight, )| { let entity_id = VarInt(entity.minecraft_id()); - if let Some(mut pending_teleport) = pending_teleport { + if !pending_teleport.confirmed { if pending_teleport.ttl == 0 { - // This needs to trigger OnInsert, so pending_teleport cannot be modified directly - commands.command_scope(|mut commands| { - commands - .entity(entity) - .insert(PendingTeleportation::new(pending_teleport.destination)); + // Resend the teleport. The player is teleported to their current position + // in case the server updated their position while pending teleport + // confirmation from the client + teleport_events.push(TeleportEvent { + player: entity, + destination: **position, + reset_velocity: false, }); } else { pending_teleport.ttl -= 1; } - } else { - let chunk_pos = position.to_chunk(); + } - let position_delta = **position - tracking.last_tick_position; - let needs_teleport = position_delta.abs().max_element() >= 8.0; - let changed_position = **position != tracking.last_tick_position; + let chunk_pos = position.to_chunk(); - let look_changed = (**yaw - ***prev_yaw).abs() >= 0.01 - || (**pitch - ***prev_pitch).abs() >= 0.01; + let position_delta = **position - tracking.last_tick_position; + let needs_teleport = position_delta.abs().max_element() >= 8.0; + let changed_position = **position != tracking.last_tick_position; - let mut bundle = DataBundle::new(&compose); + let look_changed = + (**yaw - ***prev_yaw).abs() >= 0.01 || (**pitch - ***prev_pitch).abs() >= 0.01; - // Maximum number of movement packets allowed during 1 tick is 5 - if tracking.received_movement_packets > 5 { - tracking.received_movement_packets = 1; - } + let mut bundle = DataBundle::new(&compose); + + // Maximum number of movement packets allowed during 1 tick is 5 + if tracking.received_movement_packets > 5 { + tracking.received_movement_packets = 1; + } - // Replace 100 by 300 if fall flying (aka elytra) - if f64::from(position_delta.length_squared()) + // Replace 100 by 300 if fall flying (aka elytra) + if !tracking.skip_next_check + && f64::from(position_delta.length_squared()) - tracking.server_velocity.length_squared() > 100f64 * f64::from(tracking.received_movement_packets) - { - commands.command_scope(|mut commands| { - commands - .entity(entity) - .insert(PendingTeleportation::new(tracking.last_tick_position)); - }); - tracking.received_movement_packets = 0; - return; - } + { + teleport_events.push(TeleportEvent { + player: entity, + destination: tracking.last_tick_position, + reset_velocity: false, + }); + tracking.received_movement_packets = 0; + return; + } - let grounded = is_grounded(position, &blocks); - tracking.was_on_ground = grounded; - if grounded - && !tracking.last_tick_flying - && tracking.fall_start_y - position.y > 3. - { - let event = HitGroundEvent { - client: entity, - fall_distance: tracking.fall_start_y - position.y, - }; - events.push(event); - tracking.fall_start_y = position.y; - } + let grounded = is_grounded(position, &blocks); + tracking.was_on_ground = grounded; + if grounded && !tracking.last_tick_flying && tracking.fall_start_y - position.y > 3. + { + let event = HitGroundEvent { + client: entity, + fall_distance: tracking.fall_start_y - position.y, + }; + hit_ground_events.push(event); + tracking.fall_start_y = position.y; + } - if (tracking.last_tick_flying && flight.allow) || position_delta.y >= 0. { - tracking.fall_start_y = position.y; - } + if (tracking.last_tick_flying && flight.allow) || position_delta.y >= 0. { + tracking.fall_start_y = position.y; + } - if changed_position && !needs_teleport && look_changed { - let packet = play::RotateAndMoveRelativeS2c { + if changed_position && !needs_teleport && look_changed { + let packet = play::RotateAndMoveRelativeS2c { + entity_id, + #[allow(clippy::cast_possible_truncation)] + delta: (position_delta * 4096.0).to_array().map(|x| x as i16), + yaw: ByteAngle::from_degrees(**yaw), + pitch: ByteAngle::from_degrees(**pitch), + on_ground: grounded, + }; + + bundle.add_packet(&packet).unwrap(); + } else { + if changed_position && !needs_teleport { + let packet = play::MoveRelativeS2c { entity_id, #[allow(clippy::cast_possible_truncation)] delta: (position_delta * 4096.0).to_array().map(|x| x as i16), - yaw: ByteAngle::from_degrees(**yaw), - pitch: ByteAngle::from_degrees(**pitch), on_ground: grounded, }; - bundle.add_packet(&packet).unwrap(); - } else { - if changed_position && !needs_teleport { - let packet = play::MoveRelativeS2c { - entity_id, - #[allow(clippy::cast_possible_truncation)] - delta: (position_delta * 4096.0).to_array().map(|x| x as i16), - on_ground: grounded, - }; - - bundle.add_packet(&packet).unwrap(); - } - - if look_changed { - let packet = play::RotateS2c { - entity_id, - yaw: ByteAngle::from_degrees(**yaw), - pitch: ByteAngle::from_degrees(**pitch), - on_ground: grounded, - }; - - bundle.add_packet(&packet).unwrap(); - } - let packet = play::EntitySetHeadYawS2c { - entity_id, - head_yaw: ByteAngle::from_degrees(**yaw), - }; - bundle.add_packet(&packet).unwrap(); } - if needs_teleport { - let packet = play::EntityPositionS2c { + if look_changed { + let packet = play::RotateS2c { entity_id, - position: position.as_dvec3(), yaw: ByteAngle::from_degrees(**yaw), pitch: ByteAngle::from_degrees(**pitch), on_ground: grounded, @@ -244,20 +229,39 @@ fn sync_player_entity( bundle.add_packet(&packet).unwrap(); } + let packet = play::EntitySetHeadYawS2c { + entity_id, + head_yaw: ByteAngle::from_degrees(**yaw), + }; - if velocity.0 != Vec3::ZERO { - let packet = play::EntityVelocityUpdateS2c { - entity_id, - velocity: velocity.to_packet_units(), - }; + bundle.add_packet(&packet).unwrap(); + } - bundle.add_packet(&packet).unwrap(); - velocity.0 = Vec3::ZERO; - } + if needs_teleport { + let packet = play::EntityPositionS2c { + entity_id, + position: position.as_dvec3(), + yaw: ByteAngle::from_degrees(**yaw), + pitch: ByteAngle::from_degrees(**pitch), + on_ground: grounded, + }; - bundle.broadcast_local(chunk_pos).unwrap(); + bundle.add_packet(&packet).unwrap(); } + if velocity.0 != Vec3::ZERO { + let packet = play::EntityVelocityUpdateS2c { + entity_id, + velocity: velocity.to_packet_units(), + }; + + bundle.add_packet(&packet).unwrap(); + velocity.0 = Vec3::ZERO; + } + + bundle.broadcast_local(chunk_pos).unwrap(); + + tracking.skip_next_check = false; tracking.received_movement_packets = 0; tracking.last_tick_position = **position; tracking.last_tick_flying = flight.is_flying; @@ -295,7 +299,8 @@ fn sync_player_entity( }, ); - event_writer.write_batch(events); + hit_ground_writer.write_batch(hit_ground_events); + teleport_writer.write_batch(teleport_events); } fn update_projectile_positions( @@ -385,7 +390,7 @@ impl Plugin for EntityStateSyncPlugin { entity_xp_sync, entity_metadata_sync, active_animation_sync, - sync_player_entity, + sync_player_entity.after(handle_teleports), update_projectile_positions, ), ); diff --git a/crates/hyperion/src/simulation/handlers.rs b/crates/hyperion/src/simulation/handlers.rs index f7a86346..6ab43e5d 100644 --- a/crates/hyperion/src/simulation/handlers.rs +++ b/crates/hyperion/src/simulation/handlers.rs @@ -21,11 +21,11 @@ use crate::{ net::{Compose, ConnectionId}, simulation::{ Aabb, ConfirmBlockSequences, EntitySize, Flight, MovementTracking, PendingTeleportation, - Pitch, Position, Yaw, aabb, + Pitch, Position, TeleportEvent, Yaw, aabb, animation::{self, ActiveAnimation}, block_bounds, blocks::Blocks, - event, + event, handle_teleports, metadata::{entity::Pose, living_entity::HandStates}, packet::{OrderedPacketRef, play}, }, @@ -38,7 +38,7 @@ use crate::{ functions would add complexity because most parameters will still need to be passed \ manually." )] -fn position_and_look_updates( +pub fn position_and_look_updates( mut full_reader: EventReader<'_, '_, play::Full>, mut position_reader: EventReader<'_, '_, play::PositionAndOnGround>, mut look_reader: EventReader<'_, '_, play::LookAndOnGround>, @@ -47,15 +47,24 @@ fn position_and_look_updates( '_, '_, ( - Query<'_, '_, (&EntitySize, &mut MovementTracking, &mut Position, &Yaw)>, + Query< + '_, + '_, + ( + &PendingTeleportation, + &EntitySize, + &mut MovementTracking, + &mut Position, + &Yaw, + ), + >, Query<'_, '_, (&mut Yaw, &mut Pitch)>, - Query<'_, '_, &mut Position>, + Query<'_, '_, &mut PendingTeleportation>, ), >, - teleport_query: Query<'_, '_, &PendingTeleportation>, + mut teleport_writer: EventWriter<'_, TeleportEvent>, blocks: Res<'_, Blocks>, compose: Res<'_, Compose>, - mut commands: Commands<'_, '_>, ) { let mut full_reader = full_reader.read().map(OrderedPacketRef::from).peekable(); let mut position_reader = position_reader @@ -80,9 +89,9 @@ fn position_and_look_updates( packet.sender(), packet.connection_id(), queries.p0(), + &mut teleport_writer, blocks, compose, - &mut commands, packet.position.as_vec3(), packet.on_ground, ); @@ -104,9 +113,9 @@ fn position_and_look_updates( packet.sender(), packet.connection_id(), queries.p0(), + &mut teleport_writer, blocks, compose, - &mut commands, packet.position.as_vec3(), packet.on_ground, ); @@ -125,12 +134,20 @@ fn position_and_look_updates( pitch.pitch = packet.pitch; }, packet in teleport_reader => { - let client = packet.sender(); - let Ok(pending_teleport) = teleport_query.get(client) else { - warn!("failed to confirm teleportation: client is not pending teleportation, so there is nothing to confirm"); - continue; + let mut query = queries.p2(); + let mut pending_teleport = match query.get_mut(packet.sender()) { + Ok(pending_teleport) => pending_teleport, + Err(e) => { + error!("failed to confirm teleportation: query failed: {e}"); + continue; + } }; + if pending_teleport.confirmed { + warn!("failed to confirm teleportation: client is not pending teleportation or the teleport has already been confirmed, so there is nothing to confirm"); + continue; + } + let pending_teleport_id = pending_teleport.teleport_id; if VarInt(pending_teleport_id) != packet.teleport_id { @@ -142,40 +159,7 @@ fn position_and_look_updates( continue; } - let mut query = queries.p2(); - let mut position = match query.get_mut(client) { - Ok(position) => position, - Err(e) => { - error!("failed to confirm teleportation: query failed: {e}"); - continue; - } - }; - - **position = pending_teleport.destination; - - commands.queue(move |world: &mut World| { - let Ok(mut entity) = world.get_entity_mut(client) else { - error!("failed to confirm teleportation: client entity has despawned"); - return; - }; - - let Some(pending_teleport) = entity.get::() else { - error!( - "failed to confirm teleportation: client is missing PendingTeleportation \ - component" - ); - return; - }; - - if pending_teleport.teleport_id != pending_teleport_id { - // A new pending teleport must have started between the time that this - // command was queued and the time that this command was ran. Therefore, - // this should not remove the PendingTeleportation component. - return; - } - - entity.remove::(); - }); + pending_teleport.confirmed = true; } }; if result.is_none() { @@ -187,14 +171,24 @@ fn position_and_look_updates( fn change_position_or_correct_client( client: Entity, connection_id: ConnectionId, - mut query: Query<'_, '_, (&EntitySize, &mut MovementTracking, &mut Position, &Yaw)>, + mut query: Query< + '_, + '_, + ( + &PendingTeleportation, + &EntitySize, + &mut MovementTracking, + &mut Position, + &Yaw, + ), + >, + teleport_writer: &mut EventWriter<'_, TeleportEvent>, blocks: &Blocks, compose: &Compose, - commands: &mut Commands<'_, '_>, proposed: Vec3, on_ground: bool, ) { - let (&size, mut tracking, mut pose, yaw) = match query.get_mut(client) { + let (teleport, &size, mut tracking, mut pose, yaw) = match query.get_mut(client) { Ok(data) => data, Err(e) => { error!("change_position_or_correct_client failed: query failed: {e}"); @@ -202,6 +196,12 @@ fn change_position_or_correct_client( } }; + if !teleport.confirmed { + // The client does not yet know that it has been teleported, so any position change packets + // are not useful + return; + } + if let Err(e) = try_change_position(proposed, &pose, size, blocks) { // Send error message to player let msg = format!("§c{e}"); @@ -214,9 +214,11 @@ fn change_position_or_correct_client( warn!("Failed to send error message to player: {e}"); } - commands - .entity(client) - .insert(PendingTeleportation::new(pose.position)); + teleport_writer.write(TeleportEvent { + player: client, + destination: pose.position, + reset_velocity: false, + }); } tracking.received_movement_packets = tracking.received_movement_packets.saturating_add(1); @@ -622,7 +624,7 @@ impl Plugin for HandlersPlugin { app.add_systems( FixedUpdate, ( - position_and_look_updates, + position_and_look_updates.before(handle_teleports), hand_swing, player_action, client_command, diff --git a/crates/hyperion/src/simulation/mod.rs b/crates/hyperion/src/simulation/mod.rs index d40efe1a..d4d9dc68 100644 --- a/crates/hyperion/src/simulation/mod.rs +++ b/crates/hyperion/src/simulation/mod.rs @@ -491,20 +491,43 @@ impl Velocity { } } -#[derive(Component, Default, Debug, Copy, Clone, PartialEq)] +#[derive(Event, Debug, Copy, Clone, PartialEq)] +pub struct TeleportEvent { + pub player: Entity, + pub destination: Vec3, + pub reset_velocity: bool, +} + +/// Component used to mark a pending teleportation. To teleport a player, send [`TeleportEvent`] +/// instead of modifying this directly. +#[derive(Component, Debug, Copy, Clone, PartialEq)] pub struct PendingTeleportation { pub teleport_id: i32, pub destination: Vec3, pub ttl: u8, + + /// Whether this pending teleportation has been confirmed by the client + pub confirmed: bool, } impl PendingTeleportation { #[must_use] - pub fn new(destination: Vec3) -> Self { + fn new(destination: Vec3) -> Self { Self { teleport_id: fastrand::i32(..), destination, ttl: 20, + confirmed: false, + } + } + + #[must_use] + const fn none() -> Self { + Self { + teleport_id: 0, + destination: Vec3::ZERO, + ttl: 0, + confirmed: true, } } } @@ -536,6 +559,7 @@ pub struct MovementTracking { pub server_velocity: DVec3, pub sprinting: bool, pub was_on_ground: bool, + pub skip_next_check: bool, } #[derive(Component, Default, Debug, Copy, Clone)] @@ -557,6 +581,7 @@ fn initialize_player( EntitySize::default(), Flight::default(), FlyingSpeed::default(), + PendingTeleportation::none(), hyperion_inventory::CursorItem::default(), )); @@ -638,28 +663,63 @@ fn initialize_uuid(trigger: Trigger<'_, OnAdd, EntityKind>, mut commands: Comman }); } -fn send_pending_teleportation( - trigger: Trigger<'_, OnInsert, PendingTeleportation>, - query: Query<'_, '_, (&PendingTeleportation, &Yaw, &Pitch, &ConnectionId)>, +pub fn handle_teleports( + mut reader: EventReader<'_, '_, TeleportEvent>, + mut query: Query< + '_, + '_, + ( + &mut PendingTeleportation, + &mut Position, + &mut Velocity, + &mut MovementTracking, + &Yaw, + &Pitch, + &ConnectionId, + ), + >, compose: Res<'_, Compose>, ) { - let (pending_teleportation, yaw, pitch, &connection) = match query.get(trigger.target()) { - Ok(data) => data, - Err(e) => { - error!("failed to send pending teleportation: query failed: {e}"); - return; - } - }; + for event in reader.read() { + let ( + mut pending_teleportation, + mut position, + mut velocity, + mut tracking, + yaw, + pitch, + &connection, + ) = match query.get_mut(event.player) { + Ok(data) => data, + Err(e) => { + error!("failed to handle teleport: query failed: {e}"); + return; + } + }; - let pkt = play::PlayerPositionLookS2c { - position: pending_teleportation.destination.as_dvec3(), - yaw: **yaw, - pitch: **pitch, - flags: PlayerPositionLookFlags::default(), - teleport_id: VarInt(pending_teleportation.teleport_id), - }; + *pending_teleportation = PendingTeleportation::new(event.destination); - compose.unicast(&pkt, connection).unwrap(); + let pkt = play::PlayerPositionLookS2c { + position: pending_teleportation.destination.as_dvec3(), + yaw: **yaw, + pitch: **pitch, + flags: PlayerPositionLookFlags::default(), + teleport_id: VarInt(pending_teleportation.teleport_id), + }; + + compose.unicast(&pkt, connection).unwrap(); + + // Perform teleportation + **position = pending_teleportation.destination; + tracking.received_movement_packets = 0; + tracking.skip_next_check = true; + + if event.reset_velocity { + tracking.fall_start_y = position.y; + tracking.server_velocity = DVec3::ZERO; + velocity.0 = Vec3::ZERO; + } + } } fn spawn_entities( @@ -739,7 +799,6 @@ impl Plugin for SimPlugin { fn build(&self, app: &mut App) { app.add_observer(initialize_player); app.add_observer(remove_player); - app.add_observer(send_pending_teleportation); app.add_observer(update_flight); app.add_observer(initialize_uuid); @@ -750,9 +809,10 @@ impl Plugin for SimPlugin { InventoryPlugin, MetadataPlugin, )); - app.add_systems(FixedUpdate, spawn_entities); + app.add_systems(FixedUpdate, (handle_teleports, spawn_entities)); app.add_event::(); + app.add_event::(); app.add_event::(); app.add_event::(); app.add_event::(); diff --git a/events/tag/src/plugin/attack.rs b/events/tag/src/plugin/attack.rs index e8176067..e36197cd 100644 --- a/events/tag/src/plugin/attack.rs +++ b/events/tag/src/plugin/attack.rs @@ -7,13 +7,17 @@ use glam::IVec3; use hyperion::{ BlockKind, ingress, net::{ - Compose, ConnectionId, agnostic, + Compose, ConnectionId, DataBundle, agnostic, packets::{BossBarAction, BossBarS2c}, }, runtime::AsyncRuntime, simulation::{ - PendingTeleportation, Position, Velocity, Yaw, blocks::Blocks, event, - metadata::living_entity::Health, packet::play, packet_state, + Flight, FlyingSpeed, Pitch, Position, TeleportEvent, Velocity, Xp, Yaw, + blocks::Blocks, + event, + metadata::{entity::Pose, living_entity::Health}, + packet::play, + packet_state, }, uuid::Uuid, }; @@ -22,12 +26,16 @@ use hyperion_rank_tree::Team; use hyperion_utils::{EntityExt, Prev}; use tracing::error; use valence_protocol::{ - ItemKind, ItemStack, Particle, VarInt, ident, + BlockPos, ByteAngle, GameMode, GlobalPos, ItemKind, ItemStack, Particle, VarInt, + game_mode::OptGameMode, + ident, math::{DVec3, Vec3}, packets::play::{ - DamageTiltS2c, DeathMessageS2c, EntityDamageS2c, GameMessageS2c, ParticleS2c, + DamageTiltS2c, DeathMessageS2c, EntityDamageS2c, ExperienceBarUpdateS2c, GameMessageS2c, + ParticleS2c, PlayerRespawnS2c, PlayerSpawnS2c, boss_bar_s2c::{BossBarColor, BossBarDivision, BossBarFlags}, client_status_c2s::ClientStatusC2s, + player_abilities_s2c::{PlayerAbilitiesFlags, PlayerAbilitiesS2c}, player_interact_entity_c2s::EntityInteraction, }, text::IntoText, @@ -310,18 +318,45 @@ fn handle_attacks( fn handle_respawn( mut packets: EventReader<'_, '_, play::ClientStatus>, - query: Query<'_, '_, &Team>, + mut query: Query< + '_, + '_, + ( + &hyperion::simulation::Uuid, + &Xp, + &Flight, + &FlyingSpeed, + &Team, + &Position, + &Yaw, + &Pitch, + &mut Health, + &mut Pose, + ), + >, candidates_query: Query<'_, '_, (Entity, &Position, &Team)>, mut blocks: ResMut<'_, Blocks>, runtime: Res<'_, AsyncRuntime>, - mut commands: Commands<'_, '_>, + compose: Res<'_, Compose>, + mut teleport_writer: EventWriter<'_, TeleportEvent>, ) { for packet in packets.read() { if !matches!(**packet, ClientStatusC2s::PerformRespawn) { continue; } - let team = match query.get(packet.sender()) { + let ( + uuid, + xp, + flight, + flying_speed, + team, + last_death_location, + yaw, + pitch, + mut health, + mut pose, + ) = match query.get_mut(packet.sender()) { Ok(team) => team, Err(e) => { error!("handle respawn failed: query failed: {e}"); @@ -329,6 +364,14 @@ fn handle_respawn( } }; + if !health.is_dead() { + continue; + } + + health.heal(20.); + + *pose = Pose::Standing; + let pos_vec = candidates_query .iter() .filter(|(candidate_entity, _, candidate_team)| { @@ -345,9 +388,61 @@ fn handle_respawn( find_spawn_position(&mut blocks, &runtime, &avoid_blocks()) }; - commands - .entity(packet.sender()) - .insert(PendingTeleportation::new(respawn_pos)); + teleport_writer.write(TeleportEvent { + player: packet.sender(), + destination: respawn_pos, + reset_velocity: true, + }); + + let pkt_respawn = PlayerRespawnS2c { + dimension_type_name: ident!("minecraft:overworld"), + dimension_name: ident!("minecraft:overworld"), + hashed_seed: 0, + game_mode: GameMode::Survival, + previous_game_mode: OptGameMode::default(), + is_debug: false, + is_flat: false, + copy_metadata: false, + last_death_location: Option::from(GlobalPos { + dimension_name: ident!("minecraft:overworld"), + position: BlockPos::from(last_death_location.as_dvec3()), + }), + portal_cooldown: VarInt::default(), + }; + + let pkt_xp = ExperienceBarUpdateS2c { + bar: xp.get_visual().prop, + level: VarInt(i32::from(xp.get_visual().level)), + total_xp: VarInt::default(), + }; + + let pkt_abilities = PlayerAbilitiesS2c { + flags: PlayerAbilitiesFlags::default() + .with_flying(flight.is_flying) + .with_allow_flying(flight.allow), + flying_speed: flying_speed.speed, + fov_modifier: 0.0, + }; + + let mut bundle = DataBundle::new(&compose); + bundle.add_packet(&pkt_respawn).unwrap(); + bundle.add_packet(&pkt_xp).unwrap(); + bundle.add_packet(&pkt_abilities).unwrap(); + bundle.unicast(packet.connection_id()).unwrap(); + + let pkt_add_player = PlayerSpawnS2c { + entity_id: VarInt(packet.minecraft_id()), + player_uuid: uuid.0, + position: respawn_pos.as_dvec3(), + yaw: ByteAngle::from_degrees(**yaw), + pitch: ByteAngle::from_degrees(**pitch), + }; + + compose + .broadcast(&pkt_add_player) + .exclude(packet.connection_id()) + .send() + .unwrap(); } } From 807bf72bd0433b018297cd6abd90939d5c75eaa8 Mon Sep 17 00:00:00 2001 From: TestingPlant <44930139+TestingPlant@users.noreply.github.com> Date: Tue, 8 Jul 2025 23:06:40 +0000 Subject: [PATCH 2/2] fix: remove hyperion-respawn The tag event already handles respawn. There is no need to have a second respawn handler --- Cargo.lock | 12 --- Cargo.toml | 4 - crates/hyperion-respawn/.gitignore | 1 - crates/hyperion-respawn/Cargo.toml | 17 ---- crates/hyperion-respawn/README.md | 1 - crates/hyperion-respawn/src/lib.rs | 124 ----------------------------- events/tag/Cargo.toml | 1 - events/tag/src/lib.rs | 1 - 8 files changed, 161 deletions(-) delete mode 100644 crates/hyperion-respawn/.gitignore delete mode 100644 crates/hyperion-respawn/Cargo.toml delete mode 100644 crates/hyperion-respawn/README.md delete mode 100644 crates/hyperion-respawn/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index a020d543..87f1d780 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3858,17 +3858,6 @@ dependencies = [ "valence_protocol 0.2.0-alpha.1+mc.1.20.1 (git+https://github.com/TestingPlant/valence?branch=feat-bytes)", ] -[[package]] -name = "hyperion-respawn" -version = "0.1.0" -dependencies = [ - "bevy", - "hyperion", - "tracing", - "valence_protocol 0.2.0-alpha.1+mc.1.20.1 (git+https://github.com/TestingPlant/valence?branch=feat-bytes)", - "valence_server", -] - [[package]] name = "hyperion-scheduled" version = "0.1.0" @@ -6729,7 +6718,6 @@ dependencies = [ "hyperion-permission", "hyperion-proxy-module", "hyperion-rank-tree", - "hyperion-respawn", "hyperion-scheduled", "hyperion-text", "hyperion-utils", diff --git a/Cargo.toml b/Cargo.toml index 1dd993ee..b20d4dc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,6 @@ members = [ 'crates/hyperion-proxy', 'crates/hyperion-proxy-module', 'crates/hyperion-rank-tree', - 'crates/hyperion-respawn', 'crates/hyperion-scheduled', 'crates/hyperion-stats', 'crates/hyperion-text', @@ -227,9 +226,6 @@ version = '0.3.6' features = ['rustls-tls', 'stream'] version = '0.12.12' -[workspace.dependencies.hyperion-respawn] -path = 'crates/hyperion-respawn' - [workspace.dependencies.roaring] features = ['simd'] version = '0.10.12' diff --git a/crates/hyperion-respawn/.gitignore b/crates/hyperion-respawn/.gitignore deleted file mode 100644 index ea8c4bf7..00000000 --- a/crates/hyperion-respawn/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target diff --git a/crates/hyperion-respawn/Cargo.toml b/crates/hyperion-respawn/Cargo.toml deleted file mode 100644 index a9b8014a..00000000 --- a/crates/hyperion-respawn/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "hyperion-respawn" -version = "0.1.0" -edition = "2021" -authors = ["Andrew Gazelka "] -readme = "README.md" -publish = false - -[dependencies] -bevy = {workspace = true} -hyperion = {workspace = true} -tracing = {workspace = true} -valence_protocol = {workspace = true} -valence_server = {workspace = true} - -[lints] -workspace = true diff --git a/crates/hyperion-respawn/README.md b/crates/hyperion-respawn/README.md deleted file mode 100644 index cb2e8806..00000000 --- a/crates/hyperion-respawn/README.md +++ /dev/null @@ -1 +0,0 @@ -# respawn \ No newline at end of file diff --git a/crates/hyperion-respawn/src/lib.rs b/crates/hyperion-respawn/src/lib.rs deleted file mode 100644 index 48dee2cd..00000000 --- a/crates/hyperion-respawn/src/lib.rs +++ /dev/null @@ -1,124 +0,0 @@ -use bevy::prelude::*; -use hyperion::{ - ingress, - net::{Compose, DataBundle}, - simulation::{ - metadata::{entity::Pose, living_entity::Health}, - packet::play, - Flight, FlyingSpeed, Pitch, Position, Uuid, Xp, Yaw, - }, -}; -use tracing::error; -use valence_protocol::{ - game_mode::OptGameMode, - packets::play::{ - player_abilities_s2c::{PlayerAbilitiesFlags, PlayerAbilitiesS2c}, - ClientStatusC2s, ExperienceBarUpdateS2c, HealthUpdateS2c, PlayerRespawnS2c, PlayerSpawnS2c, - }, - BlockPos, ByteAngle, GlobalPos, VarInt, -}; -use valence_server::{ident, GameMode}; - -fn handle_respawn( - mut packets: EventReader<'_, '_, play::ClientStatus>, - mut query: Query< - '_, - '_, - ( - &mut Health, - &mut Pose, - &Uuid, - &Position, - &Yaw, - &Pitch, - &Xp, - &Flight, - &FlyingSpeed, - ), - >, - compose: Res<'_, Compose>, -) { - for packet in packets.read() { - if !matches!(**packet, ClientStatusC2s::PerformRespawn) { - continue; - } - - let (mut health, mut pose, uuid, position, yaw, pitch, xp, flight, flying_speed) = - match query.get_mut(packet.sender()) { - Ok(data) => data, - Err(e) => { - error!("failed to handle respawn: query failed: {e}"); - continue; - } - }; - - health.heal(20.); - - *pose = Pose::Standing; - - let pkt_health = HealthUpdateS2c { - health: health.abs(), - food: VarInt(20), - food_saturation: 5.0, - }; - - let pkt_respawn = PlayerRespawnS2c { - dimension_type_name: ident!("minecraft:overworld"), - dimension_name: ident!("minecraft:overworld"), - hashed_seed: 0, - game_mode: GameMode::Survival, - previous_game_mode: OptGameMode::default(), - is_debug: false, - is_flat: false, - copy_metadata: false, - last_death_location: Option::from(GlobalPos { - dimension_name: ident!("minecraft:overworld"), - position: BlockPos::from(position.as_dvec3()), - }), - portal_cooldown: VarInt::default(), - }; - - let pkt_xp = ExperienceBarUpdateS2c { - bar: xp.get_visual().prop, - level: VarInt(i32::from(xp.get_visual().level)), - total_xp: VarInt::default(), - }; - - let pkt_abilities = PlayerAbilitiesS2c { - flags: PlayerAbilitiesFlags::default() - .with_flying(flight.is_flying) - .with_allow_flying(flight.allow), - flying_speed: flying_speed.speed, - fov_modifier: 0.0, - }; - - let mut bundle = DataBundle::new(&compose); - bundle.add_packet(&pkt_health).unwrap(); - bundle.add_packet(&pkt_respawn).unwrap(); - bundle.add_packet(&pkt_xp).unwrap(); - bundle.add_packet(&pkt_abilities).unwrap(); - bundle.unicast(packet.connection_id()).unwrap(); - - let pkt_add_player = PlayerSpawnS2c { - entity_id: VarInt(packet.minecraft_id()), - player_uuid: uuid.0, - position: position.as_dvec3(), - yaw: ByteAngle::from_degrees(**yaw), - pitch: ByteAngle::from_degrees(**pitch), - }; - - compose - .broadcast(&pkt_add_player) - .exclude(packet.connection_id()) - .send() - .unwrap(); - } -} - -pub struct RespawnPlugin; - -impl Plugin for RespawnPlugin { - fn build(&self, app: &mut App) { - app.add_systems(FixedUpdate, handle_respawn.after(ingress::decode::play)); - } -} diff --git a/events/tag/Cargo.toml b/events/tag/Cargo.toml index b45e046f..ab31ea63 100644 --- a/events/tag/Cargo.toml +++ b/events/tag/Cargo.toml @@ -18,7 +18,6 @@ hyperion-item = { workspace = true } hyperion-permission = { workspace = true } hyperion-proxy-module = { workspace = true } hyperion-rank-tree = { workspace = true } -hyperion-respawn = { workspace = true } hyperion-scheduled = { workspace = true } hyperion-text = { workspace = true } hyperion-utils = { workspace = true } diff --git a/events/tag/src/lib.rs b/events/tag/src/lib.rs index 26e7ebc9..1801c37e 100644 --- a/events/tag/src/lib.rs +++ b/events/tag/src/lib.rs @@ -131,7 +131,6 @@ impl Plugin for TagPlugin { hyperion_item::ItemPlugin, hyperion_permission::PermissionPlugin, hyperion_rank_tree::RankTreePlugin, - hyperion_respawn::RespawnPlugin, hyperion_proxy_module::HyperionProxyPlugin, )); app.add_observer(initialize_player);