Merge pull request #1798 from socksprox/fix/mob-combat-poses
🤖 Agent: thank you! Merged after the contribution gate (checked against vanilla 26.3; one integration branch of main + its batch: clippy -D warnings, tests, the affected parity harnesses (111/112, all pass), in game on the server thread and in-frame, lan_e2e in both host modes) and this head's verify.
This commit is contained in:
@@ -162,6 +162,13 @@ fn mob_meta(
|
||||
meta.push((3, serializer::BOOLEAN, Value::Bool(true)));
|
||||
}
|
||||
}
|
||||
// LivingEntity.DATA_LIVING_ENTITY_FLAGS: using an item 1, in the off
|
||||
// hand 2 (a skeleton draws its bow, a witch raises her potion).
|
||||
let using = i8::from(m.extra.using_item > 0) | i8::from(m.extra.using_off_hand) << 1;
|
||||
if using != 0 {
|
||||
meta.push((8, serializer::BYTE, Value::Byte(using)));
|
||||
}
|
||||
defaults.push((8, serializer::BYTE, Value::Byte(0)));
|
||||
// LivingEntity.DATA_HEALTH_ID: defined as 1, then set to the max health.
|
||||
let health = m.health.max(0.0);
|
||||
if health != 1.0 {
|
||||
@@ -293,6 +300,21 @@ fn mob_meta(
|
||||
meta.push((18, serializer::BOOLEAN, Value::Bool(true)));
|
||||
}
|
||||
}
|
||||
// Pillager.IS_CHARGING_CROSSBOW: the charge pose and the held
|
||||
// item's loading animation.
|
||||
"minecraft:pillager" => {
|
||||
if e.flag {
|
||||
meta.push((17, serializer::BOOLEAN, Value::Bool(true)));
|
||||
}
|
||||
defaults.push((17, serializer::BOOLEAN, Value::Bool(false)));
|
||||
}
|
||||
// SpellcasterIllager.DATA_SPELL_CASTING_ID: the casting pose.
|
||||
"minecraft:evoker" | "minecraft:illusioner" => {
|
||||
if e.variant2 != 0 {
|
||||
meta.push((17, serializer::BYTE, Value::Byte(e.variant2 as i8)));
|
||||
}
|
||||
defaults.push((17, serializer::BYTE, Value::Byte(0)));
|
||||
}
|
||||
// Enderman.DATA_CARRY_STATE.
|
||||
"minecraft:enderman" => {
|
||||
if let Some(state) = e.carried {
|
||||
|
||||
@@ -259,7 +259,7 @@ impl Game<'_> {
|
||||
// ItemInHandLayer submits with OverlayTexture.NO_OVERLAY: no hurt tint.
|
||||
for (item, off_hand) in [(&mob.extra.main_hand, false), (&mob.extra.off_hand, true)] {
|
||||
let Some(item) = item else { continue };
|
||||
let left = off_hand != mob.extra.main_hand_left;
|
||||
let left = off_hand != mob.main_arm_left();
|
||||
let Some(t) =
|
||||
render::held_item_transform(mob, alpha as f32, camera, self.res.mob_data(), left)
|
||||
else {
|
||||
@@ -305,7 +305,7 @@ impl Game<'_> {
|
||||
.player_model
|
||||
.as_ref()
|
||||
.is_some_and(|p| std::ptr::eq(p, mob));
|
||||
owner.main_arm_left = mob.extra.main_hand_left;
|
||||
owner.main_arm_left = mob.main_arm_left();
|
||||
if off_hand == mob.extra.using_off_hand && mob.extra.using_item > 0 {
|
||||
owner.using_this_item = true;
|
||||
owner.use_duration = 72_000;
|
||||
|
||||
@@ -1904,25 +1904,12 @@ impl Game<'_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
// LivingEntity.onSyncedDataUpdated(DATA_LIVING_ENTITY_FLAGS):
|
||||
// another player starts using the item in a hand (eating, a
|
||||
// bow drawn, a shield up), or stops.
|
||||
// LivingEntity.DATA_LIVING_ENTITY_FLAGS: for another player
|
||||
// too, remote_mob_data's apply reads index 8 above.
|
||||
let other = self
|
||||
.remote
|
||||
.as_ref()
|
||||
.is_some_and(|r| r.level_players.contains_key(&p.id) && r.entity_id != p.id);
|
||||
if other && let Some(m) = self.mobs.iter_mut().find(|m| m.id == p.id as u32) {
|
||||
for (index, _, value) in &p.values {
|
||||
if let (8, Value::Byte(flags)) = (index, value) {
|
||||
m.extra.using_off_hand = flags & 2 != 0;
|
||||
if flags & 1 == 0 {
|
||||
m.extra.using_item = 0;
|
||||
} else if m.extra.using_item == 0 {
|
||||
m.extra.using_item = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// LivingEntity.onSyncedDataUpdated(SLEEPING_POS_ID): another
|
||||
// player lies down in their bed (drawn lying in it), or gets up.
|
||||
if other {
|
||||
@@ -3406,25 +3393,28 @@ impl Game<'_> {
|
||||
m.head_yaw += wrap_degrees(*yaw - m.head_yaw) / *steps as f32;
|
||||
*steps -= 1;
|
||||
}
|
||||
// LivingEntity.updatingUsingItem for another player: the ticks the
|
||||
// item has been used before this one.
|
||||
if player && m.extra.using_item > 0 {
|
||||
let item = if m.extra.using_off_hand {
|
||||
m.extra.off_hand.clone()
|
||||
} else {
|
||||
m.extra.main_hand.clone()
|
||||
};
|
||||
if let Some(item) = item {
|
||||
let eye = if m.extra.crouching { 1.27 } else { 1.62 };
|
||||
let at = [m.position.x, m.position.y, m.position.z];
|
||||
using.push((
|
||||
item,
|
||||
m.extra.using_item as u32 - 1,
|
||||
at,
|
||||
eye,
|
||||
m.head_pitch,
|
||||
m.yaw,
|
||||
));
|
||||
// LivingEntity.updatingUsingItem: the ticks the item has been
|
||||
// used before this one. Another player's use particles; a mob's
|
||||
// drawn bow only needs the count (its pull follows it).
|
||||
if m.extra.using_item > 0 {
|
||||
if player {
|
||||
let item = if m.extra.using_off_hand {
|
||||
m.extra.off_hand.clone()
|
||||
} else {
|
||||
m.extra.main_hand.clone()
|
||||
};
|
||||
if let Some(item) = item {
|
||||
let eye = if m.extra.crouching { 1.27 } else { 1.62 };
|
||||
let at = [m.position.x, m.position.y, m.position.z];
|
||||
using.push((
|
||||
item,
|
||||
m.extra.using_item as u32 - 1,
|
||||
at,
|
||||
eye,
|
||||
m.head_pitch,
|
||||
m.yaw,
|
||||
));
|
||||
}
|
||||
}
|
||||
m.extra.using_item += 1;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,17 @@ fn apply(m: &mut Mob, values: &[(u8, i32, Value)], builtin: &Builtin) {
|
||||
// client, which draws the flames. A client doesn't count the
|
||||
// fire down: the flag goes off when the server's fire is out.
|
||||
(0, Value::Byte(flags)) => m.fire_ticks = i32::from(flags & 1),
|
||||
// LivingEntity.DATA_LIVING_ENTITY_FLAGS: using an item 1, in the
|
||||
// off hand 2. The flag starts and stops the use; the client ticks
|
||||
// the count itself (a bow's pull follows it).
|
||||
(8, Value::Byte(flags)) => {
|
||||
m.extra.using_off_hand = flags & 2 != 0;
|
||||
if flags & 1 == 0 {
|
||||
m.extra.using_item = 0;
|
||||
} else if m.extra.using_item == 0 {
|
||||
m.extra.using_item = 1;
|
||||
}
|
||||
}
|
||||
// LivingEntity.DATA_HEALTH_ID: the boss bars and the death
|
||||
// animation follow it.
|
||||
(9, Value::Float(health)) => m.health = *health,
|
||||
@@ -53,6 +64,13 @@ fn apply(m: &mut Mob, values: &[(u8, i32, Value)], builtin: &Builtin) {
|
||||
(Species::Slime | Species::MagmaCube, 18, Value::Int(size)) => {
|
||||
m.extra.size = (*size).clamp(1, 127);
|
||||
}
|
||||
// Pillager.IS_CHARGING_CROSSBOW: the charge pose (Raider's
|
||||
// IS_CELEBRATING takes 16).
|
||||
(Species::Pillager, 17, Value::Bool(charging)) => m.extra.flag = *charging,
|
||||
// SpellcasterIllager.DATA_SPELL_CASTING_ID: the casting pose.
|
||||
(Species::Evoker | Species::Illusioner, 17, Value::Byte(spell)) => {
|
||||
m.extra.variant2 = (*spell).max(0) as u8;
|
||||
}
|
||||
// Villager.DATA_VILLAGER_DATA and ZombieVillager's (after
|
||||
// Zombie's three): what VillagerProfessionLayer draws.
|
||||
(Species::Villager, 19, Value::Ids(data))
|
||||
@@ -239,4 +257,42 @@ mod tests {
|
||||
);
|
||||
assert_eq!(name(&z), ("desert", "farmer", 3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_mob_flags_drive_the_aggressive_poses() {
|
||||
let Some(mut m) = mob("minecraft:skeleton") else {
|
||||
return;
|
||||
};
|
||||
apply(&mut m, &[(15, s::BYTE, Value::Byte(2 | 4))]);
|
||||
assert!(m.ai.aggressive && m.extra.left_handed && !m.no_ai);
|
||||
apply(&mut m, &[(15, s::BYTE, Value::Byte(0))]);
|
||||
assert!(!m.ai.aggressive && !m.extra.left_handed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_living_flags_start_and_stop_a_mob_using_an_item() {
|
||||
let Some(mut m) = mob("minecraft:skeleton") else {
|
||||
return;
|
||||
};
|
||||
apply(&mut m, &[(8, s::BYTE, Value::Byte(1))]);
|
||||
assert_eq!(m.extra.using_item, 1);
|
||||
assert!(!m.extra.using_off_hand);
|
||||
// Using in the off hand.
|
||||
apply(&mut m, &[(8, s::BYTE, Value::Byte(3))]);
|
||||
assert!(m.extra.using_off_hand);
|
||||
apply(&mut m, &[(8, s::BYTE, Value::Byte(0))]);
|
||||
assert_eq!(m.extra.using_item, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_pillager_charges_and_a_spellcaster_casts() {
|
||||
let Some(mut p) = mob("minecraft:pillager") else {
|
||||
return;
|
||||
};
|
||||
apply(&mut p, &[(17, s::BOOLEAN, Value::Bool(true))]);
|
||||
assert!(p.extra.flag);
|
||||
let mut e = mob("minecraft:evoker").unwrap();
|
||||
apply(&mut e, &[(17, s::BYTE, Value::Byte(2))]);
|
||||
assert_eq!(e.extra.variant2, 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1646,6 +1646,11 @@ impl Mob {
|
||||
pub fn species(&self) -> Species {
|
||||
self.kind.species
|
||||
}
|
||||
/// `Mob.getMainArm`: the left arm when the mob is left-handed, a player's
|
||||
/// `mainHand` option for a player mob.
|
||||
pub fn main_arm_left(&self) -> bool {
|
||||
self.extra.main_hand_left || self.extra.left_handed
|
||||
}
|
||||
/// The player hit this mob (melee): LivingEntity.hurt with knockback.
|
||||
pub fn hurt(
|
||||
&mut self,
|
||||
|
||||
@@ -642,7 +642,7 @@ pub fn setup(
|
||||
} else {
|
||||
ArmPose::Empty
|
||||
};
|
||||
let mut h = if m.extra.main_hand_left {
|
||||
let mut h = if m.main_arm_left() {
|
||||
Hum::new(ArmPose::Empty, main)
|
||||
} else {
|
||||
Hum::new(main, ArmPose::Empty)
|
||||
@@ -693,7 +693,7 @@ pub fn setup(
|
||||
} else {
|
||||
ArmPose::Empty
|
||||
};
|
||||
let mut h = if m.extra.main_hand_left {
|
||||
let mut h = if m.main_arm_left() {
|
||||
Hum::new(off, pose)
|
||||
} else {
|
||||
Hum::new(pose, off)
|
||||
|
||||
@@ -621,8 +621,10 @@ impl BowAttack {
|
||||
}
|
||||
}
|
||||
}
|
||||
/// `LivingEntity.isHolding(Items.BOW)`: the bow can sit in either hand.
|
||||
fn holding_bow(m: &Mob) -> bool {
|
||||
m.extra.main_hand.as_deref() == Some("minecraft:bow")
|
||||
|| m.extra.off_hand.as_deref() == Some("minecraft:bow")
|
||||
}
|
||||
impl Goal for BowAttack {
|
||||
goal_clone!();
|
||||
@@ -715,7 +717,10 @@ impl Goal for BowAttack {
|
||||
} else {
|
||||
self.attack_time -= 1;
|
||||
if self.attack_time <= 0 && self.see_time >= -60 {
|
||||
// mob.startUsingItem(ProjectileUtil.getWeaponHoldingHand):
|
||||
// the off hand when the main one doesn't hold the bow.
|
||||
m.extra.using_item = 1;
|
||||
m.extra.using_off_hand = m.extra.main_hand.as_deref() != Some("minecraft:bow");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user