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,101 @@
/*
* 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.gui.chat;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.components.ChatComponent;
import net.minecraft.client.gui.components.ChatComponent.DisplayMode;
import net.minecraft.client.multiplayer.chat.GuiMessageSource;
import net.minecraft.client.multiplayer.chat.GuiMessageTag;
import net.minecraft.client.multiplayer.chat.GuiMessage;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MessageSignature;
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.callback.CallbackInfo;
import dev.loki.lovisual.features.command.util.CommandOutput;
import dev.loki.lovisual.features.gui.chat.model.BetterChatStoreManager;
import dev.loki.lovisual.features.gui.hud.draggable.impl.BetterChat;
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.misc.protect.NameProtect;
import dev.loki.lovisual.features.module.modules.misc.protect.StreamerMode;
import dev.loki.lovisual.Lifecycle;
@Mixin(ChatComponent.class)
public class ChatComponentMixin {
@ModifyVariable(
method = "addMessage(Lnet/minecraft/network/chat/Component;Lnet/minecraft/network/chat/MessageSignature;Lnet/minecraft/client/multiplayer/chat/GuiMessageSource;Lnet/minecraft/client/multiplayer/chat/GuiMessageTag;)V",
at = @At("HEAD"),
argsOnly = true
)
private Component lovisual$nameProtectChat(Component message) {
if (!Lifecycle.canRunHud()) return message;
Component protectedMessage = NameProtect.protect(message);
return StreamerMode.protectComponent(protectedMessage);
}
@Inject(
method = "addMessage(Lnet/minecraft/network/chat/Component;Lnet/minecraft/network/chat/MessageSignature;Lnet/minecraft/client/multiplayer/chat/GuiMessageSource;Lnet/minecraft/client/multiplayer/chat/GuiMessageTag;)V",
at = @At("HEAD"),
cancellable = true
)
private void lovisual$filterChatFull(Component message,
MessageSignature signatureData,
GuiMessageSource source,
GuiMessageTag indicator,
CallbackInfo ci) {
if (!Lifecycle.canRunHud()) return;
MessageFilter messageFilter = Modules.get(MessageFilter.class);
if (messageFilter != null && messageFilter.shouldHideChat(message)) {
ci.cancel();
return;
}
if (!CommandOutput.isLoVisualMessage(message)) {
BetterChatStoreManager.addMessage(message);
}
}
@Inject(method = "logChatMessage(Lnet/minecraft/client/multiplayer/chat/GuiMessage;)V", at = @At("HEAD"), cancellable = true)
private void lovisual$commands$suppressClientLog(GuiMessage message, CallbackInfo ci) {
if (message != null && CommandOutput.isLoVisualMessage(message.content())) {
ci.cancel();
}
}
@Inject(method = "clearMessages(Z)V", at = @At("TAIL"))
private void lovisual$betterChat$clear(boolean clearHistory, CallbackInfo ci) {
if (!Lifecycle.canRunHud()) return;
if (!BetterChat.isActive()) return;
// Never wipe BetterChat store on vanilla clear; just persist to disk.
BetterChatStoreManager.flushActive();
}
@Inject(method = "extractRenderState(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/client/gui/Font;IIILnet/minecraft/client/gui/components/ChatComponent$DisplayMode;Z)V",
at = @At("HEAD"), cancellable = true)
private void lovisual$betterChat$hideVanilla(GuiGraphicsExtractor context,
Font textRenderer,
int currentTick,
int mouseX,
int mouseY,
DisplayMode displayMode,
boolean inChat,
CallbackInfo ci) {
if (!Lifecycle.canRunHud()) return;
if (!BetterChat.isActive()) return;
BetterChat cfg = BetterChat.get();
if (cfg != null && cfg.hideVanilla()) {
ci.cancel();
}
}
}

View file

@ -0,0 +1,112 @@
/*
* 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.gui.chat;
import net.minecraft.client.gui.ActiveTextCollector;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.components.ChatComponent;
import net.minecraft.client.gui.components.ChatComponent.DisplayMode;
import net.minecraft.client.gui.components.CommandSuggestions;
import net.minecraft.client.gui.screens.ChatScreen;
import net.minecraft.client.input.KeyEvent;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import dev.loki.lovisual.features.command.core.CommandManager;
import dev.loki.lovisual.features.gui.chat.render.BetterChatRenderer;
import dev.loki.lovisual.features.gui.chat.messages.BetterChatTabHandler;
import dev.loki.lovisual.features.gui.hud.draggable.impl.BetterChat;
import dev.loki.lovisual.mixins.accessors.chat.ChatScreenSuggestorAccessor;
import dev.loki.lovisual.Lifecycle;
@Mixin(ChatScreen.class)
public class ChatScreenMixin {
@Inject(method = "extractRenderState(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("HEAD"), cancellable = true)
private void lovisual$betterChat$render(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta, CallbackInfo ci) {
if (!Lifecycle.canRunHud()) return;
if (BetterChat.isActive()) {
BetterChat cfg = BetterChat.get();
if (cfg != null && cfg.hideVanilla()) {
// Stash suggestion context; BetterChatRenderer will draw it in correct Z-order.
BetterChatRenderer.setSuggestionContext((ChatScreen) (Object) this, context, mouseX, mouseY);
ci.cancel();
}
}
}
@Redirect(
method = "mouseClicked(Lnet/minecraft/client/input/MouseButtonEvent;Z)Z",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/gui/components/ChatComponent;captureClickableText(Lnet/minecraft/client/gui/ActiveTextCollector;IILnet/minecraft/client/gui/components/ChatComponent$DisplayMode;)V"
)
)
private void lovisual$betterChat$disableVanillaChatClickHitbox(ChatComponent chatHud,
ActiveTextCollector textConsumer,
int windowHeight,
int currentTick,
DisplayMode displayMode) {
if (lovisual$betterChat$shouldHideVanillaChat()) {
return;
}
chatHud.captureClickableText(textConsumer, windowHeight, currentTick, displayMode);
}
@Inject(method = "mouseScrolled(DDDD)Z", at = @At("HEAD"), cancellable = true)
private void lovisual$betterChat$disableVanillaChatScroll(double mouseX,
double mouseY,
double horizontalAmount,
double verticalAmount,
CallbackInfoReturnable<Boolean> cir) {
if (lovisual$betterChat$shouldHideVanillaChat()) {
cir.setReturnValue(true);
}
}
@Inject(method = "keyPressed(Lnet/minecraft/client/input/KeyEvent;)Z", at = @At("HEAD"))
private void lovisual$betterChat$allowEscape(KeyEvent input, CallbackInfoReturnable<Boolean> cir) {
if (!Lifecycle.canRunHud()) return;
if (!BetterChat.isActive()) return;
if (!input.isEscape()) return;
CommandSuggestions suggestor = ((ChatScreenSuggestorAccessor) this).getLoVisual$suggestor();
if (suggestor != null) {
suggestor.setAllowSuggestions(false);
suggestor.hide();
}
}
@Inject(method = "handleChatInput(Ljava/lang/String;Z)V", at = @At("HEAD"), cancellable = true, require = 0)
private void lovisual$commands$sendMessage(String message, boolean addToHistory, CallbackInfo ci) {
if (!Lifecycle.isActive()) return;
if (CommandManager.handle(message)) {
ci.cancel();
}
}
@Inject(method = "keyPressed(Lnet/minecraft/client/input/KeyEvent;)Z", at = @At("HEAD"), cancellable = true, require = 0)
private void lovisual$betterChat$tabCompleteCommands(KeyEvent input, CallbackInfoReturnable<Boolean> cir) {
if (!Lifecycle.canRunHud()) return;
if (!BetterChat.isActive()) return;
if (BetterChatTabHandler.handleKey(input)) {
cir.setReturnValue(true);
}
}
@Unique
private boolean lovisual$betterChat$shouldHideVanillaChat() {
if (!Lifecycle.canRunHud()) return false;
if (!BetterChat.isActive()) return false;
BetterChat cfg = BetterChat.get();
return cfg != null && cfg.hideVanilla();
}
}

View file

@ -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.gui.chat;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.contextualbar.ContextualBar;
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(ContextualBar.class)
public interface ContextualBarMixin {
@Inject(method = "extractExperienceLevel", at = @At("HEAD"), cancellable = true)
private static void lovisual$drawExperienceLevel(GuiGraphicsExtractor context,
Font textRenderer,
int level,
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,151 @@
/*
* 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.gui.core;
import dev.loki.lovisual.features.module.lifecycle.Modules;
import dev.loki.lovisual.features.module.modules.misc.esp.ShitEsp;
import dev.loki.lovisual.features.module.modules.misc.items.ItemScroller;
import dev.loki.lovisual.features.module.modules.visuals.player.Animations;
import dev.loki.lovisual.features.module.modules.visuals.items.ShulkerPreview;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.joml.Matrix3x2fStack;
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;
@Mixin(AbstractContainerScreen.class)
public abstract class AbstractContainerScreenMixin {
@Shadow
protected Slot hoveredSlot;
@Shadow
protected AbstractContainerMenu menu;
@Shadow
protected int leftPos;
@Shadow
protected int topPos;
@Unique
private boolean lovisual$animations$scaleApplied;
@Inject(method = "onClose", at = @At("HEAD"), cancellable = true)
private void lovisual$inventoryMove$deferClose(CallbackInfo ci) {
// InventoryMove module removed; keep hook for compatibility.
}
/**
* Smooth open animation from the Animations module: the whole screen is drawn
* with a scale between 0.9 and 1.0 around the screen center. The animated
* value itself is advanced once per frame inside the module; this only reads
* the settled value and wraps the extraction in a matching push/pop.
*/
@Inject(
method = "extractRenderState(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V",
at = @At("HEAD")
)
private void lovisual$animations$beginOpenScale(GuiGraphicsExtractor graphics,
int mouseX,
int mouseY,
float partialTick,
CallbackInfo ci) {
lovisual$animations$scaleApplied = false;
Animations animations = Modules.get(Animations.class);
if (animations == null) return;
float scale = animations.getInventoryScale();
if (scale >= 0.999f) return;
Matrix3x2fStack pose = graphics.pose();
float centerX = graphics.guiWidth() * 0.5f;
float centerY = graphics.guiHeight() * 0.5f;
pose.pushMatrix();
pose.translate(centerX, centerY);
pose.scale(scale, scale);
pose.translate(-centerX, -centerY);
lovisual$animations$scaleApplied = true;
}
@Inject(
method = "extractRenderState(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V",
at = @At("RETURN")
)
private void lovisual$animations$endOpenScale(GuiGraphicsExtractor graphics,
int mouseX,
int mouseY,
float partialTick,
CallbackInfo ci) {
if (!lovisual$animations$scaleApplied) return;
lovisual$animations$scaleApplied = false;
graphics.pose().popMatrix();
}
@Inject(
method = "extractRenderState(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V",
at = @At("TAIL")
)
private void lovisual$shulkerPreview$draw(GuiGraphicsExtractor graphics,
int mouseX,
int mouseY,
float partialTick,
CallbackInfo ci) {
Slot slot = this.hoveredSlot;
ItemStack stack = slot == null ? ItemStack.EMPTY : slot.getItem();
if (stack.isEmpty()) return;
ShulkerPreview.onScreenExtract(graphics, stack, mouseX, mouseY);
}
@Inject(
method = "mouseScrolled(DDDD)Z",
at = @At("HEAD"),
cancellable = true
)
private void lovisual$itemScroller$quickMove(double mouseX, double mouseY,
double horizontal, double vertical,
CallbackInfoReturnable<Boolean> cir) {
ItemScroller scroller = Modules.get(ItemScroller.class);
if (scroller == null || !scroller.isEnabled()) return;
Slot slot = this.hoveredSlot;
ItemStack stack = slot == null ? null : slot.getItem();
if (scroller.onMouseScrolled(vertical, slot, stack)) {
cir.setReturnValue(true);
}
}
@Inject(method = "extractRenderState(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("TAIL"))
private void lovisual$shitEsp$highlight(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float partialTick, CallbackInfo ci) {
ShitEsp esp = Modules.get(ShitEsp.class);
if (esp == null || !esp.isEnabled() || !esp.hasAnyGroup() || menu == null) return;
for (Slot slot : menu.slots) {
if (slot == null || !slot.hasItem()) continue;
ItemStack stack = slot.getItem();
Integer col = esp.colorFor(stack);
if (col == null) continue;
int x1 = leftPos + slot.x;
int y1 = topPos + slot.y;
int x2 = x1 + 16;
int y2 = y1 + 16;
graphics.fill(RenderPipelines.GUI, x1, y1, x2, y2, col);
graphics.outline(x1, y1, x2 - x1, y2 - y1, col | 0xFF000000);
}
}
}

View file

@ -0,0 +1,35 @@
/*
* 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.gui.core;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.components.AbstractSelectionList;
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.buttons.BetterButtons;
@Mixin(AbstractSelectionList.class)
public abstract class AbstractSelectionListMixin {
@Inject(method = "enableScissor(Lnet/minecraft/client/gui/GuiGraphicsExtractor;)V", at = @At("HEAD"))
private void lovisual$pushScissor(GuiGraphicsExtractor context, CallbackInfo ci) {
AbstractSelectionList<?> self = (AbstractSelectionList<?>) (Object) this;
BetterButtons.pushScissorContext(self.getX(), self.getY(), self.getRight(), self.getBottom());
}
@Inject(
method = "extractWidgetRenderState(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiGraphicsExtractor;disableScissor()V")
)
private void lovisual$popScissor(GuiGraphicsExtractor context, int mouseX, int mouseY, float deltaTicks, CallbackInfo ci) {
BetterButtons.popScissorContext();
}
}

View file

@ -0,0 +1,47 @@
/*
* 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.gui.core;
import dev.loki.lovisual.util.screen.ClientScreen;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.components.AbstractSliderButton;
import net.minecraft.client.gui.components.AbstractWidget;
import net.minecraft.client.gui.screens.inventory.MerchantScreen;
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.buttons.BetterButtons;
import dev.loki.lovisual.mixins.accessors.widget.SliderWidgetAccessor;
@Mixin(AbstractSliderButton.class)
public abstract class AbstractSliderButtonMixin {
@Inject(method = "extractWidgetRenderState(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("HEAD"), cancellable = true)
private void lovisual$renderWidget(GuiGraphicsExtractor ctx, int mouseX, int mouseY, float delta, CallbackInfo ci) {
BetterButtons buttons = BetterButtons.get();
if (buttons == null || !buttons.useUiButtons()) return;
Minecraft mc = Minecraft.getInstance();
if (mc != null && ClientScreen.current() instanceof MerchantScreen) return;
AbstractSliderButton self = (AbstractSliderButton) (Object) this;
AbstractWidget widget = self;
boolean hovered = widget.isMouseOver(mouseX, mouseY);
boolean dragging = ((SliderWidgetAccessor) self).lovisual$isDragging();
float hover = BetterButtons.updateHover(widget, hovered || dragging);
double value = ((SliderWidgetAccessor) self).lovisual$getValue();
BetterButtons.enqueueSlider(widget, (float) value, dragging, hover);
if (hovered) {
BetterButtons.captureTooltip(widget, mouseX, mouseY);
}
ci.cancel();
}
}

View file

@ -0,0 +1,84 @@
/*
* 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.gui.core;
import net.minecraft.client.gui.components.AbstractWidget;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import dev.loki.lovisual.mixininterface.entity.IRoundedHitbox;
@Mixin(AbstractWidget.class)
public abstract class AbstractWidgetMixin implements IRoundedHitbox {
@Unique
private float lovisual$roundedRadius = 0.0f;
@Unique
private boolean lovisual$roundedEnabled = false;
@Override
public void lovisual$setRoundedHitbox(float radius) {
lovisual$roundedRadius = Math.max(0.0f, radius);
lovisual$roundedEnabled = lovisual$roundedRadius > 0.0f;
}
@Override
public void lovisual$clearRoundedHitbox() {
lovisual$roundedRadius = 0.0f;
lovisual$roundedEnabled = false;
}
@Override
public float lovisual$getRoundedHitbox() {
return lovisual$roundedRadius;
}
@Override
public boolean lovisual$useRoundedHitbox() {
return lovisual$roundedEnabled;
}
@Inject(method = "isMouseOver(DD)Z", at = @At("HEAD"), cancellable = true)
private void lovisual$isMouseOverRounded(double mouseX, double mouseY, CallbackInfoReturnable<Boolean> cir) {
if (!lovisual$roundedEnabled) return;
AbstractWidget self = (AbstractWidget) (Object) this;
float x = self.getX();
float y = self.getY();
float w = self.getWidth();
float h = self.getHeight();
if (mouseX < x || mouseY < y || mouseX > x + w || mouseY > y + h) {
cir.setReturnValue(false);
return;
}
float r = Math.min(lovisual$roundedRadius, Math.min(w, h) * 0.5f);
if (r <= 0.0f) {
cir.setReturnValue(true);
return;
}
float px = (float) (mouseX - x);
float py = (float) (mouseY - y);
if ((px >= r && px <= w - r) || (py >= r && py <= h - r)) {
cir.setReturnValue(true);
return;
}
float cx = (px < r) ? r : (w - r);
float cy = (py < r) ? r : (h - r);
float dx = px - cx;
float dy = py - cy;
cir.setReturnValue((dx * dx + dy * dy) <= (r * r));
}
}

View file

@ -0,0 +1,114 @@
/*
* 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.gui.multiplayer;
import dev.loki.lovisual.features.theme.Theme;
import dev.loki.lovisual.features.gui.hud.config.HudRenderUtil;
import dev.loki.lovisual.features.theme.Themes;
import dev.loki.lovisual.render.engine.animation.AnimationUtility;
import dev.loki.lovisual.render.engine.core.context.ViewportContext;
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
import dev.loki.lovisual.render.engine.svg.model.SvgRenderOptions;
import dev.loki.lovisual.util.proxy.ProxyBackend;
import dev.loki.lovisual.util.proxy.model.ProxyEntry;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen;
import net.minecraft.util.Mth;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Screen.class)
public abstract class JoinMultiplayerScreenMixin {
@Unique
private static final float LOVISUAL_PROXY_BUTTON_X = 8f;
@Unique
private static final float LOVISUAL_PROXY_BUTTON_Y = 8f;
@Unique
private static final float LOVISUAL_PROXY_BUTTON_W = 24f;
@Unique
private static final float LOVISUAL_PROXY_BUTTON_H = 24f;
@Unique
private float lovisual$proxyHover;
@Unique
private float lovisual$proxyPress;
@Inject(method = "extractRenderState(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("RETURN"))
private void lovisual$renderProxyEntry(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta, CallbackInfo ci) {
Screen self = (Screen) (Object) this;
if (!(self instanceof JoinMultiplayerScreen)) return;
float dt = AnimationUtility.deltaTime();
boolean hovered = lovisual$inside(mouseX, mouseY, LOVISUAL_PROXY_BUTTON_X, LOVISUAL_PROXY_BUTTON_Y,
LOVISUAL_PROXY_BUTTON_W, LOVISUAL_PROXY_BUTTON_H);
lovisual$proxyHover = lovisual$animate(lovisual$proxyHover, hovered ? 1f : 0f, dt, 11f);
lovisual$proxyPress = lovisual$animate(lovisual$proxyPress, 0f, dt, 8f);
ViewportContext.beginScaled(context);
Renderer2D.COLOR.begin();
try {
lovisual$drawProxyButton();
} finally {
Renderer2D.COLOR.render();
ViewportContext.end(context);
}
}
@Unique
private void lovisual$drawProxyButton() {
float hover = AnimationUtility.easeOutCubic(lovisual$proxyHover);
float press = AnimationUtility.easeOutCubic(lovisual$proxyPress);
Themes.Theme theme = Theme.theme();
boolean enabled = ProxyBackend.isEnabled();
ProxyEntry active = ProxyBackend.activeProxy();
boolean configured = active.isConfigured();
float size = LOVISUAL_PROXY_BUTTON_W * (1.0f + hover * 0.035f - press * 0.025f);
float x = LOVISUAL_PROXY_BUTTON_X + (LOVISUAL_PROXY_BUTTON_W - size) * 0.5f;
float y = LOVISUAL_PROXY_BUTTON_Y + (LOVISUAL_PROXY_BUTTON_H - size) * 0.5f - hover * 0.35f;
float radius = 5.0f;
float centerX = x + size * 0.5f;
float centerY = y + size * 0.5f;
int accent = enabled && configured ? theme.accent() : 0x8A94A3;
int baseTop = enabled && configured ? 0x222A20 : 0x1B2028;
int baseBottom = enabled && configured ? 0x121812 : 0x10141B;
int fillTop = HudRenderUtil.mixColor(withAlpha(baseTop, 226), withAlpha(accent, 96), (enabled && configured ? 0.18f : 0.06f) + hover * 0.12f);
int fillBottom = HudRenderUtil.mixColor(withAlpha(baseBottom, 236), withAlpha(accent, 70), (enabled && configured ? 0.12f : 0.04f) + hover * 0.08f);
int stroke = HudRenderUtil.mixColor(withAlpha(0x46505D, 180), withAlpha(accent, 218), (enabled && configured ? 0.38f : 0.10f) + hover * 0.35f);
int iconColor = enabled && configured
? HudRenderUtil.mixColor(withAlpha(0xFFFFFF, 245), withAlpha(theme.accent(), 255), 0.18f)
: withAlpha(0xC5CCD6, 235);
Renderer2D.COLOR.roundedRectSoftShadow(x, y + 0.8f, size, size, radius,
7.5f, 0.026f + hover * 0.012f, withAlpha(0x060810, 96));
Renderer2D.COLOR.roundedRectGradientQuad(x, y, size, size, radius, 1f,
fillTop, fillTop, fillBottom, fillBottom);
Renderer2D.COLOR.roundedRectStrokeGradient(x + 0.5f, y + 0.5f, size - 1.0f, size - 1.0f,
radius - 0.5f, 1f, 0.55f, HudRenderUtil.mixColor(stroke, 0xFFFFFFFF, 0.08f), stroke, 90f);
Renderer2D.COLOR.svg("shield-user", centerX - 5.6f, centerY - 5.6f, 11.2f, 11.2f, SvgRenderOptions.overrideColor(iconColor));
}
@Unique
private static float lovisual$animate(float current, float target, float dt, float speed) {
float next = AnimationUtility.approach(current, target, dt, speed);
return AnimationUtility.snap(next, target, 0.001f);
}
@Unique
private static boolean lovisual$inside(float mx, float my, float x, float y, float w, float h) {
return mx >= x && mx <= x + w && my >= y && my <= y + h;
}
@Unique
private static int withAlpha(int rgb, int alpha) {
return (rgb & 0x00FFFFFF) | (Mth.clamp(alpha, 0, 255) << 24);
}
}

View file

@ -0,0 +1,36 @@
/*
* 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.gui.multiplayer;
import net.minecraft.client.gui.screens.inventory.AbstractRecipeBookScreen;
import net.minecraft.client.gui.screens.recipebook.RecipeBookComponent;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
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.misc.vanilla.BetterMinecraft;
@Mixin(AbstractRecipeBookScreen.class)
public abstract class RecipeBookScreenMixin {
@Final
@Shadow
private RecipeBookComponent<?> recipeBookComponent;
@Inject(method = "initButton", at = @At("HEAD"), cancellable = true)
private void lovisual$disableRecipeBook(CallbackInfo ci) {
BetterMinecraft module = Modules.get(BetterMinecraft.class);
if (module != null && module.isHideRecipeBookEnabled()) {
ci.cancel();
}
}
}

View file

@ -0,0 +1,102 @@
/*
* 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.gui.screens;
import com.llamalad7.mixinextras.sugar.Local;
import dev.loki.lovisual.features.module.core.ModuleManager;
import dev.loki.lovisual.util.screen.ClientScreen;
import net.minecraft.client.DeltaTracker;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.screens.Screen;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import dev.loki.lovisual.features.gui.clickgui.editor.ClickGuiEditorScreen;
import dev.loki.lovisual.features.gui.clickgui.picker.screen.ClickGuiPickerScreen;
import dev.loki.lovisual.features.gui.clickgui.render.ClickGuiRenderer;
import dev.loki.lovisual.features.gui.clickgui.screen.ClickGuiScreen;
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.bars.CustomBar;
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.dynamicisland.DynamicIsland;
import dev.loki.lovisual.features.module.modules.misc.ClickGui;
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.renderer.Renderer2D;
@Mixin(Gui.class)
public abstract class GuiScreenMixin {
@Unique
private static void lovisual$extractCustomBarHeads(GuiGraphicsExtractor drawContext, DeltaTracker tickCounter) {
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.player == null) return;
if (ClientScreen.current() != null) return;
CustomBar bar = CustomBar.get();
if (bar == null || !bar.isHudBarEnabled() || !bar.isHotbarXpBarEnabled()) return;
if (CustomBar.shouldUseJumpBar(mc) || !CustomBar.shouldUseLocator(mc, bar)) return;
LoVisualRenderSystem.ensureFrameContext();
try (RenderPhaseScope ignored = LoVisualRenderSystem.phase(RenderPhase.SCREEN_TOP, "2d:hud:custom_bar_heads")) {
Renderer2D.withDeferredLayer(Renderer2D.Deferred2DLayer.AFTER_VANILLA_GUI, () -> {
CustomBar.renderLocatorHeads(drawContext, tickCounter, mc);
Renderer2D.COLOR.render();
});
}
}
@Inject(method = "setScreen", at = @At("HEAD"))
private void lovisual$closeClickGuiOnScreen(Screen screen, CallbackInfo ci) {
if (!ModuleManager.isEnabled("clickgui")) {
return;
}
if (ClickGuiRenderer.waitingForKey) {
return;
}
if (screen instanceof ClickGuiScreen
|| screen instanceof ClickGuiPickerScreen
|| screen instanceof ClickGuiEditorScreen) {
return;
}
if (screen != null) {
ClickGui.setSuppressScreenClose(true);
try {
ModuleManager.setEnabled("clickgui", false);
} finally {
ClickGui.setSuppressScreenClose(false);
}
}
}
@Inject(
method = "extractRenderState",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/gui/GuiGraphicsExtractor;applyCursor(Lcom/mojang/blaze3d/platform/Window;)V"
)
)
private void lovisual$extractTopLayer(DeltaTracker tickCounter, boolean renderLevel, boolean renderScreens, CallbackInfo ci,
@Local(ordinal = 0) GuiGraphicsExtractor drawContext) {
if (drawContext == null) {
return;
}
drawContext.nextStratum();
lovisual$extractCustomBarHeads(drawContext, tickCounter);
DynamicIsland.renderScreenOverlay(drawContext, tickCounter.getGameTimeDeltaTicks());
ClickGuiRenderer.renderTopLayer(drawContext, tickCounter.getGameTimeDeltaTicks());
}
}

View file

@ -0,0 +1,80 @@
/*
* 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.gui.screens;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.Tooltip;
import net.minecraft.client.gui.screens.PauseScreen;
import org.jetbrains.annotations.Nullable;
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 dev.loki.lovisual.features.module.lifecycle.Modules;
import dev.loki.lovisual.features.module.modules.combat.pvp.PvpCooldowns;
@Mixin(PauseScreen.class)
public abstract class PauseScreenMixin {
@Shadow
@Nullable
private Button disconnectButton;
@Unique
private Button lovisual$lastExitButton;
@Unique
private boolean lovisual$disconnectBlocked;
@Unique
private boolean lovisual$previousExitActive = true;
@Inject(method = "init", at = @At("TAIL"))
private void lovisual$blockDisconnectOnInit(CallbackInfo ci) {
lovisual$syncDisconnectButton();
}
@Inject(method = "extractRenderState", at = @At("HEAD"))
private void lovisual$blockDisconnectOnRender(GuiGraphicsExtractor context, int mouseX, int mouseY, float deltaTicks, CallbackInfo ci) {
lovisual$syncDisconnectButton();
}
@Unique
private void lovisual$syncDisconnectButton() {
Button button = disconnectButton;
if (button == null) {
lovisual$lastExitButton = null;
lovisual$disconnectBlocked = false;
return;
}
if (button != lovisual$lastExitButton) {
lovisual$lastExitButton = button;
lovisual$disconnectBlocked = false;
lovisual$previousExitActive = button.active;
}
PvpCooldowns cooldowns = Modules.get(PvpCooldowns.class);
boolean shouldBlock = cooldowns != null && cooldowns.shouldBlockDisconnect();
if (shouldBlock) {
if (!lovisual$disconnectBlocked) {
lovisual$previousExitActive = button.active;
}
lovisual$disconnectBlocked = true;
button.active = false;
button.setTooltip(Tooltip.create(cooldowns.getDisconnectBlockTooltip()));
return;
}
if (lovisual$disconnectBlocked) {
button.active = lovisual$previousExitActive;
button.setTooltip(null);
lovisual$disconnectBlocked = false;
}
}
}

View file

@ -0,0 +1,160 @@
/*
* 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.gui.screens;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.TitleScreen;
import net.minecraft.network.chat.ClickEvent;
import net.minecraft.resources.Identifier;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import dev.loki.lovisual.config.i18n.MainConfig;
import dev.loki.lovisual.features.command.core.CommandManager;
import dev.loki.lovisual.features.command.util.CommandPrefix;
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.buttons.BetterButtons;
import dev.loki.lovisual.features.module.lifecycle.Modules;
import dev.loki.lovisual.features.module.modules.visuals.visibility.NoRender;
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.postprocess.render.MenuBackgroundRenderer;
import dev.loki.lovisual.util.NarratorBlocker;
@Mixin(Screen.class)
public abstract class ScreenMixin {
@Unique
private RenderPhaseScope lovisual$screenPhaseScope;
@Inject(method = "defaultHandleGameClickEvent", at = @At("HEAD"), cancellable = true)
private static void lovisual$runClientChatCommand(ClickEvent event,
Minecraft client,
Screen screen,
CallbackInfo ci) {
if (!(event instanceof ClickEvent.RunCommand run)) return;
String command = run.command();
if (command == null || !CommandPrefix.isCommandLike(command)) return;
if (CommandManager.handle(command)) {
ci.cancel();
}
}
@Inject(method = "extractMenuBackgroundTexture", at = @At("HEAD"), cancellable = true)
private static void lovisual$replaceMenuBackground(GuiGraphicsExtractor context,
Identifier texture,
int x,
int y,
float u,
float v,
int width,
int height,
CallbackInfo ci) {
if (texture == null || context == null) return;
if (!Screen.MENU_BACKGROUND.equals(texture)) return;
if (x != 0 || y != 0) return;
Minecraft mc = Minecraft.getInstance();
if (mc == null) return;
if (width != context.guiWidth() || height != context.guiHeight()) return;
MainConfig cfg = MainConfig.get();
if (cfg == null) return;
String mode = cfg.getMenuBackgroundMode();
if (mode == null || mode.equalsIgnoreCase("off")) return;
boolean aurora = mode.equalsIgnoreCase("aurora");
MenuBackgroundRenderer.render(mc, aurora);
ci.cancel();
}
@Inject(method = "shouldRunNarration", at = @At("HEAD"), cancellable = true)
private void lovisual$disableNarratorUi(CallbackInfoReturnable<Boolean> cir) {
if (!NarratorBlocker.isBlocked()) return;
cir.setReturnValue(false);
}
@Inject(method = "extractBackground(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("HEAD"), cancellable = true)
private void lovisual$replaceScreenBackground(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta, CallbackInfo ci) {
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.level != null) return;
MainConfig cfg = MainConfig.get();
String mode = (cfg != null) ? cfg.getMenuBackgroundMode() : "off";
if (mode == null || mode.equalsIgnoreCase("off")) return;
boolean aurora = mode.equalsIgnoreCase("aurora");
MenuBackgroundRenderer.render(mc, aurora);
ci.cancel();
}
@Inject(method = "extractBackground(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("HEAD"), cancellable = true)
private void lovisual$noRender$cancelDarkening(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta, CallbackInfo ci) {
NoRender module = Modules.get(NoRender.class);
Minecraft mc = Minecraft.getInstance();
if (module != null && mc != null && mc.level != null && module.screenDarkeningDisabled()) {
ci.cancel();
}
}
@Inject(method = "extractTransparentBackground(Lnet/minecraft/client/gui/GuiGraphicsExtractor;)V", at = @At("HEAD"), cancellable = true)
private void lovisual$noRender$cancelInGameBackground(GuiGraphicsExtractor context, CallbackInfo ci) {
NoRender module = Modules.get(NoRender.class);
Minecraft mc = Minecraft.getInstance();
if (module != null && mc != null && mc.level != null && module.screenDarkeningDisabled()) {
ci.cancel();
}
}
@Inject(method = "extractPanorama", at = @At("HEAD"), cancellable = true, require = 0)
private void lovisual$replaceTitlePanorama(GuiGraphicsExtractor context, float delta, CallbackInfo ci) {
Screen self = (Screen) (Object) this;
if (!(self instanceof TitleScreen)) return;
MainConfig cfg = MainConfig.get();
String mode = (cfg != null) ? cfg.getMenuBackgroundMode() : "off";
if (mode == null || mode.equalsIgnoreCase("off")) return;
Minecraft mc = Minecraft.getInstance();
if (mc == null) return;
boolean aurora = mode.equalsIgnoreCase("aurora");
MenuBackgroundRenderer.render(mc, aurora);
ci.cancel();
}
@Inject(method = "extractRenderStateWithTooltipAndSubtitles(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("HEAD"))
private void lovisual$beginButtonQueue(GuiGraphicsExtractor ctx, int mouseX, int mouseY, float delta, CallbackInfo ci) {
BetterButtons.beginFrame(ctx);
}
@Inject(method = "extractRenderStateWithTooltipAndSubtitles(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("HEAD"))
private void lovisual$phaseScreenHead(GuiGraphicsExtractor ctx, int mouseX, int mouseY, float delta, CallbackInfo ci) {
Screen self = (Screen) (Object) this;
String label = "2d:screen:" + self.getClass().getSimpleName();
LoVisualRenderSystem.ensureFrameContext();
lovisual$screenPhaseScope = LoVisualRenderSystem.phase(RenderPhase.SCREEN, label);
}
@Inject(method = "extractRenderStateWithTooltipAndSubtitles(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("RETURN"))
private void lovisual$phaseScreenTail(GuiGraphicsExtractor ctx, int mouseX, int mouseY, float delta, CallbackInfo ci) {
Screen self = (Screen) (Object) this;
String label = "2d:screen:" + self.getClass().getSimpleName();
if (lovisual$screenPhaseScope != null) {
lovisual$screenPhaseScope.close();
lovisual$screenPhaseScope = null;
}
}
}

View file

@ -0,0 +1,33 @@
/*
* 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.gui.screens;
import dev.loki.lovisual.util.screen.ClientScreen;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.TitleScreen;
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.mainmenu.LoVisualMainMenuScreen;
@Mixin(TitleScreen.class)
public abstract class TitleScreenMixin {
@Inject(method = "init", at = @At("HEAD"), cancellable = true)
private void lovisual$replaceTitleScreen(CallbackInfo ci) {
if (LoVisualMainMenuScreen.shouldUseVanillaTitleScreen()) return;
Minecraft mc = Minecraft.getInstance();
if (mc == null) return;
if (ClientScreen.current() instanceof LoVisualMainMenuScreen) return;
ClientScreen.show(mc, new LoVisualMainMenuScreen());
ci.cancel();
}
}

View file

@ -0,0 +1,41 @@
/*
* 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.gui.widgets;
import dev.loki.lovisual.util.screen.ClientScreen;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.components.AbstractWidget;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.screens.inventory.MerchantScreen;
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.buttons.BetterButtons;
@Mixin(Button.Plain.class)
public abstract class ButtonWidgetTextMixin {
@Inject(method = "extractContents(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("HEAD"), cancellable = true)
private void lovisual$drawIcon(GuiGraphicsExtractor ctx, int mouseX, int mouseY, float delta, CallbackInfo ci) {
BetterButtons buttons = BetterButtons.get();
if (buttons == null || !buttons.useUiButtons()) return;
Minecraft mc = Minecraft.getInstance();
if (mc != null && ClientScreen.current() instanceof MerchantScreen) return;
AbstractWidget widget = (AbstractWidget) (Object) this;
boolean hovered = widget.isMouseOver(mouseX, mouseY);
float hover = BetterButtons.updateHover(widget, hovered, mouseX, mouseY);
BetterButtons.enqueueButton(widget, widget.getMessage(), hover, widget.isActive(), widget.isFocused());
if (hovered) {
BetterButtons.captureTooltip(widget, mouseX, mouseY);
}
ci.cancel();
}
}

View file

@ -0,0 +1,53 @@
/*
* 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.gui.widgets;
import dev.loki.lovisual.util.screen.ClientScreen;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.components.AbstractWidget;
import net.minecraft.client.gui.components.CycleButton;
import net.minecraft.client.gui.screens.inventory.MerchantScreen;
import net.minecraft.resources.Identifier;
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.buttons.BetterButtons;
import dev.loki.lovisual.mixins.accessors.widget.CyclingButtonWidgetAccessor;
@Mixin(CycleButton.class)
public abstract class CycleButtonMixin<T> {
@Inject(method = "extractContents(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("HEAD"), cancellable = true)
private void lovisual$drawIcon(GuiGraphicsExtractor ctx, int mouseX, int mouseY, float delta, CallbackInfo ci) {
BetterButtons buttons = BetterButtons.get();
if (buttons == null || !buttons.useUiButtons()) return;
Minecraft mc = Minecraft.getInstance();
if (mc != null && ClientScreen.current() instanceof MerchantScreen) return;
CycleButton<T> self = (CycleButton<T>) (Object) this;
AbstractWidget widget = self;
boolean hovered = widget.isMouseOver(mouseX, mouseY);
float hover = BetterButtons.updateHover(widget, hovered, mouseX, mouseY);
CyclingButtonWidgetAccessor<T> accessor = (CyclingButtonWidgetAccessor<T>) self;
Identifier icon = accessor.lovisual$getIcon() != null
? accessor.lovisual$getIcon().apply(self, self.getValue())
: null;
boolean showLabel = accessor.lovisual$getLabelType() != CycleButton.DisplayState.HIDE;
BetterButtons.enqueueCyclingButton(self, icon, showLabel, hover, widget.isActive(), widget.isFocused());
if (hovered) {
BetterButtons.captureTooltip(widget, mouseX, mouseY);
}
ci.cancel();
}
}

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.gui.widgets;
import dev.loki.lovisual.util.screen.ClientScreen;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.components.AbstractWidget;
import net.minecraft.client.gui.components.SpriteIconButton;
import net.minecraft.client.gui.screens.inventory.MerchantScreen;
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.buttons.BetterButtons;
@Mixin(SpriteIconButton.CenteredIcon.class)
public abstract class SpriteIconButtonCenteredIconMixin {
@Inject(method = "extractContents(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("HEAD"), cancellable = true)
private void lovisual$drawIcon(GuiGraphicsExtractor ctx, int mouseX, int mouseY, float delta, CallbackInfo ci) {
BetterButtons buttons = BetterButtons.get();
if (buttons == null || !buttons.useUiButtons()) return;
Minecraft mc = Minecraft.getInstance();
if (mc != null && ClientScreen.current() instanceof MerchantScreen) return;
SpriteIconButton self = (SpriteIconButton) (Object) this;
AbstractWidget widget = self;
boolean hovered = widget.isMouseOver(mouseX, mouseY);
float hover = BetterButtons.updateHover(widget, hovered, mouseX, mouseY);
BetterButtons.enqueueTextIconButton(self, true, hover, widget.isActive(), widget.isFocused());
if (hovered) {
BetterButtons.captureTooltip(widget, mouseX, mouseY);
}
ci.cancel();
}
}

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.gui.widgets;
import dev.loki.lovisual.util.screen.ClientScreen;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.components.AbstractWidget;
import net.minecraft.client.gui.components.SpriteIconButton;
import net.minecraft.client.gui.screens.inventory.MerchantScreen;
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.buttons.BetterButtons;
@Mixin(SpriteIconButton.TextAndIcon.class)
public abstract class SpriteIconButtonTextAndIconMixin {
@Inject(method = "extractContents(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("HEAD"), cancellable = true)
private void lovisual$drawIcon(GuiGraphicsExtractor ctx, int mouseX, int mouseY, float delta, CallbackInfo ci) {
BetterButtons buttons = BetterButtons.get();
if (buttons == null || !buttons.useUiButtons()) return;
Minecraft mc = Minecraft.getInstance();
if (mc != null && ClientScreen.current() instanceof MerchantScreen) return;
SpriteIconButton self = (SpriteIconButton) (Object) this;
AbstractWidget widget = self;
boolean hovered = widget.isMouseOver(mouseX, mouseY);
float hover = BetterButtons.updateHover(widget, hovered, mouseX, mouseY);
BetterButtons.enqueueTextIconButton(self, false, hover, widget.isActive(), widget.isFocused());
if (hovered) {
BetterButtons.captureTooltip(widget, mouseX, mouseY);
}
ci.cancel();
}
}