diff --git a/src/main/java/com/denizenscript/clientizen/Clientizen.java b/src/main/java/com/denizenscript/clientizen/Clientizen.java index cf4b0f5..ef8ae2e 100644 --- a/src/main/java/com/denizenscript/clientizen/Clientizen.java +++ b/src/main/java/com/denizenscript/clientizen/Clientizen.java @@ -5,6 +5,7 @@ import com.denizenscript.clientizen.network.NetworkManager; import com.denizenscript.clientizen.objects.ClientizenObjectRegistry; import com.denizenscript.clientizen.objects.properties.PropertyRegistry; +import com.denizenscript.clientizen.render.ClientizenAttachedEntityFeatureRenderer; import com.denizenscript.clientizen.scripts.commands.ClientizenCommandRegistry; import com.denizenscript.clientizen.scripts.containers.ClientizenContainerRegistry; import com.denizenscript.clientizen.tags.ClientizenTagContext; @@ -79,6 +80,7 @@ public void onInitializeClient() { // Initialize Clientizen systems NetworkManager.init(); ClientizenDebugScreen.register(); + ClientizenAttachedEntityFeatureRenderer.init(); ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> new ClientExecuteCommand(dispatcher)); // Check for the client scripts folder diff --git a/src/main/java/com/denizenscript/clientizen/mixin/render/EntityRenderDispatcherMixin.java b/src/main/java/com/denizenscript/clientizen/mixin/render/EntityRenderDispatcherMixin.java new file mode 100644 index 0000000..c6f08db --- /dev/null +++ b/src/main/java/com/denizenscript/clientizen/mixin/render/EntityRenderDispatcherMixin.java @@ -0,0 +1,34 @@ +package com.denizenscript.clientizen.mixin.render; + +import com.denizenscript.clientizen.scripts.commands.AttachCommand; +import net.minecraft.client.render.Frustum; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.EntityRenderDispatcher; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.Entity; +import net.minecraft.world.WorldView; +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 org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(EntityRenderDispatcher.class) +public class EntityRenderDispatcherMixin { + + @Inject(method = "shouldRender", at = @At("HEAD"), cancellable = true) + private void clientizen$cancelRenderIfAttached( + E entity, Frustum frustum, double x, double y, double z, CallbackInfoReturnable cir) { + if (AttachCommand.attachedEntities.containsKey(entity.getUuid())) { + cir.setReturnValue(false); + } + } + + @Inject(method = "renderShadow", at = @At("HEAD"), cancellable = true) + private static void clientizen$cancelAttachShadow(MatrixStack matrices, VertexConsumerProvider vertexConsumers, Entity entity, float opacity, float tickDelta, WorldView world, float radius, CallbackInfo ci) { + AttachCommand.AttachData data = AttachCommand.attachedEntities.get(entity.getUuid()); + if (data != null && data.noShadow()) { + ci.cancel(); + } + } +} diff --git a/src/main/java/com/denizenscript/clientizen/mixin/render/LivingEntityRendererMixin.java b/src/main/java/com/denizenscript/clientizen/mixin/render/LivingEntityRendererMixin.java new file mode 100644 index 0000000..13c2300 --- /dev/null +++ b/src/main/java/com/denizenscript/clientizen/mixin/render/LivingEntityRendererMixin.java @@ -0,0 +1,58 @@ +package com.denizenscript.clientizen.mixin.render; + +import com.denizenscript.clientizen.scripts.commands.AttachCommand; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.LivingEntityRenderer; +import net.minecraft.client.render.entity.model.EntityModel; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +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.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(LivingEntityRenderer.class) +public abstract class LivingEntityRendererMixin> { + + private Entity clientizen$renderingEntity; + + @Inject(method = "setupTransforms", cancellable = true, at = @At("HEAD")) + private void clientizen$cancelAttachAnimation(T entity, MatrixStack matrices, float animationProgress, float bodyYaw, float tickDelta, CallbackInfo ci) { + AttachCommand.AttachData data = AttachCommand.attachedEntities.get(entity.getUuid()); + if (data != null && data.noAnimation()) { + ci.cancel(); + } + } + + @Inject(method = "render(Lnet/minecraft/entity/LivingEntity;FFLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", at = @At("HEAD")) + private void clientizen$captureRenderingEntity(T livingEntity, float f, float g, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, CallbackInfo ci) { + clientizen$renderingEntity = livingEntity; + } + + // TODO fix this (ordinal wrong maybe) + @ModifyVariable( + method = "render(Lnet/minecraft/entity/LivingEntity;FFLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", + at = @At(value = "STORE"), ordinal = 8) + private float clientizen$cancelAttachAngles(float animationProgress) { + if (clientizen$renderingEntity != null) { + AttachCommand.AttachData data = AttachCommand.attachedEntities.get(clientizen$renderingEntity.getUuid()); + if (data != null && data.noAnimation()) { + return 0.0f; + } + } + return animationProgress; + } + + @Redirect(method = "render(Lnet/minecraft/entity/LivingEntity;FFLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/entity/model/EntityModel;setAngles(Lnet/minecraft/entity/Entity;FFFFF)V")) + private void clienizen$disableAnimations(M instance, Entity entity, float limbAngle, float limbDistance, float animationProgress, float headYaw, float headPitch) { + AttachCommand.AttachData data = AttachCommand.attachedEntities.get(entity.getUuid()); + if (data == null || !data.noAnimation()) { + //noinspection unchecked + instance.setAngles((T) entity, limbAngle, limbDistance, animationProgress, headYaw, headPitch); + } + } +} diff --git a/src/main/java/com/denizenscript/clientizen/render/ClientizenAttachedEntityFeatureRenderer.java b/src/main/java/com/denizenscript/clientizen/render/ClientizenAttachedEntityFeatureRenderer.java new file mode 100644 index 0000000..f3a5f9c --- /dev/null +++ b/src/main/java/com/denizenscript/clientizen/render/ClientizenAttachedEntityFeatureRenderer.java @@ -0,0 +1,43 @@ +package com.denizenscript.clientizen.render; + +import com.denizenscript.clientizen.objects.EntityTag; +import com.denizenscript.clientizen.scripts.commands.AttachCommand; +import net.fabricmc.fabric.api.client.rendering.v1.LivingEntityFeatureRendererRegistrationCallback; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.feature.FeatureRenderer; +import net.minecraft.client.render.entity.feature.FeatureRendererContext; +import net.minecraft.client.render.entity.model.EntityModel; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.Entity; +import net.minecraft.util.math.Direction; + +import java.util.List; + +public class ClientizenAttachedEntityFeatureRenderer> extends FeatureRenderer { + + public static void init() { + LivingEntityFeatureRendererRegistrationCallback.EVENT.register((entityType, entityRenderer, registrationHelper, context) -> { + registrationHelper.register(new ClientizenAttachedEntityFeatureRenderer<>(entityRenderer)); + }); + } + + public ClientizenAttachedEntityFeatureRenderer(FeatureRendererContext context) { + super(context); + } + + @Override + public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, T entity, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) { + List attachedEntities = AttachCommand.attachMap.get(entity.getUuid()); + if (attachedEntities == null) { + return; + } + for (EntityTag attached : attachedEntities) { + matrices.push(); + matrices.scale(1, 1, 1); + matrices.multiply(Direction.DOWN.getRotationQuaternion()); + MinecraftClient.getInstance().getEntityRenderDispatcher().render(attached.getEntity(), 0, 0, 0, attached.getEntity().getYaw(tickDelta), tickDelta, matrices, vertexConsumers, light); + matrices.pop(); + } + } +} diff --git a/src/main/java/com/denizenscript/clientizen/scripts/commands/AttachCommand.java b/src/main/java/com/denizenscript/clientizen/scripts/commands/AttachCommand.java new file mode 100644 index 0000000..f25ca9b --- /dev/null +++ b/src/main/java/com/denizenscript/clientizen/scripts/commands/AttachCommand.java @@ -0,0 +1,54 @@ +package com.denizenscript.clientizen.scripts.commands; + +import com.denizenscript.clientizen.objects.EntityTag; +import com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException; +import com.denizenscript.denizencore.objects.core.ListTag; +import com.denizenscript.denizencore.scripts.ScriptEntry; +import com.denizenscript.denizencore.scripts.commands.AbstractCommand; +import com.denizenscript.denizencore.scripts.commands.generator.ArgDefaultNull; +import com.denizenscript.denizencore.scripts.commands.generator.ArgLinear; +import com.denizenscript.denizencore.scripts.commands.generator.ArgName; +import com.denizenscript.denizencore.scripts.commands.generator.ArgPrefixed; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class AttachCommand extends AbstractCommand { + + public record AttachData(boolean noShadow, boolean noAnimation) {} + + public static Map> attachMap = new HashMap<>(); + public static Map attachedEntities = new HashMap<>(); + + public AttachCommand() { + setName("attach"); + setSyntax("attach [|...] [to:] (cancel) (no_shadow) (no_animation)"); + setRequiredArguments(2, 5); + isProcedural = false; + autoCompile(); + } + + public static void autoExecute(ScriptEntry scriptEntry, + @ArgLinear @ArgName("entities") ListTag attachingEntities, + @ArgDefaultNull @ArgPrefixed @ArgName("to") EntityTag toEntity, + @ArgName("cancel") boolean cancel, + @ArgName("no_shadow") boolean noShadow, + @ArgName("no_animation") boolean noAnimation) { + if (!cancel && toEntity == null) { + throw new InvalidArgumentsRuntimeException("Must specify an entity to attach to"); + } + List attaching = attachingEntities.filter(EntityTag.class, scriptEntry.context); + AttachData attachData = new AttachData(noShadow, noAnimation); + if (attachMap.containsKey(toEntity.uuid)) { + attachMap.get(toEntity.uuid).addAll(attaching); + } + else { + attachMap.put(toEntity.uuid, attaching); + } + for (EntityTag entityTag : attaching) { + attachedEntities.put(entityTag.uuid, attachData); + } + } +} diff --git a/src/main/java/com/denizenscript/clientizen/scripts/commands/ClientizenCommandRegistry.java b/src/main/java/com/denizenscript/clientizen/scripts/commands/ClientizenCommandRegistry.java index ac07e85..92827d4 100644 --- a/src/main/java/com/denizenscript/clientizen/scripts/commands/ClientizenCommandRegistry.java +++ b/src/main/java/com/denizenscript/clientizen/scripts/commands/ClientizenCommandRegistry.java @@ -6,8 +6,9 @@ public class ClientizenCommandRegistry { public static void registerCommands() { - registerCommand(NarrateCommand.class); + registerCommand(AttachCommand.class); registerCommand(GuiCommand.class); + registerCommand(NarrateCommand.class); registerCommand(ServerEventCommand.class); } diff --git a/src/main/resources/clientizen.accesswidener b/src/main/resources/clientizen.accesswidener index c17e954..5811b7e 100644 --- a/src/main/resources/clientizen.accesswidener +++ b/src/main/resources/clientizen.accesswidener @@ -1,3 +1,7 @@ accessWidener v2 named accessible method net/minecraft/client/world/ClientWorld getEntityLookup ()Lnet/minecraft/world/entity/EntityLookup; + +accessible method net/minecraft/client/render/entity/LivingEntityRenderer addFeature (Lnet/minecraft/client/render/entity/feature/FeatureRenderer;)Z + +accessible method net/minecraft/client/render/entity/EntityRenderer getBlockLight (Lnet/minecraft/entity/Entity;Lnet/minecraft/util/math/BlockPos;)I diff --git a/src/main/resources/clientizen.mixins.json b/src/main/resources/clientizen.mixins.json index a082892..ad0f5b3 100644 --- a/src/main/resources/clientizen.mixins.json +++ b/src/main/resources/clientizen.mixins.json @@ -10,7 +10,9 @@ "FlagCommandMixin", "IntPropertyAccessor", "gui.WScrollPanelAccessor", - "gui.WTextAccessor" + "gui.WTextAccessor", + "render.EntityRenderDispatcherMixin", + "render.LivingEntityRendererMixin" ], "injectors": { "defaultRequire": 1