Merge pull request 'Make held items follow bent arms and straighten the first-person arm' (#3) from held-items into master
Reviewed-on: #3
This commit was merged in pull request #3.
This commit is contained in:
@@ -96,6 +96,16 @@ public final class LimbBend {
|
||||
return segments;
|
||||
}
|
||||
|
||||
/** Top of the limb along Y (its pivot end), as given to the constructor. */
|
||||
public float start() {
|
||||
return start;
|
||||
}
|
||||
|
||||
/** Bottom of the limb along Y (the hand or foot end), as given to the constructor. */
|
||||
public float end() {
|
||||
return start + length;
|
||||
}
|
||||
|
||||
/** Y positions where the limb is cut into pieces, not counting its ends. */
|
||||
public float[] cuts() {
|
||||
float[] cuts = new float[segments - 1];
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.saturnclient.emotes.fabric;
|
||||
|
||||
import org.joml.Quaternionf;
|
||||
import org.saturnclient.emotes.core.bend.LimbBend;
|
||||
import org.saturnclient.emotes.core.bend.Vec3;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
/** Pose stack helpers for attaching things to bent limbs. */
|
||||
public final class BendTransforms {
|
||||
private BendTransforms() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves whatever is drawn next from the straight limb onto the bent one, as if it were fixed to the
|
||||
* limb at {@code y} (model pixels, in the limb's space). This is the same rigid move the ring at
|
||||
* {@code y} makes, so anything attached there stays attached the way the mesh does. The pose stack
|
||||
* must already be in the limb's space, i.e. after {@code ModelPart.translateAndRotate}.
|
||||
*/
|
||||
public static void attachAt(PoseStack poseStack, LimbBend bend, float y) {
|
||||
float[] center = new float[3];
|
||||
bend.bendPoint(0, y, 0, center);
|
||||
Vec3 axis = bend.axis();
|
||||
poseStack.translate(center[0] / 16, center[1] / 16, center[2] / 16);
|
||||
poseStack.mulPose(new Quaternionf().rotationAxis(bend.angleAt(y), axis.x(), axis.y(), axis.z()));
|
||||
poseStack.translate(0, -y / 16, 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.saturnclient.emotes.fabric.mixin;
|
||||
|
||||
import org.saturnclient.emotes.fabric.access.BendablePart;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
//? if >=1.21.9 {
|
||||
import net.minecraft.client.renderer.entity.player.AvatarRenderer;
|
||||
//?} else
|
||||
/*import net.minecraft.client.renderer.entity.player.PlayerRenderer;*/
|
||||
|
||||
/**
|
||||
* The first-person arm is drawn from the same shared model without calling setupAnim, so it would
|
||||
* keep whatever bend the last third-person player drawn left on it. It should never be bent.
|
||||
*/
|
||||
//? if >=1.21.9 {
|
||||
@Mixin(AvatarRenderer.class)
|
||||
//?} else
|
||||
/*@Mixin(PlayerRenderer.class)*/
|
||||
public class FirstPersonHandMixin {
|
||||
@Inject(method = "renderHand", at = @At("HEAD"))
|
||||
private void saturnEmotes$straightenArm(CallbackInfo ci, @Local(argsOnly = true) ModelPart arm) {
|
||||
((BendablePart) (Object) arm).saturnEmotes$setBend(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.saturnclient.emotes.fabric.mixin;
|
||||
|
||||
import org.saturnclient.emotes.core.bend.LimbBend;
|
||||
import org.saturnclient.emotes.fabric.BendTransforms;
|
||||
import org.saturnclient.emotes.fabric.access.BendablePart;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
import net.minecraft.client.model.HumanoidModel;
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
import net.minecraft.world.entity.HumanoidArm;
|
||||
//? if >=1.21.11 {
|
||||
import net.minecraft.client.model.player.PlayerModel;
|
||||
//?} else
|
||||
/*import net.minecraft.client.model.PlayerModel;*/
|
||||
|
||||
/**
|
||||
* Held items are placed at the hand of the straight arm. PlayerModel overrides translateToHand (for
|
||||
* slim arms) without calling HumanoidModel's, so this has to hook PlayerModel's own.
|
||||
*/
|
||||
@Mixin(PlayerModel.class)
|
||||
public class PlayerModelMixin {
|
||||
//? if >=1.21.9 {
|
||||
@Unique
|
||||
private static final String TRANSLATE_TO_HAND = "translateToHand(Lnet/minecraft/client/renderer/entity/state/AvatarRenderState;Lnet/minecraft/world/entity/HumanoidArm;Lcom/mojang/blaze3d/vertex/PoseStack;)V";
|
||||
//?} else
|
||||
/*@Unique private static final String TRANSLATE_TO_HAND = "translateToHand(Lnet/minecraft/world/entity/HumanoidArm;Lcom/mojang/blaze3d/vertex/PoseStack;)V";*/
|
||||
|
||||
/** Moves the item from the hand of the straight arm to the hand of the bent one. */
|
||||
@Inject(method = TRANSLATE_TO_HAND, at = @At("TAIL"))
|
||||
private void saturnEmotes$followBentArm(CallbackInfo ci, @Local(argsOnly = true) HumanoidArm arm,
|
||||
@Local(argsOnly = true) PoseStack poseStack) {
|
||||
HumanoidModel<?> model = (HumanoidModel<?>) (Object) this;
|
||||
ModelPart part = arm == HumanoidArm.LEFT ? model.leftArm : model.rightArm;
|
||||
LimbBend bend = ((BendablePart) (Object) part).saturnEmotes$getBend();
|
||||
if (bend != null && !bend.isStraight()) {
|
||||
BendTransforms.attachAt(poseStack, bend, bend.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,12 @@
|
||||
"package": "org.saturnclient.emotes.fabric.mixin",
|
||||
"compatibilityLevel": "${java}",
|
||||
"client": [
|
||||
"FirstPersonHandMixin",
|
||||
"HumanoidModelMixin",
|
||||
"LivingEntityRenderStateMixin",
|
||||
"LivingEntityRendererMixin",
|
||||
"ModelPartMixin"
|
||||
"ModelPartMixin",
|
||||
"PlayerModelMixin"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
|
||||
@@ -49,6 +49,29 @@ public final class EmotesGameTest implements FabricClientGameTest {
|
||||
}
|
||||
context.runOnClient(client -> Emotes.stop(client.player.getUUID()));
|
||||
}
|
||||
|
||||
// A held item should come out of the fist on the bent forearm.
|
||||
singleplayer.getServer().runCommand("give @a diamond_sword");
|
||||
context.runOnClient(client -> Emotes.play(client.player.getUUID(), TestAnimations.ALL.get("hold")));
|
||||
context.waitTicks(10);
|
||||
screenshot(context, "hold-0", 0);
|
||||
screenshot(context, "hold-90", 90);
|
||||
|
||||
// The first-person arm shares the model, so it must not keep the bend from the frame above.
|
||||
// Vanilla only draws the arm with an empty hand, and hiding the GUI hides it too.
|
||||
singleplayer.getServer().runCommand("clear @a");
|
||||
context.waitTicks(2);
|
||||
context.runOnClient(client -> {
|
||||
client.options.hideGui = false;
|
||||
client.options.setCameraType(CameraType.FIRST_PERSON);
|
||||
});
|
||||
// Give the hand time to come back up after the item change.
|
||||
context.waitTicks(20);
|
||||
context.takeScreenshot(MINECRAFT + "-hold-firstperson");
|
||||
context.runOnClient(client -> {
|
||||
Emotes.stop(client.player.getUUID());
|
||||
client.options.hideGui = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,10 @@ final class TestAnimations {
|
||||
.set(Bone.BODY, BoneTransform.IDENTITY.withBend(lean, 0))
|
||||
.build();
|
||||
}),
|
||||
// Right arm forward with the forearm folded up, as if showing off a sword. Tests held items.
|
||||
"hold", of(20, true, t -> Pose.builder()
|
||||
.set(Bone.RIGHT_ARM, BoneTransform.rotation(-0.9f, 0, 0).withBend(1.4f, 0))
|
||||
.build()),
|
||||
"twerk", of(8, true, TestAnimations::twerk));
|
||||
|
||||
private TestAnimations() {
|
||||
|
||||
Reference in New Issue
Block a user