chore(history): squash 59 commit(s) from 2026-09-23

- refactor(hud/weather/render): Scoreboard 931→240 + Rain 903→159 + UiMeshGeometry 956→99 (8.5.2)
- refactor(chams): Chams 985 → 129 + 4×≤195 (8.5.2)
- refactor(module): Module 1031 → 139 + 4×≤134 (8.5.2)
- refactor(tooltips): BetterTooltips 960 → 181 + 4×≤199 (8.5.2)
- refactor(clickgui): ClickGuiPickerState 970→166 + ModulesMenuScreen 966→145 (8.5.2)
- refactor(clickgui): CombatProtocolHeuristicsEditorState 932→79 + 4×≤192 (8.5.2)
- refactor(hud): CompactHudStatModel 894 → 125 + 4×≤175 (8.5.2)
- refactor(iris): ShaderPatchCompiler 843→66 + 4×≤174 (8.5.2)
- refactor(tab): CustomTabList 834→136 + 4×≤166 (8.5.2)
- refactor(relations): OnlineRelationPlayerPickerComponent 837→74 + 4×≤192 (8.5.2)
- refactor(hits): HitEffect 770→120 + 4×≤176 (8.5.2)
- refactor(predict): Predictions 815→150 + 4×≤195 (8.5.2)
- refactor(config): ConfigProfilesComponent 799→125 + 4×≤195 (8.5.2)
- refactor(sim): PlayerMovementSimulation 757→157 + 4×≤194 (8.5.2)
- refactor(predict): ProjectilePuncher 747→104 + 3×≤134 (8.5.2)
- refactor(crosshair): Crosshair 743→143 + 4×≤167 (8.5.2)
- refactor(mediaplayer): MediaPlayer 743→174 + 4×≤196 (8.5.2)
- refactor(hud): Itemizer 699→193 + Cooldowns 707→151 (8.5.2)
- refactor(hud): Potions 617→176 + 4×≤166 (8.5.2)
- refactor(hud): Admins 702→118 + 4×≤138 (8.5.2)
- refactor(hud): Keybinds 611→157 + 4×≤160 (8.5.2)
- refactor(hud): ModuleList 499→181 + 3×≤150 (8.5.2)
- refactor(hud): Radar 392→161 + 2×≤124 (8.5.2)
- refactor(hud): Fps 309→168 + 3×≤107 (8.5.2)
- refactor(hud): Memory 304→151 + 3×≤144 (8.5.2)
- refactor(hud): Tps 339→154 + 3×≤143 (8.5.2)
- refactor(hud): Ping 309→145 + 3×≤125 (8.5.2)
- refactor(hud): SpeedBps 334→156 + 3×≤142 (8.5.2)
- refactor(hud): GameTime 314→150 + 3×≤127 (8.5.2)
- refactor(hud): SystemTime 300→136 + 3×≤130 (8.5.2)
- refactor(hud): Coordinates 337→174 + 2×≤172 (8.5.2)
- refactor(hud): Armor 324→162 + 2×≤148 (8.5.2)
- refactor(hud): Inventory 476→153 + 3×≤181 (8.5.2)
- docs(todo): обновлён 8.5.2 — отмечены готовые гиганты (BetterButtons/InventorySwap/Svg/Темы/WorldParticles/Module/Chams и хвост)
- docs(api): папка combatant-client-26.2/docs + addons.md (API можно ломать при рефакторе)
- refactor(visuals): BlockHighlight 692→271 + 4×≤200 (8.5.2)
- refactor(mixins): GameRendererMixin 830→344 (хуки) + 4 хелпера (8.5.2)
- refactor(visuals): WorldParticles 821→260 + 6×≤200, режимы добиты (8.5.2)
- docs(todo): 8.5.2 — WorldParticles/GameRendererMixin/BlockHighlight отмечены ГОТОВО
- refactor(render): MeshBuilder 738→447 (фасад write-API) + 3 хелпера, FrameStats снесён (8.5.2)
- docs(todo): Фаза 10 — дизайн платформы (аккаунты/конфиги/аватарки), backlog RPC-чата
- docs(todo): Подсистема 3 — Аддоны 2.0 (скрипты/версии) + витрина аддонов + админка
- docs(todo): Подсистема 2 — RPC-чат/друзья/виджеты-телеметрия + рейт-лимиты, доп. правки Подсистемы 3
- chore: переименование combatant-client-26.2 -> mod, добавлены backend/ и frontend/
- docs(backend): implementation plan for accounts-service (Подсистема 1, часть 1)
- chore: пересоздать backend/frontend через cargo new и bun create vite + tailwind; убрать таблицу компонентов из TODO.md
- docs(backend): edition 2024 в плане вместо 2021
- chore: обновить версии до реально актуальных (проверено компиляцией)
- docs: правила платформы (лимит 250 строк, антипаттерны backend/frontend по итогам ресёрча) + frontend/ARCHITECTURE.md
- docs: добавить правило ≤4 файла на папку в правила платформы (backend/frontend)
- docs: commit messages in English from now on
- chore: gitignore .superpowers/ scratch directory
- docs: commit messages in English from now on (mod)
- feat(backend): scaffold accounts-service with health check
- chore(backend): remove target/ build artifacts from git, add gitignore
- chore: track docs/ in git (was accidentally gitignored, never committed)
- chore(backend): convert to Cargo workspace, plan full microservice layout
- feat(backend): accounts/avatars/device_links schema + migration
- feat(backend): argon2id password hashing
This commit is contained in:
loki5512344 2026-09-23 22:38:08 +02:00
parent ec10395511
commit 9d08fa910a
2239 changed files with 35265 additions and 28110 deletions

View file

@ -0,0 +1,141 @@
/*
* 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.hud;
import net.minecraft.client.gui.components.DebugScreenOverlay;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import dev.loki.lovisual.config.i18n.MainConfig;
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
import dev.loki.lovisual.render.engine.debug.RenderThread2DDebugRenderer;
import dev.loki.lovisual.render.engine.msaa.MsaaWorldTarget;
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
import dev.loki.lovisual.render.engine.text.font.info.FontDebugStats;
import dev.loki.lovisual.util.logging.DebugMode;
import dev.loki.lovisual.util.wav.util.WavDebugStats;
import java.util.ArrayList;
import java.util.List;
@Mixin(DebugScreenOverlay.class)
public abstract class DebugHudMixin {
@ModifyArg(
method = "extractRenderState",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/gui/components/DebugScreenOverlay;extractLines(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Ljava/util/List;Z)V",
ordinal = 0
),
index = 1
)
private List<String> lovisual$addEngineInfo(List<String> list) {
if (list == null) return list;
DebugMode debugMode = MainConfig.get().getDebugMode();
if (debugMode != DebugMode.ALL && debugMode != DebugMode.RENDER_THREAD) return list;
int samples = MainConfig.get().getMsaa3dSamples();
boolean supported = LoVisualRenderSystem.rhi().msaa().supported();
MsaaWorldTarget.DebugStatus msaaStatus = MsaaWorldTarget.debugStatus();
String msaa;
if (samples <= 1) {
msaa = "msaa: off";
} else if (supported) {
msaa = String.format("msaa: cfg=%dx target=%dx %s %dx%d alloc=%s active=%s resolve=%s",
samples,
msaaStatus.targetSamples(),
msaaStatus.state(),
msaaStatus.width(),
msaaStatus.height(),
msaaStatus.allocated() ? "yes" : "no",
msaaStatus.active() ? "yes" : "no",
msaaStatus.lastResolveOk() ? "ok" : "no"
);
} else {
msaa = "msaa: unavailable";
}
List<String> out = new ArrayList<>(list.size() + 20);
out.add("§cLoVisual Engine");
out.add("status:§aok");
out.add(msaa);
RenderThread2DDebugRenderer.Snapshot debug2d = RenderThread2DDebugRenderer.snapshot();
out.add(String.format("2d rt gui: frame=%d gui=%dx%d rects=%d err=%s",
debug2d.guiStateFrame(),
debug2d.guiWidth(),
debug2d.guiHeight(),
debug2d.guiStateRects(),
debug2d.guiStateError() == null || debug2d.guiStateError().isBlank() ? "none" : debug2d.guiStateError()
));
out.add(String.format("2d rt imm: frame=%d fb=%dx%d draws=%d/%d v=%d i=%d err=%s",
debug2d.immediateFrame(),
debug2d.framebufferWidth(),
debug2d.framebufferHeight(),
debug2d.immediateDraws(),
debug2d.immediateBatches(),
debug2d.immediateVertices(),
debug2d.immediateIndices(),
debug2d.immediateError() == null || debug2d.immediateError().isBlank() ? "none" : debug2d.immediateError()
));
Renderer2D.BatchStats batchStats = Renderer2D.getBatchStats();
out.add(String.format("ui batch: %s, last %d draws / %d batches",
batchStats.isActive() ? "active" : "idle",
batchStats.getLastDraws(),
batchStats.getLastOrder()
));
out.add(String.format("ui batch frame: %d draws / %d batches",
batchStats.getFrameDraws(),
batchStats.getFrameBatches()
));
out.add(String.format("ui batch geom: v=%d, i=%d, pool=%d",
batchStats.getLastVertices(),
batchStats.getLastIndices(),
batchStats.getPoolTotal()
));
out.add(String.format("ui batch frame geom: v=%d, i=%d",
batchStats.getFrameVertices(),
batchStats.getFrameIndices()
));
if (batchStats.getLastError() != null && !batchStats.getLastError().isBlank()) {
out.add("ui batch warn: " + batchStats.getLastError());
}
out.add(String.format("text msdf: ok=%d, fb=%d, attempts=%d",
FontDebugStats.getMsdfSuccess(),
FontDebugStats.getMsdfFallbacks(),
FontDebugStats.getMsdfAttempts()
));
if (!FontDebugStats.getLastMsdfFallback().isBlank()) {
String reason = FontDebugStats.getLastMsdfFallbackReason();
out.add("text msdf last fb: " + FontDebugStats.getLastMsdfFallback()
+ (reason.isBlank() ? "" : " (" + reason + ")"));
}
out.add(String.format("wav(al): buffers %d/%d, sources %d/%d",
WavDebugStats.getBuffersAlive(),
WavDebugStats.getBuffersCreated(),
WavDebugStats.getSourcesAlive(),
WavDebugStats.getSourcesCreated()
));
out.add(String.format("wav(al): allocs=%d (buf=%d, src=%d)",
WavDebugStats.getBuffersCreated() + WavDebugStats.getSourcesCreated(),
WavDebugStats.getBuffersCreated(),
WavDebugStats.getSourcesCreated()
));
out.add(String.format("wav(al): uploads=%d, bytes=%.2f MB",
WavDebugStats.getBufferUploads(),
WavDebugStats.getBufferBytes() / (1024.0 * 1024.0)
));
out.add("");
out.addAll(list);
return out;
}
}

View file

@ -0,0 +1,311 @@
/*
* 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.hud;
import dev.loki.lovisual.util.screen.ClientScreen;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipPositioner;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.util.FormattedCharSequence;
import net.minecraft.util.Mth;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import org.joml.Matrix3x2fStack;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import dev.loki.lovisual.features.gui.hud.base.CooldownRender;
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.tooltips.BetterTooltips;
import dev.loki.lovisual.features.module.lifecycle.Modules;
import dev.loki.lovisual.features.module.modules.combat.pvp.PvpCooldowns;
import dev.loki.lovisual.mixininterface.render.IGuiGraphics;
import dev.loki.lovisual.render.engine.core.context.ViewportContext;
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
import dev.loki.lovisual.render.engine.renderer.ui.deferred.sched.HudDeferredGuiElement;
import net.minecraft.client.gui.navigation.ScreenRectangle;
import net.minecraft.client.renderer.state.gui.GuiRenderState;
import dev.loki.lovisual.util.pvp.cooldown.CooldownRegistry;
import dev.loki.lovisual.util.pvp.cooldown.ItemCooldownSnapshot;
import dev.loki.lovisual.util.pvp.state.PvpState;
import dev.loki.lovisual.util.pvp.client.CooldownsState;
import dev.loki.lovisual.render.engine.text.font.Font;
@Mixin(GuiGraphicsExtractor.class)
public abstract class GuiGraphicsMixin implements IGuiGraphics {
@Final
@Shadow
private Matrix3x2fStack pose;
@Final
@Shadow
public GuiRenderState guiRenderState;
@Shadow
public abstract int guiWidth();
@Shadow
public abstract int guiHeight();
@Override
public void lovisual$runUnscaled(Runnable task) {
Minecraft mc = Minecraft.getInstance();
double scale = mc.getWindow().getGuiScale();
double uiScale = ViewportContext.getUiScale();
float ratio = (float) (uiScale / scale);
pose.scale(ratio, ratio);
try {
task.run();
} finally {
pose.scale((float) (scale / uiScale), (float) (scale / uiScale));
}
}
@Override
public void lovisual$withTransform(float tx, float ty, float angleRad, Runnable task) {
pose.pushMatrix();
try {
pose.translate(tx, ty);
pose.rotate(angleRad);
task.run();
} finally {
pose.popMatrix();
}
}
@Override
public void lovisual$addDeferredHudMarker(Renderer2D.Deferred2DLayer layer) {
if (layer == null || guiRenderState == null) return;
int width = Math.max(1, guiWidth());
int height = Math.max(1, guiHeight());
guiRenderState.addGlyphToCurrentLayer(new HudDeferredGuiElement(layer, new ScreenRectangle(0, 0, width, height)));
}
@Override
public void lovisual$drawItemBar(ItemStack stack, int x, int y) {
lovisual$invokeDrawItemBar(stack, x, y);
}
@Override
public void lovisual$drawCooldownProgress(ItemStack stack, int x, int y) {
lovisual$invokeDrawCooldownProgress(stack, x, y);
}
@Invoker("itemBar")
public abstract void lovisual$invokeDrawItemBar(ItemStack stack, int x, int y);
@Invoker("itemCooldown")
public abstract void lovisual$invokeDrawCooldownProgress(ItemStack stack, int x, int y);
@Accessor("deferredTooltip")
public abstract Runnable lovisual$getTooltipDrawer();
@Accessor("deferredTooltip")
public abstract void lovisual$setTooltipDrawer(Runnable drawer);
@Inject(method = "extractDeferredElements", at = @At("HEAD"))
private void lovisual$suppressVanillaTooltip(int mouseX, int mouseY, float tickDelta, CallbackInfo ci) {
if (!BetterTooltips.hasTooltip()) return;
if (lovisual$getTooltipDrawer() == null) return;
lovisual$setTooltipDrawer(null);
}
@Inject(
method = "setTooltipForNextFrame(Lnet/minecraft/client/gui/Font;Ljava/util/List;Lnet/minecraft/client/gui/screens/inventory/tooltip/ClientTooltipPositioner;IIZ)V",
at = @At("HEAD"),
cancellable = true
)
private void lovisual$drawCustomTooltip(net.minecraft.client.gui.Font tr,
java.util.List<FormattedCharSequence> lines,
ClientTooltipPositioner positioner,
int x, int y, boolean focused,
CallbackInfo ci) {
BetterTooltips tooltips = BetterTooltips.get();
if (tooltips == null || !tooltips.useCustomGuiTooltips()) return;
if (lines == null || lines.isEmpty()) return;
BetterTooltips.setDrawContext((GuiGraphicsExtractor) (Object) this);
BetterTooltips.captureTooltipOrdered(lines, positioner, x, y);
ci.cancel();
}
@Inject(
method = "setTooltipForNextFrame(Lnet/minecraft/client/gui/Font;Lnet/minecraft/world/item/ItemStack;II)V",
at = @At("HEAD"),
cancellable = true
)
private void lovisual$drawCustomItemTooltip(net.minecraft.client.gui.Font tr,
ItemStack stack,
int x, int y,
CallbackInfo ci) {
BetterTooltips tooltips = BetterTooltips.get();
if (tooltips == null || !tooltips.useCustomItemTooltips()) return;
if (stack == null || stack.isEmpty()) return;
Minecraft mc = Minecraft.getInstance();
if (mc == null) return;
java.util.List<net.minecraft.network.chat.Component> textLines = Screen.getTooltipFromItem(mc, stack);
if (textLines == null || textLines.isEmpty()) return;
java.util.List<FormattedCharSequence> ordered = new java.util.ArrayList<>();
for (net.minecraft.network.chat.Component t : textLines) {
ordered.addAll(tr.split(t, 200));
}
BetterTooltips.captureItemTooltipOrdered(
ordered,
net.minecraft.client.gui.screens.inventory.tooltip.DefaultTooltipPositioner.INSTANCE,
x, y,
stack,
(GuiGraphicsExtractor) (Object) this
);
ci.cancel();
}
@Inject(
method = "setTooltipForNextFrame(Lnet/minecraft/client/gui/Font;Ljava/util/List;Ljava/util/Optional;IILnet/minecraft/resources/Identifier;)V",
at = @At("HEAD"),
cancellable = true
)
private void lovisual$drawCustomTooltipWithData(net.minecraft.client.gui.Font tr,
java.util.List<net.minecraft.network.chat.Component> lines,
java.util.Optional<net.minecraft.world.inventory.tooltip.TooltipComponent> data,
int x, int y,
net.minecraft.resources.Identifier style,
CallbackInfo ci) {
BetterTooltips tooltips = BetterTooltips.get();
if (tooltips == null || !tooltips.useCustomItemTooltips()) return;
if (lines == null || lines.isEmpty()) return;
java.util.List<FormattedCharSequence> ordered = new java.util.ArrayList<>();
for (net.minecraft.network.chat.Component t : lines) {
ordered.addAll(tr.split(t, 200));
}
BetterTooltips.setDrawContext((GuiGraphicsExtractor) (Object) this);
ItemStack stack = BetterTooltips.consumeLastTooltipStack();
if (stack != null && !stack.isEmpty()) {
BetterTooltips.captureItemTooltipOrdered(
ordered,
net.minecraft.client.gui.screens.inventory.tooltip.DefaultTooltipPositioner.INSTANCE,
x, y,
stack,
(GuiGraphicsExtractor) (Object) this
);
} else {
BetterTooltips.captureTooltipOrdered(ordered, net.minecraft.client.gui.screens.inventory.tooltip.DefaultTooltipPositioner.INSTANCE, x, y);
}
ci.cancel();
}
@Inject(
method = "setTooltipForNextFrame(Lnet/minecraft/client/gui/Font;Ljava/util/List;Ljava/util/Optional;II)V",
at = @At("HEAD"),
cancellable = true
)
private void lovisual$drawCustomTooltipWithDataNoStyle(net.minecraft.client.gui.Font tr,
java.util.List<net.minecraft.network.chat.Component> lines,
java.util.Optional<net.minecraft.world.inventory.tooltip.TooltipComponent> data,
int x, int y,
CallbackInfo ci) {
BetterTooltips tooltips = BetterTooltips.get();
if (tooltips == null || !tooltips.useCustomItemTooltips()) return;
if (lines == null || lines.isEmpty()) return;
java.util.List<FormattedCharSequence> ordered = new java.util.ArrayList<>();
for (net.minecraft.network.chat.Component t : lines) {
ordered.addAll(tr.split(t, 200));
}
BetterTooltips.setDrawContext((GuiGraphicsExtractor) (Object) this);
ItemStack stack = BetterTooltips.consumeLastTooltipStack();
if (stack != null && !stack.isEmpty()) {
BetterTooltips.captureItemTooltipOrdered(
ordered,
net.minecraft.client.gui.screens.inventory.tooltip.DefaultTooltipPositioner.INSTANCE,
x, y,
stack,
(GuiGraphicsExtractor) (Object) this
);
} else {
BetterTooltips.captureTooltipOrdered(ordered, net.minecraft.client.gui.screens.inventory.tooltip.DefaultTooltipPositioner.INSTANCE, x, y);
}
ci.cancel();
}
@Inject(method = "itemCooldown", at = @At("HEAD"), cancellable = true)
private void lovisual$renderCustomPvpCooldown(ItemStack stack, int x, int y, CallbackInfo ci) {
if (stack.isEmpty()) return;
PvpCooldowns cooldowns = Modules.get(PvpCooldowns.class);
if (cooldowns == null || !cooldowns.isSystemEnabled()) return;
if (stack.is(Items.ENDER_PEARL) || stack.is(Items.CHORUS_FRUIT)) {
if (!PvpState.isActive()) return;
Float secondsLeft = PvpState.getSecondsLeft();
if (secondsLeft == null || secondsLeft <= 0f) return;
Float maxSeconds = PvpState.getMaxSeconds();
float total = (maxSeconds == null || maxSeconds <= 0f) ? secondsLeft : maxSeconds;
float progress = Mth.clamp(secondsLeft / total, 0f, 1f);
Minecraft mc = Minecraft.getInstance();
GuiGraphicsExtractor ctx = (GuiGraphicsExtractor) (Object) this;
float secondsLeftValue = secondsLeft;
float displaySeconds = secondsLeftValue >= 10f
? (float) ((int) secondsLeftValue)
: Math.round(secondsLeftValue * 10f) / 10f;
int top = y + Mth.floor(16.0F * (1.0F - progress));
int bottom = top + Mth.ceil(16.0F * progress);
int color = 0x80FF5656;
ctx.fill(RenderPipelines.GUI, x, top, x + 16, bottom, color);
CooldownRender.renderTime(
ctx, progress, x, y, displaySeconds
);
if (ClientScreen.current() == null) {
ci.cancel();
}
return;
}
if (!cooldowns.shouldRenderSlots()) return;
if (!CooldownRegistry.isTracked(stack.getItem())) return;
ItemCooldownSnapshot snapshot = CooldownsState.MANAGER.snapshot(stack.getItem());
if (!snapshot.visible()) return;
Minecraft mc = Minecraft.getInstance();
GuiGraphicsExtractor ctx = (GuiGraphicsExtractor) (Object) this;
float progress = snapshot.cooling() ? snapshot.cooldownProgress() : snapshot.usesProgress();
if (progress <= 0f) return;
float secondsLeft = snapshot.cooling() ? snapshot.cooldownRemainingMs() / 1000.0f : snapshot.useWindowRemainingMs() / 1000.0f;
float displaySeconds = snapshot.cooling()
? (secondsLeft >= 10 ? (float) ((int) secondsLeft) : Math.round(secondsLeft * 10f) / 10f)
: 0f;
int top = y + Mth.floor(16.0F * (1.0F - progress));
int bottom = top + Mth.ceil(16.0F * progress);
int color = snapshot.cooling()
? (CooldownsState.MANAGER.isInPvp() ? 0x80FFA500 : 0x8000A0FF)
: 0x706D9DFF;
ctx.fill(RenderPipelines.GUI, x, top, x + 16, bottom, color);
if (snapshot.cooling()) {
CooldownRender.renderTime(ctx, progress, x, y, displaySeconds);
}
if (ClientScreen.current() == null) {
ci.cancel();
}
}
}

View file

@ -0,0 +1,429 @@
/*
* 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.hud;
import net.minecraft.client.DeltaTracker;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.Hud;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.core.Holder;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.ai.attributes.Attribute;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.scores.Objective;
import org.spongepowered.asm.mixin.Final;
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.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import dev.loki.lovisual.events.Events;
import dev.loki.lovisual.events.impl.pvp.PvpOverlayEvent;
import dev.loki.lovisual.features.gui.hud.draggable.impl.panels.Scoreboard;
import dev.loki.lovisual.features.gui.hud.draggable.DraggableHudElementRegistry;
import dev.loki.lovisual.features.gui.hud.draggable.impl.status.Potions;
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.buttons.SwapTooltip;
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.bars.CustomBar;
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.bars.CustomHealthBar;
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.hotbar.CustomHotbar;
import dev.loki.lovisual.features.module.lifecycle.Modules;
import dev.loki.lovisual.features.module.modules.misc.protect.MessageFilter;
import dev.loki.lovisual.features.module.modules.visuals.player.Crosshair;
import dev.loki.lovisual.features.module.modules.visuals.visibility.NoRender;
import dev.loki.lovisual.mixininterface.render.IGuiGraphics;
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
import dev.loki.lovisual.render.engine.core.frame.RenderPhase;
import dev.loki.lovisual.render.engine.core.frame.RenderPhaseScope;
import dev.loki.lovisual.render.engine.core.context.ViewportContext;
// import dev.loki.lovisual.render.engine.debug.RenderThread2DDebugRenderer; // disabled: debug probes are manual-only
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
import dev.loki.lovisual.Lifecycle;
import dev.loki.lovisual.util.item.food.IllegalItemUtil;
import dev.loki.lovisual.util.item.rarity.RarityColorUtil;
import dev.loki.lovisual.util.item.enchant.TopEnchantUtil;
import dev.loki.lovisual.util.logging.DebugLog;
import dev.loki.lovisual.util.text.format.TextJsonUtil;
@Mixin(Hud.class)
public abstract class GuiMixin {
@Shadow
private int toolHighlightTimer;
@Shadow
private net.minecraft.world.item.ItemStack lastToolHighlight;
@Shadow
@Final
private Minecraft minecraft;
@Inject(method = "extractRenderState(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/client/DeltaTracker;)V", at = @At("TAIL"))
private void lovisual$debug2dGuiStateProbe(GuiGraphicsExtractor ctx, DeltaTracker tickCounter, CallbackInfo ci) {
// RenderThread2DDebugRenderer.extractGuiStateProbe(ctx);
}
@Unique
private static void lovisual$renderCustomBarOnly(GuiGraphicsExtractor ctx,
DeltaTracker tickCounter,
Minecraft mc,
CustomBar bar) {
boolean useJump = CustomBar.shouldUseJumpBar(mc);
boolean useLocator = !useJump && CustomBar.shouldUseLocator(mc, bar);
boolean drawXp = !useJump && !useLocator;
LoVisualRenderSystem.ensureFrameContext();
try (RenderPhaseScope ignored = LoVisualRenderSystem.phase(RenderPhase.HUD_MAIN, "2d:hud:custom_bar")) {
Renderer2D.withDeferredLayer(LOVISUAL$VANILLA_HUD_REPLACEMENT_LAYER, () -> {
ViewportContext.beginUnscaled(ctx);
Renderer2D.COLOR.begin();
if (drawXp) {
CustomBar.renderXpBarBackground(Renderer2D.COLOR, mc);
}
if (useJump) {
CustomBar.renderJumpBarBackground(Renderer2D.COLOR, mc);
}
if (useLocator) {
CustomBar.renderLocatorBackground(Renderer2D.COLOR, mc);
}
Renderer2D.COLOR.render();
ViewportContext.end(ctx);
if (drawXp) {
CustomBar.renderXpBarText(ctx, mc);
}
});
lovisual$appendVanillaHudReplacementMarker(ctx);
}
}
@Unique
private static final Renderer2D.Deferred2DLayer LOVISUAL$VANILLA_HUD_REPLACEMENT_LAYER =
Renderer2D.Deferred2DLayer.HUD_AFTER_MISC_OVERLAYS;
@Unique
private static void lovisual$appendVanillaHudReplacementMarker(GuiGraphicsExtractor ctx) {
if (!(ctx instanceof IGuiGraphics graphics)) return;
ctx.nextStratum();
graphics.lovisual$addDeferredHudMarker(LOVISUAL$VANILLA_HUD_REPLACEMENT_LAYER);
ctx.nextStratum();
}
@Unique
private static int argbFrom(float[] rgba) {
if (rgba == null || rgba.length < 3) return 0xFFFFFFFF;
float r = rgba[0], g = rgba[1], b = rgba[2];
float a = rgba.length > 3 ? rgba[3] : 1f;
int ai = (int) (a * 255) & 0xFF;
int ri = (int) (r * 255) & 0xFF;
int gi = (int) (g * 255) & 0xFF;
int bi = (int) (b * 255) & 0xFF;
return (ai << 24) | (ri << 16) | (gi << 8) | bi;
}
@Inject(
method = "displayScoreboardSidebar(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/world/scores/Objective;)V",
at = @At("HEAD"),
cancellable = true
)
private void lovisual$hideScoreboardSidebar(GuiGraphicsExtractor context, Objective objective, CallbackInfo ci) {
NoRender noRender = Modules.get(NoRender.class);
if ((noRender != null && noRender.hideScoreboard()) || Scoreboard.shouldReplaceVanilla()) {
ci.cancel();
}
}
@Inject(
method = "setOverlayMessage(Lnet/minecraft/network/chat/Component;Z)V",
at = @At("HEAD"),
cancellable = true
)
private void lovisual$filterOverlayMessage(net.minecraft.network.chat.Component message, boolean tinted, CallbackInfo ci) {
if (DebugLog.serverOnly()) {
if (message == null) {
// DebugLog.server("HUD overlay message: tinted=%s raw=null", tinted);
} else {
String raw = message.getString();
String json = TextJsonUtil.toJson(message);
// DebugLog.server("HUD overlay message: tinted=%s raw=\"%s\" json=%s", tinted, raw, json);
}
}
if (Events.BUS.hasListeners(PvpOverlayEvent.class)) {
Events.BUS.post(new PvpOverlayEvent(message, tinted));
}
MessageFilter messageFilter = Modules.get(MessageFilter.class);
if (messageFilter != null && messageFilter.shouldHideHud(message)) {
ci.cancel();
}
}
@Inject(
method = "extractEffects(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/client/DeltaTracker;)V",
at = @At("HEAD"),
cancellable = true
)
private void lovisual$hideVanillaPotions(GuiGraphicsExtractor context, DeltaTracker tickCounter, CallbackInfo ci) {
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.player == null) return;
if (!DraggableHudElementRegistry.isEnabled(Potions.class)) return;
if (mc.player.getActiveEffects().isEmpty()) return;
ci.cancel();
}
@Inject(
method = "extractCrosshair(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/client/DeltaTracker;)V",
at = @At("HEAD"),
cancellable = true
)
private void lovisual$replaceCrosshair(GuiGraphicsExtractor context, DeltaTracker tickCounter, CallbackInfo ci) {
Crosshair crosshair = Modules.get(Crosshair.class);
if (crosshair != null && crosshair.shouldHideVanilla()) {
ci.cancel();
}
}
@Inject(
method = "extractVignette(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/world/entity/Entity;)V",
at = @At("HEAD"),
cancellable = true
)
private void lovisual$killVignette(GuiGraphicsExtractor context, Entity entity, CallbackInfo ci) {
// Completely remove vanilla vignette post-processing
ci.cancel();
}
@Inject(
method = "extractItemHotbar(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/client/DeltaTracker;)V",
at = @At("HEAD"),
cancellable = true
)
private void lovisual$customHotbar(GuiGraphicsExtractor ctx, DeltaTracker tickCounter, CallbackInfo ci) {
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.player == null) return;
CustomHotbar hotbar = CustomHotbar.get();
CustomBar bar = CustomBar.get();
if (hotbar == null && bar == null) return;
boolean hotbarEnabled = hotbar != null && hotbar.isHudHotbarEnabled();
boolean barEnabled = bar != null && bar.isHudBarEnabled() && bar.isHotbarXpBarEnabled();
if (!hotbarEnabled && !barEnabled) return;
if (!hotbarEnabled) return;
boolean useJump = barEnabled && CustomBar.shouldUseJumpBar(mc);
boolean useLocator = barEnabled && !useJump && CustomBar.shouldUseLocator(mc, bar);
boolean drawXp = barEnabled && !useJump && !useLocator;
LoVisualRenderSystem.ensureFrameContext();
try (RenderPhaseScope ignored = LoVisualRenderSystem.phase(RenderPhase.HUD_MAIN, "2d:hud:custom_hotbar")) {
Renderer2D.withDeferredLayer(LOVISUAL$VANILLA_HUD_REPLACEMENT_LAYER, () -> {
ViewportContext.beginUnscaled(ctx);
Renderer2D.COLOR.begin();
if (hotbarEnabled) {
CustomHotbar.renderBackground(Renderer2D.COLOR, mc);
}
if (drawXp) {
CustomBar.renderXpBarBackground(Renderer2D.COLOR, mc);
}
if (barEnabled && useJump) {
CustomBar.renderJumpBarBackground(Renderer2D.COLOR, mc);
}
if (barEnabled && useLocator) {
CustomBar.renderLocatorBackground(Renderer2D.COLOR, mc);
}
Renderer2D.COLOR.render();
ViewportContext.end(ctx);
if (hotbarEnabled) {
CustomHotbar.renderItems(ctx, tickCounter, mc);
}
});
lovisual$appendVanillaHudReplacementMarker(ctx);
if (drawXp) {
CustomBar.renderXpBarText(ctx, mc);
}
if (hotbarEnabled) {
ci.cancel();
}
}
}
@Inject(
method = "extractItemHotbar(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/client/DeltaTracker;)V",
at = @At("TAIL")
)
private void lovisual$customBarAfterVanillaHotbar(GuiGraphicsExtractor ctx, DeltaTracker tickCounter, CallbackInfo ci) {
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.player == null) return;
CustomHotbar hotbar = CustomHotbar.get();
if (hotbar != null && hotbar.isHudHotbarEnabled()) return;
CustomBar bar = CustomBar.get();
if (bar == null || !bar.isHudBarEnabled() || !bar.isHotbarXpBarEnabled()) return;
lovisual$renderCustomBarOnly(ctx, tickCounter, mc, bar);
}
@Inject(
method = "extractPortalOverlay(Lnet/minecraft/client/gui/GuiGraphicsExtractor;F)V",
at = @At("HEAD"),
cancellable = true
)
private void lovisual$disableVanillaPortalOverlay(GuiGraphicsExtractor context, float nauseaStrength, CallbackInfo ci) {
ci.cancel();
}
@Inject(
method = "extractSleepOverlay(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/client/DeltaTracker;)V",
at = @At("HEAD"),
cancellable = true
)
private void lovisual$disableVanillaSleepOverlay(GuiGraphicsExtractor context, DeltaTracker tickCounter, CallbackInfo ci) {
ci.cancel();
}
@Inject(
method = "extractHearts(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/world/entity/player/Player;IIIIFIIIZ)V",
at = @At("HEAD"),
cancellable = true
)
private void lovisual$renderHealthBar(GuiGraphicsExtractor context,
Player player,
int x, int y, int lines,
int regeneratingHeartIndex, float maxHealth,
int lastHealth, int health,
int absorption, boolean blinking,
CallbackInfo ci) {
if (!Lifecycle.canRunHud()) return;
if (!(player instanceof LocalPlayer clientPlayer)) return;
Minecraft mc = Minecraft.getInstance();
if (mc == null) return;
if (!CustomHealthBar.shouldRenderHealthBar(mc, clientPlayer, maxHealth)) return;
LoVisualRenderSystem.ensureFrameContext();
try (RenderPhaseScope ignored = LoVisualRenderSystem.phase(RenderPhase.HUD_MAIN, "2d:hud:custom_health")) {
Renderer2D.withDeferredLayer(LOVISUAL$VANILLA_HUD_REPLACEMENT_LAYER, () -> {
ViewportContext.beginUnscaled(context);
Renderer2D.COLOR.begin();
CustomHealthBar.HealthTextInfo textInfo =
CustomHealthBar.renderHealthBarBackground(
Renderer2D.COLOR,
mc,
clientPlayer,
x,
y,
maxHealth
);
CustomHealthBar.renderHealthBarText(textInfo);
Renderer2D.COLOR.render();
ViewportContext.end(context);
});
lovisual$appendVanillaHudReplacementMarker(context);
ci.cancel();
}
}
@Inject(method = "extractSelectedItemName", at = @At("HEAD"), cancellable = true)
private void lovisual$colorizeHeldItemTooltip(GuiGraphicsExtractor context, CallbackInfo ci) {
if (!Lifecycle.canRunHud()) return;
SwapTooltip swapTooltip = SwapTooltip.get();
if (swapTooltip == null || !swapTooltip.isEnabled()) {
return;
}
if (this.toolHighlightTimer <= 0 || this.lastToolHighlight == null || this.lastToolHighlight.isEmpty()) {
SwapTooltip.clear();
return;
}
MutableComponent name = Component.empty().append(this.lastToolHighlight.getHoverName());
if (this.lastToolHighlight.has(net.minecraft.core.component.DataComponents.CUSTOM_NAME)) {
name.withStyle(net.minecraft.ChatFormatting.ITALIC);
}
int illegalColor = IllegalItemUtil.illegalColor();
int topColor = TopEnchantUtil.topColor();
int rarityColor = argbFrom(RarityColorUtil.INSTANCE.getRarityColor(this.lastToolHighlight));
boolean hasIllegal = IllegalItemUtil.isIllegal(this.lastToolHighlight);
boolean hasTop = !hasIllegal && TopEnchantUtil.hasTopEnchant(this.lastToolHighlight);
int nameColor = hasIllegal ? illegalColor : (hasTop ? topColor : rarityColor);
int alpha = (int) ((float) this.toolHighlightTimer * 256.0F / 10.0F);
if (alpha > 255) alpha = 255;
if (alpha <= 0) {
SwapTooltip.clear();
return;
}
float fade = alpha / 255f;
int argb = 0xFF000000 | (nameColor & 0x00FFFFFF);
SwapTooltip.capture(
name.getString(),
argb,
fade,
this.minecraft.gameMode.canHurtPlayer()
);
ci.cancel();
}
@Redirect(
method = "extractPlayerHealth(Lnet/minecraft/client/gui/GuiGraphicsExtractor;)V",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/entity/player/Player;getAbsorptionAmount()F"
)
)
private float lovisual$ignoreAbsorptionForLayout(Player player) {
if (!Lifecycle.canRunHud()) {
return player.getAbsorptionAmount();
}
Minecraft mc = Minecraft.getInstance();
if (mc != null && player instanceof LocalPlayer clientPlayer) {
float actualMaxHealth = (float) player.getAttributeValue(Attributes.MAX_HEALTH);
if (CustomHealthBar.shouldRenderHealthBar(mc, clientPlayer, actualMaxHealth)) {
return 0.0f;
}
}
return player.getAbsorptionAmount();
}
@Redirect(
method = "extractPlayerHealth(Lnet/minecraft/client/gui/GuiGraphicsExtractor;)V",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/entity/player/Player;getAttributeValue(Lnet/minecraft/core/Holder;)D"
)
)
private double lovisual$clampMaxHealthForLayout(Player player, Holder<Attribute> attribute) {
if (!Lifecycle.canRunHud()) {
return player.getAttributes().getValue(attribute);
}
if (attribute == Attributes.MAX_HEALTH) {
Minecraft mc = Minecraft.getInstance();
if (mc != null && player instanceof LocalPlayer clientPlayer) {
float actualMaxHealth = (float) player.getAttributes().getValue(attribute);
if (CustomHealthBar.shouldRenderHealthBar(mc, clientPlayer, actualMaxHealth)) {
return 20.0;
}
}
}
return player.getAttributes().getValue(attribute);
}
}

View file

@ -0,0 +1,263 @@
/*
* 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.hud;
import com.mojang.blaze3d.ProjectionType;
import com.mojang.blaze3d.buffers.GpuBufferSlice;
import com.mojang.blaze3d.pipeline.RenderTarget;
import com.mojang.blaze3d.systems.RenderSystem;
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
import dev.loki.lovisual.render.engine.renderer.ui.deferred.sched.HudDeferredGuiElement;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.render.GuiRenderer;
import net.minecraft.client.renderer.Projection;
import net.minecraft.client.renderer.ProjectionMatrixBuffer;
import net.minecraft.client.renderer.state.WindowRenderState;
import net.minecraft.client.renderer.state.gui.GuiElementRenderState;
import net.minecraft.client.renderer.state.gui.GuiRenderState;
import org.joml.Matrix4f;
import org.spongepowered.asm.mixin.Final;
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;
import java.util.List;
import java.util.function.Supplier;
@Mixin(GuiRenderer.class)
public abstract class GuiRendererMixin {
@Shadow
@Final
private List<?> draws;
@Shadow
private int firstDrawIndexAfterBlur;
@Shadow
@Final
private Projection guiProjection;
@Shadow
@Final
private ProjectionMatrixBuffer guiProjectionMatrixBuffer;
@Unique
private final ObjectArrayList<QueuedHudMarker> lovisual$hudMarkers = new ObjectArrayList<>(16);
@Unique
private GuiRenderState.TraverseRange lovisual$currentTraverseRange;
@Shadow
private void executeDrawRange(Supplier<String> supplier,
RenderTarget renderTarget,
GpuBufferSlice gpuBufferSlice,
int start,
int end) {
throw new AssertionError();
}
@Inject(method = "render", at = @At("HEAD"))
private void lovisual$clearHudMarkersAtFrameStart(CallbackInfo ci) {
lovisual$hudMarkers.clear();
}
@Inject(method = "render", at = @At("RETURN"))
private void lovisual$clearHudMarkersAtFrameEnd(CallbackInfo ci) {
lovisual$hudMarkers.clear();
}
@Inject(method = "addElementsToMeshes", at = @At("HEAD"))
private void lovisual$enterTraverseRange(GuiRenderState.TraverseRange range, CallbackInfo ci) {
lovisual$currentTraverseRange = range;
}
@Inject(method = "addElementsToMeshes", at = @At("RETURN"))
private void lovisual$exitTraverseRange(GuiRenderState.TraverseRange range, CallbackInfo ci) {
lovisual$currentTraverseRange = null;
}
@Inject(method = "addElementToMesh", at = @At("HEAD"), cancellable = true)
private void lovisual$captureHudDeferredMarker(GuiElementRenderState element, CallbackInfo ci) {
if (!(element instanceof HudDeferredGuiElement marker)) return;
int drawIndex = draws != null ? draws.size() : 0;
GuiRenderState.TraverseRange range = lovisual$currentTraverseRange != null
? lovisual$currentTraverseRange
: GuiRenderState.TraverseRange.BEFORE_BLUR;
lovisual$hudMarkers.add(new QueuedHudMarker(drawIndex, range, marker.layer()));
ci.cancel();
}
@Inject(method = "draw", at = @At("HEAD"), cancellable = true)
private void lovisual$drawWithDeferredHudMarkers(CallbackInfo ci) {
if (lovisual$hudMarkers.isEmpty()) return;
if (draws == null || draws.isEmpty()) {
lovisual$drainHudMarkersUpTo(Integer.MAX_VALUE);
ci.cancel();
return;
}
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.gameRenderer == null) {
lovisual$drainHudMarkersUpTo(Integer.MAX_VALUE);
ci.cancel();
return;
}
WindowRenderState window = mc.gameRenderer.gameRenderState().windowRenderState;
if (window == null || window.guiScale == 0) {
lovisual$drainHudMarkersUpTo(Integer.MAX_VALUE);
ci.cancel();
return;
}
guiProjection.setupOrtho(
1000.0F,
11000.0F,
(float) window.width / (float) window.guiScale,
(float) window.height / (float) window.guiScale,
true
);
RenderSystem.setProjectionMatrix(guiProjectionMatrixBuffer.getBuffer(guiProjection), ProjectionType.ORTHOGRAPHIC);
RenderTarget target = mc.gameRenderer.mainRenderTarget();
if (target == null) {
lovisual$drainHudMarkersUpTo(Integer.MAX_VALUE);
ci.cancel();
return;
}
GpuBufferSlice transform = RenderSystem.getDynamicUniforms()
.writeTransform(new Matrix4f().setTranslation(0.0F, 0.0F, -11000.0F));
int beforeBlurEnd = Math.min(firstDrawIndexAfterBlur, draws.size());
if (beforeBlurEnd > 0) {
lovisual$executeDrawRangeWithHudMarkers(
() -> "gui before blur",
target,
transform,
0,
beforeBlurEnd,
GuiRenderState.TraverseRange.BEFORE_BLUR
);
} else {
lovisual$drainHudMarkersInRange(GuiRenderState.TraverseRange.BEFORE_BLUR, Integer.MAX_VALUE);
}
boolean hasAfterBlurMarkers = lovisual$hasPendingHudMarkersInRange(GuiRenderState.TraverseRange.AFTER_BLUR);
if (draws.size() <= firstDrawIndexAfterBlur && !hasAfterBlurMarkers) {
lovisual$drainHudMarkersUpTo(Integer.MAX_VALUE);
ci.cancel();
return;
}
RenderSystem.getDevice().createCommandEncoder().clearDepthTexture(target.getDepthTexture(), 0.0D);
mc.gameRenderer.processBlurEffect();
lovisual$executeDrawRangeWithHudMarkers(
() -> "gui after blur",
target,
transform,
firstDrawIndexAfterBlur,
draws.size(),
GuiRenderState.TraverseRange.AFTER_BLUR
);
lovisual$drainHudMarkersUpTo(Integer.MAX_VALUE);
ci.cancel();
}
@Unique
private void lovisual$executeDrawRangeWithHudMarkers(Supplier<String> label,
RenderTarget target,
GpuBufferSlice transform,
int start,
int end,
GuiRenderState.TraverseRange range) {
int cursor = start;
for (int i = 0, size = lovisual$hudMarkers.size(); i < size; i++) {
QueuedHudMarker marker = lovisual$hudMarkers.get(i);
if (marker == null || marker.drained || marker.range != range) continue;
int markerIndex = marker.drawIndex;
if (markerIndex < start) {
lovisual$drainHudMarker(marker);
continue;
}
if (markerIndex > end) continue;
if (cursor < markerIndex) {
executeDrawRange(label, target, transform, cursor, markerIndex);
}
lovisual$drainHudMarker(marker);
cursor = Math.max(cursor, markerIndex);
}
if (cursor < end) {
executeDrawRange(label, target, transform, cursor, end);
}
}
@Unique
private boolean lovisual$hasPendingHudMarkersInRange(GuiRenderState.TraverseRange range) {
for (int i = 0, size = lovisual$hudMarkers.size(); i < size; i++) {
QueuedHudMarker marker = lovisual$hudMarkers.get(i);
if (marker != null && !marker.drained && marker.range == range) {
return true;
}
}
return false;
}
@Unique
private void lovisual$drainHudMarkersInRange(GuiRenderState.TraverseRange range, int drawIndexInclusive) {
for (int i = 0, size = lovisual$hudMarkers.size(); i < size; i++) {
QueuedHudMarker marker = lovisual$hudMarkers.get(i);
if (marker == null || marker.drained || marker.range != range) continue;
if (marker.drawIndex <= drawIndexInclusive) {
lovisual$drainHudMarker(marker);
}
}
}
@Unique
private void lovisual$drainHudMarkersUpTo(int drawIndexInclusive) {
for (int i = 0, size = lovisual$hudMarkers.size(); i < size; i++) {
QueuedHudMarker marker = lovisual$hudMarkers.get(i);
if (marker == null || marker.drained) continue;
if (marker.drawIndex <= drawIndexInclusive) {
lovisual$drainHudMarker(marker);
}
}
}
@Unique
private void lovisual$drainHudMarker(QueuedHudMarker marker) {
if (marker == null || marker.drained) return;
marker.drained = true;
Renderer2D.drainDeferred2D(marker.layer);
}
@Unique
private static final class QueuedHudMarker {
private final int drawIndex;
private final GuiRenderState.TraverseRange range;
private final Renderer2D.Deferred2DLayer layer;
private boolean drained;
private QueuedHudMarker(int drawIndex,
GuiRenderState.TraverseRange range,
Renderer2D.Deferred2DLayer layer) {
this.drawIndex = drawIndex;
this.range = range;
this.layer = layer;
}
}
}

View file

@ -0,0 +1,104 @@
/*
* 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.hud.accessibility;
import net.minecraft.client.GameNarrator;
import net.minecraft.client.Minecraft;
import net.minecraft.client.NarratorStatus;
import net.minecraft.network.chat.Component;
import org.spongepowered.asm.mixin.Final;
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;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import dev.loki.lovisual.util.NarratorBlocker;
@Mixin(GameNarrator.class)
public class NarratorManagerMixin {
@Shadow
@Final
private Minecraft minecraft;
@Inject(method = "sayChatQueued(Lnet/minecraft/network/chat/Component;)V", at = @At("HEAD"), cancellable = true)
private void lovisual$muteChat(Component message, CallbackInfo ci) {
if (shouldMute()) {
NarratorBlocker.enforce(this.minecraft);
ci.cancel();
}
}
@Inject(method = "saySystemChatQueued(Lnet/minecraft/network/chat/Component;)V", at = @At("HEAD"), cancellable = true)
private void lovisual$muteNarrator(Component message, CallbackInfo ci) {
if (shouldMute()) {
NarratorBlocker.enforce(this.minecraft);
ci.cancel();
}
}
@Inject(method = "saySystemQueued(Lnet/minecraft/network/chat/Component;)V", at = @At("HEAD"), cancellable = true)
private void lovisual$muteSystem(Component message, CallbackInfo ci) {
if (shouldMute()) {
NarratorBlocker.enforce(this.minecraft);
ci.cancel();
}
}
@Inject(method = "saySystemNow(Lnet/minecraft/network/chat/Component;)V", at = @At("HEAD"), cancellable = true)
private void lovisual$muteImmediateText(Component message, CallbackInfo ci) {
if (shouldMute()) {
NarratorBlocker.enforce(this.minecraft);
ci.cancel();
}
}
@Inject(method = "saySystemNow(Ljava/lang/String;)V", at = @At("HEAD"), cancellable = true)
private void lovisual$muteImmediateString(String message, CallbackInfo ci) {
if (shouldMute()) {
NarratorBlocker.enforce(this.minecraft);
ci.cancel();
}
}
@Inject(method = "narrateMessage(Ljava/lang/String;Z)V", at = @At("HEAD"), cancellable = true)
private void lovisual$muteSay(String message, boolean interrupt, CallbackInfo ci) {
if (shouldMute()) {
NarratorBlocker.enforce(this.minecraft);
ci.cancel();
}
}
@Inject(method = "updateNarratorStatus", at = @At("HEAD"), cancellable = true)
private void lovisual$blockToggle(NarratorStatus mode, CallbackInfo ci) {
if (!shouldMute()) return;
NarratorBlocker.enforce(this.minecraft);
ci.cancel();
}
@Inject(method = "checkStatus", at = @At("HEAD"), cancellable = true)
private void lovisual$skipNarratorLibraryCheck(boolean narratorEnabled, CallbackInfo ci) {
if (!shouldMute()) return;
NarratorBlocker.enforce(this.minecraft);
ci.cancel();
}
@Inject(method = "isActive", at = @At("HEAD"), cancellable = true)
private void lovisual$reportInactive(CallbackInfoReturnable<Boolean> cir) {
if (!shouldMute()) return;
cir.setReturnValue(false);
}
@Unique
private boolean shouldMute() {
return NarratorBlocker.isBlocked();
}
}

View file

@ -0,0 +1,45 @@
/*
* 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.hud.accessibility;
import net.minecraft.client.Minecraft;
import net.minecraft.client.NarratorStatus;
import net.minecraft.client.OptionInstance;
import net.minecraft.client.Options;
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;
import dev.loki.lovisual.util.NarratorBlocker;
import java.io.File;
@Mixin(Options.class)
public abstract class OptionsNarratorMixin {
@Inject(method = "<init>(Lnet/minecraft/client/Minecraft;Ljava/io/File;)V", at = @At("RETURN"))
private void lovisual$enforceNarratorDefaults(Minecraft client, File optionsFile, CallbackInfo ci) {
NarratorBlocker.enforce((Options) (Object) this);
}
@Inject(method = "save", at = @At("HEAD"))
private void lovisual$enforceNarratorBeforeWrite(CallbackInfo ci) {
NarratorBlocker.enforce((Options) (Object) this);
}
@Inject(method = "narrator", at = @At("HEAD"))
private void lovisual$enforceNarratorGetter(CallbackInfoReturnable<OptionInstance<NarratorStatus>> cir) {
NarratorBlocker.enforce((Options) (Object) this);
}
@Inject(method = "narratorHotkey", at = @At("HEAD"))
private void lovisual$enforceNarratorHotkeyGetter(CallbackInfoReturnable<OptionInstance<Boolean>> cir) {
NarratorBlocker.enforce((Options) (Object) this);
}
}

View file

@ -0,0 +1,79 @@
/*
* 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.hud.bars;
import dev.loki.lovisual.events.Events;
import dev.loki.lovisual.events.impl.pvp.CombatProtocolBossbarEvent;
import dev.loki.lovisual.mixins.accessors.hud.BossHealthOverlayAccessor;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.components.BossHealthOverlay;
import net.minecraft.client.gui.components.LerpingBossEvent;
import net.minecraft.network.chat.Component;
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 java.util.ArrayList;
import java.util.List;
import dev.loki.lovisual.util.pvp.client.CooldownsState;
@Mixin(BossHealthOverlay.class)
public class BossHealthOverlayMixin {
@Inject(method = "extractRenderState", at = @At("HEAD"))
private void onRender(GuiGraphicsExtractor ctx, CallbackInfo ci) {
if (Events.BUS.hasListeners(CombatProtocolBossbarEvent.class)) {
List<String> names = new ArrayList<>();
for (LerpingBossEvent bar : ((BossHealthOverlayAccessor) this).getBars().values()) {
if (bar == null) continue;
Component name = bar.getName();
if (name != null && !name.getString().isBlank()) {
names.add(name.getString());
}
}
if (!names.isEmpty()) {
Events.BUS.post(new CombatProtocolBossbarEvent(names));
}
}
// PvP bossbar detection disabled: bossbar no longer reliable.
/*
BossBarHud hud = (BossBarHud) (Object) this;
Map<UUID, ClientBossBar> bars =
((BossHealthOverlayAccessor) hud).getBars();
boolean pvpActive = false;
// проверяем только боссбары
for (ClientBossBar bar : bars.values()) {
String name = bar.getName().getString();
if (name.startsWith("Режим PVP -")) {
pvpActive = true;
break;
}
}
// === только тут меняем состояние PvP ===
if (pvpActive && !CooldownsState.MANAGER.isInPvp()) {
//System.out.println("[PvPDetector] → ENTER PVP");
CooldownsState.MANAGER.enterPvp();
}
if (!pvpActive && CooldownsState.MANAGER.isInPvp()) {
//System.out.println("[PvPDetector] → EXIT PVP");
CooldownsState.MANAGER.exitPvp();
}
*/
}
}

View file

@ -0,0 +1,51 @@
/*
* 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.hud.bars;
import net.minecraft.client.DeltaTracker;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.contextualbar.ExperienceBar;
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 dev.loki.lovisual.features.gui.hud.nondraggable.impl.bars.CustomBar;
import dev.loki.lovisual.Lifecycle;
@Mixin(ExperienceBar.class)
public abstract class ExperienceBarMixin {
@Inject(method = "extractBackground", at = @At("HEAD"), cancellable = true)
private void lovisual$renderXpBar(GuiGraphicsExtractor context, DeltaTracker tickCounter, CallbackInfo ci) {
if (!Lifecycle.canRunHud()) return;
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.player == null) return;
CustomBar bar = CustomBar.get();
if (bar == null) return;
if (!bar.isHudBarEnabled() || !bar.isHotbarXpBarEnabled()) return;
// We render XP/Locator ourselves from GuiMixin to avoid vanilla bar state.
ci.cancel();
}
@Inject(method = "extractRenderState", at = @At("HEAD"), cancellable = true)
private void lovisual$renderXpAddons(GuiGraphicsExtractor context, DeltaTracker tickCounter, CallbackInfo ci) {
if (!Lifecycle.canRunHud()) return;
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.player == null) return;
CustomBar bar = CustomBar.get();
if (bar == null) return;
if (!bar.isHudBarEnabled() || !bar.isHotbarXpBarEnabled()) return;
ci.cancel();
}
}

View file

@ -0,0 +1,49 @@
/*
* 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.hud.bars;
import net.minecraft.client.DeltaTracker;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.contextualbar.JumpableVehicleBar;
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 dev.loki.lovisual.features.gui.hud.nondraggable.impl.bars.CustomBar;
import dev.loki.lovisual.Lifecycle;
@Mixin(JumpableVehicleBar.class)
public abstract class JumpBarMixin {
@Inject(method = "extractBackground", at = @At("HEAD"), cancellable = true)
private void lovisual$renderJumpBar(GuiGraphicsExtractor context, DeltaTracker tickCounter, CallbackInfo ci) {
if (!Lifecycle.canRunHud()) return;
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.player == null) return;
CustomBar bar = CustomBar.get();
if (bar == null) return;
if (!bar.isHudBarEnabled() || !bar.isHotbarXpBarEnabled()) return;
ci.cancel();
}
@Inject(method = "extractRenderState", at = @At("HEAD"), cancellable = true)
private void lovisual$renderJumpAddons(GuiGraphicsExtractor context, DeltaTracker tickCounter, CallbackInfo ci) {
if (!Lifecycle.canRunHud()) return;
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.player == null) return;
CustomBar bar = CustomBar.get();
if (bar == null) return;
if (!bar.isHudBarEnabled() || !bar.isHotbarXpBarEnabled()) return;
ci.cancel();
}
}

View file

@ -0,0 +1,52 @@
/*
* 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.hud.bars;
import net.minecraft.client.DeltaTracker;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.contextualbar.LocatorBar;
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 dev.loki.lovisual.features.gui.hud.nondraggable.impl.bars.CustomBar;
import dev.loki.lovisual.Lifecycle;
@Mixin(LocatorBar.class)
public abstract class LocatorBarMixin {
@Inject(method = "extractBackground", at = @At("HEAD"), cancellable = true)
private void lovisual$renderLocatorBar(GuiGraphicsExtractor context, DeltaTracker tickCounter, CallbackInfo ci) {
if (!Lifecycle.canRunHud()) return;
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.player == null) return;
CustomBar bar = CustomBar.get();
if (bar == null) return;
if (!bar.isHudBarEnabled() || !bar.isHotbarXpBarEnabled()) return;
// Always mute vanilla locator rendering; we draw everything ourselves.
ci.cancel();
}
@Inject(method = "extractRenderState", at = @At("HEAD"), cancellable = true)
private void lovisual$renderLocatorAddons(GuiGraphicsExtractor context, DeltaTracker tickCounter, CallbackInfo ci) {
if (!Lifecycle.canRunHud()) return;
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.player == null) return;
CustomBar bar = CustomBar.get();
if (bar == null) return;
if (!bar.isHudBarEnabled() || !bar.isHotbarXpBarEnabled()) return;
// Always mute vanilla locator addons (markers/arrows).
ci.cancel();
}
}

View file

@ -0,0 +1,37 @@
/*
* 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.hud.tablist;
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.tab.CustomTabList;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.components.PlayerTabOverlay;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.Scoreboard;
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;
@Mixin(PlayerTabOverlay.class)
public abstract class PlayerTabOverlayMixin {
@Inject(
method = "extractRenderState(Lnet/minecraft/client/gui/GuiGraphicsExtractor;ILnet/minecraft/world/scores/Scoreboard;Lnet/minecraft/world/scores/Objective;)V",
at = @At("HEAD"),
cancellable = true
)
private void lovisual$extractCustomTabList(GuiGraphicsExtractor context,
int width,
Scoreboard scoreboard,
Objective objective,
CallbackInfo ci) {
if (!CustomTabList.shouldReplaceVanilla()) return;
CustomTabList.renderVanillaTabStratum(context, width);
ci.cancel();
}
}

View file

@ -0,0 +1,29 @@
/*
* 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.hud.tooltips;
import net.minecraft.client.gui.components.toasts.Toast;
import net.minecraft.client.gui.components.toasts.ToastManager;
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 dev.loki.lovisual.features.module.lifecycle.Modules;
import dev.loki.lovisual.features.module.modules.visuals.visibility.NoRender;
@Mixin(ToastManager.class)
public abstract class ToastManagerMixin {
@Inject(method = "addToast(Lnet/minecraft/client/gui/components/toasts/Toast;)V", at = @At("HEAD"), cancellable = true)
private void lovisual$noRender$hideToasts(Toast toast, CallbackInfo ci) {
NoRender module = Modules.get(NoRender.class);
if (module != null && module.hideToastHints()) {
ci.cancel();
}
}
}

View file

@ -0,0 +1,44 @@
/*
* 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.hud.tooltips;
import com.mojang.blaze3d.pipeline.RenderPipeline;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.screens.inventory.tooltip.TooltipRenderUtil;
import net.minecraft.resources.Identifier;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.tooltips.BetterTooltips;
@Mixin(TooltipRenderUtil.class)
public class TooltipRenderUtilMixin {
@Redirect(
method = "extractTooltipBackground",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/gui/GuiGraphicsExtractor;blitSprite(Lcom/mojang/blaze3d/pipeline/RenderPipeline;Lnet/minecraft/resources/Identifier;IIII)V"
)
)
private static void redirectDrawGuiTexture(
GuiGraphicsExtractor ctx,
RenderPipeline pipeline,
Identifier sprite,
int x, int y, int w, int h
) {
BetterTooltips tooltips = BetterTooltips.get();
if (tooltips == null || !tooltips.isTooltipAlphaEnabled()) {
ctx.blitSprite(pipeline, sprite, x, y, w, h);
return;
}
int argb = tooltips.tooltipColor();
ctx.blitSprite(pipeline, sprite, x, y, w, h, argb);
}
}