LoVisual/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/GuiGraphicsMixin.java
2026-07-30 11:12:56 +02:00

311 lines
13 KiB
Java

/*
* This file is part of the Combatant Client distribution.
* Copyright (c) 2026 pivosos2007.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.mixins;
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.CooldownRender;
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.BetterTooltips;
import dev.loki.lovisual.features.module.Modules;
import dev.loki.lovisual.features.module.modules.combat.PvpCooldowns;
import dev.loki.lovisual.mixininterface.IGuiGraphics;
import dev.loki.lovisual.render.engine.core.ViewportContext;
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
import dev.loki.lovisual.render.engine.renderer.ui.HudDeferredGuiElement;
import net.minecraft.client.gui.navigation.ScreenRectangle;
import net.minecraft.client.renderer.state.gui.GuiRenderState;
import dev.loki.lovisual.util.pvp.CooldownRegistry;
import dev.loki.lovisual.util.pvp.ItemCooldownSnapshot;
import dev.loki.lovisual.util.pvp.PvpState;
import dev.loki.lovisual.util.pvp.client.CooldownsState;
@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 combatant$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 combatant$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 combatant$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 combatant$drawItemBar(ItemStack stack, int x, int y) {
combatant$invokeDrawItemBar(stack, x, y);
}
@Override
public void combatant$drawCooldownProgress(ItemStack stack, int x, int y) {
combatant$invokeDrawCooldownProgress(stack, x, y);
}
@Invoker("itemBar")
public abstract void combatant$invokeDrawItemBar(ItemStack stack, int x, int y);
@Invoker("itemCooldown")
public abstract void combatant$invokeDrawCooldownProgress(ItemStack stack, int x, int y);
@Accessor("deferredTooltip")
public abstract Runnable combatant$getTooltipDrawer();
@Accessor("deferredTooltip")
public abstract void combatant$setTooltipDrawer(Runnable drawer);
@Inject(method = "extractDeferredElements", at = @At("HEAD"))
private void combatant$suppressVanillaTooltip(int mouseX, int mouseY, float tickDelta, CallbackInfo ci) {
if (!BetterTooltips.hasTooltip()) return;
if (combatant$getTooltipDrawer() == null) return;
combatant$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 combatant$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 combatant$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 combatant$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 combatant$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 combatant$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();
}
}
}