mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Merge pull request #5790 from kacpherek/fix/glider-any-slot
🤖 Agent: Review by Ludovic's AI agent (Claude). Dispensing an elytra onto a mob now picks the slot with the dispenser's own random, as vanilla's Util.getRandom does; the level random was used before (fixed on the branch). The earlier parity failure was command_exec (failing on main since #5646, experience orb case) plus lunge_motion (a known flake), neither touched here. Verify green on this head.
This commit is contained in:
3 files changed
+48
-32
No files matched your search
@@ -2996,6 +2996,25 @@ fn view_vector(yaw: f32, pitch: f32) -> [f64; 3] {
|
||||
let (y, p) = (f64::from(yaw).to_radians(), f64::from(pitch).to_radians());
|
||||
[-y.sin() * p.cos(), -p.sin(), y.cos() * p.cos()]
|
||||
}
|
||||
/// `LivingEntity.canGlideUsing`: for each worn piece (head, chest, legs,
|
||||
/// feet), whether it is a glider (its own component, or one the server
|
||||
/// patched onto the stack) in the slot its `equippable` names, and will not
|
||||
/// break with its next point of wear (`ItemStack.nextDamageWillBreak`).
|
||||
fn gliding_pieces(items: &Items, armor: &[Option<ItemStack>; 4]) -> [bool; 4] {
|
||||
const SLOTS: [&str; 4] = ["head", "chest", "legs", "feet"];
|
||||
std::array::from_fn(|i| {
|
||||
let Some(stack) = armor[i].as_ref() else {
|
||||
return false;
|
||||
};
|
||||
let info = items.get(&stack.item);
|
||||
let glider = info.is_some_and(|d| d.glider)
|
||||
|| stack
|
||||
.data()
|
||||
.is_some_and(|d| d.other("minecraft:glider").is_some());
|
||||
let will_break = info.is_some_and(|d| d.max_damage > 0 && stack.damage + 1 >= d.max_damage);
|
||||
glider && !will_break && info.and_then(|d| d.equip_slot.as_deref()) == Some(SLOTS[i])
|
||||
})
|
||||
}
|
||||
/// `Camera.setRotation` (`rotationYXZ(π - yaw, -pitch, 0)`) for the player's
|
||||
/// view, turned round in third-person front view (`Camera.setup`).
|
||||
fn camera_rotation(camera_type: perspective::CameraType, yaw: f32, pitch: f32) -> glam::Quat {
|
||||
@@ -3234,15 +3253,31 @@ impl Game<'_> {
|
||||
self.stack_in_mut(self.held_slot())
|
||||
}
|
||||
/// The slot of the hand that acts: the selected one, or `OFFHAND_SLOT`.
|
||||
/// `LivingEntity.canGlideUsing`: a glider in the chest slot that will
|
||||
/// not break with its next point of wear.
|
||||
/// `LivingEntity.canGlide`: some worn piece `gliding_pieces` lets glide.
|
||||
fn glider_worn(&self) -> bool {
|
||||
self.armor[1].as_ref().is_some_and(|s| {
|
||||
self.res
|
||||
.items()
|
||||
.get(&s.item)
|
||||
.is_some_and(|i| i.glider && (i.max_damage == 0 || s.damage + 1 < i.max_damage))
|
||||
})
|
||||
gliding_pieces(self.res.items(), &self.armor).contains(&true)
|
||||
}
|
||||
/// `LivingEntity.updateFallFlying`: the glide's wear. `Util.getRandom`
|
||||
/// picks one of the gliders that will not break (from the entity's own
|
||||
/// random), and
|
||||
/// `ItemStack.hurtAndBreak` takes `points` of its durability (Unbreaking
|
||||
/// may save it). Callers skip creative.
|
||||
pub(super) fn wear_glider(&mut self, points: u32) {
|
||||
let gliding = gliding_pieces(self.res.items(), &self.armor);
|
||||
let pieces: Vec<usize> = (0..4).filter(|&i| gliding[i]).collect();
|
||||
if pieces.is_empty() {
|
||||
return;
|
||||
}
|
||||
// `Util.getRandom(slotsWithGliders, this.random)`: the entity's own
|
||||
// random, which never touches the level's stream.
|
||||
let pick = dispenser::entity_random().next_int_bound(pieces.len() as i32);
|
||||
let pick = pieces[pick.unwrap_or(0) as usize];
|
||||
if let Some(stack) = self.armor[pick].clone() {
|
||||
let loss = self.wear(&stack, points);
|
||||
if let Some(piece) = self.armor[pick].as_mut() {
|
||||
piece.damage += loss;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// `LivingEntity.canFreeze`'s armor half: any worn piece in
|
||||
/// `freeze_immune_wearables`.
|
||||
@@ -6985,15 +7020,8 @@ impl Game<'_> {
|
||||
lan_particles::send_soul_particle(host, at, velocity);
|
||||
}
|
||||
}
|
||||
// `ItemStack.hurtAndBreak` for the glide: Unbreaking can save the wear.
|
||||
if glider_wear > 0
|
||||
&& !self.creative
|
||||
&& let Some(stack) = self.armor[1].clone()
|
||||
{
|
||||
let loss = self.wear(&stack, glider_wear);
|
||||
if let Some(elytra) = self.armor[1].as_mut() {
|
||||
elytra.damage += loss;
|
||||
}
|
||||
if glider_wear > 0 && !self.creative {
|
||||
self.wear_glider(glider_wear);
|
||||
}
|
||||
self.tick_avatar(before, riding);
|
||||
self.end_spin_attack(spin_hit);
|
||||
|
||||
@@ -4096,15 +4096,8 @@ impl Game<'_> {
|
||||
if next % 10 != 0 {
|
||||
return;
|
||||
}
|
||||
if (next / 10) % 2 == 0
|
||||
&& !creative
|
||||
&& let Some(stack) = game.armor[1].clone()
|
||||
{
|
||||
// `ItemStack.hurtAndBreak`: Unbreaking can save the wear.
|
||||
let loss = game.wear(&stack, 1);
|
||||
if let Some(elytra) = game.armor[1].as_mut() {
|
||||
elytra.damage += loss;
|
||||
}
|
||||
if (next / 10) % 2 == 0 && !creative {
|
||||
game.wear_glider(1);
|
||||
}
|
||||
let ctx = game.player_context(None);
|
||||
game.post_game_event("minecraft:elytra_glide", at, ctx);
|
||||
|
||||
@@ -50,12 +50,7 @@ impl Game<'_> {
|
||||
// hears it, and the replay follows, but nothing counts.
|
||||
let waiting = g.awaiting_teleport.is_some();
|
||||
// The server's copy of a guest doesn't follow its armor (Game.glider_worn).
|
||||
let glider = g.armor[1].as_ref().is_some_and(|s| {
|
||||
self.res
|
||||
.items()
|
||||
.get(&s.item)
|
||||
.is_some_and(|i| i.glider && (i.max_damage == 0 || s.damage + 1 < i.max_damage))
|
||||
});
|
||||
let glider = super::gliding_pieces(self.res.items(), &g.armor).contains(&true);
|
||||
if let Some(motion) = g.motion.as_deref_mut() {
|
||||
motion.glider = glider;
|
||||
motion.item_use_speed = item_use.map(|(speed, _)| speed);
|
||||
|
||||
Reference in new issue
Block a user