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:
parent
ec10395511
commit
9d08fa910a
2239 changed files with 35265 additions and 28110 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
429
mod/src/main/java/dev/loki/lovisual/mixins/hud/GuiMixin.java
Normal file
429
mod/src/main/java/dev/loki/lovisual/mixins/hud/GuiMixin.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue