Seat camel husk jockey's parched in camel second seat (fixes #9961)

Co-Authored-By: Antigravity <[email protected]>
This commit is contained in:
AxxioandAntigravity committed 2026-11-05 18:57:44 +01:00
1 parent fb6121bce9
commit 8c3f6e7d7b
5 files changed
+177 -23

No files matched your search

@@ -166,6 +166,19 @@ impl Game<'_> {
riding.add(vehicle as i32, m.id as i32);
}
}
for (&vehicle, pass) in &mut riding.passengers {
if let Some(driver) = self
.mobs
.iter()
.find(|m| m.id as i32 == vehicle)
.and_then(|m| m.extra.rider)
&& let Some(pos) = pass.iter().position(|&p| p == driver as i32)
&& pos != 0
{
let d = pass.remove(pos);
pass.insert(0, d);
}
}
}
/// Boats, minecarts, armor stands, item frames, paintings and the
+10 -9
View File
@@ -750,17 +750,18 @@ impl Game<'_> {
}
self.lan = Some(host);
}
let rider = self
let riders: Vec<u32> = self
.mobs
.iter()
.find(|m| m.id == mob)
.and_then(|m| m.extra.rider);
if let Some(rider) = rider
&& let Some(r) = self.mobs.iter_mut().find(|m| m.id == rider)
{
let mut events = Vec::new();
r.cause_fall_damage_from(distance, modifier, &mut events);
let _ = self.handle_mob_events(events);
.filter(|m| m.extra.mount == Some(mob))
.map(|m| m.id)
.collect();
for rider in riders {
if let Some(r) = self.mobs.iter_mut().find(|m| m.id == rider) {
let mut events = Vec::new();
r.cause_fall_damage_from(distance, modifier, &mut events);
let _ = self.handle_mob_events(events);
}
}
}
/// The player this game runs (the local one, or the server player it acts
+70 -10
View File
@@ -83,6 +83,17 @@ impl Game<'_> {
.map(|(i, m)| (m.id, i))
.collect();
let mut riding: HashMap<u32, u32> = HashMap::new();
// If the vehicle already has a first rider that is still validly riding it, preserve it.
for m in &self.mobs {
if let Some(r) = m.extra.rider
&& let Some(&j) = index.get(&r)
&& self.mobs[j].extra.mount == Some(m.id)
&& self.mobs[j].alive()
&& !self.mobs[j].removed
{
riding.insert(m.id, r);
}
}
for i in 0..self.mobs.len() {
let Some(v) = self.mobs[i].extra.mount else {
continue;
@@ -98,7 +109,7 @@ impl Game<'_> {
.get(&v)
.is_some_and(|&j| self.mobs[j].alive() && !self.mobs[j].removed));
if valid {
riding.insert(v, self.mobs[i].id);
riding.entry(v).or_insert(self.mobs[i].id);
} else {
let m = &mut self.mobs[i];
m.extra.mount = None;
@@ -158,16 +169,28 @@ impl Game<'_> {
if horizon_client::mobs::camel::is_camel(vehicle.species()) {
// Camel.getPassengerAttachmentPoint: the seat follows the
// rider's place in `getPassengers` (load order).
let aboard: Vec<usize> = (0..self.mobs.len())
let mut aboard: Vec<usize> = (0..self.mobs.len())
.filter(|&k| self.mobs[k].extra.mount == Some(v))
.collect();
let seat = aboard.iter().position(|&k| k == i).unwrap_or(0);
let player_aboard = vehicle.rider.is_some() || self.riding == Some(v);
let (seat, count) = if player_aboard {
let mob_seat = aboard.iter().position(|&k| k == i).unwrap_or(0);
(mob_seat + 1, aboard.len() + 1)
} else {
if let Some(driver) = vehicle.extra.rider
&& let Some(pos) =
aboard.iter().position(|&k| self.mobs[k].id == driver)
&& pos != 0
{
let driver_idx = aboard.remove(pos);
aboard.insert(0, driver_idx);
}
let seat = aboard.iter().position(|&k| k == i).unwrap_or(0);
(seat, aboard.len())
};
let animal = horizon_client::mobs::drops::is_animal(self.mobs[i].species());
attach = horizon_client::mobs::camel::passenger_attachment_at(
vehicle,
seat,
aboard.len(),
animal,
vehicle, seat, count, animal,
);
}
if vehicle.species() == horizon_client::mobs::Species::HappyGhast {
@@ -415,8 +438,7 @@ impl Game<'_> {
}
}
/// `Husk.finalizeSpawn`: where a camel husk fits, one natural husk in ten
/// takes an iron spear and rides one (with a parched; Horizon seats one
/// rider, so the parched stands beside it).
/// takes an iron spear and rides one, with a parched in its second seat.
fn spawn_camel_husk_jockey(&mut self, id: u32, at: [f64; 3]) {
let (bx, by, bz) = (at[0].floor(), at[1].floor(), at[2].floor());
let room = {
@@ -436,7 +458,9 @@ impl Game<'_> {
return;
};
self.mount_mob(id, camel);
let _ = self.spawn_mob("minecraft:parched", at, false);
if let Some(parched) = self.spawn_mob("minecraft:parched", at, false) {
self.mount_mob(parched, camel);
}
}
/// `Drowned.finalizeSpawn`: half the natural drowned holding a trident,
/// outside the rivers and oceans that breed more drowned, ride a zombie
@@ -636,4 +660,40 @@ mod tests {
assert!(!r(Species::Drowned, true).nautilus);
assert!(!r(Species::Zombie, false).camel_husk);
}
#[test]
fn camel_two_seats_place_second_rider_behind_driver() {
let pos = Vec3::new(10.0, 64.0, 20.0);
let rider_offset = 0.7; // Humanoid riding offset
// Facing south (yaw = 0): +z is forward
let driver_attach = [0.0, 2.0, 0.5];
let passenger_attach = [0.0, 2.0, -0.7];
let driver_pos = riders::seat(driver_attach, rider_offset, pos, 0.0);
let passenger_pos = riders::seat(passenger_attach, rider_offset, pos, 0.0);
assert!(
(driver_pos.z - 20.5).abs() < 1e-6,
"driver is 0.5 blocks ahead"
);
assert!(
(passenger_pos.z - 19.3).abs() < 1e-6,
"passenger is 0.7 blocks behind"
);
assert!(
passenger_pos.z < driver_pos.z,
"passenger sits behind driver"
);
// Facing north (yaw = 180): -z is forward
let driver_north = riders::seat(driver_attach, rider_offset, pos, 180.0);
let passenger_north = riders::seat(passenger_attach, rider_offset, pos, 180.0);
assert!((driver_north.z - 19.5).abs() < 1e-6);
assert!((passenger_north.z - 20.7).abs() < 1e-6);
assert!(
passenger_north.z > driver_north.z,
"passenger sits behind driver facing north"
);
}
}
+26 -2
View File
@@ -855,7 +855,7 @@ impl Game<'_> {
if let Some(r) = self.load_mob(rider, Some(id))
&& let Some(v) = self.mobs.iter_mut().find(|m| m.id == id)
{
v.extra.rider = Some(r);
v.extra.rider.get_or_insert(r);
}
}
Some(id)
@@ -886,9 +886,20 @@ impl Game<'_> {
/// The mobs riding entity `id` under `tag`'s `Passengers`, after the
/// passengers it kept from loading.
pub(super) fn put_mob_passengers(&mut self, tag: &mut Tag, id: u32) {
let riders: Vec<usize> = (0..self.mobs.len())
let mut riders: Vec<usize> = (0..self.mobs.len())
.filter(|&j| self.mobs[j].extra.mount == Some(id) && self.mobs[j].alive())
.collect();
if let Some(driver) = self
.mobs
.iter()
.find(|m| m.id == id)
.and_then(|m| m.extra.rider)
&& let Some(pos) = riders.iter().position(|&j| self.mobs[j].id == driver)
&& pos != 0
{
let d = riders.remove(pos);
riders.insert(0, d);
}
let riders: Vec<Tag> = riders.into_iter().map(|j| self.mob_tree_nbt(j)).collect();
add_passengers(tag, riders);
}
@@ -2265,6 +2276,19 @@ mod tests {
assert!(tag.get("Passengers").is_none());
}
#[test]
fn a_vehicle_with_multiple_passengers_saves_and_loads_driver_first() {
let (camel, husk, parched) = (1, 2, 3);
let mounts = [(husk, Some(camel)), (parched, Some(camel))];
assert_eq!(vehicle_tree(camel, &mounts), vec![camel, husk, parched]);
let mut tag = entity("minecraft:camel_husk");
add_passengers(
&mut tag,
vec![entity("minecraft:husk"), entity("minecraft:parched")],
);
assert_eq!(passengers(&tag), ["minecraft:husk", "minecraft:parched"]);
}
/// A portal arrival or a `/summon` is told as a fresh add, and the mark
/// never reaches the entity's kept or saved compound.
#[test]
+58 -2
View File
@@ -193,10 +193,23 @@ pub fn link(mobs: &mut [Mob], rider: u32, vehicle: u32) {
m.extra.mount = Some(vehicle);
m.extra.passenger = true;
}
if let Some(m) = mobs.iter_mut().find(|m| m.id == vehicle) {
if let Some(m) = mobs.iter_mut().find(|m| m.id == vehicle)
&& m.extra.rider.is_none()
{
m.extra.rider = Some(rider);
}
}
/// How many passengers can ride this vehicle. Camels carry two riders
/// (unless a baby), happy ghasts carry four, and other mobs carry one.
pub fn max_passengers(v: &Mob) -> usize {
if super::camel::is_camel(v.species()) {
if v.is_baby() { 0 } else { 2 }
} else if v.species() == Species::HappyGhast {
4
} else {
1
}
}
/// `Piglin.getTopPassenger`: down the chain of first passengers from
/// `vehicle`, stopping at the `counter`th seat.
fn top_passenger(mobs: &[Mob], vehicle: u32, counter: i32) -> u32 {
@@ -246,7 +259,13 @@ pub fn start_riding(mobs: &mut [Mob], rider: u32, vehicle: u32) -> bool {
let Some(v) = mobs.iter().find(|m| m.id == vehicle) else {
return false;
};
if r.extra.boarding_cooldown > 0 || v.extra.rider.is_some() || v.rider.is_some() {
let count = usize::from(v.rider.is_some())
+ mobs
.iter()
.filter(|m| m.extra.mount == Some(vehicle))
.count()
.max(usize::from(v.extra.rider.is_some()));
if r.extra.boarding_cooldown > 0 || count >= max_passengers(v) {
return false;
}
link(mobs, rider, vehicle);
@@ -635,6 +654,43 @@ mod tests {
];
assert!(start_riding(&mut others, 1, 2));
assert_eq!(others[0].extra.variant3, 4);
// A camel carries two riders: the first becomes extra.rider, the second
// mounts as a passenger without overwriting extra.rider, and a third is refused.
let mut camel_ride = [
mob(1, "minecraft:husk", Species::Husk, 0),
mob(2, "minecraft:parched", Species::Parched, 0),
mob(3, "minecraft:zombie", Species::Zombie, 0),
mob(10, "minecraft:camel_husk", Species::CamelHusk, 0),
];
assert!(start_riding(&mut camel_ride, 1, 10), "first rider boards");
assert_eq!(camel_ride[0].extra.mount, Some(10));
assert_eq!(camel_ride[3].extra.rider, Some(1), "first rider is driver");
assert!(start_riding(&mut camel_ride, 2, 10), "second rider boards");
assert_eq!(camel_ride[1].extra.mount, Some(10));
assert_eq!(
camel_ride[3].extra.rider,
Some(1),
"driver remains first rider"
);
assert!(
!start_riding(&mut camel_ride, 3, 10),
"third rider is refused"
);
assert_eq!(camel_ride[2].extra.mount, None);
// A baby camel carries no riders.
let mut baby_camel = [
mob(1, "minecraft:husk", Species::Husk, 0),
mob(10, "minecraft:camel", Species::Camel, 0),
];
baby_camel[1].extra.age = -1;
assert!(
!start_riding(&mut baby_camel, 1, 10),
"baby camel refuses riders"
);
}
}