Properly render both arms in first person when holding an entity

This commit is contained in:
Sollace 2023-01-09 01:25:52 +01:00
parent 015d3b29fa
commit 44802abf1b
7 changed files with 152 additions and 14 deletions

View file

@ -0,0 +1,32 @@
package com.minelittlepony.unicopia.client;
import java.util.Optional;
import com.minelittlepony.unicopia.client.render.AccessoryFeatureRenderer;
import com.minelittlepony.unicopia.entity.player.Pony;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.item.HeldItemRenderer;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.Arm;
public class FirstPersonRendererOverrides {
public static final FirstPersonRendererOverrides INSTANCE = new FirstPersonRendererOverrides();
public Optional<HeldItemRenderer.HandRenderType> getHandRenderType(PlayerEntity player) {
return Pony.of(player).getEntityInArms().map(e -> HeldItemRenderer.HandRenderType.RENDER_BOTH_HANDS);
}
public boolean beforeRenderHands(ArmRenderer sender, float tickDelta, MatrixStack matrices, VertexConsumerProvider.Immediate vertexConsumers, ClientPlayerEntity player, int light) {
var root = AccessoryFeatureRenderer.FeatureRoot.of(player);
return root != null && root.getAccessories().beforeRenderArms(sender, tickDelta, matrices, vertexConsumers, player, light);
}
public interface ArmRenderer {
void invokeRenderArmHoldingItem(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, float equipProgress, float swingProgress, Arm arm);
float getEquipProgress(float tickDelta);
}
}

View file

@ -1,9 +1,14 @@
package com.minelittlepony.unicopia.client.render; package com.minelittlepony.unicopia.client.render;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.client.FirstPersonRendererOverrides.ArmRenderer;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.model.ModelPart; import net.minecraft.client.model.ModelPart;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.entity.feature.FeatureRenderer; import net.minecraft.client.render.entity.feature.FeatureRenderer;
import net.minecraft.client.render.entity.feature.FeatureRendererContext; import net.minecraft.client.render.entity.feature.FeatureRendererContext;
@ -39,6 +44,14 @@ public class AccessoryFeatureRenderer<
features.forEach(feature -> feature.renderArm(matrices, vertexConsumers, light, entity, arm, side)); features.forEach(feature -> feature.renderArm(matrices, vertexConsumers, light, entity, arm, side));
} }
public boolean beforeRenderArms(ArmRenderer sender, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, T entity, int light) {
boolean cancelled = false;
for (var feature : features) {
cancelled |= feature.beforeRenderArms(sender, tickDelta, matrices, vertexConsumers, entity, light);
}
return cancelled;
}
public interface FeatureFactory<T extends LivingEntity> { public interface FeatureFactory<T extends LivingEntity> {
Feature<T> create(FeatureRendererContext<T, ? extends BipedEntityModel<T>> context); Feature<T> create(FeatureRendererContext<T, ? extends BipedEntityModel<T>> context);
} }
@ -46,7 +59,10 @@ public class AccessoryFeatureRenderer<
public interface Feature<T extends LivingEntity> { public interface Feature<T extends LivingEntity> {
void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, T entity, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch); void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, T entity, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch);
default void renderArm(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, T entity, ModelPart arm, Arm side) { default void renderArm(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, T entity, ModelPart arm, Arm side) {}
default boolean beforeRenderArms(ArmRenderer sender, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, T entity, int light) {
return false;
} }
} }
@ -54,5 +70,14 @@ public class AccessoryFeatureRenderer<
T extends LivingEntity, T extends LivingEntity,
M extends BipedEntityModel<T>> { M extends BipedEntityModel<T>> {
AccessoryFeatureRenderer<T, M> getAccessories(); AccessoryFeatureRenderer<T, M> getAccessories();
@SuppressWarnings("unchecked")
@Nullable
static <T extends LivingEntity, M extends BipedEntityModel<T>> FeatureRoot<T, M> of(T entity) {
var renderer = MinecraftClient.getInstance().getEntityRenderDispatcher().getRenderer(entity);
if (renderer instanceof FeatureRoot) {
return (FeatureRoot<T, M>)renderer;
}
return null;
}
} }
} }

View file

@ -1,5 +1,6 @@
package com.minelittlepony.unicopia.client.render; package com.minelittlepony.unicopia.client.render;
import com.minelittlepony.unicopia.client.FirstPersonRendererOverrides.ArmRenderer;
import com.minelittlepony.unicopia.client.minelittlepony.MineLPDelegate; import com.minelittlepony.unicopia.client.minelittlepony.MineLPDelegate;
import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.duck.EntityDuck; import com.minelittlepony.unicopia.entity.duck.EntityDuck;
@ -20,7 +21,10 @@ import net.minecraft.util.math.*;
public class HeldEntityFeatureRenderer<E extends LivingEntity> implements AccessoryFeatureRenderer.Feature<E> { public class HeldEntityFeatureRenderer<E extends LivingEntity> implements AccessoryFeatureRenderer.Feature<E> {
private final FeatureRendererContext<E, ? extends BipedEntityModel<E>> context;
public HeldEntityFeatureRenderer(FeatureRendererContext<E, ? extends BipedEntityModel<E>> context) { public HeldEntityFeatureRenderer(FeatureRendererContext<E, ? extends BipedEntityModel<E>> context) {
this.context = context;
} }
@Override @Override
@ -47,23 +51,37 @@ public class HeldEntityFeatureRenderer<E extends LivingEntity> implements Access
@Override @Override
public void renderArm(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, E entity, ModelPart arm, Arm side) { public void renderArm(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, E entity, ModelPart arm, Arm side) {
Pony.of(entity).flatMap(Pony::getEntityInArms).ifPresent(passenger -> {
float tickDelta = MinecraftClient.getInstance().getTickDelta();
}
@Override
public boolean beforeRenderArms(ArmRenderer sender, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, E entity, int light) {
return Pony.of(entity).flatMap(Pony::getEntityInArms).filter(passenger -> {
float swingProgress = entity.getHandSwingProgress(MinecraftClient.getInstance().getTickDelta());
float f = -0.4f * MathHelper.sin(MathHelper.sqrt(swingProgress) * (float)Math.PI);
float g = 0.2f * MathHelper.sin(MathHelper.sqrt(swingProgress) * ((float)Math.PI * 2));
float h = -0.2f * MathHelper.sin(swingProgress * (float)Math.PI);
matrices.push(); matrices.push();
matrices.translate(0.8F, 0.4F, -0.1F); matrices.translate(f, g, h);
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(90)); matrices.translate(0, -1.3F, -1.3F);
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(-60 - 13)); matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(13));
matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(-30));
matrices.translate(0, 0, -0.2F);
if (!(passenger instanceof Pony)) { if (!(passenger instanceof Pony)) {
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(90)); matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(90));
} }
matrices.translate(-passenger.asEntity().getWidth() / 16F, 0.3F, 0);
renderCarriedEntity(passenger.asEntity(), matrices, vertexConsumers, light, tickDelta); renderCarriedEntity(passenger.asEntity(), matrices, vertexConsumers, light, tickDelta);
matrices.pop(); matrices.pop();
});
float equipProgress = 1 - sender.getEquipProgress(tickDelta);
matrices.push();
sender.invokeRenderArmHoldingItem(matrices, vertexConsumers, light, equipProgress, swingProgress, Arm.LEFT);
matrices.pop();
matrices.push();
sender.invokeRenderArmHoldingItem(matrices, vertexConsumers, light, equipProgress, swingProgress, Arm.RIGHT);
matrices.pop();
return true;
}).isPresent();
} }
private void renderCarriedEntity(LivingEntity p, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, float tickDelta) { private void renderCarriedEntity(LivingEntity p, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, float tickDelta) {

View file

@ -0,0 +1,58 @@
package com.minelittlepony.unicopia.mixin.client;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.gen.Invoker;
import org.spongepowered.asm.mixin.injection.*;
import org.spongepowered.asm.mixin.injection.At.Shift;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import com.minelittlepony.unicopia.client.FirstPersonRendererOverrides;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.item.HeldItemRenderer;
import net.minecraft.client.render.item.HeldItemRenderer.HandRenderType;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.Arm;
import net.minecraft.util.math.MathHelper;
@Mixin(HeldItemRenderer.class)
abstract class MixinHeldItemRenderer implements FirstPersonRendererOverrides.ArmRenderer {
@Shadow
private float equipProgressMainHand;
@Shadow
private float prevEquipProgressMainHand;
@Override
@Invoker("renderArmHoldingItem")
public abstract void invokeRenderArmHoldingItem(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, float equipProgress, float swingProgress, Arm arm);
@Override
public float getEquipProgress(float tickDelta) {
return MathHelper.lerp(tickDelta, prevEquipProgressMainHand, equipProgressMainHand);
}
@Inject(
method = "getHandRenderType",
at = @At("HEAD"),
cancellable = true
)
private static void onGetHandRenderType(ClientPlayerEntity player, CallbackInfoReturnable<HandRenderType> info) {
FirstPersonRendererOverrides.INSTANCE.getHandRenderType(player).ifPresent(info::setReturnValue);
}
@Inject(
method = "renderItem(FLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider$Immediate;Lnet/minecraft/client/network/ClientPlayerEntity;I)V",
at = @At(
value = "FIELD",
target = "net/minecraft/client/render/item/HeldItemRenderer$HandRenderType.renderMainHand",
shift = Shift.BEFORE
),
cancellable = true)
private void onRenderItem(float tickDelta, MatrixStack matrices, VertexConsumerProvider.Immediate vertexConsumers, ClientPlayerEntity player, int light, CallbackInfo info) {
if (FirstPersonRendererOverrides.INSTANCE.beforeRenderHands(this, tickDelta, matrices, vertexConsumers, player, light)) {
info.cancel();
}
}
}

View file

@ -21,14 +21,17 @@ import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.Arm; import net.minecraft.util.Arm;
@Mixin(PlayerEntityRenderer.class) @Mixin(PlayerEntityRenderer.class)
abstract class MixinPlayerEntityRenderer extends LivingEntityRenderer<AbstractClientPlayerEntity, PlayerEntityModel<AbstractClientPlayerEntity>> { abstract class MixinPlayerEntityRenderer
extends LivingEntityRenderer<AbstractClientPlayerEntity, PlayerEntityModel<AbstractClientPlayerEntity>>
implements FeatureRoot<AbstractClientPlayerEntity, PlayerEntityModel<AbstractClientPlayerEntity>> {
@Nullable @Nullable
private AccessoryFeatureRenderer<AbstractClientPlayerEntity, PlayerEntityModel<AbstractClientPlayerEntity>> accessories; private AccessoryFeatureRenderer<AbstractClientPlayerEntity, PlayerEntityModel<AbstractClientPlayerEntity>> accessories;
MixinPlayerEntityRenderer() { super(null, null, 0); } MixinPlayerEntityRenderer() { super(null, null, 0); }
@Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private AccessoryFeatureRenderer<AbstractClientPlayerEntity, PlayerEntityModel<AbstractClientPlayerEntity>> getAccessories() { public AccessoryFeatureRenderer<AbstractClientPlayerEntity, PlayerEntityModel<AbstractClientPlayerEntity>> getAccessories() {
if (accessories == null) { if (accessories == null) {
accessories = features.stream() accessories = features.stream()
.filter(a -> a instanceof FeatureRoot) .filter(a -> a instanceof FeatureRoot)

View file

@ -1,3 +1,4 @@
accessWidener v1 named accessWidener v1 named
accessible class net/minecraft/client/render/RenderLayer$MultiPhaseParameters accessible class net/minecraft/client/render/RenderLayer$MultiPhaseParameters
accessible method net/minecraft/client/render/RenderLayer of (Ljava/lang/String;Lnet/minecraft/client/render/VertexFormat;Lnet/minecraft/client/render/VertexFormat$DrawMode;IZZLnet/minecraft/client/render/RenderLayer$MultiPhaseParameters;)Lnet/minecraft/client/render/RenderLayer$MultiPhase; accessible method net/minecraft/client/render/RenderLayer of (Ljava/lang/String;Lnet/minecraft/client/render/VertexFormat;Lnet/minecraft/client/render/VertexFormat$DrawMode;IZZLnet/minecraft/client/render/RenderLayer$MultiPhaseParameters;)Lnet/minecraft/client/render/RenderLayer$MultiPhase;
accessible class net/minecraft/client/render/item/HeldItemRenderer$HandRenderType

View file

@ -47,6 +47,7 @@
"client.MixinCamera", "client.MixinCamera",
"client.MixinEntityRenderDispatcher", "client.MixinEntityRenderDispatcher",
"client.MixinGameRenderer", "client.MixinGameRenderer",
"client.MixinHeldItemRenderer",
"client.MixinInGameHud", "client.MixinInGameHud",
"client.MixinInGameHud$HeartType", "client.MixinInGameHud$HeartType",
"client.MixinItem", "client.MixinItem",