mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 17:21:49 -10:00
Vehicle teleport: review fixes (rotation packet, cooldown per tick, rider's vehicle)
- /rotate on a server's player sends ClientboundPlayerRotationPacket (ServerPlayer.forceSetRotation) when its turn ends, riding or not, instead of a zero-motion teleport (or nothing for a rider). - The guest's boarding cooldown counts down once per tick in guest_server_tick, not once per enter_guest; a vehicle dropped in guest_vehicle_tick also starts it. - A teleported vehicle's riding guests hear the vehicle's teleport (ClientboundTeleportEntityPacket), so a driven boat is not put back by the client's next MoveVehicle. Found by martipops's review. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
7bacc86a5e
commit
2c7b49062e
4 files changed
+54
-4
No files matched your search
@@ -2862,6 +2862,9 @@ struct Game<'a> {
|
||||
/// last tick (`game/boarding.rs`).
|
||||
boarding_cooldown: i32,
|
||||
riding_at_tick: Option<u32>,
|
||||
/// A rotation-only write (`/rotate`, `Entity.forceSetRotation`) to the player of this
|
||||
/// turn: `leave_guest` sends it as `ClientboundPlayerRotationPacket`, not a teleport.
|
||||
rotation_forced: bool,
|
||||
/// The mount the last `position_rider` put the player on, to tell the tick
|
||||
/// a seat is taken (`Entity.moveTo`, no camera sweep) from the ones that
|
||||
/// follow it (`positionRider`, interpolated as usual).
|
||||
@@ -19156,6 +19159,7 @@ impl<'a> Game<'a> {
|
||||
riding: None,
|
||||
boarding_cooldown: 0,
|
||||
riding_at_tick: None,
|
||||
rotation_forced: false,
|
||||
posed_rider: None,
|
||||
pending_vehicle: None,
|
||||
ender_items: vec![None; 27],
|
||||
|
||||
@@ -523,9 +523,10 @@ impl Game<'_> {
|
||||
self.right = play.using.is_some() || self.using.is_some();
|
||||
// The guest's vehicle (Entity.vehicle) for its turn.
|
||||
let riding = std::mem::replace(&mut self.riding, g.vehicle);
|
||||
// Entity.baseTick: the guest's boarding cooldown, for its turn.
|
||||
// The guest's boarding cooldown for its turn; `guest_server_tick`
|
||||
// counts it down once per tick (Entity.baseTick), not per turn.
|
||||
let host_boarding = (self.boarding_cooldown, self.riding_at_tick);
|
||||
self.boarding_cooldown = super::boarding::tick_down(g.boarding_cooldown);
|
||||
self.boarding_cooldown = g.boarding_cooldown;
|
||||
self.riding_at_tick = g.vehicle;
|
||||
// `Entity.isPassenger()` for that turn, from the same vehicle: the
|
||||
// guest's body keeps no swim (`Entity.updateSwimming` needs it false).
|
||||
@@ -979,9 +980,19 @@ impl Game<'_> {
|
||||
std::mem::swap(&mut self.armor, &mut g.armor);
|
||||
std::mem::swap(&mut self.offhand, &mut g.offhand);
|
||||
std::mem::swap(&mut self.carried, &mut g.carried);
|
||||
// A teleport with a rotation (commands) turns the guest.
|
||||
let turned = [self.yaw, self.pitch] != [g.yaw, g.pitch];
|
||||
// A teleport with a rotation (commands) turns the guest. `/rotate`
|
||||
// (ServerPlayer.forceSetRotation) sends the rotation alone, rider or not.
|
||||
let forced = std::mem::take(&mut self.rotation_forced);
|
||||
let turned = !forced && [self.yaw, self.pitch] != [g.yaw, g.pitch];
|
||||
[g.yaw, g.pitch] = [self.yaw, self.pitch];
|
||||
if forced {
|
||||
g.send(&C::PlayerRotation(play::PlayerRotation {
|
||||
yaw: g.yaw,
|
||||
relative_yaw: false,
|
||||
pitch: g.pitch,
|
||||
relative_pitch: false,
|
||||
}));
|
||||
}
|
||||
self.yaw = yaw;
|
||||
self.pitch = pitch;
|
||||
self.eye = eye.1;
|
||||
@@ -4304,6 +4315,10 @@ impl Game<'_> {
|
||||
/// regeneration and the food tick (ServerPlayer.doTick). Their hurt
|
||||
/// sounds are heard at the guest.
|
||||
fn guest_server_tick(&mut self, host: &mut LanHost, conn: u32) {
|
||||
// Entity.baseTick: the boarding cooldown counts down once a tick.
|
||||
if let Some(g) = host.hub.guest_mut(conn) {
|
||||
g.boarding_cooldown = super::boarding::tick_down(g.boarding_cooldown);
|
||||
}
|
||||
self.guest_replay(host, conn);
|
||||
self.guest_vehicle_tick(host, conn);
|
||||
// The last move's fall stands in for the server's delta movement.
|
||||
|
||||
@@ -234,6 +234,8 @@ impl Game<'_> {
|
||||
if dead || (boat.is_none() && !cart && !mount && !cushion) {
|
||||
let g = &mut host.hub.guests[gi];
|
||||
(g.vehicle, g.vehicle_good) = (None, None);
|
||||
// Entity.removePassenger: the rider waits 60 ticks to board again.
|
||||
g.boarding_cooldown = super::boat_passengers::BOARDING_COOLDOWN;
|
||||
return;
|
||||
}
|
||||
let mut ejected = false;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
use super::Game;
|
||||
use super::commands::Target;
|
||||
use horizon_core::geometry::Vec3;
|
||||
use horizon_protocol::packets::play::{self, Clientbound as C};
|
||||
|
||||
/// `Entity.calculatePassengerTransition` with no relative axes: the passenger
|
||||
/// keeps its offset from the vehicle.
|
||||
@@ -68,11 +69,24 @@ impl Game<'_> {
|
||||
p.position = passenger_destination(old, new, p.position);
|
||||
self.previous_position = p.position;
|
||||
}
|
||||
let [yaw, pitch] = self.vehicle_rotation(vehicle);
|
||||
if let Some(host) = self.lan.as_mut() {
|
||||
for i in 0..host.hub.guests.len() {
|
||||
if host.hub.guests[i].vehicle != Some(vehicle) {
|
||||
continue;
|
||||
}
|
||||
// Entity.sendTeleportTransitionToRidingPlayers: the rider's
|
||||
// client moves the vehicle it drives itself, so it hears the
|
||||
// vehicle's teleport (else its next MoveVehicle undoes it).
|
||||
host.hub.guests[i].send(&C::TeleportEntity(play::TeleportEntity {
|
||||
id: vehicle as i32,
|
||||
position: [new.x, new.y, new.z],
|
||||
delta: [0.0; 3],
|
||||
yaw,
|
||||
pitch,
|
||||
relatives: 0,
|
||||
on_ground: false,
|
||||
}));
|
||||
let g = host.hub.guests[i].position;
|
||||
let at = passenger_destination(old, new, Vec3::new(g[0], g[1], g[2]));
|
||||
host.hub.teleport_keeping_motion(i, [at.x, at.y, at.z]);
|
||||
@@ -80,6 +94,19 @@ impl Game<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A vehicle's yaw and pitch, for the riders' teleport packet.
|
||||
fn vehicle_rotation(&self, id: u32) -> [f32; 2] {
|
||||
if let Some(b) = self.boats.iter().find(|b| b.id == id) {
|
||||
[b.yaw, 0.0]
|
||||
} else if let Some(c) = self.carts.iter().find(|c| c.id == id) {
|
||||
[c.yaw, c.pitch]
|
||||
} else if let Some(m) = self.mobs.iter().find(|m| m.id == id) {
|
||||
[m.yaw, m.head_pitch]
|
||||
} else {
|
||||
[0.0, 0.0]
|
||||
}
|
||||
}
|
||||
|
||||
/// `Entity.forceSetRotation`: yaw, head yaw, pitch and their old values
|
||||
/// only. Position, motion, the vehicle and the riders stay.
|
||||
pub(super) fn set_rotation(&mut self, t: Target, rotation: [f32; 2]) {
|
||||
@@ -100,6 +127,8 @@ impl Game<'_> {
|
||||
if let Some(pitch) = pitch {
|
||||
self.pitch = pitch;
|
||||
}
|
||||
// A server's player hears the turn alone when its turn ends.
|
||||
self.rotation_forced = self.lan_acting.is_some();
|
||||
}
|
||||
Target::Mob(id) => {
|
||||
if let Some(m) = self.mobs.iter_mut().find(|m| m.id == id) {
|
||||
|
||||
Reference in new issue
Block a user