From 8181f11c5a2ea1f2e228915627caa95e52b13496 Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Fri, 18 Sep 2026 18:05:18 +0200 Subject: [PATCH] =?UTF-8?q?fix(render):=20=D1=87=D0=B8=D0=BD=D0=B8=D0=BC?= =?UTF-8?q?=20EndCrystal=20=D0=BF=D0=BE=D0=B4=20=D0=BD=D0=BE=D0=B2=D1=8B?= =?UTF-8?q?=D0=B9=20MC=2026.2=20API,=20AutoLeave=20race=20=D0=B8=20InvisES?= =?UTF-8?q?P=20alpha?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/modules/player/auto/AutoLeave.java | 7 +- .../living/LivingEntityRendererMixin.java | 36 +++++++++ .../render/entity/EndCrystalModelMixin.java | 40 +++++++++ .../entity/EndCrystalModelTintMixin.java | 50 ++++++++++++ .../entity/EndCrystalRendererMixin.java | 81 ------------------- .../level/FireworkParticlesStarterMixin.java | 14 ++-- .../fade/ViewObstructionFadeContext.java | 5 ++ .../render/invisible/SeeInvisiblesReveal.java | 13 +++ .../src/main/resources/lovisual.mixins.json | 3 +- 9 files changed, 160 insertions(+), 89 deletions(-) create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/render/entity/EndCrystalModelMixin.java create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/render/entity/EndCrystalModelTintMixin.java delete mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/render/entity/EndCrystalRendererMixin.java diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/player/auto/AutoLeave.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/player/auto/AutoLeave.java index bc858fa..533c738 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/player/auto/AutoLeave.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/player/auto/AutoLeave.java @@ -110,7 +110,12 @@ public class AutoLeave extends Module { private void leave(Minecraft mc) { policy.reset(); - mc.disconnect(null, false); + if (mc.getConnection() == null) return; // already disconnected/disconnecting elsewhere + try { + mc.disconnect(null, false); + } catch (IllegalStateException e) { + // A disconnect (e.g. server-initiated) was already in progress this same tick; nothing left to do. + } } /** diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/entity/living/LivingEntityRendererMixin.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/entity/living/LivingEntityRendererMixin.java index 637eabc..551486e 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/entity/living/LivingEntityRendererMixin.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/entity/living/LivingEntityRendererMixin.java @@ -181,6 +181,42 @@ public abstract class LivingEntityRendererMixin original) { + if (!(state2 instanceof LivingEntityRenderState livingState) || !SeeInvisiblesReveal.isRevealedTranslucent(livingState)) { + original.call(collector, model, state2, poseStack, renderType, light, overlay, color, sprite, outlineColor, crumbling); + return; + } + float priorAlpha = ViewObstructionFadeContext.isActive() ? ViewObstructionFadeContext.alpha() : 1.0f; + ViewObstructionFadeContext.push(true, priorAlpha * SeeInvisiblesReveal.revealAlpha()); + try { + original.call(collector, model, state2, poseStack, renderType, light, overlay, color, sprite, outlineColor, crumbling); + } finally { + ViewObstructionFadeContext.pop(); + } + } + @Inject(method = "submit", at = @At("RETURN")) private void lovisual$popViewObstructionFadeContext(S state, com.mojang.blaze3d.vertex.PoseStack matrixStack, net.minecraft.client.renderer.SubmitNodeCollector orderedRenderCommandQueue, net.minecraft.client.renderer.state.level.CameraRenderState cameraRenderState, CallbackInfo ci) { ViewObstructionFadeContext.pop(); diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/render/entity/EndCrystalModelMixin.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/render/entity/EndCrystalModelMixin.java new file mode 100644 index 0000000..beb7ff3 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/render/entity/EndCrystalModelMixin.java @@ -0,0 +1,40 @@ +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 loki5512344. + * + * Licensed under the GNU General Public License v3.0. + */ + +package dev.loki.lovisual.mixins.render.entity; + +import dev.loki.lovisual.features.module.lifecycle.Modules; +import dev.loki.lovisual.features.module.modules.visuals.scene.EndCrystal; +import net.minecraft.client.model.object.crystal.EndCrystalModel; +import net.minecraft.client.renderer.entity.state.EndCrystalRenderState; +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; + +/** + * Скрытие частей модели энд-кристалла по настройкам {@link EndCrystal}. + * {@code outerGlass}, {@code innerGlass} и {@code cube} — вложенные друг в + * друга {@link net.minecraft.client.model.geom.ModelPart}, поэтому вместо + * {@code visible} (которое обрезало бы и дочерние части) используется + * {@code skipDraw} — он прячет только собственную геометрию части, не + * затрагивая рендер детей. + */ +@Mixin(EndCrystalModel.class) +public abstract class EndCrystalModelMixin { + + @Inject(method = "setupAnim", at = @At("TAIL")) + private void lovisual$applyPartVisibility(EndCrystalRenderState state, CallbackInfo ci) { + EndCrystalModel self = (EndCrystalModel) (Object) this; + EndCrystal module = Modules.get(EndCrystal.class); + if (module == null || !module.isEnabled()) return; + + self.outerGlass.skipDraw = !module.outerGlassVisible.get(); + self.innerGlass.skipDraw = !module.innerGlassVisible.get(); + self.cube.skipDraw = !module.cubeVisible.get(); + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/render/entity/EndCrystalModelTintMixin.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/render/entity/EndCrystalModelTintMixin.java new file mode 100644 index 0000000..d7d3cbd --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/render/entity/EndCrystalModelTintMixin.java @@ -0,0 +1,50 @@ +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 loki5512344. + * + * Licensed under the GNU General Public License v3.0. + */ + +package dev.loki.lovisual.mixins.render.entity; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import dev.loki.lovisual.features.module.lifecycle.Modules; +import dev.loki.lovisual.features.module.modules.visuals.scene.EndCrystal; +import net.minecraft.client.model.Model; +import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.client.model.object.crystal.EndCrystalModel; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +/** + * Тонировка модели энд-кристалла по настройкам {@link EndCrystal}. + * {@code Model#renderToBuffer} — единственная точка, где весь меш (все части + * разом) отправляется на отрисовку с одним общим ARGB-цветом, поэтому тут же + * подменяется итоговый {@code color} для {@link EndCrystalModel}, если у + * модуля выбран Custom-режим окраски. + */ +@Mixin(Model.class) +public abstract class EndCrystalModelTintMixin { + + @WrapOperation( + method = "renderToBuffer", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/model/geom/ModelPart;render(Lcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;III)V") + ) + private void lovisual$tintEndCrystal(ModelPart root, PoseStack poseStack, VertexConsumer buffer, int light, + int overlay, int color, Operation original) { + if ((Object) this instanceof EndCrystalModel) { + EndCrystal module = Modules.get(EndCrystal.class); + if (module != null && module.isEnabled()) { + int tint = module.resolveTintArgb(); + if (tint != -1) { + original.call(root, poseStack, buffer, light, overlay, tint); + return; + } + } + } + original.call(root, poseStack, buffer, light, overlay, color); + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/render/entity/EndCrystalRendererMixin.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/render/entity/EndCrystalRendererMixin.java deleted file mode 100644 index bf5f134..0000000 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/render/entity/EndCrystalRendererMixin.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of the LoVisual Client distribution. - * Copyright (c) 2026 loki5512344. - * - * Licensed under the GNU General Public License v3.0. - */ - -package dev.loki.lovisual.mixins.render.entity; - -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.blaze3d.vertex.VertexConsumer; -import dev.loki.lovisual.features.module.lifecycle.Modules; -import dev.loki.lovisual.features.module.modules.visuals.scene.EndCrystal; -import net.minecraft.client.model.geom.ModelPart; -import net.minecraft.client.renderer.entity.EndCrystalRenderer; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -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; - -/** - * Тонировка стекла/куба энд-кристалла и скрытие отдельных частей модели по - * настройкам {@link EndCrystal}. Ванильный {@code render(...)} зовёт - * {@code this.glass.render(...)} дважды подряд (внешний куб, затем повёрнутый - * вложенный) и {@code this.cube.render(...)} один раз — различаем эти три - * вызова счётчиком {@link #lovisual$glassCallOrdinal}, сбрасываемым в начале - * каждого рендера кристалла. - */ -@Mixin(EndCrystalRenderer.class) -public abstract class EndCrystalRendererMixin { - - @Shadow - private ModelPart glass; - @Shadow - private ModelPart cube; - - @Unique - private int lovisual$glassCallOrdinal; - - @Inject( - method = "render(Lnet/minecraft/world/entity/boss/enderdragon/EndCrystal;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", - at = @At("HEAD") - ) - private void lovisual$resetOrdinal(Object entity, float entityYaw, float partialTicks, PoseStack poseStack, - Object buffer, int packedLight, CallbackInfo ci) { - lovisual$glassCallOrdinal = 0; - } - - @WrapOperation( - method = "render(Lnet/minecraft/world/entity/boss/enderdragon/EndCrystal;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", - at = @At(value = "INVOKE", target = "Lnet/minecraft/client/model/geom/ModelPart;render(Lcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;II)V") - ) - private void lovisual$tintOrHideModelPart(ModelPart instance, PoseStack poseStack, VertexConsumer vertexConsumer, - int light, int overlay, Operation original) { - EndCrystal module = Modules.get(EndCrystal.class); - if (module == null || !module.isEnabled()) { - original.call(instance, poseStack, vertexConsumer, light, overlay); - return; - } - - boolean visible; - if (instance == this.cube) { - visible = module.cubeVisible.get(); - } else { - lovisual$glassCallOrdinal++; - visible = lovisual$glassCallOrdinal == 1 ? module.outerGlassVisible.get() : module.innerGlassVisible.get(); - } - if (!visible) return; - - int tint = module.resolveTintArgb(); - if (tint == -1) { - original.call(instance, poseStack, vertexConsumer, light, overlay); - } else { - instance.render(poseStack, vertexConsumer, light, overlay, tint); - } - } -} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/render/level/FireworkParticlesStarterMixin.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/render/level/FireworkParticlesStarterMixin.java index c42980b..c8fd554 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/render/level/FireworkParticlesStarterMixin.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/render/level/FireworkParticlesStarterMixin.java @@ -11,42 +11,44 @@ import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Coerce; /** * Vanilla bug: FireworkParticles$Starter.createParticle assumes ParticleEngine.createParticle never returns null. * Our ParticleManagerMixin returns null when a ParticleSpawnEvent is cancelled (e.g. FpsOptimizer.disable_particles). * This causes NPE: sparkParticle is null -> setTrail() crashes. - * Fix: guard every SparkParticle setter to be null-safe. Use Object for private inner type. + * Fix: guard every SparkParticle setter to be null-safe. SparkParticle is a private inner class, so we use Object + * with @Coerce to avoid InvalidInjectionException (expected SparkParticle vs Object). */ @Mixin(targets = "net.minecraft.client.particle.FireworkParticles$Starter") public abstract class FireworkParticlesStarterMixin { @WrapOperation(method = "createParticle", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/FireworkParticles$SparkParticle;setTrail(Z)V")) - private void lovisual$guardSetTrail(Object instance, boolean trail, Operation original) { + private void lovisual$guardSetTrail(@Coerce Object instance, boolean trail, Operation original) { if (instance == null) return; original.call(instance, trail); } @WrapOperation(method = "createParticle", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/FireworkParticles$SparkParticle;setTwinkle(Z)V")) - private void lovisual$guardSetTwinkle(Object instance, boolean twinkle, Operation original) { + private void lovisual$guardSetTwinkle(@Coerce Object instance, boolean twinkle, Operation original) { if (instance == null) return; original.call(instance, twinkle); } @WrapOperation(method = "createParticle", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/FireworkParticles$SparkParticle;setAlpha(F)V")) - private void lovisual$guardSetAlpha(Object instance, float alpha, Operation original) { + private void lovisual$guardSetAlpha(@Coerce Object instance, float alpha, Operation original) { if (instance == null) return; original.call(instance, alpha); } @WrapOperation(method = "createParticle", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/FireworkParticles$SparkParticle;setColor(I)V")) - private void lovisual$guardSetColor(Object instance, int color, Operation original) { + private void lovisual$guardSetColor(@Coerce Object instance, int color, Operation original) { if (instance == null) return; original.call(instance, color); } @WrapOperation(method = "createParticle", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/FireworkParticles$SparkParticle;setFadeColor(I)V")) - private void lovisual$guardSetFadeColor(Object instance, int fadeColor, Operation original) { + private void lovisual$guardSetFadeColor(@Coerce Object instance, int fadeColor, Operation original) { if (instance == null) return; original.call(instance, fadeColor); } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/fade/ViewObstructionFadeContext.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/fade/ViewObstructionFadeContext.java index 97e804a..c68bf03 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/fade/ViewObstructionFadeContext.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/fade/ViewObstructionFadeContext.java @@ -25,6 +25,11 @@ public enum ViewObstructionFadeContext { )); } + /** Pushes an explicit fade state, used when combining multiple independent fade sources. */ + public static void push(boolean active, float alpha) { + STACK.get().addLast(active ? new State(true, alpha) : State.INACTIVE); + } + public static void pop() { ArrayDeque stack = STACK.get(); if (!stack.isEmpty()) { diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/invisible/SeeInvisiblesReveal.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/invisible/SeeInvisiblesReveal.java index 0fd4864..9b1e6c1 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/invisible/SeeInvisiblesReveal.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/invisible/SeeInvisiblesReveal.java @@ -39,6 +39,19 @@ public enum SeeInvisiblesReveal { state.isInvisible = false; } + /** @return true if the entity was revealed this frame and should render below full opacity. */ + public static boolean isRevealedTranslucent(LivingEntityRenderState state) { + InvisEsp module = Modules.get(InvisEsp.class); + return module != null && module.isEnabled() && module.isTranslucentReveal() + && state instanceof SeeInvisiblesState seeInvis && seeInvis.lovisual$isSeeInvisRevealed(); + } + + /** @return the configured InvisESP reveal opacity, or fully opaque if the module is missing. */ + public static float revealAlpha() { + InvisEsp module = Modules.get(InvisEsp.class); + return module == null ? 1.0f : module.getOpacity(); + } + public static RenderType renderTypeFor(RenderType original, Identifier texture) { InvisEsp module = Modules.get(InvisEsp.class); if (module == null || !module.isEnabled() || IrisRuntime.isRenderingShadowPass()) { diff --git a/combatant-client-26.2/src/main/resources/lovisual.mixins.json b/combatant-client-26.2/src/main/resources/lovisual.mixins.json index d7b16e5..939f3fb 100644 --- a/combatant-client-26.2/src/main/resources/lovisual.mixins.json +++ b/combatant-client-26.2/src/main/resources/lovisual.mixins.json @@ -140,7 +140,8 @@ "network.game.PlayerInfoMixin", "network.packet.ClientCommonPacketListenerImplMixin", "network.packet.ServerboundUseItemPacketMixin", - "render.entity.EndCrystalRendererMixin", + "render.entity.EndCrystalModelMixin", + "render.entity.EndCrystalModelTintMixin", "render.entity.EntityRenderDispatcherMixin", "render.entity.ItemEntityRenderStateMixin", "render.entity.ItemEntityRendererMixin",