chore(history): squash 13 commit(s) from 2026-08-01
- refactor: Фаза 3 — разбить CustomBar (1495→426 фасад + 11 хелперов ≤200) - docs: Фаза 3 завершена — обновить TODO (8 гигантов разбито, долг по >200) - refactor: Фаза 4 — Security: BackdoorProtection инстанс-сервис, удалить StaffHeuristics - refactor: Фаза 5 — нормализация модулей: ленивый Minecraft, GameClock - feat: ShaderSky, Chams hand shaders + dual, BackSword, TargetESP blackhole, TargetManager 10s target - fix: GUI — убрать пустую категорию Movement, починить HUD в настройках - feat: кнопка-лупа поиска в меню модулей - fix: крэш без Sodium (BedwarsESP/BlockObservationHub) + тесты на настройки HUD - fix: диагностика HUD-виджетов в консоль (почему вкладка HUD пустая) - fix: подробная диагностика скана HUD-виджетов (found/registered/errors) - fix: HUD-виджеты загружаются лениво (вкладки HUD/UI пустые in-game) - fix: HUD-виджеты не регистрировались in-game — classloader ClassGraph vs Knot - fix: убрать дебаг-логи HUD (System.out)
This commit is contained in:
parent
52d87368c9
commit
ff42902c43
90 changed files with 4290 additions and 1949 deletions
|
|
@ -3,6 +3,7 @@ package dev.loki.lovisual.features.gui.clickgui;
|
|||
import dev.loki.lovisual.features.gui.clickgui.sections.settings.SettingsTabRuntime;
|
||||
import dev.loki.lovisual.render.engine.math.HudScale;
|
||||
import dev.loki.lovisual.util.wav.ClickGuiSounds;
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
||||
final class ClickGuiInputHandler {
|
||||
|
||||
|
|
@ -163,18 +164,18 @@ final class ClickGuiInputHandler {
|
|||
}
|
||||
|
||||
private static void updateMouse(double x, double y, boolean fromScaledCoords) {
|
||||
if (ClickGuiRenderer.MC == null || ClickGuiRenderer.MC.getWindow() == null) {
|
||||
if (Minecraft.getInstance() == null || Minecraft.getInstance().getWindow() == null) {
|
||||
ClickGuiRenderer.mouseX = (float) x;
|
||||
ClickGuiRenderer.mouseY = (float) y;
|
||||
return;
|
||||
}
|
||||
if (fromScaledCoords) {
|
||||
double scaledW = ClickGuiRenderer.MC.getWindow().getGuiScaledWidth();
|
||||
double scaledH = ClickGuiRenderer.MC.getWindow().getGuiScaledHeight();
|
||||
double fbW = ClickGuiRenderer.MC.getWindow().getWidth();
|
||||
double fbH = ClickGuiRenderer.MC.getWindow().getHeight();
|
||||
double sx = scaledW > 0 ? fbW / scaledW : ClickGuiRenderer.MC.getWindow().getGuiScale();
|
||||
double sy = scaledH > 0 ? fbH / scaledH : ClickGuiRenderer.MC.getWindow().getGuiScale();
|
||||
double scaledW = Minecraft.getInstance().getWindow().getGuiScaledWidth();
|
||||
double scaledH = Minecraft.getInstance().getWindow().getGuiScaledHeight();
|
||||
double fbW = Minecraft.getInstance().getWindow().getWidth();
|
||||
double fbH = Minecraft.getInstance().getWindow().getHeight();
|
||||
double sx = scaledW > 0 ? fbW / scaledW : Minecraft.getInstance().getWindow().getGuiScale();
|
||||
double sy = scaledH > 0 ? fbH / scaledH : Minecraft.getInstance().getWindow().getGuiScale();
|
||||
double fbX = x * sx;
|
||||
double fbY = y * sy;
|
||||
float uiScale = HudScale.scale((int) fbW, (int) fbH);
|
||||
|
|
@ -182,9 +183,9 @@ final class ClickGuiInputHandler {
|
|||
ClickGuiRenderer.mouseY = (float) (fbY / uiScale);
|
||||
return;
|
||||
}
|
||||
double scale = ClickGuiRenderer.MC.getWindow().getGuiScale();
|
||||
double fbW = ClickGuiRenderer.MC.getWindow().getWidth();
|
||||
double fbH = ClickGuiRenderer.MC.getWindow().getHeight();
|
||||
double scale = Minecraft.getInstance().getWindow().getGuiScale();
|
||||
double fbW = Minecraft.getInstance().getWindow().getWidth();
|
||||
double fbH = Minecraft.getInstance().getWindow().getHeight();
|
||||
double fbX = x * scale;
|
||||
double fbY = y * scale;
|
||||
float uiScale = HudScale.scale((int) fbW, (int) fbH);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import dev.loki.lovisual.features.module.ModuleManager;
|
|||
import dev.loki.lovisual.render.engine.animation.AnimationUtility;
|
||||
import dev.loki.lovisual.render.helpers.SystemCursor;
|
||||
import dev.loki.lovisual.util.screen.ClientScreen;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
final class ClickGuiLifecycle {
|
||||
|
|
@ -50,14 +51,14 @@ final class ClickGuiLifecycle {
|
|||
}
|
||||
|
||||
private static void detachScreenForExitOverlay() {
|
||||
if (ClickGuiRenderer.MC == null) return;
|
||||
if (Minecraft.getInstance() == null) return;
|
||||
if (ClientScreen.current() instanceof ClickGuiScreen cs) {
|
||||
ClickGuiRenderer.mainScreen = cs;
|
||||
}
|
||||
if (ClientScreen.current() instanceof ClickGuiScreen
|
||||
|| ClientScreen.current() instanceof ClickGuiPickerScreen
|
||||
|| ClientScreen.current() instanceof ClickGuiEditorScreen) {
|
||||
ClientScreen.show(ClickGuiRenderer.MC, null);
|
||||
ClientScreen.show(Minecraft.getInstance(), null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -82,7 +83,7 @@ final class ClickGuiLifecycle {
|
|||
|
||||
static void onClose() {
|
||||
if (!ClickGuiRenderer.cursorShown) return;
|
||||
long handle = ClickGuiRenderer.MC.getWindow().handle();
|
||||
long handle = Minecraft.getInstance().getWindow().handle();
|
||||
|
||||
GLFW.glfwSetInputMode(handle, GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED);
|
||||
SystemCursor.invalidate();
|
||||
|
|
@ -97,17 +98,17 @@ final class ClickGuiLifecycle {
|
|||
}
|
||||
|
||||
static void releaseCursorForGameplay() {
|
||||
if (ClickGuiRenderer.MC == null || ClickGuiRenderer.MC.getWindow() == null) return;
|
||||
if (Minecraft.getInstance() == null || Minecraft.getInstance().getWindow() == null) return;
|
||||
if (ClientScreen.current() == null) {
|
||||
try {
|
||||
ClickGuiRenderer.MC.mouseHandler.grabMouse();
|
||||
Minecraft.getInstance().mouseHandler.grabMouse();
|
||||
SystemCursor.invalidate();
|
||||
} catch (Throwable ignored) {
|
||||
GLFW.glfwSetInputMode(ClickGuiRenderer.MC.getWindow().handle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED);
|
||||
GLFW.glfwSetInputMode(Minecraft.getInstance().getWindow().handle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED);
|
||||
SystemCursor.invalidate();
|
||||
}
|
||||
} else {
|
||||
GLFW.glfwSetInputMode(ClickGuiRenderer.MC.getWindow().handle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED);
|
||||
GLFW.glfwSetInputMode(Minecraft.getInstance().getWindow().handle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED);
|
||||
SystemCursor.invalidate();
|
||||
}
|
||||
ClickGuiRenderer.cursorShown = false;
|
||||
|
|
@ -116,9 +117,9 @@ final class ClickGuiLifecycle {
|
|||
|
||||
static void ensureCursorShown() {
|
||||
if (ClickGuiRenderer.cursorShown) return;
|
||||
if (ClickGuiRenderer.MC == null || ClickGuiRenderer.MC.getWindow() == null) return;
|
||||
if (Minecraft.getInstance() == null || Minecraft.getInstance().getWindow() == null) return;
|
||||
GLFW.glfwSetInputMode(
|
||||
ClickGuiRenderer.MC.getWindow().handle(),
|
||||
Minecraft.getInstance().getWindow().handle(),
|
||||
GLFW.GLFW_CURSOR,
|
||||
GLFW.GLFW_CURSOR_NORMAL
|
||||
);
|
||||
|
|
@ -145,10 +146,10 @@ final class ClickGuiLifecycle {
|
|||
ClickGuiRenderer.finishingClose = true;
|
||||
ClickGuiRenderer.closingForExit = false;
|
||||
onClose();
|
||||
if (ClickGuiRenderer.MC != null && (ClientScreen.current() instanceof ClickGuiScreen
|
||||
if (Minecraft.getInstance() != null && (ClientScreen.current() instanceof ClickGuiScreen
|
||||
|| ClientScreen.current() instanceof ClickGuiPickerScreen
|
||||
|| ClientScreen.current() instanceof ClickGuiEditorScreen)) {
|
||||
ClientScreen.show(ClickGuiRenderer.MC, null);
|
||||
ClientScreen.show(Minecraft.getInstance(), null);
|
||||
}
|
||||
ClickGuiScreenManager.clearScreens();
|
||||
ClickGuiRenderer.finishingClose = false;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package dev.loki.lovisual.features.gui.clickgui;
|
|||
import dev.loki.lovisual.mixininterface.IGuiGraphics;
|
||||
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
|
||||
import dev.loki.lovisual.render.helpers.ScissorFunction;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
|
|
@ -26,7 +27,7 @@ final class ClickGuiPickerIconManager {
|
|||
ClickGuiRenderer.pickerIconScissorActive = false;
|
||||
return;
|
||||
}
|
||||
if (ClickGuiRenderer.MC == null) {
|
||||
if (Minecraft.getInstance() == null) {
|
||||
ClickGuiRenderer.pickerIcons.clear();
|
||||
ClickGuiRenderer.pickerIconScissorActive = false;
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import dev.loki.lovisual.render.engine.renderer.Renderer2D;
|
|||
import dev.loki.lovisual.render.engine.text.TextRenderer;
|
||||
import dev.loki.lovisual.render.helpers.SystemCursor;
|
||||
import dev.loki.lovisual.util.screen.ClientScreen;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
|
|
@ -49,15 +50,15 @@ final class ClickGuiRenderEngine {
|
|||
try {
|
||||
if (enabled && !ClickGuiRenderer.closingForExit && !ClickGuiRenderer.cursorShown) {
|
||||
GLFW.glfwSetInputMode(
|
||||
ClickGuiRenderer.MC.getWindow().handle(),
|
||||
Minecraft.getInstance().getWindow().handle(),
|
||||
GLFW.GLFW_CURSOR,
|
||||
GLFW.GLFW_CURSOR_NORMAL
|
||||
);
|
||||
SystemCursor.invalidate();
|
||||
ClickGuiRenderer.cursorShown = true;
|
||||
}
|
||||
int fbw = ClickGuiRenderer.MC.getWindow().getWidth();
|
||||
int fbh = ClickGuiRenderer.MC.getWindow().getHeight();
|
||||
int fbw = Minecraft.getInstance().getWindow().getWidth();
|
||||
int fbh = Minecraft.getInstance().getWindow().getHeight();
|
||||
int vw = Math.max(1, Math.round(HudScale.virtualWidth(fbw, fbh)));
|
||||
int vh = Math.max(1, Math.round(HudScale.virtualHeight(fbw, fbh)));
|
||||
ClickGuiRenderer.fbWidth = vw;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import dev.loki.lovisual.features.gui.clickgui.sections.SettingsSection;
|
|||
import dev.loki.lovisual.features.gui.clickgui.settings.*;
|
||||
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
|
||||
import dev.loki.lovisual.render.engine.text.TextRenderer;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
|
|
@ -26,7 +25,6 @@ import java.util.concurrent.CompletableFuture;
|
|||
public enum ClickGuiRenderer {
|
||||
;
|
||||
|
||||
static final Minecraft MC = Minecraft.getInstance();
|
||||
static final ModulesSection MODULES_SECTION = new ModulesSection();
|
||||
static final SettingsSection SETTINGS_SECTION = new SettingsSection();
|
||||
static final String MODULES_TAB_ID = "lovisual:modules";
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import dev.loki.lovisual.features.gui.clickgui.settings.ProtocolHeuristicsSettin
|
|||
import dev.loki.lovisual.features.gui.clickgui.settings.TextListSetting;
|
||||
import dev.loki.lovisual.util.screen.ClientScreen;
|
||||
import dev.loki.lovisual.util.wav.ClickGuiSounds;
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
|
|
@ -17,17 +18,17 @@ final class ClickGuiScreenManager {
|
|||
}
|
||||
|
||||
static void openClickGuiScreen() {
|
||||
if (ClickGuiRenderer.MC == null) return;
|
||||
if (Minecraft.getInstance() == null) return;
|
||||
if (ClientScreen.current() != null) return;
|
||||
if (ClickGuiRenderer.mainScreen == null) {
|
||||
ClickGuiRenderer.mainScreen = new ClickGuiScreen();
|
||||
}
|
||||
ClickGuiSounds.guiOpen();
|
||||
ClientScreen.show(ClickGuiRenderer.MC, ClickGuiRenderer.mainScreen);
|
||||
ClientScreen.show(Minecraft.getInstance(), ClickGuiRenderer.mainScreen);
|
||||
}
|
||||
|
||||
static void captureMainScreen() {
|
||||
if (ClickGuiRenderer.MC != null && ClientScreen.current() instanceof ClickGuiScreen cs) {
|
||||
if (Minecraft.getInstance() != null && ClientScreen.current() instanceof ClickGuiScreen cs) {
|
||||
ClickGuiRenderer.mainScreen = cs;
|
||||
}
|
||||
}
|
||||
|
|
@ -40,14 +41,14 @@ final class ClickGuiScreenManager {
|
|||
}
|
||||
|
||||
private static void openPickerScreen() {
|
||||
if (ClickGuiRenderer.MC == null) return;
|
||||
if (Minecraft.getInstance() == null) return;
|
||||
if (ClientScreen.current() instanceof ClickGuiScreen cs) {
|
||||
ClickGuiRenderer.mainScreen = cs;
|
||||
}
|
||||
if (ClickGuiRenderer.pickerScreen == null) {
|
||||
ClickGuiRenderer.pickerScreen = new ClickGuiPickerScreen();
|
||||
}
|
||||
ClientScreen.show(ClickGuiRenderer.MC, ClickGuiRenderer.pickerScreen);
|
||||
ClientScreen.show(Minecraft.getInstance(), ClickGuiRenderer.pickerScreen);
|
||||
}
|
||||
|
||||
static void clearScreens() {
|
||||
|
|
@ -86,11 +87,11 @@ final class ClickGuiScreenManager {
|
|||
ClickGuiRenderer.picker = null;
|
||||
ClickGuiRenderer.protocolHeuristicsEditor = null;
|
||||
ClickGuiRenderer.textEditor = null;
|
||||
if (ClickGuiRenderer.MC != null && ClientScreen.current() instanceof ClickGuiPickerScreen) {
|
||||
if (Minecraft.getInstance() != null && ClientScreen.current() instanceof ClickGuiPickerScreen) {
|
||||
if (ClickGuiRenderer.mainScreen == null) {
|
||||
ClickGuiRenderer.mainScreen = new ClickGuiScreen();
|
||||
}
|
||||
ClientScreen.show(ClickGuiRenderer.MC, ClickGuiRenderer.mainScreen);
|
||||
ClientScreen.show(Minecraft.getInstance(), ClickGuiRenderer.mainScreen);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import dev.loki.lovisual.render.engine.renderer.Renderer2D;
|
|||
import dev.loki.lovisual.render.engine.text.TextRenderer;
|
||||
import dev.loki.lovisual.render.helpers.SystemCursor;
|
||||
import dev.loki.lovisual.util.screen.ClientScreen;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
|
||||
final class ClickGuiTopLayer {
|
||||
|
|
@ -18,7 +19,7 @@ final class ClickGuiTopLayer {
|
|||
}
|
||||
|
||||
static boolean shouldRenderAsTopLayer() {
|
||||
return ClickGuiRenderer.MC != null && (ClickGuiLifecycle.isLifecycleActive()
|
||||
return Minecraft.getInstance() != null && (ClickGuiLifecycle.isLifecycleActive()
|
||||
|| ClientScreen.current() instanceof ClickGuiScreen
|
||||
|| ClientScreen.current() instanceof ClickGuiPickerScreen
|
||||
|| ClientScreen.current() instanceof ClickGuiEditorScreen);
|
||||
|
|
@ -50,11 +51,11 @@ final class ClickGuiTopLayer {
|
|||
}
|
||||
|
||||
static void renderEditor(Renderer2D rendererIn, TextRenderer fallback, GuiGraphicsExtractor ctx, float tickDelta) {
|
||||
if (ClickGuiRenderer.MC == null || ClickGuiRenderer.MC.getWindow() == null) return;
|
||||
if (Minecraft.getInstance() == null || Minecraft.getInstance().getWindow() == null) return;
|
||||
ClickGuiLifecycle.init();
|
||||
ClickGuiRenderer.renderer = rendererIn;
|
||||
int fbw = ClickGuiRenderer.MC.getWindow().getWidth();
|
||||
int fbh = ClickGuiRenderer.MC.getWindow().getHeight();
|
||||
int fbw = Minecraft.getInstance().getWindow().getWidth();
|
||||
int fbh = Minecraft.getInstance().getWindow().getHeight();
|
||||
int vw = Math.max(1, Math.round(HudScale.virtualWidth(fbw, fbh)));
|
||||
int vh = Math.max(1, Math.round(HudScale.virtualHeight(fbw, fbh)));
|
||||
ClickGuiRenderer.fbWidth = vw;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import java.util.Locale;
|
|||
|
||||
public enum ModulesMenuCategory {
|
||||
COMBAT("Combat", "swords", "combat"),
|
||||
MOVEMENT("Movement", "accessibility", "movement"),
|
||||
PLAYER("Player", "user", "player"),
|
||||
VISUALS("Visuals", "tree-pine", "visual", "render", "visuals"),
|
||||
OTHER("Other", "ellipsis", "other", "misc", "miscellaneous", "exploit", "world");
|
||||
|
|
|
|||
|
|
@ -63,6 +63,11 @@ public final class ModulesMenuScreen {
|
|||
private float searchY;
|
||||
private float searchW;
|
||||
private float searchH;
|
||||
private float searchBtnX;
|
||||
private float searchBtnY;
|
||||
private float searchBtnW;
|
||||
private float searchBtnH;
|
||||
private float searchBtnHoverAnim;
|
||||
|
||||
private TextRenderer regular;
|
||||
private TextRenderer medium;
|
||||
|
|
@ -207,6 +212,11 @@ public final class ModulesMenuScreen {
|
|||
|
||||
float hiddenSearchY = visibleSearchY + 12.0f * scale;
|
||||
searchY = AnimationUtility.lerp(hiddenSearchY, visibleSearchY, searchAnim);
|
||||
|
||||
searchBtnW = 22.0f * scale;
|
||||
searchBtnH = 22.0f * scale;
|
||||
searchBtnX = areaX + areaW - searchBtnW - 14.0f * scale;
|
||||
searchBtnY = areaY + 12.0f * scale;
|
||||
}
|
||||
|
||||
public void render(float mouseX, float mouseY) {
|
||||
|
|
@ -246,6 +256,7 @@ public final class ModulesMenuScreen {
|
|||
}
|
||||
}
|
||||
|
||||
renderSearchButton(mouseX, mouseY);
|
||||
renderSearch(mouseX, mouseY);
|
||||
renderHints();
|
||||
|
||||
|
|
@ -268,6 +279,11 @@ public final class ModulesMenuScreen {
|
|||
public boolean mousePressed(float mouseX, float mouseY, int button) {
|
||||
if (!isInteractive()) return false;
|
||||
|
||||
if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT && inside(mouseX, mouseY, searchBtnX, searchBtnY, searchBtnW, searchBtnH)) {
|
||||
ClickGuiSearch.setActive(!ClickGuiSearch.isActive());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT && inside(mouseX, mouseY, searchX, searchY, searchW, searchH)) {
|
||||
ClickGuiSearch.setActive(true);
|
||||
return true;
|
||||
|
|
@ -726,6 +742,36 @@ public final class ModulesMenuScreen {
|
|||
if (pageClip) ScissorFunction.pop();
|
||||
}
|
||||
|
||||
private void renderSearchButton(float mouseX, float mouseY) {
|
||||
float alpha = easeOutCubic(screenAnim);
|
||||
if (alpha <= 0.001f) return;
|
||||
|
||||
boolean hovered = inside(mouseX, mouseY, searchBtnX, searchBtnY, searchBtnW, searchBtnH);
|
||||
float dt = AnimationUtility.deltaTime();
|
||||
searchBtnHoverAnim = AnimationUtility.approach(searchBtnHoverAnim, hovered ? 1.0f : 0.0f, dt, 12.0f);
|
||||
searchBtnHoverAnim = AnimationUtility.snap(searchBtnHoverAnim, hovered ? 1.0f : 0.0f, 0.01f);
|
||||
|
||||
int bg = withAlpha(ModulesMenuStyle.panelBgGlassDark(), alpha * (0.45f + 0.35f * searchBtnHoverAnim));
|
||||
LayoutRender2D.roundedQuad(searchBtnX, searchBtnY, searchBtnW, searchBtnH, 6.0f * scale, bg, bg, bg, bg);
|
||||
LayoutRender2D.roundedStrokeQuad(
|
||||
searchBtnX, searchBtnY, searchBtnW, searchBtnH, 6.0f * scale, 0.5f * scale,
|
||||
withAlpha(ModulesMenuStyle.textMuted(), alpha * 0.35f),
|
||||
withAlpha(ModulesMenuStyle.textMuted(), alpha * 0.35f),
|
||||
withAlpha(ModulesMenuStyle.textMuted(), alpha * 0.35f),
|
||||
withAlpha(ModulesMenuStyle.textMuted(), alpha * 0.35f)
|
||||
);
|
||||
|
||||
TextRenderer icons = Fonts.renderer("Icons", FontInfo.Type.Regular, ClickGuiRenderer.getInterRegular());
|
||||
float iconSize = 9.0f * scale;
|
||||
float iconX = searchBtnX + (searchBtnW - ClickGuiRenderer.textWidth(icons, "s", iconSize)) * 0.5f;
|
||||
float iconY = searchBtnY + middle(textHeight(icons, iconSize), searchBtnH);
|
||||
int iconColor = withAlpha(
|
||||
ClickGuiSearch.isActive() ? 0xFFF5F5FF : hovered ? 0xFFD0D0DC : 0xFF878894,
|
||||
alpha
|
||||
);
|
||||
ClickGuiRenderer.drawText(icons, "s", iconX, iconY, iconSize, iconColor, false);
|
||||
}
|
||||
|
||||
private void renderSearch(float mouseX, float mouseY) {
|
||||
if (searchAnim <= 0.001f && !ClickGuiSearch.isActive()) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -63,15 +63,19 @@ public enum MenuSettingsResolver {
|
|||
case HUD -> {
|
||||
for (DraggableHudElement widget : DraggableHudElementRegistry.getWidgets()) {
|
||||
if (widget == null) continue;
|
||||
out.add(new ModuleComponent.CardEntry(
|
||||
widget.getId(),
|
||||
widget.getTitle(),
|
||||
"",
|
||||
"",
|
||||
!widget.getSettingDefs().isEmpty(),
|
||||
widget.isEnabled(),
|
||||
true
|
||||
));
|
||||
try {
|
||||
out.add(new ModuleComponent.CardEntry(
|
||||
widget.getId(),
|
||||
widget.getTitle(),
|
||||
"",
|
||||
"",
|
||||
!widget.getSettingDefs().isEmpty(),
|
||||
widget.isEnabled(),
|
||||
true
|
||||
));
|
||||
} catch (Throwable ignored) {
|
||||
// один сломанный виджет не должен опустошать всю категорию HUD
|
||||
}
|
||||
}
|
||||
}
|
||||
case UI -> {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import dev.loki.lovisual.features.gui.clickgui.layout.screen.settings.render.Lay
|
|||
import dev.loki.lovisual.features.gui.clickgui.util.ClickGuiMath;
|
||||
import dev.loki.lovisual.features.module.modules.misc.DefineTarget;
|
||||
import dev.loki.lovisual.features.relations.PlayerRelations;
|
||||
import dev.loki.lovisual.features.relations.StaffHeuristicsConfig;
|
||||
import dev.loki.lovisual.render.engine.animation.AnimationUtility;
|
||||
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
|
||||
import dev.loki.lovisual.render.engine.svg.SvgRenderOptions;
|
||||
|
|
@ -38,14 +37,10 @@ public final class RelationsComponent {
|
|||
private final RelationPlayerCardComponent cardComponent = new RelationPlayerCardComponent();
|
||||
private final OnlineRelationPlayerPickerComponent onlinePicker;
|
||||
private final List<CardEntryHit> cardHits = new ArrayList<>();
|
||||
private final List<ChipHit> chipHits = new ArrayList<>();
|
||||
|
||||
private RelationTab tab = RelationTab.FRIENDS;
|
||||
private ActiveField activeField = ActiveField.NONE;
|
||||
private String playerInput = "";
|
||||
private String prefixInput = "";
|
||||
private String suffixInput = "";
|
||||
private String containsInput = "";
|
||||
private String statusMessage;
|
||||
private long statusUntilMs;
|
||||
|
||||
|
|
@ -55,13 +50,6 @@ public final class RelationsComponent {
|
|||
private Rect playerInputRect = Rect.ZERO;
|
||||
private Rect addTypedButton = Rect.ZERO;
|
||||
private Rect reservedPickerButton = Rect.ZERO;
|
||||
private Rect enabledToggle = Rect.ZERO;
|
||||
private Rect prefixInputRect = Rect.ZERO;
|
||||
private Rect suffixInputRect = Rect.ZERO;
|
||||
private Rect containsInputRect = Rect.ZERO;
|
||||
private Rect prefixAddButton = Rect.ZERO;
|
||||
private Rect suffixAddButton = Rect.ZERO;
|
||||
private Rect containsAddButton = Rect.ZERO;
|
||||
|
||||
private float scroll;
|
||||
private float smoothedScroll;
|
||||
|
|
@ -107,7 +95,6 @@ public final class RelationsComponent {
|
|||
|
||||
public void render(float menuX, float menuY, float menuW, float menuH, float mx, float my, float scale) {
|
||||
cardHits.clear();
|
||||
chipHits.clear();
|
||||
SettingsGuiPalette palette = SettingsGuiPalette.current();
|
||||
movementInputBlocked = activeField != ActiveField.NONE || onlinePicker.blocksMovementInput();
|
||||
|
||||
|
|
@ -131,12 +118,6 @@ public final class RelationsComponent {
|
|||
return;
|
||||
}
|
||||
|
||||
if (tab == RelationTab.STAFF) {
|
||||
float heurH = 78f * scale;
|
||||
renderHeuristicsPanel(areaX, y, areaW, heurH, mx, my, scale, palette);
|
||||
y += heurH + 8f * scale;
|
||||
}
|
||||
|
||||
listX = areaX;
|
||||
listY = y;
|
||||
listW = areaW;
|
||||
|
|
@ -211,39 +192,6 @@ public final class RelationsComponent {
|
|||
if (onlinePicker.isVisible()) {
|
||||
return false;
|
||||
}
|
||||
if (tab == RelationTab.STAFF) {
|
||||
if (enabledToggle.contains(mx, my)) {
|
||||
StaffHeuristicsConfig cfg = StaffHeuristicsConfig.get();
|
||||
cfg.setEnabled(!cfg.enabled());
|
||||
return true;
|
||||
}
|
||||
if (prefixInputRect.contains(mx, my)) {
|
||||
activeField = ActiveField.PREFIX;
|
||||
movementInputBlocked = true;
|
||||
clearStatus();
|
||||
return true;
|
||||
}
|
||||
if (suffixInputRect.contains(mx, my)) {
|
||||
activeField = ActiveField.SUFFIX;
|
||||
movementInputBlocked = true;
|
||||
clearStatus();
|
||||
return true;
|
||||
}
|
||||
if (containsInputRect.contains(mx, my)) {
|
||||
activeField = ActiveField.CONTAINS;
|
||||
movementInputBlocked = true;
|
||||
clearStatus();
|
||||
return true;
|
||||
}
|
||||
if (prefixAddButton.contains(mx, my)) return commitHeuristic(ActiveField.PREFIX);
|
||||
if (suffixAddButton.contains(mx, my)) return commitHeuristic(ActiveField.SUFFIX);
|
||||
if (containsAddButton.contains(mx, my)) return commitHeuristic(ActiveField.CONTAINS);
|
||||
for (ChipHit hit : chipHits) {
|
||||
if (!hit.delete().contains(mx, my)) continue;
|
||||
removeHeuristic(hit.kind(), hit.value());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (CardEntryHit entryHit : cardHits) {
|
||||
CardHit hit = entryHit.hit();
|
||||
|
|
@ -274,8 +222,7 @@ public final class RelationsComponent {
|
|||
return true;
|
||||
}
|
||||
if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_KP_ENTER) {
|
||||
if (activeField == ActiveField.PLAYER) commitPlayerInput();
|
||||
else commitHeuristic(activeField);
|
||||
commitPlayerInput();
|
||||
return true;
|
||||
}
|
||||
if (keyCode == GLFW.GLFW_KEY_BACKSPACE) {
|
||||
|
|
@ -365,80 +312,6 @@ public final class RelationsComponent {
|
|||
drawIconButton(reservedPickerButton, onlineMode ? "x" : "user-plus", pickerHoverAnim, false, scale, palette);
|
||||
}
|
||||
|
||||
private void renderHeuristicsPanel(float x, float y, float w, float h, float mx, float my, float scale, SettingsGuiPalette palette) {
|
||||
StaffHeuristicsConfig cfg = StaffHeuristicsConfig.get();
|
||||
ClickGuiRenderer.drawBlur(x, y, w, h, 5f * scale, 0xFF000000, 190f / 255f);
|
||||
LayoutRender2D.roundedQuad(x, y, w, h, 5f * scale,
|
||||
palette.moduleCardTop(), palette.moduleCardTopStrong(), palette.moduleCardBottom(), palette.moduleCardBottomStrong());
|
||||
LayoutRender2D.roundedStroke(x, y, w, h, 5f * scale, 0.5f * scale, palette.menuWindowStroke());
|
||||
|
||||
ClickGuiRenderer.drawText(ClickGuiRenderer.getInterMedium(), tr("heuristics.title", "Staff heuristics"), x + 8f * scale, y + 6f * scale, 8f * scale, palette.moduleTitleText(), false);
|
||||
enabledToggle = new Rect(x + w - 56f * scale, y + 5f * scale, 48f * scale, 14f * scale);
|
||||
drawToggle(enabledToggle, cfg.enabled(), mx, my, scale, palette);
|
||||
|
||||
float top = y + 25f * scale;
|
||||
float gap = 7f * scale;
|
||||
float colW = (w - 16f * scale - gap * 2f) / 3f;
|
||||
renderHeuristicColumn(ActiveField.PREFIX, tr("heuristics.prefixes", "Prefixes"), cfg.prefixes(), x + 8f * scale, top, colW, h - 31f * scale, mx, my, scale, palette);
|
||||
renderHeuristicColumn(ActiveField.SUFFIX, tr("heuristics.suffixes", "Suffixes"), cfg.suffixes(), x + 8f * scale + colW + gap, top, colW, h - 31f * scale, mx, my, scale, palette);
|
||||
renderHeuristicColumn(ActiveField.CONTAINS, tr("heuristics.contains", "Contains"), cfg.contains(), x + 8f * scale + (colW + gap) * 2f, top, colW, h - 31f * scale, mx, my, scale, palette);
|
||||
}
|
||||
|
||||
private void renderHeuristicColumn(ActiveField kind,
|
||||
String title,
|
||||
Set<String> entries,
|
||||
float x,
|
||||
float y,
|
||||
float w,
|
||||
float h,
|
||||
float mx,
|
||||
float my,
|
||||
float scale,
|
||||
SettingsGuiPalette palette) {
|
||||
ClickGuiRenderer.drawText(ClickGuiRenderer.getInterRegular(), title, x, y, 6.7f * scale, palette.panelMuted(), false);
|
||||
Rect field = new Rect(x, y + 10f * scale, w - 16f * scale, 14f * scale);
|
||||
Rect add = new Rect(x + w - 14f * scale, y + 10f * scale, 14f * scale, 14f * scale);
|
||||
if (kind == ActiveField.PREFIX) {
|
||||
prefixInputRect = field;
|
||||
prefixAddButton = add;
|
||||
} else if (kind == ActiveField.SUFFIX) {
|
||||
suffixInputRect = field;
|
||||
suffixAddButton = add;
|
||||
} else {
|
||||
containsInputRect = field;
|
||||
containsAddButton = add;
|
||||
}
|
||||
drawInput(field, fieldText(kind), tr("placeholder.rule", "Rule"), activeField == kind, mx, my, scale, palette);
|
||||
drawIconButton(add, "check", add.contains(mx, my) ? 1f : 0f, false, scale, palette);
|
||||
|
||||
float chipX = x;
|
||||
float chipY = y + 29f * scale;
|
||||
float chipH = 11f * scale;
|
||||
List<String> sorted = sorted(entries);
|
||||
for (String entry : sorted) {
|
||||
float textSize = 5.9f * scale;
|
||||
String fitted = ClickGuiRenderer.fitText(ClickGuiRenderer.getInterRegular(), entry, textSize, w - 14f * scale);
|
||||
float chipW = Math.min(w, ClickGuiRenderer.textWidth(ClickGuiRenderer.getInterRegular(), fitted, textSize) + 16f * scale);
|
||||
if (chipX + chipW > x + w + 0.1f) {
|
||||
chipX = x;
|
||||
chipY += chipH + 3f * scale;
|
||||
}
|
||||
if (chipY + chipH > y + h) break;
|
||||
Rect chip = new Rect(chipX, chipY, chipW, chipH);
|
||||
Rect del = new Rect(chipX + chipW - 10f * scale, chipY + 1f * scale, 9f * scale, 9f * scale);
|
||||
LayoutRender2D.roundedQuad(chip.x(), chip.y(), chip.w(), chip.h(), 3f * scale,
|
||||
LayoutRender2D.alpha(palette.panelPillBase(), 0.95f),
|
||||
LayoutRender2D.alpha(palette.panelPillBase(), 0.88f),
|
||||
LayoutRender2D.alpha(palette.panelPillBase(), 0.88f),
|
||||
LayoutRender2D.alpha(palette.panelPillBase(), 0.95f));
|
||||
ClickGuiRenderer.drawText(ClickGuiRenderer.getInterRegular(), fitted, chipX + 4f * scale, chipY + 3f * scale, textSize, palette.panelText(), false);
|
||||
Renderer2D.COLOR.svg("x", del.x() + 1.8f * scale, del.y() + 1.8f * scale, 5.4f * scale, 5.4f * scale,
|
||||
SvgRenderOptions.overrideColor(palette.panelMuted()));
|
||||
chipHits.add(new ChipHit(kind, entry, del));
|
||||
chipX += chipW + 3f * scale;
|
||||
}
|
||||
}
|
||||
|
||||
private void renderCards(float x, float y, float w, float h, List<String> entries, float mx, float my, float scale, SettingsGuiPalette palette) {
|
||||
float gap = 6f * scale;
|
||||
float reservedScrollbarW = 9f * scale;
|
||||
|
|
@ -565,19 +438,6 @@ public final class RelationsComponent {
|
|||
iconSize, iconSize, SvgRenderOptions.overrideColor(iconColor));
|
||||
}
|
||||
|
||||
private void drawToggle(Rect rect, boolean enabled, float mx, float my, float scale, SettingsGuiPalette palette) {
|
||||
boolean hover = rect.contains(mx, my);
|
||||
int bg = enabled
|
||||
? SettingsGuiPalette.mix(palette.panelPillActive(), palette.menuCategorySelectedRight(), hover ? 0.22f : 0.12f)
|
||||
: SettingsGuiPalette.mix(palette.panelPillBase(), palette.panelBgRight(), hover ? 0.22f : 0.12f);
|
||||
LayoutRender2D.roundedQuad(rect.x(), rect.y(), rect.w(), rect.h(), 4f * scale, bg, bg, SettingsGuiPalette.darken(bg, 0.10f), bg);
|
||||
String label = enabled ? tr("heuristics.enabled", "Enabled") : tr("heuristics.disabled", "Disabled");
|
||||
float size = 6.2f * scale;
|
||||
float tw = ClickGuiRenderer.textWidth(ClickGuiRenderer.getInterMedium(), label, size);
|
||||
ClickGuiRenderer.drawText(ClickGuiRenderer.getInterMedium(), label, rect.x() + (rect.w() - tw) * 0.5f, rect.y() + 4.1f * scale, size,
|
||||
enabled ? palette.panelText() : palette.panelMuted(), false);
|
||||
}
|
||||
|
||||
private boolean switchTab(RelationTab next) {
|
||||
if (tab != next) {
|
||||
tab = next;
|
||||
|
|
@ -607,39 +467,6 @@ public final class RelationsComponent {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean commitHeuristic(ActiveField kind) {
|
||||
String value = StaffHeuristicsConfig.cleanRule(fieldText(kind));
|
||||
if (value.isBlank()) {
|
||||
setStatus(tr("status.rule_empty", "Rule is empty"));
|
||||
return true;
|
||||
}
|
||||
boolean changed = switch (kind) {
|
||||
case PREFIX -> StaffHeuristicsConfig.get().addPrefix(value);
|
||||
case SUFFIX -> StaffHeuristicsConfig.get().addSuffix(value);
|
||||
case CONTAINS -> StaffHeuristicsConfig.get().addContains(value);
|
||||
default -> false;
|
||||
};
|
||||
if (changed) {
|
||||
setFieldText(kind, "");
|
||||
activeField = ActiveField.NONE;
|
||||
movementInputBlocked = false;
|
||||
setStatus(tr("status.rule_added", "Rule added"));
|
||||
} else {
|
||||
setStatus(tr("status.rule_exists", "Rule already exists"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void removeHeuristic(ActiveField kind, String value) {
|
||||
boolean changed = switch (kind) {
|
||||
case PREFIX -> StaffHeuristicsConfig.get().removePrefix(value);
|
||||
case SUFFIX -> StaffHeuristicsConfig.get().removeSuffix(value);
|
||||
case CONTAINS -> StaffHeuristicsConfig.get().removeContains(value);
|
||||
default -> false;
|
||||
};
|
||||
if (changed) setStatus(tr("status.rule_removed", "Rule removed"));
|
||||
}
|
||||
|
||||
private List<String> filteredEntries(List<String> entries) {
|
||||
if (!ClickGuiSearch.isActive() || ClickGuiSearch.getText().isBlank()) return entries;
|
||||
String q = ClickGuiSearch.getText().toLowerCase(Locale.ROOT);
|
||||
|
|
@ -668,7 +495,7 @@ public final class RelationsComponent {
|
|||
private void appendToField(ActiveField field, String raw) {
|
||||
if (raw == null || raw.isEmpty()) return;
|
||||
String current = fieldText(field);
|
||||
int max = field == ActiveField.PLAYER ? 32 : 48;
|
||||
int max = 32;
|
||||
String next = current + raw;
|
||||
if (next.length() > max) next = next.substring(0, max);
|
||||
setFieldText(field, next);
|
||||
|
|
@ -677,9 +504,6 @@ public final class RelationsComponent {
|
|||
private String fieldText(ActiveField field) {
|
||||
return switch (field) {
|
||||
case PLAYER -> playerInput;
|
||||
case PREFIX -> prefixInput;
|
||||
case SUFFIX -> suffixInput;
|
||||
case CONTAINS -> containsInput;
|
||||
default -> "";
|
||||
};
|
||||
}
|
||||
|
|
@ -688,9 +512,6 @@ public final class RelationsComponent {
|
|||
String next = value == null ? "" : value;
|
||||
switch (field) {
|
||||
case PLAYER -> playerInput = next;
|
||||
case PREFIX -> prefixInput = next;
|
||||
case SUFFIX -> suffixInput = next;
|
||||
case CONTAINS -> containsInput = next;
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
|
|
@ -777,10 +598,7 @@ public final class RelationsComponent {
|
|||
|
||||
private enum ActiveField {
|
||||
NONE,
|
||||
PLAYER,
|
||||
PREFIX,
|
||||
SUFFIX,
|
||||
CONTAINS
|
||||
PLAYER
|
||||
}
|
||||
|
||||
private enum RelationTab {
|
||||
|
|
@ -831,9 +649,6 @@ public final class RelationsComponent {
|
|||
private record CardEntryHit(String name, CardHit hit) {
|
||||
}
|
||||
|
||||
private record ChipHit(ActiveField kind, String value, Rect delete) {
|
||||
}
|
||||
|
||||
private record Rect(float x, float y, float w, float h) {
|
||||
static final Rect ZERO = new Rect(0f, 0f, 0f, 0f);
|
||||
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ import net.minecraft.network.chat.Component;
|
|||
|
||||
public enum CooldownRender {
|
||||
;
|
||||
private static final Minecraft mc = Minecraft.getInstance();
|
||||
private static final float SCALE = 0.75f; // немного меньше для аккуратного влезания в слот
|
||||
|
||||
public static void renderTime(GuiGraphicsExtractor ctx, float progress, int x, int y, float secondsLeft) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.font == null || ctx == null) return;
|
||||
|
||||
// формируем текст
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ public enum HudElementAutoLoader {
|
|||
for (ClassInfo info : scan.getClassesWithAnnotation(HudElementInfo.class.getName())) {
|
||||
addCandidate(elements, info);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
DebugLog.error("HUD scan failed for %s", t, basePackage);
|
||||
}
|
||||
|
||||
elements.sort(Comparator
|
||||
|
|
@ -88,7 +90,9 @@ public enum HudElementAutoLoader {
|
|||
if (info.isAbstract()) return;
|
||||
|
||||
try {
|
||||
Class<?> type = info.loadClass();
|
||||
// Class.forName с classloader'ом мода (Knot), а не ClassGraph — иначе
|
||||
// getAnnotation по-рефлексии не находит аннотацию из-за другого classloader'а.
|
||||
Class<?> type = Class.forName(info.getName(), false, HudElementAutoLoader.class.getClassLoader());
|
||||
int order = 0;
|
||||
HudElementInfo elementInfo = type.getAnnotation(HudElementInfo.class);
|
||||
if (elementInfo != null) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
package dev.loki.lovisual.features.gui.hud;
|
||||
|
||||
import dev.loki.lovisual.util.logging.DebugLog;
|
||||
|
||||
/**
|
||||
* HUD widget bootstrapper.
|
||||
*/
|
||||
|
|
@ -14,13 +16,31 @@ public enum HudElements {
|
|||
;
|
||||
|
||||
private static boolean initialized = false;
|
||||
private static boolean loaded = false;
|
||||
|
||||
public static void init() {
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
|
||||
HudGlobalConfig.get();
|
||||
try {
|
||||
HudGlobalConfig.get();
|
||||
} catch (Throwable t) {
|
||||
DebugLog.error("Failed to init HUD global config", t);
|
||||
}
|
||||
|
||||
HudElementAutoLoader.loadDraggable("dev.loki.lovisual.features.gui.hud.draggable.impl");
|
||||
ensureLoaded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ленивая загрузка виджетов при первом обращении — не зависит от порядка init-вызовов.
|
||||
*/
|
||||
public static synchronized void ensureLoaded() {
|
||||
if (loaded) return;
|
||||
loaded = true;
|
||||
try {
|
||||
HudElementAutoLoader.loadDraggable("dev.loki.lovisual.features.gui.hud.draggable.impl");
|
||||
} catch (Throwable t) {
|
||||
DebugLog.error("Failed to load draggable HUD widgets", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,10 +77,12 @@ public enum DraggableHudElementRegistry {
|
|||
}
|
||||
|
||||
public static List<DraggableHudElement> getWidgets() {
|
||||
HudElements.ensureLoaded();
|
||||
return Collections.unmodifiableList(WIDGETS);
|
||||
}
|
||||
|
||||
public static DraggableHudElement getById(String id) {
|
||||
HudElements.ensureLoaded();
|
||||
return findById(id);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ public final class Admins extends DraggableHudElement {
|
|||
}
|
||||
|
||||
boolean chatPreview = ClientScreen.current() instanceof ChatScreen;
|
||||
List<StaffData.StaffEntry> entries = filterStaff(StaffData.getEntries());
|
||||
List<StaffData.StaffEntry> entries = filterStaff(StaffData.INSTANCE.getEntries());
|
||||
List<AnimatedEntry> animatedEntries = animateEntries(entries);
|
||||
boolean showExampleRow = entries.isEmpty() && (forceVisible || chatPreview);
|
||||
boolean showWidget = !entries.isEmpty() || showExampleRow;
|
||||
|
|
|
|||
|
|
@ -13,11 +13,24 @@ public enum StaticHudElementBootstrap {
|
|||
;
|
||||
|
||||
private static boolean initialized = false;
|
||||
private static boolean loaded = false;
|
||||
|
||||
public static void init() {
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
|
||||
HudElementAutoLoader.loadStatic("dev.loki.lovisual.features.gui.hud.nondraggable.impl");
|
||||
ensureLoaded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ленивая загрузка static-элементов при первом обращении.
|
||||
*/
|
||||
public static synchronized void ensureLoaded() {
|
||||
if (loaded) return;
|
||||
loaded = true;
|
||||
try {
|
||||
HudElementAutoLoader.loadStatic("dev.loki.lovisual.features.gui.hud.nondraggable.impl");
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,10 +38,12 @@ public enum StaticHudElementRegistry {
|
|||
}
|
||||
|
||||
public static List<AbstractHudElement> getAll() {
|
||||
StaticHudElementBootstrap.ensureLoaded();
|
||||
return Collections.unmodifiableList(ELEMENTS);
|
||||
}
|
||||
|
||||
public static AbstractHudElement getById(String id) {
|
||||
StaticHudElementBootstrap.ensureLoaded();
|
||||
if (id == null) return null;
|
||||
for (AbstractHudElement e : ELEMENTS) {
|
||||
if (id.equals(e.getId())) return e;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 pivosos2007.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.features.gui.hud.nondraggable.impl;
|
||||
|
||||
import dev.loki.lovisual.config.values.EnumValue;
|
||||
import dev.loki.lovisual.render.engine.color.RenderColor;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.world.waypoints.TrackedWaypoint;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
final class CustomBarModel {
|
||||
|
||||
private CustomBarModel() {
|
||||
}
|
||||
|
||||
public enum BarMode implements EnumValue.IdProvider {
|
||||
LEVEL("level"),
|
||||
LOCATOR("locator"),
|
||||
AUTO("auto");
|
||||
|
||||
private final String id;
|
||||
|
||||
BarMode(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
public enum XpBarStyle implements EnumValue.IdProvider {
|
||||
GRADIENT("gradient"),
|
||||
RAINBOW("rainbow"),
|
||||
CUSTOM("custom");
|
||||
|
||||
private final String id;
|
||||
|
||||
XpBarStyle(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
public enum LocatorLabelMode implements EnumValue.IdProvider {
|
||||
ALWAYS("always"),
|
||||
CENTER("center"),
|
||||
TARGETS("targets"),
|
||||
CENTER_TARGETS("center_targets");
|
||||
|
||||
private final String id;
|
||||
|
||||
LocatorLabelMode(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class XpTextInfo {
|
||||
public final String text;
|
||||
public final int x;
|
||||
public final int y;
|
||||
public final int color;
|
||||
public final float scale;
|
||||
|
||||
XpTextInfo(String text, int x, int y, int color, float scale) {
|
||||
this.text = text;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.color = color;
|
||||
this.scale = scale;
|
||||
}
|
||||
}
|
||||
|
||||
record LocatorLabel(String name, String distance, float alpha, boolean target, boolean active, String sort) {
|
||||
}
|
||||
|
||||
record BarColors(int track,
|
||||
int fillStart,
|
||||
int fillEnd,
|
||||
int stroke,
|
||||
int trackTexture,
|
||||
int fillTexture,
|
||||
int locatorTexture) {
|
||||
}
|
||||
|
||||
static final class LocatorCandidate {
|
||||
final TrackedWaypoint waypoint;
|
||||
final UUID uuid;
|
||||
final float yaw;
|
||||
final float dist;
|
||||
final String name;
|
||||
final String nameSort;
|
||||
final String key;
|
||||
final String keySort;
|
||||
|
||||
LocatorCandidate(TrackedWaypoint waypoint, UUID uuid, float yaw, float dist, String name, String key) {
|
||||
this.waypoint = waypoint;
|
||||
this.uuid = uuid;
|
||||
this.yaw = yaw;
|
||||
this.dist = dist;
|
||||
this.name = name != null ? name : "";
|
||||
this.nameSort = this.name.toLowerCase(Locale.ROOT);
|
||||
this.key = key;
|
||||
this.keySort = key != null ? key : "";
|
||||
}
|
||||
}
|
||||
|
||||
record LocatorHead(float x, float y, float size, float radius, AbstractClientPlayer player,
|
||||
Identifier skin, RenderColor color, RenderColor outlineColor, float outlineThickness,
|
||||
boolean target) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 pivosos2007.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.features.gui.hud.nondraggable.impl;
|
||||
|
||||
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.CustomBarModel.BarColors;
|
||||
import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines;
|
||||
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.util.Mth;
|
||||
|
||||
import static dev.loki.lovisual.features.gui.hud.nondraggable.impl.CustomBar.*;
|
||||
|
||||
final class CustomJumpBarRenderer {
|
||||
|
||||
private CustomJumpBarRenderer() {
|
||||
}
|
||||
|
||||
static boolean shouldUseJumpBar(Minecraft mc) {
|
||||
if (mc == null || mc.player == null) return false;
|
||||
var mount = mc.player.jumpableVehicle();
|
||||
return mount != null && (mc.player.getJumpRidingScale() > 0.0f || mount.getJumpCooldown() > 0);
|
||||
}
|
||||
|
||||
static void renderJumpBarBackground(Renderer2D r2d, Minecraft mc) {
|
||||
if (mc == null || mc.player == null) return;
|
||||
CustomBar bm = CustomBar.get();
|
||||
if (bm == null || !bm.isHudBarEnabled() || !bm.isHotbarXpBarEnabled()) return;
|
||||
float progress = mc.player.jumpableVehicle() != null && mc.player.jumpableVehicle().getJumpCooldown() <= 0
|
||||
? Mth.clamp(mc.player.getJumpRidingScale(), 0.0f, 1.0f)
|
||||
: 1.0f;
|
||||
float scale = (float) mc.getWindow().getGuiScale();
|
||||
float x = (mc.getWindow().getGuiScaledWidth() - XP_BAR_WIDTH) * 0.5f;
|
||||
float y = mc.getWindow().getGuiScaledHeight() - 22f - XP_BAR_OFFSET - XP_BAR_HEIGHT;
|
||||
CustomXpBarRenderer.drawVanillaGlassTrack(r2d, x, y, scale, progress, bm, false);
|
||||
}
|
||||
|
||||
static void renderJumpBarTexture(GuiGraphicsExtractor ctx, Minecraft mc) {
|
||||
if (ctx == null || mc == null || mc.player == null) return;
|
||||
CustomBar bm = CustomBar.get();
|
||||
if (bm == null || !bm.isHudBarEnabled() || !bm.isHotbarXpBarEnabled()) return;
|
||||
var mount = mc.player.jumpableVehicle();
|
||||
if (mount == null) return;
|
||||
|
||||
int x = Mth.floor((mc.getWindow().getGuiScaledWidth() - XP_BAR_WIDTH) * 0.5f);
|
||||
int y = Mth.floor(mc.getWindow().getGuiScaledHeight() - 22f - XP_BAR_OFFSET - XP_BAR_HEIGHT);
|
||||
BarColors colors = CustomXpBarRenderer.colors(bm);
|
||||
ctx.blitSprite(LoVisualRenderPipelines.GUI_TEXTURE_LOOKUP, JUMP_BACKGROUND, x, y, 182, 5,
|
||||
CustomXpBarRenderer.applyBarAlpha(colors.trackTexture(), bm));
|
||||
if (mount.getJumpCooldown() > 0) {
|
||||
ctx.blitSprite(LoVisualRenderPipelines.GUI_TEXTURE_LOOKUP, JUMP_COOLDOWN, x, y, 182, 5,
|
||||
CustomXpBarRenderer.applyBarAlpha(colors.fillTexture(), bm));
|
||||
return;
|
||||
}
|
||||
int progress = Mth.lerpDiscrete(mc.player.getJumpRidingScale(), 0, 182);
|
||||
if (progress > 0) {
|
||||
ctx.blitSprite(LoVisualRenderPipelines.GUI_TEXTURE_LOOKUP, JUMP_PROGRESS, 182, 5, 0, 0, x, y, progress, 5,
|
||||
CustomXpBarRenderer.applyBarAlpha(colors.fillTexture(), bm));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 pivosos2007.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.features.gui.hud.nondraggable.impl;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.client.waypoints.ClientWaypointManager;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.waypoints.PartialTickSupplier;
|
||||
import net.minecraft.world.waypoints.TrackedWaypoint;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
final class CustomLocatorCollector {
|
||||
|
||||
private CustomLocatorCollector() {
|
||||
}
|
||||
|
||||
static List<CustomBarModel.LocatorCandidate> collect(Minecraft mc, CustomBar bm, TrackedWaypoint.Camera yawProvider,
|
||||
PartialTickSupplier tickProgress, Set<UUID> mapPlayers,
|
||||
ClientWaypointManager handler) {
|
||||
List<CustomBarModel.LocatorCandidate> candidates = new ArrayList<>();
|
||||
|
||||
handler.forEachWaypoint(mc.player, waypoint -> {
|
||||
if (isSourcePlayer(mc.player, waypoint)) return;
|
||||
UUID wpUuid = waypoint.id().left().orElse(null);
|
||||
if (wpUuid != null && !mapPlayers.isEmpty() && mapPlayers.contains(wpUuid)) return;
|
||||
|
||||
double yaw = waypoint.yawAngleToCamera(mc.level, yawProvider, tickProgress);
|
||||
if (yaw <= -CustomBar.LOCATOR_ANGLE_RANGE || yaw >= CustomBar.LOCATOR_ANGLE_RANGE) return;
|
||||
|
||||
float dist = Mth.sqrt((float) waypoint.distanceSquared(mc.player));
|
||||
String name = CustomLocatorResolver.getWaypointName(mc, waypoint, wpUuid);
|
||||
String key = locatorLabelKey(waypoint, wpUuid);
|
||||
|
||||
candidates.add(new CustomBarModel.LocatorCandidate(
|
||||
waypoint,
|
||||
wpUuid,
|
||||
(float) yaw,
|
||||
dist,
|
||||
name,
|
||||
key
|
||||
));
|
||||
});
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
static boolean isSourcePlayer(LocalPlayer player, TrackedWaypoint waypoint) {
|
||||
if (player == null || waypoint == null) return false;
|
||||
return waypoint.id().left()
|
||||
.map(uuid -> uuid.equals(player.getUUID()))
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
static String locatorLabelKey(TrackedWaypoint waypoint, UUID uuid) {
|
||||
if (uuid != null) return uuid.toString();
|
||||
return waypoint.id().right().orElse(null);
|
||||
}
|
||||
|
||||
static final class Pass {
|
||||
final GuiGraphicsExtractor ctx;
|
||||
final Minecraft mc;
|
||||
final CustomBar bm;
|
||||
final PartialTickSupplier tickProgress;
|
||||
final float barY;
|
||||
final int startX;
|
||||
final boolean drawArrows;
|
||||
final boolean drawHeads;
|
||||
final boolean labelsEnabled;
|
||||
final CustomBarModel.LocatorLabelMode labelMode;
|
||||
final float labelAngle;
|
||||
final float dt;
|
||||
final Set<String> seenLabels;
|
||||
final List<CustomBarModel.LocatorLabel> labels;
|
||||
final List<CustomBarModel.LocatorHead> heads;
|
||||
|
||||
Pass(GuiGraphicsExtractor ctx, Minecraft mc, CustomBar bm, PartialTickSupplier tickProgress,
|
||||
float barY, int startX, boolean drawArrows, boolean drawHeads,
|
||||
boolean labelsEnabled, CustomBarModel.LocatorLabelMode labelMode, float labelAngle, float dt,
|
||||
Set<String> seenLabels, List<CustomBarModel.LocatorLabel> labels, List<CustomBarModel.LocatorHead> heads) {
|
||||
this.ctx = ctx;
|
||||
this.mc = mc;
|
||||
this.bm = bm;
|
||||
this.tickProgress = tickProgress;
|
||||
this.barY = barY;
|
||||
this.startX = startX;
|
||||
this.drawArrows = drawArrows;
|
||||
this.drawHeads = drawHeads;
|
||||
this.labelsEnabled = labelsEnabled;
|
||||
this.labelMode = labelMode;
|
||||
this.labelAngle = labelAngle;
|
||||
this.dt = dt;
|
||||
this.seenLabels = seenLabels;
|
||||
this.labels = labels;
|
||||
this.heads = heads;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 pivosos2007.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.features.gui.hud.nondraggable.impl;
|
||||
|
||||
import dev.loki.lovisual.features.gui.hud.HudRenderUtil;
|
||||
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
|
||||
import dev.loki.lovisual.render.helpers.PlayerHeadRenderer;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
final class CustomLocatorHeads {
|
||||
|
||||
private CustomLocatorHeads() {
|
||||
}
|
||||
|
||||
static void render(GuiGraphicsExtractor ctx, CustomBar bm, List<CustomBarModel.LocatorHead> heads, float scale) {
|
||||
boolean startedHeadsBatch = CustomLocatorRenderer.beginOwnedColorBatch();
|
||||
try {
|
||||
for (int i = heads.size() - 1; i >= 0; i--) {
|
||||
CustomBarModel.LocatorHead head = heads.get(i);
|
||||
float headX = head.x() * scale;
|
||||
float headYScaled = head.y() * scale;
|
||||
float headSize = head.size() * scale;
|
||||
float headRadius = head.radius() * scale;
|
||||
float headOutline = head.outlineThickness() * scale;
|
||||
if (head.target()) {
|
||||
int glow = CustomXpBarRenderer.applyBarAlpha(
|
||||
HudRenderUtil.scaleAlpha(0xFF000000 | bm.getLocatorSearchColorRgb(), 0.28f), bm);
|
||||
Renderer2D.COLOR.roundedRect(
|
||||
headX - 1.9f * scale,
|
||||
headYScaled - 1.9f * scale,
|
||||
headSize + 3.8f * scale,
|
||||
headSize + 3.8f * scale,
|
||||
headRadius + 2.4f * scale,
|
||||
scale,
|
||||
glow
|
||||
);
|
||||
}
|
||||
if (head.player() != null) {
|
||||
PlayerHeadRenderer.drawRounded(
|
||||
ctx,
|
||||
headX, headYScaled, headSize,
|
||||
headRadius,
|
||||
head.player(),
|
||||
head.color(),
|
||||
true,
|
||||
head.outlineColor(),
|
||||
headOutline,
|
||||
false
|
||||
);
|
||||
} else if (head.skin() != null) {
|
||||
PlayerHeadRenderer.drawRounded(
|
||||
ctx,
|
||||
headX, headYScaled, headSize,
|
||||
headRadius,
|
||||
head.skin(),
|
||||
head.color(),
|
||||
true,
|
||||
head.outlineColor(),
|
||||
headOutline,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
CustomLocatorRenderer.renderOwnedColorBatch(startedHeadsBatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 pivosos2007.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.features.gui.hud.nondraggable.impl;
|
||||
|
||||
import dev.loki.lovisual.render.engine.animation.AnimationUtility;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static dev.loki.lovisual.features.gui.hud.nondraggable.impl.CustomBar.*;
|
||||
|
||||
final class CustomLocatorLabelCache {
|
||||
|
||||
private static final Map<String, Float> ANIM = new HashMap<>();
|
||||
private static final Map<String, CustomBarModel.LocatorLabel> CACHE = new HashMap<>();
|
||||
|
||||
private CustomLocatorLabelCache() {
|
||||
}
|
||||
|
||||
static float updateAnim(String key, float target, float dt) {
|
||||
float current = ANIM.getOrDefault(key, 0f);
|
||||
float next = AnimationUtility.approach(current, target, dt, LOCATOR_LABEL_ANIM_SPEED);
|
||||
next = AnimationUtility.snap(next, target, 0.01f);
|
||||
ANIM.put(key, next);
|
||||
return next;
|
||||
}
|
||||
|
||||
static void cache(String key, CustomBarModel.LocatorLabel label) {
|
||||
CACHE.put(key, label);
|
||||
}
|
||||
|
||||
static void decay(Set<String> seen, float dt) {
|
||||
if (ANIM.isEmpty()) return;
|
||||
var it = ANIM.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
var entry = it.next();
|
||||
if (seen.contains(entry.getKey())) continue;
|
||||
float next = AnimationUtility.approach(entry.getValue(), 0f, dt, LOCATOR_LABEL_ANIM_SPEED);
|
||||
next = AnimationUtility.snap(next, 0f, 0.01f);
|
||||
if (next <= 0.0f) {
|
||||
CACHE.remove(entry.getKey());
|
||||
it.remove();
|
||||
} else {
|
||||
entry.setValue(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void appendCached(List<CustomBarModel.LocatorLabel> labels) {
|
||||
if (labels == null || CACHE.isEmpty()) return;
|
||||
for (Map.Entry<String, CustomBarModel.LocatorLabel> entry : CACHE.entrySet()) {
|
||||
float alpha = ANIM.getOrDefault(entry.getKey(), 0f);
|
||||
if (alpha <= 0.01f) continue;
|
||||
CustomBarModel.LocatorLabel cached = entry.getValue();
|
||||
labels.add(new CustomBarModel.LocatorLabel(cached.name(), cached.distance(), alpha, cached.target(), cached.active(), cached.sort()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 pivosos2007.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.features.gui.hud.nondraggable.impl;
|
||||
|
||||
import dev.loki.lovisual.render.engine.math.ColorMath;
|
||||
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
|
||||
import net.minecraft.util.Mth;
|
||||
|
||||
import static dev.loki.lovisual.features.gui.hud.nondraggable.impl.CustomBar.*;
|
||||
import static dev.loki.lovisual.features.theme.Theme.theme;
|
||||
|
||||
final class CustomLocatorLabelPanel {
|
||||
|
||||
private CustomLocatorLabelPanel() {
|
||||
}
|
||||
|
||||
static void drawPanel(Renderer2D r2d, CustomBar bm, float x, float y, float w, float h,
|
||||
float radius, float scale, float alpha, boolean hasTarget) {
|
||||
if (r2d == null || alpha <= 0.001f || w <= 0f || h <= 0f) return;
|
||||
|
||||
float effectiveAlpha = Mth.clamp(alpha * (bm != null ? bm.getHudOverlayAlphaFactor() : 1.0f), 0.0f, 1.0f);
|
||||
float materialAlpha = Mth.clamp(0.78f + effectiveAlpha * 0.22f, 0.0f, 1.0f);
|
||||
float blurAlpha = effectiveAlpha;
|
||||
float thickness = 15.5f * Math.max(0.001f, scale);
|
||||
|
||||
r2d.liquidGlassRect(
|
||||
x,
|
||||
y,
|
||||
w,
|
||||
h,
|
||||
radius,
|
||||
thickness,
|
||||
0xFFFFFFFF,
|
||||
materialAlpha,
|
||||
blurAlpha,
|
||||
-26.0f,
|
||||
0.96f,
|
||||
0.98f,
|
||||
0.46f,
|
||||
0.172f * materialAlpha,
|
||||
2.65f
|
||||
);
|
||||
|
||||
r2d.roundedRect(
|
||||
x,
|
||||
y,
|
||||
w,
|
||||
h,
|
||||
radius,
|
||||
scale,
|
||||
CustomXpBarRenderer.applyBarAlpha(ColorMath.scaleAlpha(theme().windowBg(), alpha * 0.22f), bm)
|
||||
);
|
||||
r2d.roundedRectStrokeGradient(
|
||||
x,
|
||||
y,
|
||||
w,
|
||||
h,
|
||||
radius,
|
||||
Math.max(0.45f, 0.55f * scale),
|
||||
CustomXpBarRenderer.applyBarAlpha(ColorMath.scaleAlpha(theme().textPrimary(), alpha * 0.18f), bm),
|
||||
CustomXpBarRenderer.applyBarAlpha(ColorMath.scaleAlpha(theme().strokeSoft(), alpha * 0.20f), bm),
|
||||
118f
|
||||
);
|
||||
}
|
||||
|
||||
static void drawRow(Renderer2D r2d, CustomBar bm, CustomBarModel.LocatorLabel label,
|
||||
float x, float y, float w, float h, float radius, float scale, float alpha) {
|
||||
if (r2d == null || label == null || alpha <= 0.001f || w <= 0f || h <= 0f) return;
|
||||
|
||||
int rowFill = label.target()
|
||||
? CustomXpBarRenderer.applyBarAlpha(ColorMath.scaleAlpha(theme().textPrimary(), alpha * 0.105f), bm)
|
||||
: CustomXpBarRenderer.applyBarAlpha(ColorMath.scaleAlpha(theme().surfaceHover(), alpha * 0.055f), bm);
|
||||
r2d.roundedRect(
|
||||
x,
|
||||
y,
|
||||
w,
|
||||
h,
|
||||
radius,
|
||||
scale,
|
||||
rowFill
|
||||
);
|
||||
|
||||
if (label.target()) {
|
||||
float markerW = LOCATOR_LABEL_ACCENT_W * scale;
|
||||
float markerH = Math.max(1.0f * scale, h - 4.2f * scale);
|
||||
r2d.roundedRect(
|
||||
x + 1.8f * scale,
|
||||
y + (h - markerH) * 0.5f,
|
||||
markerW,
|
||||
markerH,
|
||||
markerW * 0.5f,
|
||||
0f,
|
||||
CustomXpBarRenderer.applyBarAlpha(ColorMath.scaleAlpha(theme().textPrimary(), alpha * 0.72f), bm)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 pivosos2007.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.features.gui.hud.nondraggable.impl;
|
||||
|
||||
import dev.loki.lovisual.render.engine.animation.AnimationUtility;
|
||||
import dev.loki.lovisual.render.engine.color.RenderColor;
|
||||
import dev.loki.lovisual.render.engine.math.ColorMath;
|
||||
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
|
||||
import dev.loki.lovisual.render.engine.text.Fonts;
|
||||
import dev.loki.lovisual.render.engine.text.TextRenderer;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.util.Mth;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import static dev.loki.lovisual.features.gui.hud.nondraggable.impl.CustomBar.*;
|
||||
import static dev.loki.lovisual.features.theme.Theme.theme;
|
||||
|
||||
final class CustomLocatorLabels {
|
||||
|
||||
private CustomLocatorLabels() {
|
||||
}
|
||||
|
||||
static void render(GuiGraphicsExtractor ctx, CustomBar bm, float screenW, float barY, float scale,
|
||||
List<CustomBarModel.LocatorLabel> labels) {
|
||||
TextRenderer tr = Fonts.renderer("Iosevka");
|
||||
Renderer2D r2d = Renderer2D.COLOR;
|
||||
float labelScale = LOCATOR_LABEL_SCALE * scale;
|
||||
List<CustomBarModel.LocatorLabel> layoutLabels = new ArrayList<>();
|
||||
for (CustomBarModel.LocatorLabel label : labels) {
|
||||
if (label.active()) {
|
||||
layoutLabels.add(label);
|
||||
}
|
||||
}
|
||||
if (layoutLabels.isEmpty()) {
|
||||
layoutLabels.addAll(labels);
|
||||
}
|
||||
layoutLabels.sort(Comparator
|
||||
.comparing((CustomBarModel.LocatorLabel label) -> !label.target())
|
||||
.thenComparing(label -> label.sort() == null ? "" : label.sort())
|
||||
.thenComparing(CustomBarModel.LocatorLabel::name));
|
||||
List<CustomBarModel.LocatorLabel> draw = layoutLabels.size() > LOCATOR_LABEL_MAX_ROWS
|
||||
? new ArrayList<>(layoutLabels.subList(0, LOCATOR_LABEL_MAX_ROWS))
|
||||
: layoutLabels;
|
||||
|
||||
tr.begin(labelScale, false, false);
|
||||
float textH = (float) tr.getHeight(true);
|
||||
float markerReserve = draw.stream().anyMatch(CustomBarModel.LocatorLabel::target) ? 4.7f * scale : 0.0f;
|
||||
float chipPadX = LOCATOR_LABEL_CHIP_PAD_X * scale;
|
||||
float chipPadY = LOCATOR_LABEL_CHIP_PAD_Y * scale;
|
||||
float distanceGap = 5.6f * scale;
|
||||
float contentW = 0.0f;
|
||||
for (CustomBarModel.LocatorLabel label : draw) {
|
||||
float nameW = (float) tr.getWidth(label.name(), true);
|
||||
float distanceW = label.distance().isEmpty() ? 0.0f : (float) tr.getWidth(label.distance(), true);
|
||||
float rowTextW = nameW + (distanceW > 0.0f ? distanceGap + distanceW : 0.0f);
|
||||
contentW = Math.max(contentW, markerReserve + rowTextW);
|
||||
}
|
||||
tr.end();
|
||||
|
||||
float padX = LOCATOR_LABEL_PADDING_X * scale;
|
||||
float padY = LOCATOR_LABEL_PADDING_Y * scale;
|
||||
float rowGap = LOCATOR_LABEL_ROW_GAP * scale;
|
||||
float chipH = textH + chipPadY * 2.0f;
|
||||
float rowStep = chipH + rowGap;
|
||||
float rowW = Math.max(58.0f * scale, contentW + chipPadX * 2.0f);
|
||||
float panelW = padX * 2.0f + rowW;
|
||||
float panelH = padY * 2.0f + draw.size() * chipH + Math.max(0, draw.size() - 1) * rowGap;
|
||||
float panelX = (screenW * scale - panelW) * 0.5f;
|
||||
panelX = Mth.clamp(panelX, 4.0f * scale, screenW * scale - panelW - 4.0f * scale);
|
||||
float panelY = barY * scale - LOCATOR_LABEL_OFFSET * scale - panelH;
|
||||
panelY = Math.max(4.0f * scale, panelY);
|
||||
|
||||
float panelAlpha = 0.0f;
|
||||
boolean hasTarget = false;
|
||||
for (CustomBarModel.LocatorLabel label : draw) {
|
||||
panelAlpha = Math.max(panelAlpha, Mth.clamp(label.alpha(), 0f, 1f));
|
||||
hasTarget |= label.target();
|
||||
}
|
||||
|
||||
boolean startedLabelBatch = CustomLocatorRenderer.beginOwnedColorBatch();
|
||||
try {
|
||||
float ease = locatorLabelEase(panelAlpha);
|
||||
float radius = LOCATOR_LABEL_RADIUS * scale;
|
||||
float fadedPanelY = panelY + (1.0f - ease) * 2.2f * scale;
|
||||
CustomLocatorLabelPanel.drawPanel(r2d, bm, panelX, fadedPanelY, panelW, panelH, radius, scale, ease, hasTarget);
|
||||
|
||||
float rowX = panelX + padX;
|
||||
float rowY = fadedPanelY + padY;
|
||||
float chipR = Math.min(chipH * 0.48f, LOCATOR_LABEL_CHIP_RADIUS * scale);
|
||||
for (int row = 0; row < draw.size(); row++) {
|
||||
CustomBarModel.LocatorLabel label = draw.get(row);
|
||||
float anim = Mth.clamp(label.alpha(), 0f, 1f);
|
||||
CustomLocatorLabelPanel.drawRow(r2d, bm, label, rowX, rowY + row * rowStep, rowW, chipH, chipR, scale, anim);
|
||||
}
|
||||
|
||||
tr.begin(labelScale, false, false);
|
||||
for (int row = 0; row < draw.size(); row++) {
|
||||
CustomBarModel.LocatorLabel label = draw.get(row);
|
||||
float anim = Mth.clamp(label.alpha(), 0f, 1f);
|
||||
float textBaseX = rowX + chipPadX + markerReserve;
|
||||
float textY = rowY + row * rowStep + (chipH - textH) * 0.5f;
|
||||
int nameArgb = CustomXpBarRenderer.applyBarAlpha(
|
||||
ColorMath.scaleAlpha(theme().textPrimary(), anim * (label.target() ? 1.0f : 0.92f)), bm);
|
||||
tr.render(label.name(), textBaseX, textY, new RenderColor(nameArgb), true);
|
||||
if (!label.distance().isEmpty()) {
|
||||
float distanceW = (float) tr.getWidth(label.distance(), true);
|
||||
float distanceX = rowX + rowW - chipPadX - distanceW;
|
||||
int distanceArgb = CustomXpBarRenderer.applyBarAlpha(
|
||||
ColorMath.scaleAlpha(theme().textPrimary(), anim * 0.90f), bm);
|
||||
tr.render(label.distance(), distanceX, textY, new RenderColor(distanceArgb), true);
|
||||
}
|
||||
}
|
||||
tr.end();
|
||||
} finally {
|
||||
CustomLocatorRenderer.renderOwnedColorBatch(startedLabelBatch);
|
||||
}
|
||||
}
|
||||
|
||||
private static float locatorLabelEase(float alpha) {
|
||||
return AnimationUtility.easeOutCubic(alpha);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 pivosos2007.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.features.gui.hud.nondraggable.impl;
|
||||
|
||||
import dev.loki.lovisual.render.engine.color.RenderColor;
|
||||
import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.waypoints.TrackedWaypoint;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
import static dev.loki.lovisual.features.gui.hud.nondraggable.impl.CustomBar.*;
|
||||
|
||||
final class CustomLocatorMarkers {
|
||||
|
||||
private CustomLocatorMarkers() {
|
||||
}
|
||||
|
||||
static void process(CustomLocatorCollector.Pass pass, CustomBarModel.LocatorCandidate candidate) {
|
||||
CustomBar bm = pass.bm;
|
||||
Minecraft mc = pass.mc;
|
||||
|
||||
float yaw = candidate.yaw;
|
||||
int offset = Mth.floor(yaw * (LOCATOR_HEAD_RANGE / 2f) / LOCATOR_ANGLE_RANGE);
|
||||
float drawX = pass.startX + offset;
|
||||
|
||||
float dist = candidate.dist;
|
||||
UUID wpUuid = candidate.uuid;
|
||||
|
||||
boolean isTarget = CustomLocatorResolver.isLocatorTarget(wpUuid, bm);
|
||||
boolean isDimmed = CustomLocatorResolver.isLocatorDimmed(wpUuid, bm);
|
||||
float headScale = locatorHeadScale(dist, isTarget, isDimmed, bm);
|
||||
float headSize = LOCATOR_HEAD_SIZE * headScale;
|
||||
float headRadius = LOCATOR_HEAD_RADIUS * headScale;
|
||||
float headY = pass.barY + (LOCATOR_BAR_HEIGHT - headSize) * 0.5f + LOCATOR_HEAD_GAP;
|
||||
|
||||
int baseColor = CustomLocatorResolver.resolveWaypointColor(candidate.waypoint);
|
||||
RenderColor headColor = new RenderColor(CustomXpBarRenderer.applyBarAlpha(0xFFFFFFFF, bm));
|
||||
RenderColor outlineColor = isTarget
|
||||
? new RenderColor(CustomXpBarRenderer.applyBarAlpha(0xFF000000 | bm.getLocatorSearchColorRgb(), bm))
|
||||
: locatorOutlineColor(baseColor, dist, bm);
|
||||
float outlineThickness = locatorOutlineThickness(dist) * headScale;
|
||||
if (isTarget) {
|
||||
outlineThickness = Math.max(outlineThickness, 1.85f * headScale);
|
||||
}
|
||||
|
||||
AbstractClientPlayer player = CustomLocatorResolver.resolveWaypointPlayer(mc, wpUuid);
|
||||
Identifier skin = (player == null) ? CustomLocatorResolver.resolveWaypointSkin(mc, wpUuid, candidate.waypoint) : null;
|
||||
|
||||
if (pass.drawHeads) {
|
||||
pass.heads.add(new CustomBarModel.LocatorHead(
|
||||
drawX, headY, headSize, headRadius,
|
||||
player, skin, headColor, outlineColor, outlineThickness, isTarget
|
||||
));
|
||||
}
|
||||
|
||||
TrackedWaypoint.PitchDirection pitch = candidate.waypoint.pitchDirectionToCamera(mc.level, mc.gameRenderer, pass.tickProgress);
|
||||
if (pass.drawArrows && (pitch == TrackedWaypoint.PitchDirection.UP || pitch == TrackedWaypoint.PitchDirection.DOWN)) {
|
||||
Identifier arrow = (pitch == TrackedWaypoint.PitchDirection.DOWN) ? LOCATOR_ARROW_DOWN : LOCATOR_ARROW_UP;
|
||||
int xOffset = Mth.floor(drawX + (headSize - LOCATOR_ARROW_W) * 0.5f);
|
||||
int yOffset = (pitch == TrackedWaypoint.PitchDirection.DOWN)
|
||||
? Mth.floor(headY + headSize + LOCATOR_ARROW_GAP)
|
||||
: Mth.floor(headY - LOCATOR_ARROW_H - LOCATOR_ARROW_GAP);
|
||||
pass.ctx.blitSprite(
|
||||
LoVisualRenderPipelines.GUI_TEXTURE_LOOKUP,
|
||||
arrow,
|
||||
xOffset,
|
||||
yOffset,
|
||||
LOCATOR_ARROW_W,
|
||||
LOCATOR_ARROW_H,
|
||||
CustomXpBarRenderer.applyBarAlpha(CustomXpBarRenderer.colors(bm).locatorTexture(), bm)
|
||||
);
|
||||
}
|
||||
|
||||
if (!pass.labelsEnabled || pass.labels == null || pass.seenLabels == null) return;
|
||||
|
||||
String labelKey = candidate.key != null ? candidate.key : candidate.nameSort;
|
||||
boolean showLabel = shouldShowLocatorLabel(pass.labelMode, yaw, pass.labelAngle, isTarget);
|
||||
if (showLabel && labelKey != null && !labelKey.isBlank()) {
|
||||
pass.seenLabels.add(labelKey);
|
||||
}
|
||||
float anim = labelKey != null && !labelKey.isBlank()
|
||||
? CustomLocatorLabelCache.updateAnim(labelKey, showLabel ? 1f : 0f, pass.dt)
|
||||
: (showLabel ? 1f : 0f);
|
||||
if (anim <= 0.01f) return;
|
||||
|
||||
String name = candidate.name;
|
||||
if (name == null || name.isBlank()) return;
|
||||
|
||||
String distanceText = "";
|
||||
if (bm.isLocatorLabelDistanceEnabled()) {
|
||||
distanceText = formatDistance(dist);
|
||||
}
|
||||
|
||||
CustomBarModel.LocatorLabel label = new CustomBarModel.LocatorLabel(name, distanceText, anim, isTarget, showLabel, candidate.nameSort);
|
||||
pass.labels.add(label);
|
||||
if (labelKey != null && !labelKey.isBlank()) {
|
||||
CustomLocatorLabelCache.cache(labelKey, label);
|
||||
}
|
||||
}
|
||||
|
||||
private static float locatorHeadScale(float dist, boolean isTarget, boolean isDimmed, CustomBar bm) {
|
||||
if (isTarget) return 1.0f;
|
||||
boolean dimmedOnly = bm != null && bm.isLocatorDimmedScaleOnly();
|
||||
if (dimmedOnly && !isDimmed) return 1.0f;
|
||||
|
||||
float range = LOCATOR_HEAD_SCALE_END - LOCATOR_HEAD_SCALE_START;
|
||||
if (range <= 0.0f) return 1.0f;
|
||||
float t = Mth.clamp((dist - LOCATOR_HEAD_SCALE_START) / range, 0f, 1f);
|
||||
return Mth.lerp(t, 1.0f, LOCATOR_HEAD_MIN_SCALE);
|
||||
}
|
||||
|
||||
private static RenderColor locatorOutlineColor(int baseArgb, float dist, CustomBar bm) {
|
||||
float t = Mth.clamp(dist / LOCATOR_OUTLINE_DISTANCE, 0f, 1f);
|
||||
int baseRgb = baseArgb & 0x00FFFFFF;
|
||||
int rgb = dev.loki.lovisual.render.engine.math.ColorMath.mixRgb(baseRgb, 0x000000, 0.25f + 0.35f * t);
|
||||
int alpha = Math.round(Mth.lerp(230f, 120f, t));
|
||||
int argb = CustomXpBarRenderer.applyBarAlpha((alpha << 24) | rgb, bm);
|
||||
return new RenderColor(argb);
|
||||
}
|
||||
|
||||
private static float locatorOutlineThickness(float dist) {
|
||||
float t = Mth.clamp(dist / LOCATOR_OUTLINE_DISTANCE, 0f, 1f);
|
||||
return Mth.lerp(LOCATOR_OUTLINE_MAX, LOCATOR_OUTLINE_MIN, t);
|
||||
}
|
||||
|
||||
private static boolean shouldShowLocatorLabel(CustomBarModel.LocatorLabelMode mode, float yaw, float angle, boolean isTarget) {
|
||||
float abs = Math.abs(yaw);
|
||||
return switch (mode) {
|
||||
case ALWAYS -> true;
|
||||
case CENTER -> abs <= angle;
|
||||
case TARGETS -> isTarget;
|
||||
case CENTER_TARGETS -> isTarget || abs <= angle;
|
||||
};
|
||||
}
|
||||
|
||||
private static String formatDistance(float dist) {
|
||||
if (!Float.isFinite(dist)) return "";
|
||||
return String.format(Locale.ROOT, "%.0fm", dist);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 pivosos2007.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.features.gui.hud.nondraggable.impl;
|
||||
|
||||
import dev.loki.lovisual.render.engine.animation.AnimationUtility;
|
||||
import dev.loki.lovisual.render.engine.core.ViewportContext;
|
||||
import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines;
|
||||
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
|
||||
import dev.loki.lovisual.util.map.MapPlayersCache;
|
||||
import net.minecraft.client.DeltaTracker;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.waypoints.ClientWaypointManager;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraft.world.waypoints.PartialTickSupplier;
|
||||
import net.minecraft.world.waypoints.TrackedWaypoint;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
final class CustomLocatorRenderer {
|
||||
|
||||
private CustomLocatorRenderer() {
|
||||
}
|
||||
|
||||
static void renderLocatorBackground(Renderer2D r2d, Minecraft mc) {
|
||||
if (mc == null || mc.player == null) return;
|
||||
CustomBar bm = CustomBar.get();
|
||||
if (bm == null || !bm.isHudBarEnabled() || !bm.isHotbarXpBarEnabled()) return;
|
||||
float scale = (float) mc.getWindow().getGuiScale();
|
||||
float screenW = mc.getWindow().getGuiScaledWidth();
|
||||
float screenH = mc.getWindow().getGuiScaledHeight();
|
||||
|
||||
float baseY = screenH - 22f;
|
||||
float x = (screenW - CustomBar.XP_BAR_WIDTH) * 0.5f;
|
||||
float y = baseY - CustomBar.XP_BAR_OFFSET - CustomBar.LOCATOR_BAR_HEIGHT;
|
||||
CustomXpBarRenderer.drawVanillaGlassTrack(r2d, x, y, scale, 1.0f, bm, true);
|
||||
}
|
||||
|
||||
static void renderLocatorTexture(GuiGraphicsExtractor ctx, Minecraft mc) {
|
||||
if (ctx == null || mc == null || mc.player == null) return;
|
||||
CustomBar bm = CustomBar.get();
|
||||
if (bm == null || !bm.isHudBarEnabled() || !bm.isHotbarXpBarEnabled()) return;
|
||||
int x = Mth.floor((mc.getWindow().getGuiScaledWidth() - CustomBar.XP_BAR_WIDTH) * 0.5f);
|
||||
int y = Mth.floor(mc.getWindow().getGuiScaledHeight() - 22f - CustomBar.XP_BAR_OFFSET - CustomBar.LOCATOR_BAR_HEIGHT);
|
||||
ctx.blitSprite(LoVisualRenderPipelines.GUI_TEXTURE_LOOKUP, CustomBar.LOCATOR_BACKGROUND, x, y, 182, 5,
|
||||
CustomXpBarRenderer.applyBarAlpha(CustomXpBarRenderer.colors(bm).locatorTexture(), bm));
|
||||
}
|
||||
|
||||
static void renderLocatorArrows(GuiGraphicsExtractor ctx, DeltaTracker tickCounter, Minecraft mc) {
|
||||
renderLocatorMarkers(ctx, tickCounter, mc, true, false);
|
||||
}
|
||||
|
||||
static void renderLocatorHeads(GuiGraphicsExtractor ctx, DeltaTracker tickCounter, Minecraft mc) {
|
||||
renderLocatorMarkers(ctx, tickCounter, mc, false, true);
|
||||
}
|
||||
|
||||
static void renderLocatorMarkers(GuiGraphicsExtractor ctx, DeltaTracker tickCounter, Minecraft mc) {
|
||||
renderLocatorMarkers(ctx, tickCounter, mc, true, true);
|
||||
}
|
||||
|
||||
static void renderLocatorMarkers(GuiGraphicsExtractor ctx, DeltaTracker tickCounter, Minecraft mc,
|
||||
boolean drawArrows, boolean drawHeads) {
|
||||
if (ctx == null || mc == null || mc.player == null || mc.level == null) return;
|
||||
CustomBar bm = CustomBar.get();
|
||||
if (bm == null || !bm.isHudBarEnabled() || !bm.isHotbarXpBarEnabled()) return;
|
||||
if (mc.getConnection() == null) return;
|
||||
ClientWaypointManager handler = mc.getConnection().getWaypointManager();
|
||||
if (handler == null || !handler.hasWaypoints()) return;
|
||||
|
||||
float screenW = mc.getWindow().getGuiScaledWidth();
|
||||
float screenH = mc.getWindow().getGuiScaledHeight();
|
||||
float baseY = screenH - 22f;
|
||||
float barX = (screenW - CustomBar.XP_BAR_WIDTH) * 0.5f;
|
||||
float barY = baseY - CustomBar.XP_BAR_OFFSET - CustomBar.LOCATOR_BAR_HEIGHT;
|
||||
|
||||
int startX = Mth.floor(barX + CustomBar.LOCATOR_HEAD_RANGE * 0.5f);
|
||||
PartialTickSupplier tickProgress = entity -> tickCounter.getGameTimeDeltaPartialTick(false);
|
||||
var camera = mc.gameRenderer.mainCamera();
|
||||
|
||||
TrackedWaypoint.Camera yawProvider = new TrackedWaypoint.Camera() {
|
||||
@Override
|
||||
public float yaw() {
|
||||
return camera.yRot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3 position() {
|
||||
return camera.position();
|
||||
}
|
||||
};
|
||||
|
||||
Set<UUID> mapPlayers = MapPlayersCache.getPlayers(mc, bm);
|
||||
boolean labelsEnabled = drawHeads && bm.isLocatorLabelsEnabled();
|
||||
CustomBarModel.LocatorLabelMode labelMode = labelsEnabled
|
||||
? bm.getLocatorLabelMode()
|
||||
: CustomBarModel.LocatorLabelMode.ALWAYS;
|
||||
float labelAngle = labelsEnabled ? bm.getLocatorLabelAngle() : 0f;
|
||||
float dt = drawHeads ? AnimationUtility.deltaTime() : 0.0f;
|
||||
|
||||
List<CustomBarModel.LocatorCandidate> candidates =
|
||||
CustomLocatorCollector.collect(mc, bm, yawProvider, tickProgress, mapPlayers, handler);
|
||||
if (candidates.isEmpty() && !drawHeads) return;
|
||||
|
||||
candidates.sort((a, b) -> {
|
||||
int cmp = Float.compare(a.dist, b.dist);
|
||||
if (cmp != 0) return cmp;
|
||||
cmp = a.nameSort.compareTo(b.nameSort);
|
||||
if (cmp != 0) return cmp;
|
||||
return a.keySort.compareTo(b.keySort);
|
||||
});
|
||||
|
||||
List<CustomBarModel.LocatorHead> heads = new ArrayList<>();
|
||||
Set<String> seenLabels = labelsEnabled ? new HashSet<>() : null;
|
||||
List<CustomBarModel.LocatorLabel> labels = labelsEnabled ? new ArrayList<>() : null;
|
||||
|
||||
CustomLocatorCollector.Pass pass = new CustomLocatorCollector.Pass(
|
||||
ctx, mc, bm, tickProgress, barY, startX, drawArrows, drawHeads,
|
||||
labelsEnabled, labelMode, labelAngle, dt, seenLabels, labels, heads);
|
||||
for (CustomBarModel.LocatorCandidate candidate : candidates) {
|
||||
CustomLocatorMarkers.process(pass, candidate);
|
||||
}
|
||||
|
||||
if (!drawHeads) return;
|
||||
|
||||
if (labelsEnabled && labels != null && seenLabels != null) {
|
||||
CustomLocatorLabelCache.decay(seenLabels, dt);
|
||||
labels.clear();
|
||||
CustomLocatorLabelCache.appendCached(labels);
|
||||
}
|
||||
|
||||
if (heads.isEmpty() && (labels == null || labels.isEmpty())) return;
|
||||
|
||||
float scale = (float) mc.getWindow().getGuiScale();
|
||||
ViewportContext.beginUnscaled(ctx);
|
||||
try {
|
||||
if (!heads.isEmpty()) {
|
||||
CustomLocatorHeads.render(ctx, bm, heads, scale);
|
||||
}
|
||||
if (labelsEnabled && labels != null && !labels.isEmpty()) {
|
||||
CustomLocatorLabels.render(ctx, bm, screenW, barY, scale, labels);
|
||||
}
|
||||
} finally {
|
||||
ViewportContext.end(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean beginOwnedColorBatch() {
|
||||
if (Renderer2D.UI_BATCHER.isActive()) {
|
||||
return false;
|
||||
}
|
||||
Renderer2D.COLOR.begin();
|
||||
return true;
|
||||
}
|
||||
|
||||
static void renderOwnedColorBatch(boolean owned) {
|
||||
if (owned) {
|
||||
Renderer2D.COLOR.render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 pivosos2007.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.features.gui.hud.nondraggable.impl;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import dev.loki.lovisual.util.player.PlayerSkinResolver;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.PlayerInfo;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.waypoints.TrackedWaypoint;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
final class CustomLocatorResolver {
|
||||
|
||||
private CustomLocatorResolver() {
|
||||
}
|
||||
|
||||
static int resolveWaypointColor(TrackedWaypoint waypoint) {
|
||||
CustomBar bm = CustomBar.get();
|
||||
|
||||
UUID wpUuid = waypoint.id().left().orElse(null);
|
||||
boolean isPlayerWaypoint = wpUuid != null;
|
||||
|
||||
if (bm != null && bm.isLocatorSearchEnabled() && isPlayerWaypoint) {
|
||||
Set<String> targets = bm.getLocatorSearchPlayers();
|
||||
if (targets != null && !targets.isEmpty()) {
|
||||
|
||||
if (isPlayerInTargets(wpUuid, targets)) {
|
||||
return 0xFF000000 | bm.getLocatorSearchColorRgb();
|
||||
}
|
||||
|
||||
return 0xFF000000 | bm.getLocatorDimmedColorRgb();
|
||||
}
|
||||
}
|
||||
|
||||
var cfg = waypoint.icon();
|
||||
if (cfg != null && cfg.color.isPresent()) {
|
||||
return 0xFF000000 | (cfg.color.get() & 0x00FFFFFF);
|
||||
}
|
||||
|
||||
int rgb = waypoint.id()
|
||||
.map(CustomLocatorResolver::colorFromUuid, CustomLocatorResolver::colorFromName);
|
||||
return 0xFF000000 | (rgb & 0x00FFFFFF);
|
||||
}
|
||||
|
||||
static AbstractClientPlayer resolveWaypointPlayer(Minecraft mc, UUID uuid) {
|
||||
if (mc == null || mc.level == null || uuid == null) return null;
|
||||
var player = mc.level.getPlayerByUUID(uuid);
|
||||
return player instanceof AbstractClientPlayer clientPlayer ? clientPlayer : null;
|
||||
}
|
||||
|
||||
static Identifier resolveWaypointSkin(Minecraft mc, UUID uuid, TrackedWaypoint waypoint) {
|
||||
GameProfile profile = null;
|
||||
if (mc != null && mc.getConnection() != null && uuid != null) {
|
||||
PlayerInfo entry = mc.getConnection().getPlayerInfo(uuid);
|
||||
if (entry != null && entry.getProfile() != null) {
|
||||
profile = entry.getProfile();
|
||||
}
|
||||
}
|
||||
|
||||
if (profile == null && uuid != null) {
|
||||
profile = new GameProfile(uuid, uuid.toString());
|
||||
}
|
||||
|
||||
if (profile == null) {
|
||||
String name = waypoint.id().right().orElse(null);
|
||||
if (name != null && !name.isBlank()) {
|
||||
profile = new GameProfile(offlineUuid(name), name);
|
||||
}
|
||||
}
|
||||
|
||||
return PlayerSkinResolver.resolveProfileSkin(profile);
|
||||
}
|
||||
|
||||
static String getWaypointName(Minecraft mc, TrackedWaypoint waypoint, UUID uuid) {
|
||||
if (mc != null && mc.getConnection() != null && uuid != null) {
|
||||
PlayerInfo entry = mc.getConnection().getPlayerInfo(uuid);
|
||||
if (entry != null && entry.getProfile() != null) {
|
||||
String name = entry.getProfile().name();
|
||||
if (name != null && !name.isBlank()) return name;
|
||||
}
|
||||
}
|
||||
|
||||
String fallback = waypoint.id().right().orElse(null);
|
||||
if (fallback != null && !fallback.isBlank()) return fallback;
|
||||
return uuid != null ? uuid.toString() : null;
|
||||
}
|
||||
|
||||
static boolean isLocatorTarget(UUID wpUuid, CustomBar bm) {
|
||||
if (bm == null || wpUuid == null || !bm.isLocatorSearchEnabled()) return false;
|
||||
Set<String> targets = bm.getLocatorSearchPlayers();
|
||||
if (targets == null || targets.isEmpty()) return false;
|
||||
return isPlayerInTargets(wpUuid, targets);
|
||||
}
|
||||
|
||||
static boolean isLocatorDimmed(UUID wpUuid, CustomBar bm) {
|
||||
if (bm == null || wpUuid == null || !bm.isLocatorSearchEnabled()) return false;
|
||||
Set<String> targets = bm.getLocatorSearchPlayers();
|
||||
if (targets == null || targets.isEmpty()) return false;
|
||||
return !isPlayerInTargets(wpUuid, targets);
|
||||
}
|
||||
|
||||
static UUID offlineUuid(String name) {
|
||||
if (name == null || name.isBlank()) return new UUID(0L, 0L);
|
||||
String key = "OfflinePlayer:" + name;
|
||||
return UUID.nameUUIDFromBytes(key.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
static boolean isPlayerInTargets(UUID wpUuid, Set<String> targets) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.getConnection() == null) return false;
|
||||
|
||||
for (String raw : targets) {
|
||||
if (raw == null) continue;
|
||||
String t = raw.trim();
|
||||
if (t.isEmpty()) continue;
|
||||
|
||||
if (t.equalsIgnoreCase(wpUuid.toString())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PlayerInfo entry = findEntryByName(mc, t);
|
||||
if (entry != null && entry.getProfile() != null) {
|
||||
if (wpUuid.equals(entry.getProfile().id())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static PlayerInfo findEntryByName(Minecraft mc, String name) {
|
||||
for (PlayerInfo e : mc.getConnection().getOnlinePlayers()) {
|
||||
if (e.getProfile() != null
|
||||
&& e.getProfile().name().equalsIgnoreCase(name)) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static int colorFromUuid(UUID uuid) {
|
||||
if (uuid == null) return 0xFFFFFFFF;
|
||||
return colorFromHash(uuid.hashCode());
|
||||
}
|
||||
|
||||
static int colorFromName(String name) {
|
||||
if (name == null) return 0xFFFFFFFF;
|
||||
return colorFromHash(name.hashCode());
|
||||
}
|
||||
|
||||
static int colorFromHash(int hash) {
|
||||
float hue = (hash & 0xFFFF) / 65535.0f;
|
||||
int rgb = Mth.hsvToRgb(hue, 0.9f, 0.9f);
|
||||
return rgb & 0x00FFFFFF;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 pivosos2007.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.features.gui.hud.nondraggable.impl;
|
||||
|
||||
import dev.loki.lovisual.features.gui.hud.HudRenderUtil;
|
||||
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.CustomBarModel.BarColors;
|
||||
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.CustomBarModel.XpTextInfo;
|
||||
import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines;
|
||||
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
|
||||
import dev.loki.lovisual.render.engine.text.TextRenderer;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.util.Mth;
|
||||
|
||||
import static dev.loki.lovisual.features.gui.hud.nondraggable.impl.CustomBar.*;
|
||||
import static dev.loki.lovisual.features.theme.Theme.theme;
|
||||
|
||||
final class CustomXpBarRenderer {
|
||||
|
||||
private CustomXpBarRenderer() {
|
||||
}
|
||||
|
||||
static void renderXpBarBackground(Renderer2D r2d, Minecraft mc) {
|
||||
if (mc == null || mc.player == null) return;
|
||||
CustomBar bm = CustomBar.get();
|
||||
if (bm == null || !bm.isHudBarEnabled() || !bm.isHotbarXpBarEnabled()) return;
|
||||
|
||||
float progress = Mth.clamp(mc.player.experienceProgress, 0.0f, 1.0f);
|
||||
float scale = (float) mc.getWindow().getGuiScale();
|
||||
float screenW = mc.getWindow().getGuiScaledWidth();
|
||||
float screenH = mc.getWindow().getGuiScaledHeight();
|
||||
|
||||
float baseY = screenH - 22f;
|
||||
|
||||
float x = (screenW - XP_BAR_WIDTH) * 0.5f;
|
||||
float y = baseY - XP_BAR_OFFSET - XP_BAR_HEIGHT;
|
||||
drawVanillaGlassTrack(r2d, x, y, scale, progress, bm, false);
|
||||
}
|
||||
|
||||
static void drawXpBar(Renderer2D r2d, float barX, float barY, float scale, float progress, CustomBar bm) {
|
||||
if (r2d == null || bm == null) return;
|
||||
drawVanillaGlassTrack(r2d, barX, barY, scale, progress, bm, false);
|
||||
}
|
||||
|
||||
static void renderXpBarTexture(GuiGraphicsExtractor ctx, Minecraft mc) {
|
||||
if (ctx == null || mc == null || mc.player == null) return;
|
||||
CustomBar bm = CustomBar.get();
|
||||
if (bm == null || !bm.isHudBarEnabled() || !bm.isHotbarXpBarEnabled()) return;
|
||||
int k = mc.player.getXpNeededForNextLevel();
|
||||
if (k <= 0) return;
|
||||
|
||||
int x = Mth.floor((mc.getWindow().getGuiScaledWidth() - XP_BAR_WIDTH) * 0.5f);
|
||||
int y = Mth.floor(mc.getWindow().getGuiScaledHeight() - 22f - XP_BAR_OFFSET - XP_BAR_HEIGHT);
|
||||
BarColors colors = colors(bm);
|
||||
ctx.blitSprite(LoVisualRenderPipelines.GUI_TEXTURE_LOOKUP, XP_BACKGROUND, x, y, 182, 5,
|
||||
applyBarAlpha(colors.trackTexture(), bm));
|
||||
int progress = (int) (Mth.clamp(mc.player.experienceProgress, 0.0f, 1.0f) * 183.0f);
|
||||
if (progress > 0) {
|
||||
ctx.blitSprite(LoVisualRenderPipelines.GUI_TEXTURE_LOOKUP, XP_PROGRESS, 182, 5, 0, 0, x, y, progress, 5,
|
||||
applyBarAlpha(colors.fillTexture(), bm));
|
||||
}
|
||||
}
|
||||
|
||||
static void drawVanillaGlassTrack(Renderer2D r2d, float barX, float barY, float scale,
|
||||
float progress, CustomBar bm, boolean locator) {
|
||||
if (r2d == null || bm == null) return;
|
||||
|
||||
float x = barX * scale;
|
||||
float y = barY * scale;
|
||||
float w = XP_BAR_WIDTH * scale;
|
||||
float h = XP_BAR_HEIGHT * scale;
|
||||
float radius = XP_BAR_RADIUS * scale;
|
||||
BarColors colors = colors(bm);
|
||||
|
||||
HudRenderUtil.drawLiquidGlass(x, y, w, h, radius, scale, false, BAR_BLUR_ALPHA, BAR_GLASS_ALPHA);
|
||||
|
||||
int trackA = applyBarAlpha(colors.track(), bm);
|
||||
int stroke = applyBarAlpha(colors.stroke(), bm);
|
||||
r2d.roundedRectGradient(x, y, w, h, radius, BAR_SOFTNESS * scale, trackA,
|
||||
HudRenderUtil.scaleAlpha(trackA, 0.76f), 0.0f);
|
||||
|
||||
float fillW = XP_BAR_WIDTH * Mth.clamp(progress, 0.0f, 1.0f) * scale;
|
||||
if (fillW > 0.0f && !locator) {
|
||||
float fillRadius = Math.min(radius, Math.min(fillW, h) * 0.5f);
|
||||
r2d.roundedRectMaskedQuad(
|
||||
x, y, fillW, h,
|
||||
x, y, w, h,
|
||||
radius, BAR_SOFTNESS * scale,
|
||||
colors.fillStart(), colors.fillEnd(), colors.fillEnd(), colors.fillStart()
|
||||
);
|
||||
if (fillRadius > 0.0f) {
|
||||
r2d.roundedRectGradient(x, y, fillW, Math.max(scale, h * 0.42f), fillRadius,
|
||||
BAR_SOFTNESS * scale, HudRenderUtil.scaleAlpha(0xFFFFFFFF, 0.10f),
|
||||
HudRenderUtil.scaleAlpha(0xFFFFFFFF, 0.02f), 90.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (LOCATOR_TICK_COUNT > 0 && locator) {
|
||||
float lineW = Math.max(1.0f, 0.65f * scale);
|
||||
float step = w / (LOCATOR_TICK_COUNT + 1f);
|
||||
int tickColor = HudRenderUtil.scaleAlpha(colors.fillTexture(), 0.36f);
|
||||
for (int i = 1; i <= LOCATOR_TICK_COUNT; i++) {
|
||||
float lx = x + step * i - lineW * 0.5f;
|
||||
r2d.quad(lx, y + scale, lineW, Math.max(scale, h - 2.0f * scale), tickColor);
|
||||
}
|
||||
}
|
||||
|
||||
r2d.roundedRectStroke(x, y, w, h, radius, BAR_SOFTNESS * scale, Math.max(0.65f, 0.72f * scale), stroke);
|
||||
}
|
||||
|
||||
static BarColors colors(CustomBar bm) {
|
||||
|
||||
if (bm != null && bm.isCustomMode()) {
|
||||
int track = HudRenderUtil.scaleAlpha(bm.customBgColor.getArgb(), BAR_TRACK_ALPHA);
|
||||
int fillStart = HudRenderUtil.scaleAlpha(bm.customFillColor.getArgb(), BAR_FILL_ALPHA);
|
||||
int fillEnd = HudRenderUtil.scaleAlpha(bm.customFillGradientColor.getArgb(), BAR_FILL_ALPHA);
|
||||
int stroke = HudRenderUtil.scaleAlpha(HudRenderUtil.mixColor(fillEnd, track, 0.38f), BAR_STROKE_ALPHA);
|
||||
int textureTrack = forceOpaque(HudRenderUtil.mixColor(track, 0xFFFFFFFF, 0.18f));
|
||||
int textureFill = forceOpaque(HudRenderUtil.mixColor(fillStart, fillEnd, 0.48f));
|
||||
int locator = forceOpaque(HudRenderUtil.mixColor(fillEnd, track, 0.16f));
|
||||
return new BarColors(track, fillStart, fillEnd, stroke, textureTrack, textureFill, locator);
|
||||
}
|
||||
|
||||
int window = theme().windowBg();
|
||||
int surface = theme().surface();
|
||||
int accent = theme().accent();
|
||||
int soft = theme().accentSoft();
|
||||
int track = HudRenderUtil.scaleAlpha(HudRenderUtil.mixColor(window, surface, 0.44f), BAR_TRACK_ALPHA);
|
||||
int fillStart = HudRenderUtil.scaleAlpha(HudRenderUtil.mixColor(soft, accent, 0.24f), BAR_FILL_ALPHA);
|
||||
int fillEnd = HudRenderUtil.scaleAlpha(HudRenderUtil.mixColor(accent, theme().textPrimary(), 0.18f), BAR_FILL_ALPHA);
|
||||
int stroke = HudRenderUtil.scaleAlpha(HudRenderUtil.mixColor(theme().windowStroke(), accent, 0.18f), BAR_STROKE_ALPHA);
|
||||
int textureTrack = forceOpaque(HudRenderUtil.mixColor(track, theme().textMuted(), 0.22f));
|
||||
int textureFill = forceOpaque(HudRenderUtil.mixColor(fillStart, fillEnd, 0.50f));
|
||||
int locator = forceOpaque(HudRenderUtil.mixColor(accent, 0xFF1E7A4C, 0.42f));
|
||||
return new BarColors(track, fillStart, fillEnd, stroke, textureTrack, textureFill, locator);
|
||||
}
|
||||
|
||||
static int forceOpaque(int argb) {
|
||||
return 0xFF000000 | (argb & 0x00FFFFFF);
|
||||
}
|
||||
static void renderXpBarText(GuiGraphicsExtractor ctx, Minecraft mc) {
|
||||
if (mc == null || mc.player == null || ctx == null) return;
|
||||
CustomBar bm = CustomBar.get();
|
||||
if (bm == null || !bm.isHudBarEnabled() || !bm.isHotbarXpBarEnabled() || !bm.isHotbarXpBarShowLevel()) return;
|
||||
if (mc.player.experienceLevel <= 0) return;
|
||||
|
||||
Component text = Component.translatable("gui.experience.level", mc.player.experienceLevel);
|
||||
int x = (ctx.guiWidth() - mc.font.width(text)) / 2 + XP_TEXT_OFFSET;
|
||||
int y = ctx.guiHeight() - 24 - 9 - 2 + XP_TEXT_OFFSET;
|
||||
int color = applyBarAlpha(bm.getXpTextArgb(), bm);
|
||||
ctx.text(mc.font, text, x + 1, y, 0xFF000000, false);
|
||||
ctx.text(mc.font, text, x - 1, y, 0xFF000000, false);
|
||||
ctx.text(mc.font, text, x, y + 1, 0xFF000000, false);
|
||||
ctx.text(mc.font, text, x, y - 1, 0xFF000000, false);
|
||||
ctx.text(mc.font, text, x, y, color, false);
|
||||
}
|
||||
|
||||
static void renderPreview(Renderer2D r2d, float x, float y, float scale, float progress) {
|
||||
CustomBar bm = CustomBar.get();
|
||||
if (bm == null) return;
|
||||
drawXpBar(r2d, x, y, scale, progress, bm);
|
||||
}
|
||||
|
||||
static XpTextInfo buildPreviewText(float barX, float barY, float scale, int level) {
|
||||
CustomBar bm = CustomBar.get();
|
||||
if (bm == null) return new XpTextInfo("", 0, 0, 0, (float) XP_TEXT_SCALE * scale);
|
||||
if (level <= 0) return new XpTextInfo("", 0, 0, 0, bm.getXpTextScale() * scale);
|
||||
|
||||
String text = String.valueOf(level);
|
||||
TextRenderer tr = TextRenderer.get();
|
||||
float textScale = bm.getXpTextScale() * scale;
|
||||
tr.begin(textScale, false, false);
|
||||
int textW = (int) Math.ceil(tr.getWidth(text, true));
|
||||
int textH = (int) Math.ceil(tr.getHeight(true));
|
||||
tr.end();
|
||||
|
||||
float barW = XP_BAR_WIDTH * scale;
|
||||
int tx = Math.round(barX * scale + (barW - textW) * 0.5f) + Math.round(XP_TEXT_OFFSET * scale);
|
||||
int ty = Math.round(barY * scale - textH - XP_TEXT_OFFSET * scale) + Math.round(XP_TEXT_OFFSET * scale);
|
||||
|
||||
int color = applyBarAlpha(bm.getXpTextArgb(), bm);
|
||||
return new XpTextInfo(text, tx, ty, color, textScale);
|
||||
}
|
||||
|
||||
static int applyBarAlpha(int argb, CustomBar bm) {
|
||||
if (bm == null) return argb;
|
||||
int a = (argb >>> 24) & 0xFF;
|
||||
int na = (int) (a * bm.getHudOverlayAlphaFactor());
|
||||
return (argb & 0x00FFFFFF) | ((na & 0xFF) << 24);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
package dev.loki.lovisual.features.module.modules.misc;
|
||||
|
||||
import dev.loki.lovisual.util.time.GameClock;
|
||||
|
||||
import net.fabricmc.fabric.api.event.player.AttackEntityCallback;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
|
|
@ -268,7 +270,7 @@ public class DefineTarget extends Module {
|
|||
}
|
||||
|
||||
private void notifyBlock(Entity target) {
|
||||
long now = System.currentTimeMillis();
|
||||
long now = GameClock.millis();
|
||||
if (now - lastBlockToast < 600) return;
|
||||
lastBlockToast = now;
|
||||
String name = target.getName() != null ? target.getName().getString() : target.getType().toString();
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
package dev.loki.lovisual.features.module.modules.player;
|
||||
|
||||
import dev.loki.lovisual.util.time.GameClock;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
|
@ -66,13 +68,13 @@ public class AutoTool extends Module {
|
|||
}
|
||||
InventorySwap.INSTANCE.selectHotbar(bestSlot);
|
||||
}
|
||||
lastBreakMs = System.currentTimeMillis();
|
||||
lastBreakMs = GameClock.millis();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (originalSlot >= 0) {
|
||||
long now = System.currentTimeMillis();
|
||||
long now = GameClock.millis();
|
||||
if (now - lastBreakMs >= restoreDelayMs.get()) {
|
||||
InventorySwap.INSTANCE.selectHotbar(originalSlot);
|
||||
originalSlot = -1;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
package dev.loki.lovisual.features.module.modules.player;
|
||||
|
||||
import dev.loki.lovisual.util.time.GameClock;
|
||||
|
||||
import dev.loki.lovisual.config.common.CommonSettingSchemas;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
|
|
@ -184,7 +186,7 @@ public final class NoDelay extends Module {
|
|||
return false;
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long now = GameClock.millis();
|
||||
if (ctx.target == UseTarget.THROWABLE) {
|
||||
if (pressStartThrow == 0L) {
|
||||
pressStartThrow = now;
|
||||
|
|
|
|||
|
|
@ -28,8 +28,6 @@ import java.util.List;
|
|||
)
|
||||
public class ShitDropper extends Module {
|
||||
|
||||
private static final Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
private static final String SETTING_TRASH_ITEM_IDS = "trash_item_ids";
|
||||
private static final String SETTING_MAX_TRASH_SLOTS = "max_trash_slots";
|
||||
private static final String SETTING_STACKS_PER_TICK = "stacks_per_tick";
|
||||
|
|
@ -79,6 +77,7 @@ public class ShitDropper extends Module {
|
|||
|
||||
@Override
|
||||
public void onTick() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || mc.player == null || mc.gameMode == null) return;
|
||||
|
||||
var handler = mc.player.containerMenu;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,287 @@
|
|||
package dev.loki.lovisual.features.module.modules.visuals;
|
||||
|
||||
import dev.loki.lovisual.config.values.*;
|
||||
import dev.loki.lovisual.features.module.Module;
|
||||
import dev.loki.lovisual.features.module.ModuleCategory;
|
||||
import dev.loki.lovisual.features.module.ModuleInfo;
|
||||
import dev.loki.lovisual.features.module.WorldPhase;
|
||||
import dev.loki.lovisual.features.theme.Theme;
|
||||
import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines;
|
||||
import dev.loki.lovisual.render.engine.renderer.Renderer3D;
|
||||
import dev.loki.lovisual.render.engine.uniform.MeshBuilder;
|
||||
import dev.loki.lovisual.util.time.GameClock;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
//todo Description
|
||||
@ModuleInfo(id = "backsword", displayName = "BackSword", category = ModuleCategory.VISUALS)
|
||||
public class BackSword extends Module {
|
||||
|
||||
// базовый подъём катаны, чтобы лежала на спине, а не у ног
|
||||
private static final float BACK_SWORD_LIFT = 0.85f;
|
||||
|
||||
private static final String SETTING_TARGET = "target";
|
||||
private static final String SETTING_COLOR_MODE = "color_mode";
|
||||
private static final String SETTING_CUSTOM_COLOR = "custom_color";
|
||||
private static final String SETTING_GLOW = "glow";
|
||||
private static final String SETTING_GLOW_LEVEL = "glow_level";
|
||||
private static final String SETTING_FILL_ALPHA = "fill_alpha";
|
||||
private static final String SETTING_OUTLINE_ALPHA = "outline_alpha";
|
||||
private static final String SETTING_OFFSET_Y = "offset_y";
|
||||
private static final String SETTING_OFFSET_Z = "offset_z";
|
||||
|
||||
private final ModeValue target = modeSetting(
|
||||
"backSwordTarget", SETTING_TARGET, "Self", "Self", "All Players", "Self and Others");
|
||||
private final ModeValue colorMode = modeSetting(
|
||||
"backSwordColorMode", SETTING_COLOR_MODE, "Theme", "Theme", "Custom", "Rainbow");
|
||||
private final RGBColorValue customColor =
|
||||
visibleWhen(colorNoAlpha("backSwordCustomColor", SETTING_CUSTOM_COLOR, "#E91E63"), this::isCustomColor);
|
||||
private final BooleanValue glow =
|
||||
bool("backSwordGlow", SETTING_GLOW, true);
|
||||
private final NumberValue<Float> glowLevel =
|
||||
visibleWhen(num("backSwordGlowLevel", SETTING_GLOW_LEVEL, 50.0f, 0.0f, 100.0f), glow::get);
|
||||
private final NumberValue<Float> fillAlpha =
|
||||
num("backSwordFillAlpha", SETTING_FILL_ALPHA, 20.0f, 0.0f, 100.0f);
|
||||
private final NumberValue<Float> outlineAlpha =
|
||||
num("backSwordOutlineAlpha", SETTING_OUTLINE_ALPHA, 85.0f, 0.0f, 100.0f);
|
||||
private final NumberValue<Float> offsetY =
|
||||
num("backSwordOffsetY", SETTING_OFFSET_Y, 0.1f, -0.5f, 0.7f);
|
||||
private final NumberValue<Float> offsetZ =
|
||||
num("backSwordOffsetZ", SETTING_OFFSET_Z, 0.28f, 0.0f, 0.6f);
|
||||
|
||||
@Override
|
||||
public WorldPhase getWorldPhase() {
|
||||
return WorldPhase.END_MAIN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || mc.level == null || mc.player == null || renderer == null) return;
|
||||
|
||||
boolean firstPerson = mc.options.getCameraType().isFirstPerson();
|
||||
boolean targetSelf = "Self".equals(target.get()) || "Self and Others".equals(target.get());
|
||||
boolean targetOthers = "All Players".equals(target.get()) || "Self and Others".equals(target.get());
|
||||
|
||||
if (!firstPerson && targetSelf && mc.player.isAlive()) {
|
||||
renderSword(renderer, mc.player, tickDelta);
|
||||
}
|
||||
if (targetOthers) {
|
||||
for (Player player : mc.level.players()) {
|
||||
if (player == mc.player || !player.isAlive()) continue;
|
||||
renderSword(renderer, player, tickDelta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void renderSword(Renderer3D renderer, Player player, float tickDelta) {
|
||||
Vec3 pos = player.getPosition(tickDelta);
|
||||
float bodyYaw = Mth.rotLerp(tickDelta, player.yBodyRotO, player.yBodyRot);
|
||||
float offY = offsetY.get();
|
||||
float offZ = offsetZ.get();
|
||||
|
||||
MeshBuilder fill = renderer.batch(LoVisualRenderPipelines.WORLD_COLORED_DEPTH, Renderer3D.DepthMode.MAIN);
|
||||
MeshBuilder glowMesh = null;
|
||||
if (glow.get()) {
|
||||
glowMesh = renderer.batch(LoVisualRenderPipelines.WORLD_COLORED_ADDITIVE_DEPTH, Renderer3D.DepthMode.PRE_DEPTH);
|
||||
}
|
||||
if (fill == null) return;
|
||||
|
||||
int fillA = Math.round(fillAlpha.get() / 100f * 255f);
|
||||
int outA = Math.round(outlineAlpha.get() / 100f * 255f);
|
||||
|
||||
Shade fillShade = shade(fillA);
|
||||
Shade edgeShade = edgeShade(outA);
|
||||
|
||||
drawKatana(fill, pos, bodyYaw, offY, offZ, fillShade);
|
||||
drawKatanaEdge(fill, pos, bodyYaw, offY, offZ, edgeShade, 0.004f);
|
||||
|
||||
if (glowMesh != null) {
|
||||
float glowBase = glowLevel.get() / 100f;
|
||||
float pulse = (float) (Math.sin(GameClock.millis() / 700.0) * 0.12 + 0.88);
|
||||
glowBase *= pulse;
|
||||
int layers = 12;
|
||||
float maxEx = 0.045f;
|
||||
for (int i = 0; i < layers; i++) {
|
||||
float t = (float) i / (layers - 1);
|
||||
float falloff = (1f - t) * (1f - t);
|
||||
int ga = (int) (glowBase * falloff * 180f);
|
||||
if (ga <= 1) continue;
|
||||
drawKatanaGlow(glowMesh, pos, bodyYaw, offY, offZ, shade(ga), t * maxEx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Shade shade(int alpha) {
|
||||
return switch (colorMode.get()) {
|
||||
case "Custom" -> {
|
||||
int c = customColor.getArgb();
|
||||
yield fixedShade(c, alpha);
|
||||
}
|
||||
case "Rainbow" -> (lx, ly, lz) -> {
|
||||
float hue = ((ly + 0.6f) * 0.8f + (GameClock.millis() / 1000f) * 0.15f) % 1.0f;
|
||||
int[] rgb = hsvToRgb(hue, 0.85f, 1.0f);
|
||||
return argb(rgb[0], rgb[1], rgb[2], alpha);
|
||||
};
|
||||
default -> {
|
||||
yield fixedShade(Theme.theme().accent(), alpha);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Shade edgeShade(int alpha) {
|
||||
if ("Rainbow".equals(colorMode.get())) return shade(alpha);
|
||||
int c = "Custom".equals(colorMode.get()) ? customColor.getArgb() : Theme.theme().accent();
|
||||
int r = Math.min(((c >>> 16) & 0xFF) + 40, 255);
|
||||
int g = Math.min(((c >>> 8) & 0xFF) + 40, 255);
|
||||
int b = Math.min((c & 0xFF) + 40, 255);
|
||||
return fixedShade(r, g, b, alpha);
|
||||
}
|
||||
|
||||
private static Shade fixedShade(int argb, int alpha) {
|
||||
int r = (argb >>> 16) & 0xFF, g = (argb >>> 8) & 0xFF, b = argb & 0xFF;
|
||||
return (lx, ly, lz) -> argb(r, g, b, alpha);
|
||||
}
|
||||
|
||||
private static Shade fixedShade(int r, int g, int b, int alpha) {
|
||||
return (lx, ly, lz) -> argb(r, g, b, alpha);
|
||||
}
|
||||
|
||||
private boolean isCustomColor() {
|
||||
return "Custom".equals(colorMode.get());
|
||||
}
|
||||
|
||||
// ===================== Геометрия катаны =====================
|
||||
|
||||
private static void drawKatana(MeshBuilder mesh, Vec3 pos, float bodyYaw, float offY, float offZ, Shade shade) {
|
||||
drawBox(mesh, pos, bodyYaw, offY, offZ, shade, -0.028f, -0.42f, -0.016f, 0.028f, 0.0f, 0.016f);
|
||||
drawBox(mesh, pos, bodyYaw, offY, offZ, shade, -0.032f, -0.46f, -0.018f, 0.032f, -0.42f, 0.018f);
|
||||
drawBox(mesh, pos, bodyYaw, offY, offZ, shade, -0.085f, -0.005f, -0.034f, 0.085f, 0.005f, 0.034f);
|
||||
drawBlade(mesh, pos, bodyYaw, offY, offZ, shade);
|
||||
}
|
||||
|
||||
private static void drawBlade(MeshBuilder mesh, Vec3 pos, float bodyYaw, float offY, float offZ, Shade shade) {
|
||||
float bw = 0.022f, bBack = 0.006f, bEdge = 0.004f;
|
||||
float bx0 = -bw, bx1 = bw, by = 0.005f;
|
||||
float bzBack = bBack, bzEdge = -bEdge;
|
||||
|
||||
float mw = 0.015f, mBack = 0.004f, mEdge = 0.002f;
|
||||
float mx0 = -mw, mx1 = mw, my = 0.50f;
|
||||
float mzBack = mBack, mzEdge = -mEdge;
|
||||
|
||||
float tx = 0f, ty = 1.02f, tz = 0f;
|
||||
|
||||
quad(mesh, pos, bodyYaw, offY, offZ, shade, bx0, by, bzBack, bx1, by, bzBack, mx1, my, mzBack, mx0, my, mzBack);
|
||||
quad(mesh, pos, bodyYaw, offY, offZ, shade, bx1, by, bzBack, bx1, by, bzEdge, mx1, my, mzEdge, mx1, my, mzBack);
|
||||
quad(mesh, pos, bodyYaw, offY, offZ, shade, bx0, by, bzBack, bx0, by, bzEdge, mx0, my, mzEdge, mx0, my, mzBack);
|
||||
quad(mesh, pos, bodyYaw, offY, offZ, shade, bx0, by, bzEdge, bx1, by, bzEdge, mx1, my, mzEdge, mx0, my, mzEdge);
|
||||
|
||||
quad(mesh, pos, bodyYaw, offY, offZ, shade, mx0, my, mzBack, mx1, my, mzBack, tx, ty, tz, tx, ty, tz);
|
||||
quad(mesh, pos, bodyYaw, offY, offZ, shade, mx1, my, mzBack, mx1, my, mzEdge, tx, ty, tz, tx, ty, tz);
|
||||
quad(mesh, pos, bodyYaw, offY, offZ, shade, mx0, my, mzBack, mx0, my, mzEdge, tx, ty, tz, tx, ty, tz);
|
||||
quad(mesh, pos, bodyYaw, offY, offZ, shade, mx0, my, mzEdge, mx1, my, mzEdge, tx, ty, tz, tx, ty, tz);
|
||||
}
|
||||
|
||||
private static void drawKatanaEdge(MeshBuilder mesh, Vec3 pos, float bodyYaw, float offY, float offZ, Shade shade, float e) {
|
||||
drawBox(mesh, pos, bodyYaw, offY, offZ, shade, -0.028f - e, -0.42f - e, -0.016f - e, 0.028f + e, 0.0f + e, 0.016f + e);
|
||||
drawBox(mesh, pos, bodyYaw, offY, offZ, shade, -0.032f - e, -0.46f - e, -0.018f - e, 0.032f + e, -0.42f + e, 0.018f + e);
|
||||
drawBox(mesh, pos, bodyYaw, offY, offZ, shade, -0.085f - e, -0.005f - e, -0.034f - e, 0.085f + e, 0.005f + e, 0.034f + e);
|
||||
drawBlade(mesh, pos, bodyYaw, offY, offZ, shade);
|
||||
}
|
||||
|
||||
private static void drawKatanaGlow(MeshBuilder mesh, Vec3 pos, float bodyYaw, float offY, float offZ, Shade shade, float ex) {
|
||||
drawBox(mesh, pos, bodyYaw, offY, offZ, shade, -0.028f - ex, -0.42f - ex, -0.016f - ex, 0.028f + ex, 0.0f + ex, 0.016f + ex);
|
||||
drawBox(mesh, pos, bodyYaw, offY, offZ, shade, -0.032f - ex, -0.46f - ex, -0.018f - ex, 0.032f + ex, -0.42f + ex, 0.018f + ex);
|
||||
drawBox(mesh, pos, bodyYaw, offY, offZ, shade, -0.085f - ex, -0.005f - ex, -0.034f - ex, 0.085f + ex, 0.005f + ex, 0.034f + ex);
|
||||
|
||||
float bw = 0.022f + ex, bBack = 0.006f + ex, bEdge = 0.004f + ex;
|
||||
float mw = 0.015f + ex, mBack = 0.004f + ex, mEdge = 0.002f + ex;
|
||||
float bx0 = -bw, bx1 = bw, by = 0.005f - ex;
|
||||
float mx0 = -mw, mx1 = mw, my = 0.50f;
|
||||
float mzBack = mBack, mzEdge = -mEdge;
|
||||
float bzBack = bBack, bzEdge = -bEdge;
|
||||
float tx = 0f, ty = 1.02f + ex, tz = 0f;
|
||||
|
||||
quad(mesh, pos, bodyYaw, offY, offZ, shade, bx0, by, bzBack, bx1, by, bzBack, mx1, my, mzBack, mx0, my, mzBack);
|
||||
quad(mesh, pos, bodyYaw, offY, offZ, shade, bx1, by, bzBack, bx1, by, bzEdge, mx1, my, mzEdge, mx1, my, mzBack);
|
||||
quad(mesh, pos, bodyYaw, offY, offZ, shade, bx0, by, bzBack, bx0, by, bzEdge, mx0, my, mzEdge, mx0, my, mzBack);
|
||||
quad(mesh, pos, bodyYaw, offY, offZ, shade, bx0, by, bzEdge, bx1, by, bzEdge, mx1, my, mzEdge, mx0, my, mzEdge);
|
||||
quad(mesh, pos, bodyYaw, offY, offZ, shade, mx0, my, mzBack, mx1, my, mzBack, tx, ty, tz, tx, ty, tz);
|
||||
quad(mesh, pos, bodyYaw, offY, offZ, shade, mx1, my, mzBack, mx1, my, mzEdge, tx, ty, tz, tx, ty, tz);
|
||||
quad(mesh, pos, bodyYaw, offY, offZ, shade, mx0, my, mzBack, mx0, my, mzEdge, tx, ty, tz, tx, ty, tz);
|
||||
quad(mesh, pos, bodyYaw, offY, offZ, shade, mx0, my, mzEdge, mx1, my, mzEdge, tx, ty, tz, tx, ty, tz);
|
||||
}
|
||||
|
||||
private static void drawBox(MeshBuilder mesh, Vec3 pos, float bodyYaw, float offY, float offZ, Shade shade,
|
||||
float x0, float y0, float z0, float x1, float y1, float z1) {
|
||||
quad(mesh, pos, bodyYaw, offY, offZ, shade, x0, y0, z0, x1, y0, z0, x1, y0, z1, x0, y0, z1);
|
||||
quad(mesh, pos, bodyYaw, offY, offZ, shade, x0, y1, z0, x0, y1, z1, x1, y1, z1, x1, y1, z0);
|
||||
quad(mesh, pos, bodyYaw, offY, offZ, shade, x0, y0, z0, x0, y1, z0, x1, y1, z0, x1, y0, z0);
|
||||
quad(mesh, pos, bodyYaw, offY, offZ, shade, x0, y0, z1, x1, y0, z1, x1, y1, z1, x0, y1, z1);
|
||||
quad(mesh, pos, bodyYaw, offY, offZ, shade, x0, y0, z0, x0, y0, z1, x0, y1, z1, x0, y1, z0);
|
||||
quad(mesh, pos, bodyYaw, offY, offZ, shade, x1, y0, z0, x1, y1, z0, x1, y1, z1, x1, y0, z1);
|
||||
}
|
||||
|
||||
private static void quad(MeshBuilder mesh, Vec3 pos, float bodyYaw, float offY, float offZ, Shade shade,
|
||||
float x0, float y0, float z0, float x1, float y1, float z1,
|
||||
float x2, float y2, float z2, float x3, float y3, float z3) {
|
||||
mesh.ensureQuadCapacity();
|
||||
int i0 = addVertex(mesh, pos, bodyYaw, offY, offZ, x0, y0, z0, shade.at(x0, y0, z0));
|
||||
int i1 = addVertex(mesh, pos, bodyYaw, offY, offZ, x1, y1, z1, shade.at(x1, y1, z1));
|
||||
int i2 = addVertex(mesh, pos, bodyYaw, offY, offZ, x2, y2, z2, shade.at(x2, y2, z2));
|
||||
int i3 = addVertex(mesh, pos, bodyYaw, offY, offZ, x3, y3, z3, shade.at(x3, y3, z3));
|
||||
mesh.quad(i0, i1, i2, i3);
|
||||
}
|
||||
|
||||
private static int addVertex(MeshBuilder mesh, Vec3 pos, float bodyYaw, float offY, float offZ,
|
||||
float lx, float ly, float lz, int argb) {
|
||||
Vec3 w = katanaVertex(lx, ly, lz, pos, bodyYaw, offY, offZ);
|
||||
mesh.vec3(w.x, w.y, w.z)
|
||||
.color((argb >>> 16) & 0xFF, (argb >>> 8) & 0xFF, argb & 0xFF, (argb >>> 24) & 0xFF);
|
||||
return mesh.next();
|
||||
}
|
||||
|
||||
private static Vec3 katanaVertex(float lx, float ly, float lz, Vec3 pos, float bodyYaw, float offY, float offZ) {
|
||||
float x = lx * 1.15f, y = ly * 1.15f, z = lz * 1.15f;
|
||||
float c45 = 0.7071067812f, s45 = -0.7071067812f;
|
||||
float rzx = x * c45 - y * s45;
|
||||
float rzy = x * s45 + y * c45;
|
||||
float c3 = 0.9986295347f, s3 = -0.0523359562f;
|
||||
float rxy = rzy * c3 - z * s3;
|
||||
float rxz = rzy * s3 + z * c3;
|
||||
float ox = rzx - 0.15f, oy = rxy + offY + BACK_SWORD_LIFT, oz = rxz + offZ;
|
||||
float yaw = (float) Math.toRadians(180f - bodyYaw);
|
||||
float cy = (float) Math.cos(yaw), sy = (float) Math.sin(yaw);
|
||||
float wx = ox * cy + oz * sy;
|
||||
float wz = -ox * sy + oz * cy;
|
||||
return new Vec3(pos.x + wx, pos.y + oy, pos.z + wz);
|
||||
}
|
||||
|
||||
private static int argb(int r, int g, int b, int a) {
|
||||
return (Mth.clamp(a, 0, 255) << 24) | (Mth.clamp(r, 0, 255) << 16) | (Mth.clamp(g, 0, 255) << 8) | Mth.clamp(b, 0, 255);
|
||||
}
|
||||
|
||||
private static int[] hsvToRgb(float h, float s, float v) {
|
||||
int i = (int) (h * 6f);
|
||||
float f = h * 6f - i;
|
||||
float p = v * (1 - s);
|
||||
float q = v * (1 - f * s);
|
||||
float t = v * (1 - (1 - f) * s);
|
||||
float r, g, b;
|
||||
switch (i % 6) {
|
||||
case 0 -> { r = v; g = t; b = p; }
|
||||
case 1 -> { r = q; g = v; b = p; }
|
||||
case 2 -> { r = p; g = v; b = t; }
|
||||
case 3 -> { r = p; g = q; b = v; }
|
||||
case 4 -> { r = t; g = p; b = v; }
|
||||
default -> { r = v; g = p; b = q; }
|
||||
}
|
||||
return new int[]{(int) (r * 255), (int) (g * 255), (int) (b * 255)};
|
||||
}
|
||||
|
||||
private interface Shade {
|
||||
int at(float lx, float ly, float lz);
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ package dev.loki.lovisual.features.module.modules.visuals;
|
|||
|
||||
import dev.loki.lovisual.features.theme.Theme;
|
||||
import com.mojang.blaze3d.GpuFormat;
|
||||
import com.mojang.blaze3d.pipeline.RenderPipeline;
|
||||
import com.mojang.blaze3d.pipeline.TextureTarget;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.textures.AddressMode;
|
||||
|
|
@ -39,6 +40,8 @@ import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines;
|
|||
import dev.loki.lovisual.render.engine.postprocess.PostProcessManager;
|
||||
import dev.loki.lovisual.render.engine.postprocess.PostProcessPass;
|
||||
import dev.loki.lovisual.render.engine.renderer.FullScreenRenderer;
|
||||
import dev.loki.lovisual.render.engine.uniform.impl.HandBlendUniforms;
|
||||
import dev.loki.lovisual.render.engine.uniform.impl.HandChamsUniforms;
|
||||
import dev.loki.lovisual.render.engine.uniform.impl.HandGlassUniforms;
|
||||
import dev.loki.lovisual.render.engine.uniform.impl.HandMetallicUniforms;
|
||||
import dev.loki.lovisual.render.engine.uniform.impl.HandSmokeUniforms;
|
||||
|
|
@ -81,9 +84,40 @@ public class Chams extends Module {
|
|||
private static final String SETTING_METALLIC_BRUSHED_LINES = "metallic_brushed_lines";
|
||||
private static final String SETTING_METALLIC_FLAKES = "metallic_flakes";
|
||||
private static final String SETTING_METALLIC_PRISM = "metallic_prism";
|
||||
private static final String SETTING_DUAL_SHADER = "dual_shader";
|
||||
private static final String SETTING_SHADER_1 = "shader1";
|
||||
private static final String SETTING_SHADER_2 = "shader2";
|
||||
private static final String SETTING_BLEND_MODE = "blend_mode";
|
||||
private static final String SETTING_BLEND_INTENSITY = "blend_intensity";
|
||||
private static final String SETTING_SHADER_1_STRENGTH = "shader1_strength";
|
||||
private static final String SETTING_SHADER_2_STRENGTH = "shader2_strength";
|
||||
private static final String SETTING_SNOW_COLOR = "snow_color";
|
||||
private static final String SETTING_SNOW_INTENSITY = "snow_intensity";
|
||||
private static final String SETTING_SNOW_SPEED = "snow_speed";
|
||||
private static final String SETTING_CHROMA_SPEED = "chroma_speed";
|
||||
private static final String SETTING_CHROMA_SATURATION = "chroma_saturation";
|
||||
private static final String SETTING_CHROMA_BRIGHTNESS = "chroma_brightness";
|
||||
private static final String SETTING_SOLID_COLOR1 = "solid_color1";
|
||||
private static final String SETTING_SOLID_COLOR2 = "solid_color2";
|
||||
private static final String SETTING_SOLID_SPEED = "solid_speed";
|
||||
private static final String SETTING_STRIPES_COLOR1 = "stripes_color1";
|
||||
private static final String SETTING_STRIPES_COLOR2 = "stripes_color2";
|
||||
private static final String SETTING_STRIPES_WIDTH = "stripes_width";
|
||||
private static final String SETTING_STRIPES_SPEED = "stripes_speed";
|
||||
private static final String SETTING_GLOW_SHADER_FILL = "glow_shader_fill";
|
||||
private static final String SETTING_GLOW_SHADER_RADIUS = "glow_shader_radius";
|
||||
private static final String SETTING_GLOW_SHADER_POWER = "glow_shader_power";
|
||||
private static final String SETTING_GLOW_SHADER_DISPERSION = "glow_shader_dispersion";
|
||||
private static final String SETTING_GLOW_SHADER_COLOR = "glow_shader_color";
|
||||
private static final String SETTING_BALATRO_COLOR1 = "balatro_color1";
|
||||
private static final String SETTING_BALATRO_COLOR2 = "balatro_color2";
|
||||
private static final String SETTING_BALATRO_COLOR3 = "balatro_color3";
|
||||
private static final String SETTING_BALATRO_CONTRAST = "balatro_contrast";
|
||||
private static final String SETTING_BALATRO_LIGHTING = "balatro_lighting";
|
||||
private static final String SETTING_HIDE_HAND = "hide_hand";
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final ModeValue mode =
|
||||
modeSetting("handChamsMode", SETTING_MODE, "Smoke", "Smoke", "Metallic", "Glass");
|
||||
modeSetting("handChamsMode", SETTING_MODE, "Smoke", "Smoke", "Metallic", "Glass", "Snow", "Chroma", "Solid", "Stripes", "Glow", "Balatro");
|
||||
|
||||
private final BooleanValue hands =
|
||||
bool("chamsHands", SETTING_HANDS, true);
|
||||
|
|
@ -147,8 +181,72 @@ public class Chams extends Module {
|
|||
visibleWhen(num("handChamsMetallicFlakes", SETTING_METALLIC_FLAKES, 0.25f, 0.0f, 1.0f), this::isMetallicMode);
|
||||
private final NumberValue<Float> metallicPrism =
|
||||
visibleWhen(num("handChamsMetallicPrism", SETTING_METALLIC_PRISM, 0.18f, 0.0f, 1.0f), this::isMetallicMode);
|
||||
private final BooleanValue dualShader =
|
||||
bool("chamsDualShader", SETTING_DUAL_SHADER, false);
|
||||
private final ModeValue shader1 =
|
||||
visibleWhen(modeSetting("chamsShader1", SETTING_SHADER_1, "Glow", "Snow", "Chroma", "Solid", "Stripes", "Glow", "Balatro"), dualShader::get);
|
||||
private final ModeValue shader2 =
|
||||
visibleWhen(modeSetting("chamsShader2", SETTING_SHADER_2, "Chroma", "Snow", "Chroma", "Solid", "Stripes", "Glow", "Balatro"), dualShader::get);
|
||||
private final ModeValue blendMode =
|
||||
visibleWhen(modeSetting("chamsBlendMode", SETTING_BLEND_MODE, "Add", "Mix", "Add", "Multiply", "Screen", "Overlay", "Difference"), dualShader::get);
|
||||
private final NumberValue<Float> blendIntensity =
|
||||
visibleWhen(num("chamsBlendIntensity", SETTING_BLEND_INTENSITY, 1.0f, 0.0f, 1.0f), dualShader::get);
|
||||
private final NumberValue<Float> shader1Strength =
|
||||
visibleWhen(num("chamsShader1Strength", SETTING_SHADER_1_STRENGTH, 1.0f, 0.0f, 1.0f), dualShader::get);
|
||||
private final NumberValue<Float> shader2Strength =
|
||||
visibleWhen(num("chamsShader2Strength", SETTING_SHADER_2_STRENGTH, 1.0f, 0.0f, 1.0f), dualShader::get);
|
||||
private final RGBColorValue snowColor =
|
||||
visibleWhen(colorNoAlpha("chamsSnowColor", SETTING_SNOW_COLOR, "#FFFFFF"), this::isSnowMode);
|
||||
private final NumberValue<Float> snowIntensity =
|
||||
visibleWhen(num("chamsSnowIntensity", SETTING_SNOW_INTENSITY, 1.0f, 0.1f, 3.0f), this::isSnowMode);
|
||||
private final NumberValue<Float> snowSpeed =
|
||||
visibleWhen(num("chamsSnowSpeed", SETTING_SNOW_SPEED, 1.0f, 0.1f, 2.0f), this::isSnowMode);
|
||||
private final NumberValue<Float> chromaSpeed =
|
||||
visibleWhen(num("chamsChromaSpeed", SETTING_CHROMA_SPEED, 0.3f, 0.05f, 1.0f), this::isChromaMode);
|
||||
private final NumberValue<Float> chromaSaturation =
|
||||
visibleWhen(num("chamsChromaSaturation", SETTING_CHROMA_SATURATION, 0.5f, 0.1f, 1.0f), this::isChromaMode);
|
||||
private final NumberValue<Float> chromaBrightness =
|
||||
visibleWhen(num("chamsChromaBrightness", SETTING_CHROMA_BRIGHTNESS, 1.0f, 0.5f, 1.5f), this::isChromaMode);
|
||||
private final RGBColorValue solidColor1 =
|
||||
visibleWhen(colorNoAlpha("chamsSolidColor1", SETTING_SOLID_COLOR1, "#E91E63"), this::isSolidMode);
|
||||
private final RGBColorValue solidColor2 =
|
||||
visibleWhen(colorNoAlpha("chamsSolidColor2", SETTING_SOLID_COLOR2, "#9B59B6"), this::isSolidMode);
|
||||
private final NumberValue<Float> solidSpeed =
|
||||
visibleWhen(num("chamsSolidSpeed", SETTING_SOLID_SPEED, 0.5f, 0.1f, 1.0f), this::isSolidMode);
|
||||
private final RGBColorValue stripesColor1 =
|
||||
visibleWhen(colorNoAlpha("chamsStripesColor1", SETTING_STRIPES_COLOR1, "#778889"), this::isStripesMode);
|
||||
private final RGBColorValue stripesColor2 =
|
||||
visibleWhen(colorNoAlpha("chamsStripesColor2", SETTING_STRIPES_COLOR2, "#5A6566"), this::isStripesMode);
|
||||
private final NumberValue<Float> stripesWidth =
|
||||
visibleWhen(num("chamsStripesWidth", SETTING_STRIPES_WIDTH, 0.3f, 0.05f, 0.5f), this::isStripesMode);
|
||||
private final NumberValue<Float> stripesSpeed =
|
||||
visibleWhen(num("chamsStripesSpeed", SETTING_STRIPES_SPEED, 0.3f, 0.1f, 2.0f), this::isStripesMode);
|
||||
private final BooleanValue glowShaderFill =
|
||||
visibleWhen(bool("chamsGlowShaderFill", SETTING_GLOW_SHADER_FILL, true), this::isGlowShaderMode);
|
||||
private final NumberValue<Float> glowShaderRadius =
|
||||
visibleWhen(num("chamsGlowShaderRadius", SETTING_GLOW_SHADER_RADIUS, 0.04f, 0.01f, 0.2f), this::isGlowShaderMode);
|
||||
private final NumberValue<Float> glowShaderPower =
|
||||
visibleWhen(num("chamsGlowShaderPower", SETTING_GLOW_SHADER_POWER, 0.75f, 0.1f, 2.0f), this::isGlowShaderMode);
|
||||
private final NumberValue<Float> glowShaderDispersion =
|
||||
visibleWhen(num("chamsGlowShaderDispersion", SETTING_GLOW_SHADER_DISPERSION, 5.0f, 1.0f, 10.0f), this::isGlowShaderMode);
|
||||
private final RGBColorValue glowShaderColor =
|
||||
visibleWhen(colorNoAlpha("chamsGlowShaderColor", SETTING_GLOW_SHADER_COLOR, "#3498DB"), this::isGlowShaderMode);
|
||||
private final RGBColorValue balatroColor1 =
|
||||
visibleWhen(colorNoAlpha("chamsBalatroColor1", SETTING_BALATRO_COLOR1, "#DE443B"), this::isBalatroMode);
|
||||
private final RGBColorValue balatroColor2 =
|
||||
visibleWhen(colorNoAlpha("chamsBalatroColor2", SETTING_BALATRO_COLOR2, "#006BB4"), this::isBalatroMode);
|
||||
private final RGBColorValue balatroColor3 =
|
||||
visibleWhen(colorNoAlpha("chamsBalatroColor3", SETTING_BALATRO_COLOR3, "#162325"), this::isBalatroMode);
|
||||
private final NumberValue<Float> balatroContrast =
|
||||
visibleWhen(num("chamsBalatroContrast", SETTING_BALATRO_CONTRAST, 0.6f, 0.1f, 1.0f), this::isBalatroMode);
|
||||
private final NumberValue<Float> balatroLighting =
|
||||
visibleWhen(num("chamsBalatroLighting", SETTING_BALATRO_LIGHTING, 0.6f, 0.0f, 1.0f), this::isBalatroMode);
|
||||
private final BooleanValue hideHand =
|
||||
bool("chamsHideHand", SETTING_HIDE_HAND, false);
|
||||
private final PostProcessPass handsPass = new HandsPass();
|
||||
private TextureTarget handMask;
|
||||
private TextureTarget dualA;
|
||||
private TextureTarget dualB;
|
||||
private GpuSampler handMaskSampler;
|
||||
private int bufferW = -1;
|
||||
private int bufferH = -1;
|
||||
|
|
@ -475,6 +573,43 @@ public class Chams extends Module {
|
|||
return "Glass".equals(mode.get());
|
||||
}
|
||||
|
||||
private boolean isSnowMode() {
|
||||
return "Snow".equals(mode.get());
|
||||
}
|
||||
|
||||
private boolean isChromaMode() {
|
||||
return "Chroma".equals(mode.get());
|
||||
}
|
||||
|
||||
private boolean isSolidMode() {
|
||||
return "Solid".equals(mode.get());
|
||||
}
|
||||
|
||||
private boolean isStripesMode() {
|
||||
return "Stripes".equals(mode.get());
|
||||
}
|
||||
|
||||
private boolean isGlowShaderMode() {
|
||||
return "Glow".equals(mode.get());
|
||||
}
|
||||
|
||||
private boolean isBalatroMode() {
|
||||
return "Balatro".equals(mode.get());
|
||||
}
|
||||
|
||||
private void ensureDualBuffers() {
|
||||
int w = mc.getWindow().getWidth();
|
||||
int h = mc.getWindow().getHeight();
|
||||
if (w <= 0 || h <= 0) return;
|
||||
if (dualA == null) {
|
||||
dualA = new TextureTarget("lovisual-hand-dual-a", w, h, true, GpuFormat.RGBA8_UNORM);
|
||||
dualB = new TextureTarget("lovisual-hand-dual-b", w, h, true, GpuFormat.RGBA8_UNORM);
|
||||
} else if (w != bufferW || h != bufferH) {
|
||||
dualA.resize(w, h);
|
||||
dualB.resize(w, h);
|
||||
}
|
||||
}
|
||||
|
||||
private int materialFillRgb() {
|
||||
int fill = fillColor.getArgb();
|
||||
if ("Theme".equals(visualColorSource.get())) {
|
||||
|
|
@ -530,6 +665,12 @@ public class Chams extends Module {
|
|||
|
||||
float rawTime = getRawTime();
|
||||
|
||||
if (dualShader.get()) {
|
||||
renderDualShader(src, dst, maskView, rawTime);
|
||||
maskReady = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if ("Glass".equals(mode.get())) {
|
||||
int fillArgb = fillColor.getArgb();
|
||||
|
||||
|
|
@ -540,7 +681,7 @@ public class Chams extends Module {
|
|||
|
||||
int w = mc.getWindow().getWidth();
|
||||
int h = mc.getWindow().getHeight();
|
||||
float glassAlpha = clamp01(fillA);
|
||||
float glassAlpha = hideHand.get() ? 1.0f : clamp01(fillA);
|
||||
float glassStrength = Math.max(0.0f, fillA * 4.0f);
|
||||
float glassLevel = Math.min(glassStrength, 4.0f);
|
||||
|
||||
|
|
@ -576,7 +717,7 @@ public class Chams extends Module {
|
|||
int glowRgb = glowRgb(baseRgb);
|
||||
int shadowRgb = shadowRgb(baseRgb);
|
||||
|
||||
float fillA = channel(fillArgb, 24);
|
||||
float fillA = hideHand.get() ? 1.0f : channel(fillArgb, 24);
|
||||
float glowA = glow.get() ? glowAlpha.get() : 0.0f;
|
||||
float shadowA = shadow.get() ? shadowAlpha.get() : 0.0f;
|
||||
|
||||
|
|
@ -597,13 +738,13 @@ public class Chams extends Module {
|
|||
.sampler("u_Src", src, PostProcessManager.getSampler())
|
||||
.sampler("u_Mask", maskView, getHandMaskSampler())
|
||||
.end();
|
||||
} else {
|
||||
} else if ("Smoke".equals(mode.get())) {
|
||||
int fillArgb = fillColor.getArgb();
|
||||
int fillRgb = materialFillRgb();
|
||||
int glowRgb = glowRgb(fillRgb);
|
||||
int shadowRgb = shadowRgb(fillRgb);
|
||||
|
||||
float fillA = channel(fillArgb, 24);
|
||||
float fillA = hideHand.get() ? 1.0f : channel(fillArgb, 24);
|
||||
float glowA = glow.get() ? glowAlpha.get() : 0.0f;
|
||||
float shadowA = shadow.get() ? shadowAlpha.get() : 0.0f;
|
||||
|
||||
|
|
@ -626,12 +767,130 @@ public class Chams extends Module {
|
|||
.sampler("u_Src", src, PostProcessManager.getSampler())
|
||||
.sampler("u_Mask", maskView, getHandMaskSampler())
|
||||
.end();
|
||||
} else {
|
||||
renderChamsEffect(src, dst, maskView, mode.get(), rawTime);
|
||||
}
|
||||
|
||||
maskReady = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void renderChamsEffect(GpuTextureView src, GpuTextureView dst, GpuTextureView maskView, String chamsMode, float rawTime) {
|
||||
int w = mc.getWindow().getWidth();
|
||||
int h = mc.getWindow().getHeight();
|
||||
float time = rawTime;
|
||||
int colorA = 0xFFFFFFFF, colorB = 0xFFFFFFFF, colorC = 0xFFFFFFFF, colorD = 0xFFFFFFFF;
|
||||
float chromaSpeedV = 0.3f, chromaSat = 0.5f, chromaBright = 1.0f;
|
||||
float snowInt = 1.0f, snowSpd = 1.0f;
|
||||
float stripeW = 0.3f, stripeSpd = 0.3f;
|
||||
boolean glowFill = true;
|
||||
float glowRadiusV = 0.04f, glowPowerV = 0.75f, glowDisp = 5.0f;
|
||||
|
||||
switch (chamsMode) {
|
||||
case "Snow" -> {
|
||||
colorA = snowColor.getArgb();
|
||||
snowInt = snowIntensity.get();
|
||||
snowSpd = snowSpeed.get();
|
||||
}
|
||||
case "Chroma" -> {
|
||||
chromaSpeedV = chromaSpeed.get();
|
||||
chromaSat = chromaSaturation.get();
|
||||
chromaBright = chromaBrightness.get();
|
||||
}
|
||||
case "Solid" -> {
|
||||
colorA = solidColor1.getArgb();
|
||||
colorB = solidColor2.getArgb();
|
||||
time = rawTime * solidSpeed.get();
|
||||
}
|
||||
case "Stripes" -> {
|
||||
colorA = stripesColor1.getArgb();
|
||||
colorB = stripesColor2.getArgb();
|
||||
stripeW = stripesWidth.get();
|
||||
stripeSpd = stripesSpeed.get();
|
||||
}
|
||||
case "Glow" -> {
|
||||
colorC = glowShaderColor.getArgb();
|
||||
glowFill = glowShaderFill.get();
|
||||
glowRadiusV = glowShaderRadius.get();
|
||||
glowPowerV = glowShaderPower.get();
|
||||
glowDisp = glowShaderDispersion.get();
|
||||
}
|
||||
default -> {
|
||||
colorA = balatroColor1.getArgb();
|
||||
colorB = balatroColor2.getArgb();
|
||||
colorC = balatroColor3.getArgb();
|
||||
colorD = rgb01(balatroContrast.get(), balatroLighting.get());
|
||||
}
|
||||
}
|
||||
|
||||
HandChamsUniforms.update(colorA, colorB, colorC, colorD, w, h, time,
|
||||
hideHand.get() ? 1.0f : 0.85f, chromaSpeedV, chromaSat, chromaBright, snowInt, snowSpd, stripeW, stripeSpd,
|
||||
glowFill, glowRadiusV, glowPowerV, glowDisp, hideHand.get());
|
||||
|
||||
RenderPipeline pipeline = switch (chamsMode) {
|
||||
case "Snow" -> LoVisualRenderPipelines.HAND_CHAMS_SNOW;
|
||||
case "Chroma" -> LoVisualRenderPipelines.HAND_CHAMS_CHROMA;
|
||||
case "Solid" -> LoVisualRenderPipelines.HAND_CHAMS_SOLID;
|
||||
case "Stripes" -> LoVisualRenderPipelines.HAND_CHAMS_STRIPES;
|
||||
case "Glow" -> LoVisualRenderPipelines.HAND_CHAMS_GLOW;
|
||||
default -> LoVisualRenderPipelines.HAND_CHAMS_BALATRO;
|
||||
};
|
||||
|
||||
FullScreenRenderer.begin("LoVisual Fullscreen Pass")
|
||||
.attachment(dst)
|
||||
.pipeline(pipeline)
|
||||
.uniform("HandChams", HandChamsUniforms.get())
|
||||
.sampler("u_Src", src, PostProcessManager.getSampler())
|
||||
.sampler("u_Mask", maskView, getHandMaskSampler())
|
||||
.end();
|
||||
}
|
||||
|
||||
private void renderDualShader(GpuTextureView src, GpuTextureView dst, GpuTextureView maskView, float rawTime) {
|
||||
ensureDualBuffers();
|
||||
if (dualA == null || dualB == null
|
||||
|| dualA.getColorTextureView() == null || dualB.getColorTextureView() == null) {
|
||||
return;
|
||||
}
|
||||
clearTexture(dualA);
|
||||
clearTexture(dualB);
|
||||
|
||||
renderChamsEffect(src, dualA.getColorTextureView(), maskView, shader1.get(), rawTime);
|
||||
renderChamsEffect(src, dualB.getColorTextureView(), maskView, shader2.get(), rawTime);
|
||||
|
||||
HandBlendUniforms.update(blendModeIndex(), blendIntensity.get(), shader1Strength.get(), shader2Strength.get());
|
||||
FullScreenRenderer.begin("LoVisual Fullscreen Pass")
|
||||
.attachment(dst)
|
||||
.pipeline(LoVisualRenderPipelines.HAND_CHAMS_BLEND)
|
||||
.uniform("HandBlend", HandBlendUniforms.get())
|
||||
.sampler("u_Shader1", dualA.getColorTextureView(), PostProcessManager.getSampler())
|
||||
.sampler("u_Shader2", dualB.getColorTextureView(), PostProcessManager.getSampler())
|
||||
.sampler("u_Mask", maskView, getHandMaskSampler())
|
||||
.end();
|
||||
}
|
||||
|
||||
private int blendModeIndex() {
|
||||
return switch (blendMode.get()) {
|
||||
case "Add" -> 1;
|
||||
case "Multiply" -> 2;
|
||||
case "Screen" -> 3;
|
||||
case "Overlay" -> 4;
|
||||
case "Difference" -> 5;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
private static int rgb01(float r01, float g01) {
|
||||
int r = Math.round(clamp01(r01) * 255f);
|
||||
int g = Math.round(clamp01(g01) * 255f);
|
||||
return 0xFF000000 | (r << 16) | (g << 8);
|
||||
}
|
||||
|
||||
private static void clearTexture(TextureTarget target) {
|
||||
if (target == null || target.getColorTexture() == null) return;
|
||||
var encoder = RenderSystem.getDevice().createCommandEncoder();
|
||||
encoder.clearColorTexture(target.getColorTexture(), new org.joml.Vector4f(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
}
|
||||
|
||||
private float getRawTime() {
|
||||
return (float) (Util.getMillis() / 1000.0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
package dev.loki.lovisual.features.module.modules.visuals;
|
||||
|
||||
import dev.loki.lovisual.util.time.GameClock;
|
||||
|
||||
import dev.loki.lovisual.features.module.*;
|
||||
import dev.loki.lovisual.features.module.Module;
|
||||
import dev.loki.lovisual.render.engine.core.ViewportContext;
|
||||
|
|
@ -370,7 +372,7 @@ public class Crosshair extends Module {
|
|||
if (event.getPlayer() != mc.player || !(event.getTarget() instanceof Player player)) {
|
||||
return;
|
||||
}
|
||||
lastVulcanReachAttackMs = System.currentTimeMillis();
|
||||
lastVulcanReachAttackMs = GameClock.millis();
|
||||
lastVulcanReachTarget = player;
|
||||
}
|
||||
|
||||
|
|
@ -385,7 +387,7 @@ public class Crosshair extends Module {
|
|||
|
||||
Entity target = mc.level.getEntity(packet.entityId());
|
||||
if (target instanceof Player player) {
|
||||
lastVulcanReachAttackMs = System.currentTimeMillis();
|
||||
lastVulcanReachAttackMs = GameClock.millis();
|
||||
lastVulcanReachTarget = player;
|
||||
}
|
||||
}
|
||||
|
|
@ -599,7 +601,7 @@ public class Crosshair extends Module {
|
|||
}
|
||||
|
||||
private Player resolveVulcanReachTarget() {
|
||||
long now = System.currentTimeMillis();
|
||||
long now = GameClock.millis();
|
||||
HitResult hit = mc.hitResult;
|
||||
if (hit instanceof EntityHitResult entityHit
|
||||
&& entityHit.getEntity() instanceof Player player
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
package dev.loki.lovisual.features.module.modules.visuals;
|
||||
|
||||
import dev.loki.lovisual.util.time.GameClock;
|
||||
|
||||
import com.mojang.blaze3d.textures.GpuTextureView;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.util.Mth;
|
||||
|
|
@ -111,7 +113,7 @@ public class DamageTint extends Module implements PostProcessPass {
|
|||
float contrast = t * contrastStrength.get();
|
||||
|
||||
if (pulse.get()) {
|
||||
float time = (System.currentTimeMillis() % 100000L) / 1000.0f;
|
||||
float time = (GameClock.millis() % 100000L) / 1000.0f;
|
||||
float wave = Mth.sin(time * pulseSpeed.get() * 6.2831855f);
|
||||
float amp = Mth.lerp(t, pulseMin.get(), pulseMax.get());
|
||||
strength *= 1.0f + (wave * amp);
|
||||
|
|
@ -140,7 +142,7 @@ public class DamageTint extends Module implements PostProcessPass {
|
|||
}
|
||||
|
||||
private float smoothTowards(float target) {
|
||||
long now = System.currentTimeMillis();
|
||||
long now = GameClock.millis();
|
||||
float dt = (lastUpdateMs == 0L) ? 0.016f : (now - lastUpdateMs) / 1000.0f;
|
||||
lastUpdateMs = now;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
package dev.loki.lovisual.features.module.modules.visuals;
|
||||
|
||||
import dev.loki.lovisual.util.time.GameClock;
|
||||
|
||||
import dev.loki.lovisual.render.engine.animation.AnimationUtility;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
|
||||
|
|
@ -49,7 +51,7 @@ final class EspColorUtil {
|
|||
}
|
||||
|
||||
static float shaderSmokeTime() {
|
||||
return (System.currentTimeMillis() % 600_000L) / 1000.0f;
|
||||
return (GameClock.millis() % 600_000L) / 1000.0f;
|
||||
}
|
||||
|
||||
static int deriveSmokeColor(int rgb, int layer) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
package dev.loki.lovisual.features.module.modules.visuals;
|
||||
|
||||
import dev.loki.lovisual.util.time.GameClock;
|
||||
|
||||
import com.mojang.blaze3d.pipeline.RenderPipeline;
|
||||
import dev.loki.lovisual.config.values.*;
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
|
@ -404,7 +406,7 @@ public class HitEffect extends Module {
|
|||
|
||||
private void addWave(BlockPos pos) {
|
||||
if (mc.level != null) {
|
||||
waves.add(new WaveEffect(pos, System.currentTimeMillis()));
|
||||
waves.add(new WaveEffect(pos, GameClock.millis()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -684,7 +686,7 @@ public class HitEffect extends Module {
|
|||
}
|
||||
|
||||
private boolean isExpired() {
|
||||
return System.currentTimeMillis() - startTime > Math.max(1L, waveDurationMs.get());
|
||||
return GameClock.millis() - startTime > Math.max(1L, waveDurationMs.get());
|
||||
}
|
||||
|
||||
private void render(Renderer3D renderer) {
|
||||
|
|
@ -693,7 +695,7 @@ public class HitEffect extends Module {
|
|||
|
||||
long durationMs = Math.max(1L, waveDurationMs.get());
|
||||
int maxRadius = Math.max(1, waveRadius.get());
|
||||
long elapsed = System.currentTimeMillis() - startTime;
|
||||
long elapsed = GameClock.millis() - startTime;
|
||||
float progress = Mth.clamp((float) elapsed / durationMs, 0f, 1f);
|
||||
|
||||
float currentRadius = progress * maxRadius;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
package dev.loki.lovisual.features.module.modules.visuals;
|
||||
|
||||
import dev.loki.lovisual.util.time.GameClock;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.core.particles.ParticleTypes;
|
||||
|
|
@ -149,7 +151,7 @@ public class KillEffect extends Module {
|
|||
public void onTick() {
|
||||
if (!isEnabled() || mc.level == null || mc.player == null) return;
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long now = GameClock.millis();
|
||||
handled.entrySet().removeIf(e -> now - e.getValue() > 6000);
|
||||
|
||||
for (LivingEntity entity : mc.level.getEntitiesOfClass(LivingEntity.class, mc.player.getBoundingBox().inflate(128), e -> true)) {
|
||||
|
|
@ -461,11 +463,11 @@ public class KillEffect extends Module {
|
|||
}
|
||||
|
||||
private boolean isExpired() {
|
||||
return System.currentTimeMillis() - createdAtMs > LIGHTNING_LIFE_MS;
|
||||
return GameClock.millis() - createdAtMs > LIGHTNING_LIFE_MS;
|
||||
}
|
||||
|
||||
private float getAlpha() {
|
||||
long age = System.currentTimeMillis() - createdAtMs;
|
||||
long age = GameClock.millis() - createdAtMs;
|
||||
float appear = smoothProgress(Math.min(1f, age / (float) LIGHTNING_APPEAR_MS));
|
||||
float fade = 1f;
|
||||
if (age > LIGHTNING_APPEAR_MS) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
package dev.loki.lovisual.features.module.modules.visuals;
|
||||
|
||||
import dev.loki.lovisual.util.time.GameClock;
|
||||
|
||||
import com.mojang.blaze3d.textures.GpuTextureView;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import dev.loki.lovisual.config.values.ModeValue;
|
||||
|
|
@ -145,7 +147,7 @@ public class PostFX extends Module implements PostProcessPass {
|
|||
int h = mc.getWindow().getHeight();
|
||||
float invW = w > 0 ? 1.0f / w : 0.0f;
|
||||
float invH = h > 0 ? 1.0f / h : 0.0f;
|
||||
float time = (System.currentTimeMillis() % 100000L) / 1000.0f;
|
||||
float time = (GameClock.millis() % 100000L) / 1000.0f;
|
||||
|
||||
boolean lutAvailable = lutMode > 0.0f;
|
||||
fillUniforms(invW, invH, time, presetName, isCustom, lutAvailable);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
package dev.loki.lovisual.features.module.modules.visuals;
|
||||
|
||||
import dev.loki.lovisual.config.values.ModeValue;
|
||||
import dev.loki.lovisual.config.values.NumberValue;
|
||||
import dev.loki.lovisual.features.module.Module;
|
||||
import dev.loki.lovisual.features.module.ModuleCategory;
|
||||
import dev.loki.lovisual.features.module.ModuleInfo;
|
||||
|
||||
//todo Description
|
||||
@ModuleInfo(id = "shadersky", displayName = "ShaderSky", category = ModuleCategory.VISUALS)
|
||||
public class ShaderSky extends Module {
|
||||
|
||||
private static final String SETTING_MODE = "mode";
|
||||
private static final String SETTING_SPEED = "speed";
|
||||
private static final String SETTING_SCALE = "scale";
|
||||
private static final String SETTING_INTENSITY = "intensity";
|
||||
private static final String SETTING_ALPHA = "alpha";
|
||||
private static final String SETTING_THUNDER_INTERVAL = "thunder_interval";
|
||||
private static final String SETTING_THUNDER_CHANCE = "thunder_chance";
|
||||
private static final String SETTING_THUNDER_GLOW = "thunder_glow";
|
||||
|
||||
private final ModeValue mode = modeSetting(
|
||||
"shaderSkyMode",
|
||||
SETTING_MODE,
|
||||
"Thunder",
|
||||
"Thunder", "Pulsar"
|
||||
);
|
||||
private final NumberValue<Float> speed =
|
||||
num("shaderSkySpeed", SETTING_SPEED, 1.0f, 0.1f, 5.0f);
|
||||
private final NumberValue<Float> scale =
|
||||
num("shaderSkyScale", SETTING_SCALE, 5.0f, 1.0f, 20.0f);
|
||||
private final NumberValue<Float> intensity =
|
||||
num("shaderSkyIntensity", SETTING_INTENSITY, 0.01f, 0.001f, 0.05f);
|
||||
private final NumberValue<Float> alpha =
|
||||
num("shaderSkyAlpha", SETTING_ALPHA, 1.0f, 0.3f, 1.0f);
|
||||
private final NumberValue<Float> thunderInterval =
|
||||
visibleWhen(num("shaderSkyThunderInterval", SETTING_THUNDER_INTERVAL, 4.0f, 1.0f, 10.0f), this::isThunderMode);
|
||||
private final NumberValue<Float> thunderChance =
|
||||
visibleWhen(num("shaderSkyThunderChance", SETTING_THUNDER_CHANCE, 0.65f, 0.0f, 1.0f), this::isThunderMode);
|
||||
private final NumberValue<Float> thunderGlow =
|
||||
visibleWhen(num("shaderSkyThunderGlow", SETTING_THUNDER_GLOW, 1.0f, 0.1f, 3.0f), this::isThunderMode);
|
||||
|
||||
public String getMode() {
|
||||
return mode.get();
|
||||
}
|
||||
|
||||
public float getSpeed() {
|
||||
return speed.get();
|
||||
}
|
||||
|
||||
public float getScale() {
|
||||
return scale.get();
|
||||
}
|
||||
|
||||
public float getIntensity() {
|
||||
return intensity.get();
|
||||
}
|
||||
|
||||
public float getAlpha() {
|
||||
return alpha.get();
|
||||
}
|
||||
|
||||
public float getThunderInterval() {
|
||||
return thunderInterval.get();
|
||||
}
|
||||
|
||||
public float getThunderChance() {
|
||||
return thunderChance.get();
|
||||
}
|
||||
|
||||
public float getThunderGlow() {
|
||||
return thunderGlow.get();
|
||||
}
|
||||
|
||||
private boolean isThunderMode() {
|
||||
return "Thunder".equals(mode.get());
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
package dev.loki.lovisual.features.module.modules.visuals;
|
||||
|
||||
import dev.loki.lovisual.util.time.GameClock;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.resources.language.I18n;
|
||||
|
|
@ -86,7 +88,7 @@ public class SoundESP extends Module {
|
|||
if (!isEnabled() || mc.level == null || mc.player == null) return;
|
||||
if (id == null || pos == null) return;
|
||||
synchronized (soundLock) {
|
||||
sounds.add(new SoundPing(id, pos, System.currentTimeMillis()));
|
||||
sounds.add(new SoundPing(id, pos, GameClock.millis()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -101,7 +103,7 @@ public class SoundESP extends Module {
|
|||
public void onRenderHudEngine(Renderer2D renderer, TextRenderer textRenderer, GuiGraphicsExtractor ctx, float tickDelta) {
|
||||
if (!isEnabled() || mc.level == null || mc.player == null) return;
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long now = GameClock.millis();
|
||||
int lifetime = Math.max(1, lifetimeMs.get());
|
||||
List<SoundPing> snapshot = new ArrayList<>();
|
||||
Vec3 camPos = mc.gameRenderer.mainCamera().position();
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@
|
|||
|
||||
package dev.loki.lovisual.features.module.modules.visuals;
|
||||
|
||||
import dev.loki.lovisual.util.time.GameClock;
|
||||
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.util.Mth;
|
||||
|
|
@ -62,6 +65,9 @@ public class TargetESP extends Module {
|
|||
private static final String SETTING_ESP_AMPLITUDE = "esp_amplitude";
|
||||
private static final String SETTING_CAPTURE_SPIN_SPEED = "capture_spin_speed";
|
||||
private static final String SETTING_CRYSTAL_HIT_RED = "crystal_hit_red";
|
||||
private static final String SETTING_BLACKHOLE_SIZE = "blackhole_size";
|
||||
private static final String SETTING_BLACKHOLE_SPIN_SPEED = "blackhole_spin_speed";
|
||||
private static final String SETTING_BLACKHOLE_STYLE = "blackhole_style";
|
||||
private static final Identifier GHOST_PARTICLE_TEXTURE = TextureStorage.FIRE_FLY;
|
||||
private static final Identifier CAPTURE_MARK_TEXTURE = TextureStorage.CAPTURE;
|
||||
private static final Identifier CRYSTAL_BLOOM = TextureStorage.BLOOM;
|
||||
|
|
@ -81,7 +87,7 @@ public class TargetESP extends Module {
|
|||
"targetEspMode",
|
||||
SETTING_MODE,
|
||||
"ghosts",
|
||||
"ghosts", "ring", "capture_mark", "crystals"
|
||||
"ghosts", "ring", "capture_mark", "crystals", "blackhole"
|
||||
);
|
||||
private final RGBAColorValue colorValue = color("targetEspColor", SETTING_COLOR, "#FFFF5555");
|
||||
private final ModeValue colorMode = visibleWhen(modeSetting(
|
||||
|
|
@ -118,6 +124,12 @@ public class TargetESP extends Module {
|
|||
visibleWhen(num("targetEspCaptureSpinSpeed", SETTING_CAPTURE_SPIN_SPEED, 1.0f, 0.2f, 4.0f), this::isCaptureMarkMode);
|
||||
private final BooleanValue crystalHitRed =
|
||||
visibleWhen(bool("targetEspCrystalHitRed", SETTING_CRYSTAL_HIT_RED, true), this::isCrystalsMode);
|
||||
private final NumberValue<Float> blackholeSize =
|
||||
visibleWhen(num("targetEspBlackholeSize", SETTING_BLACKHOLE_SIZE, 1.1f, 0.5f, 3.0f), this::isBlackholeMode);
|
||||
private final NumberValue<Float> blackholeSpinSpeed =
|
||||
visibleWhen(num("targetEspBlackholeSpinSpeed", SETTING_BLACKHOLE_SPIN_SPEED, 1.0f, 0.2f, 4.0f), this::isBlackholeMode);
|
||||
private final ModeValue blackholeStyle =
|
||||
visibleWhen(modeSetting("targetEspBlackholeStyle", SETTING_BLACKHOLE_STYLE, "Single", "Single", "Body"), this::isBlackholeMode);
|
||||
private final List<CrystalInstance> crystalList = new ArrayList<>();
|
||||
private LivingEntity target;
|
||||
private LivingEntity renderTarget;
|
||||
|
|
@ -250,6 +262,168 @@ public class TargetESP extends Module {
|
|||
addRotatedBillboard(mesh, tPosX, cy, tPosZ, size, angle, palette);
|
||||
}
|
||||
|
||||
private static final float[][] BODY_HOLES = {
|
||||
{0.0f, 0.60f}, // голова
|
||||
{-0.48f, 0.36f}, // левая рука
|
||||
{0.48f, 0.36f}, // правая рука
|
||||
{0.0f, 0.05f}, // грудь
|
||||
{-0.30f, -0.42f},// левая нога
|
||||
{0.30f, -0.42f} // правая нога
|
||||
};
|
||||
|
||||
private static void renderBlackhole(Renderer3D renderer,
|
||||
LivingEntity target,
|
||||
float tickDelta,
|
||||
IntFunction<Integer> palette,
|
||||
float size,
|
||||
float spinSpeed,
|
||||
float alpha,
|
||||
String style) {
|
||||
if (renderer == null || target == null) return;
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.gameRenderer == null || mc.gameRenderer.mainCamera() == null) return;
|
||||
Vec3 pos = RenderMath.getLerpedPos(target, tickDelta);
|
||||
Vec3 baseCenter = new Vec3(pos.x, pos.y + target.getBbHeight() * 0.5, pos.z);
|
||||
|
||||
// Камера-зависимый billboard-базис: правый/верх в мировых координатах
|
||||
Camera cam = mc.gameRenderer.mainCamera();
|
||||
float yaw = (float) Math.toRadians(cam.yRot());
|
||||
float pitch = (float) Math.toRadians(cam.xRot());
|
||||
Vec3 fwd = new Vec3(-Math.sin(yaw) * Math.cos(pitch), -Math.sin(pitch), Math.cos(yaw) * Math.cos(pitch)).normalize();
|
||||
Vec3 rgt = new Vec3(0.0, 1.0, 0.0).cross(fwd);
|
||||
if (rgt.lengthSqr() < 1.0E-6) {
|
||||
rgt = new Vec3(1.0, 0.0, 0.0);
|
||||
} else {
|
||||
rgt = rgt.normalize();
|
||||
}
|
||||
Vec3 upp = fwd.cross(rgt).normalize();
|
||||
Vector3f right = new Vector3f((float) rgt.x, (float) rgt.y, (float) rgt.z);
|
||||
Vector3f up = new Vector3f((float) upp.x, (float) upp.y, (float) upp.z);
|
||||
|
||||
MeshBuilder diskMesh = renderer.batch(
|
||||
LoVisualRenderPipelines.WORLD_COLORED_ADDITIVE_DEPTH,
|
||||
Renderer3D.DepthMode.PRE_DEPTH
|
||||
);
|
||||
MeshBuilder coreMesh = renderer.batch(
|
||||
LoVisualRenderPipelines.WORLD_COLORED_DEPTH,
|
||||
Renderer3D.DepthMode.PRE_DEPTH
|
||||
);
|
||||
if (diskMesh == null || coreMesh == null) return;
|
||||
|
||||
float time = (GameClock.millis() % 100000L) / 1000.0f;
|
||||
|
||||
if ("Body".equals(style)) {
|
||||
float holeSize = size * 0.34f;
|
||||
for (float[] off : BODY_HOLES) {
|
||||
Vec3 center = new Vec3(
|
||||
baseCenter.x + rgt.x * off[0] + upp.x * off[1],
|
||||
baseCenter.y + rgt.y * off[0] + upp.y * off[1],
|
||||
baseCenter.z + rgt.z * off[0] + upp.z * off[1]
|
||||
);
|
||||
appendBlackhole(diskMesh, coreMesh, center, right, up, holeSize, time, spinSpeed, palette, alpha);
|
||||
}
|
||||
} else {
|
||||
appendBlackhole(diskMesh, coreMesh, baseCenter, right, up, size, time, spinSpeed, palette, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
private static void appendBlackhole(MeshBuilder diskMesh,
|
||||
MeshBuilder coreMesh,
|
||||
Vec3 center,
|
||||
Vector3f right,
|
||||
Vector3f up,
|
||||
float size,
|
||||
float time,
|
||||
float spinSpeed,
|
||||
IntFunction<Integer> palette,
|
||||
float alpha) {
|
||||
float horizon = 0.35f * size;
|
||||
int rings = 4;
|
||||
int segs = 64;
|
||||
|
||||
// аккреционный диск: вращающаяся спираль на аддитивном бленде
|
||||
for (int ring = 0; ring < rings; ring++) {
|
||||
float r0 = horizon + (size - horizon) * (ring / (float) rings);
|
||||
float r1 = horizon + (size - horizon) * ((ring + 1) / (float) rings);
|
||||
for (int s = 0; s < segs; s++) {
|
||||
float a0 = (float) (Math.PI * 2.0 * s / segs);
|
||||
float a1 = (float) (Math.PI * 2.0 * (s + 1) / segs);
|
||||
int v0 = addBlackholeVertex(diskMesh, center, right, up, r0, a0, horizon, size, time, spinSpeed, palette);
|
||||
int v1 = addBlackholeVertex(diskMesh, center, right, up, r1, a0, horizon, size, time, spinSpeed, palette);
|
||||
int v2 = addBlackholeVertex(diskMesh, center, right, up, r1, a1, horizon, size, time, spinSpeed, palette);
|
||||
int v3 = addBlackholeVertex(diskMesh, center, right, up, r0, a1, horizon, size, time, spinSpeed, palette);
|
||||
diskMesh.triangle(v0, v1, v2);
|
||||
diskMesh.triangle(v0, v2, v3);
|
||||
}
|
||||
}
|
||||
|
||||
// чёрное ядро закрывает центр диска
|
||||
int coreArgb = applyOpacity(0xFF080808, alpha);
|
||||
int coreCenter = addBlackholeCoreVertex(coreMesh, center, right, up, 0f, 0f, coreArgb);
|
||||
int coreSegs = 32;
|
||||
for (int s = 0; s < coreSegs; s++) {
|
||||
float a0 = (float) (Math.PI * 2.0 * s / coreSegs);
|
||||
float a1 = (float) (Math.PI * 2.0 * (s + 1) / coreSegs);
|
||||
int c0 = addBlackholeCoreVertex(coreMesh, center, right, up, horizon, a0, coreArgb);
|
||||
int c1 = addBlackholeCoreVertex(coreMesh, center, right, up, horizon, a1, coreArgb);
|
||||
coreMesh.triangle(coreCenter, c1, c0);
|
||||
}
|
||||
}
|
||||
|
||||
private static int addBlackholeVertex(MeshBuilder mesh,
|
||||
Vec3 center,
|
||||
Vector3f right,
|
||||
Vector3f up,
|
||||
float r,
|
||||
float angle,
|
||||
float horizon,
|
||||
float size,
|
||||
float time,
|
||||
float spinSpeed,
|
||||
IntFunction<Integer> palette) {
|
||||
float dist = r / size;
|
||||
float rot = time * 2.5f * spinSpeed + 1.3f / (dist + 0.05f);
|
||||
float ca = (float) Math.cos(rot);
|
||||
float sa = (float) Math.sin(rot);
|
||||
float ux = dist * (float) Math.cos(angle);
|
||||
float uy = dist * (float) Math.sin(angle);
|
||||
float rx = ux * ca - uy * sa;
|
||||
float ry = ux * sa + uy * ca;
|
||||
float spiral = Math.abs((float) Math.sin(rx * 14.0f + ry * 4.0f) * 0.5f + 0.5f);
|
||||
float falloff = (float) Math.pow(horizon / Math.max(dist, 0.001f), 2.2);
|
||||
float outerFade = smoothstep(1.0f, horizon, dist);
|
||||
float brightness = falloff * (0.55f + 0.45f * spiral) * outerFade;
|
||||
|
||||
int color = palette.apply((int) ((Math.toDegrees(angle) + time * 90f * spinSpeed) % 360f));
|
||||
color = applyOpacity(color, brightness);
|
||||
|
||||
float wx = (float) center.x + right.x() * ux * size + up.x() * uy * size;
|
||||
float wy = (float) center.y + right.y() * ux * size + up.y() * uy * size;
|
||||
float wz = (float) center.z + right.z() * ux * size + up.z() * uy * size;
|
||||
return addVertex(mesh, wx, wy, wz, color);
|
||||
}
|
||||
|
||||
private static int addBlackholeCoreVertex(MeshBuilder mesh,
|
||||
Vec3 center,
|
||||
Vector3f right,
|
||||
Vector3f up,
|
||||
float r,
|
||||
float angle,
|
||||
int argb) {
|
||||
float ux = r * (float) Math.cos(angle);
|
||||
float uy = r * (float) Math.sin(angle);
|
||||
float wx = (float) center.x + right.x() * ux + up.x() * uy;
|
||||
float wy = (float) center.y + right.y() * ux + up.y() * uy;
|
||||
float wz = (float) center.z + right.z() * ux + up.z() * uy;
|
||||
return addVertex(mesh, wx, wy, wz, argb);
|
||||
}
|
||||
|
||||
private static float smoothstep(float edge0, float edge1, float x) {
|
||||
float t = (x - edge0) / (edge1 - edge0);
|
||||
t = Math.max(0f, Math.min(1f, t));
|
||||
return t * t * (3f - 2f * t);
|
||||
}
|
||||
|
||||
private static void appendCrystalGeometry(MeshBuilder mesh,
|
||||
Vec3 targetPos,
|
||||
CrystalInstance crystal,
|
||||
|
|
@ -553,6 +727,9 @@ public class TargetESP extends Module {
|
|||
renderCaptureMark(renderer, renderTarget, tickDelta, palette);
|
||||
} else if ("crystals".equals(mode)) {
|
||||
renderCrystals(renderer, renderTarget, tickDelta);
|
||||
} else if ("blackhole".equals(mode)) {
|
||||
renderBlackhole(renderer, renderTarget, tickDelta, palette,
|
||||
blackholeSize.get(), blackholeSpinSpeed.get(), targetAlpha, blackholeStyle.get());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -719,6 +896,10 @@ public class TargetESP extends Module {
|
|||
return "crystals".equals(espMode.get());
|
||||
}
|
||||
|
||||
private boolean isBlackholeMode() {
|
||||
return "blackhole".equals(espMode.get());
|
||||
}
|
||||
|
||||
private boolean usesSecondaryColor() {
|
||||
return AnimatedRenderColors.usesSecondary(animatedColorMode());
|
||||
}
|
||||
|
|
@ -778,11 +959,11 @@ public class TargetESP extends Module {
|
|||
}
|
||||
|
||||
private float selfRotationDeg() {
|
||||
return (System.currentTimeMillis() % 36000L) / 100.0f * rotationSpeed;
|
||||
return (GameClock.millis() % 36000L) / 100.0f * rotationSpeed;
|
||||
}
|
||||
|
||||
private float pulsation() {
|
||||
return 1.0f + (float) (Math.sin(System.currentTimeMillis() / 500.0) * 0.1f);
|
||||
return 1.0f + (float) (Math.sin(GameClock.millis() / 500.0) * 0.1f);
|
||||
}
|
||||
|
||||
private void append(MeshBuilder additiveMesh,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
package dev.loki.lovisual.features.module.modules.visuals;
|
||||
|
||||
import dev.loki.lovisual.util.time.GameClock;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.util.Mth;
|
||||
|
|
@ -303,7 +305,7 @@ public final class TazikHat extends Module {
|
|||
return;
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long now = GameClock.millis();
|
||||
|
||||
for (Player player : mc.level.players()) {
|
||||
if (player == null || player.isRemoved()) continue;
|
||||
|
|
@ -423,7 +425,7 @@ public final class TazikHat extends Module {
|
|||
}
|
||||
|
||||
private void renderFlying(RenderBuffers buffers, float tickDelta) {
|
||||
long now = System.currentTimeMillis();
|
||||
long now = GameClock.millis();
|
||||
|
||||
for (FlyingTazik tazik : flying.values()) {
|
||||
float life = (now - tazik.createdAtMs) / (float) DEATH_LIFE_MS;
|
||||
|
|
@ -654,7 +656,7 @@ public final class TazikHat extends Module {
|
|||
index
|
||||
);
|
||||
|
||||
float circumference = 0.5f + 0.5f * Mth.sin(index * 0.47f + System.currentTimeMillis() * 0.0007f);
|
||||
float circumference = 0.5f + 0.5f * Mth.sin(index * 0.47f + GameClock.millis() * 0.0007f);
|
||||
int mixed = multiplyRgb(dynamic, verticalLight + circumference * 0.16f);
|
||||
|
||||
return withAlpha(mixed, Math.round(alpha(mixed) * alphaMul));
|
||||
|
|
@ -669,7 +671,7 @@ public final class TazikHat extends Module {
|
|||
index + 120
|
||||
);
|
||||
|
||||
float t = 0.5f + 0.5f * Mth.sin(index * 0.71f + System.currentTimeMillis() * 0.0011f);
|
||||
float t = 0.5f + 0.5f * Mth.sin(index * 0.71f + GameClock.millis() * 0.0011f);
|
||||
int mixed = mix(dynamic, brighten(palette.primary, 1.35f), t);
|
||||
|
||||
return withAlpha(mixed, Math.round(alpha(mixed) * alphaMul));
|
||||
|
|
@ -783,7 +785,7 @@ public final class TazikHat extends Module {
|
|||
this.scale = scale;
|
||||
this.palette = palette;
|
||||
this.seed = seed;
|
||||
this.createdAtMs = System.currentTimeMillis();
|
||||
this.createdAtMs = GameClock.millis();
|
||||
|
||||
float power = deathFlightPower.get();
|
||||
|
||||
|
|
@ -797,7 +799,7 @@ public final class TazikHat extends Module {
|
|||
}
|
||||
|
||||
private boolean update() {
|
||||
long age = System.currentTimeMillis() - createdAtMs;
|
||||
long age = GameClock.millis() - createdAtMs;
|
||||
if (age > DEATH_LIFE_MS) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
package dev.loki.lovisual.features.module.modules.visuals;
|
||||
|
||||
import dev.loki.lovisual.util.time.GameClock;
|
||||
|
||||
import com.mojang.blaze3d.textures.GpuTextureView;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.util.Mth;
|
||||
|
|
@ -70,7 +72,7 @@ public class TotemFX extends Module implements PostProcessPass {
|
|||
|
||||
public void onTotemPop() {
|
||||
if (!isEnabled()) return;
|
||||
startMs = System.currentTimeMillis();
|
||||
startMs = GameClock.millis();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -107,7 +109,7 @@ public class TotemFX extends Module implements PostProcessPass {
|
|||
float radius = Mth.clamp(vignetteRadius.get(), 0.05f, 0.95f);
|
||||
float softness = Mth.clamp(vignetteSoftness.get(), 0.01f, 1.0f);
|
||||
|
||||
float time = (System.currentTimeMillis() % 100000L) / 1000.0f;
|
||||
float time = (GameClock.millis() % 100000L) / 1000.0f;
|
||||
float pulse = 1.0f + (Mth.sin(time * pulseSpeed.get() * 6.2831855f) * pulseAmount.get());
|
||||
pulse = Mth.clamp(pulse, 0.0f, 2.0f);
|
||||
vig *= pulse;
|
||||
|
|
@ -135,7 +137,7 @@ public class TotemFX extends Module implements PostProcessPass {
|
|||
|
||||
private float getEnvelope() {
|
||||
if (startMs < 0L) return 0.0f;
|
||||
float t = (System.currentTimeMillis() - startMs) / 1000.0f;
|
||||
float t = (GameClock.millis() - startMs) / 1000.0f;
|
||||
float in = Math.max(0.01f, fadeIn.get());
|
||||
float mid = Math.max(0.0f, hold.get());
|
||||
float out = Math.max(0.01f, fadeOut.get());
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
package dev.loki.lovisual.features.module.modules.visuals;
|
||||
|
||||
import dev.loki.lovisual.util.time.GameClock;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.math.Axis;
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
|
@ -343,7 +345,7 @@ public class ViewModel extends Module {
|
|||
case "Helicopter": {
|
||||
ms.scale(scale, scale, scale);
|
||||
ms.translate(0.0, -0.2, 0.0);
|
||||
float rotation = (System.currentTimeMillis() % 2000L) / 2000.0f * 360.0f * power;
|
||||
float rotation = (GameClock.millis() % 2000L) / 2000.0f * 360.0f * power;
|
||||
ms.mulPose(Axis.ZP.rotationDegrees(rotation));
|
||||
ms.translate(0.0, 0.3, 0.0);
|
||||
break;
|
||||
|
|
@ -438,7 +440,7 @@ public class ViewModel extends Module {
|
|||
|
||||
case "Shake": {
|
||||
ms.scale(scale, scale, scale);
|
||||
float s = (float) Math.sin((System.currentTimeMillis() % 200L) / 200.0f * Math.PI * 2.0f) * 5.0f;
|
||||
float s = (float) Math.sin((GameClock.millis() % 200L) / 200.0f * Math.PI * 2.0f) * 5.0f;
|
||||
ms.mulPose(Axis.ZP.rotationDegrees(s));
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
package dev.loki.lovisual.features.module.modules.visuals;
|
||||
|
||||
import dev.loki.lovisual.util.time.GameClock;
|
||||
|
||||
import dev.loki.lovisual.config.values.*;
|
||||
import dev.loki.lovisual.features.theme.Theme;
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
|
@ -903,7 +905,7 @@ public class WorldParticles extends Module {
|
|||
this.rotation = Vec3.ZERO;
|
||||
this.motion = motion;
|
||||
this.rotationMotion = rotationMotion;
|
||||
this.spawnMs = System.currentTimeMillis();
|
||||
this.spawnMs = GameClock.millis();
|
||||
this.lifeMs = Math.max(1L, lifeMs);
|
||||
this.size = size;
|
||||
this.visualPhase = visualPhase;
|
||||
|
|
@ -921,7 +923,7 @@ public class WorldParticles extends Module {
|
|||
}
|
||||
|
||||
private float alpha() {
|
||||
long age = System.currentTimeMillis() - spawnMs;
|
||||
long age = GameClock.millis() - spawnMs;
|
||||
if (age <= 0L) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
|
@ -965,7 +967,7 @@ public class WorldParticles extends Module {
|
|||
}
|
||||
|
||||
private long ageMs() {
|
||||
return System.currentTimeMillis() - spawnMs;
|
||||
return GameClock.millis() - spawnMs;
|
||||
}
|
||||
|
||||
private float lifeProgress() {
|
||||
|
|
@ -973,7 +975,7 @@ public class WorldParticles extends Module {
|
|||
}
|
||||
|
||||
private boolean isExpired() {
|
||||
return System.currentTimeMillis() - spawnMs >= lifeMs + ALPHA_ANIM_MS;
|
||||
return GameClock.millis() - spawnMs >= lifeMs + ALPHA_ANIM_MS;
|
||||
}
|
||||
|
||||
private Vec3 interpolatePos(float tickDelta) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
package dev.loki.lovisual.features.module.modules.visuals;
|
||||
|
||||
import dev.loki.lovisual.util.time.GameClock;
|
||||
|
||||
import dev.loki.lovisual.config.values.*;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.fog.FogData;
|
||||
|
|
@ -140,7 +142,7 @@ public class WorldTweaks extends Module {
|
|||
long time = client.level.getGameTime() % 24000L;
|
||||
return (time + RenderState.tickDelta) / 20.0f;
|
||||
}
|
||||
long nowMs = System.currentTimeMillis();
|
||||
long nowMs = GameClock.millis();
|
||||
return (nowMs % 100000L) / 1000.0f;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ public enum CategoryRules {
|
|||
|
||||
PlayerRelations rel = PlayerRelations.get();
|
||||
|
||||
if (rel.isStaff(name) || StaffHeuristicsConfig.get().matches(name) || matchesTrackedStaff(name))
|
||||
if (rel.isStaff(name))
|
||||
return CategoryType.STAFF;
|
||||
|
||||
if (rel.isFriend(name))
|
||||
|
|
@ -39,13 +39,4 @@ public enum CategoryRules {
|
|||
|
||||
return CategoryType.DEFAULT;
|
||||
}
|
||||
|
||||
private static boolean matchesTrackedStaff(String name) {
|
||||
if (name == null || name.isBlank()) return false;
|
||||
for (StaffTracker.StaffInfo info : StaffTracker.all().values()) {
|
||||
if (info == null || info.name == null || !info.name.equalsIgnoreCase(name)) continue;
|
||||
if (StaffHeuristicsConfig.get().matches(info.name, info.displayName)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,12 +14,16 @@ import java.util.*;
|
|||
/**
|
||||
* Provides sorted staff entries based on PlayerRelations and StaffTracker.
|
||||
*/
|
||||
public enum StaffData {
|
||||
;
|
||||
public final class StaffData {
|
||||
|
||||
public static List<StaffEntry> getEntries() {
|
||||
public static final StaffData INSTANCE = new StaffData();
|
||||
|
||||
private StaffData() {
|
||||
}
|
||||
|
||||
public List<StaffEntry> getEntries() {
|
||||
Set<String> staffList = PlayerRelations.get().getStaff();
|
||||
Map<UUID, StaffTracker.StaffInfo> map = StaffTracker.all();
|
||||
Map<UUID, StaffTracker.StaffInfo> map = StaffTracker.INSTANCE.all();
|
||||
List<StaffEntry> out = new ArrayList<>();
|
||||
|
||||
for (String rawName : staffList) {
|
||||
|
|
@ -47,23 +51,6 @@ public enum StaffData {
|
|||
));
|
||||
}
|
||||
|
||||
for (StaffTracker.StaffInfo info : map.values()) {
|
||||
if (info == null || info.name == null || info.name.isBlank()) continue;
|
||||
if (containsIgnoreCase(staffList, info.name)) continue;
|
||||
if (!StaffHeuristicsConfig.get().matches(info.name, info.displayName)) continue;
|
||||
|
||||
out.add(new StaffEntry(
|
||||
info.name,
|
||||
info.displayName != null
|
||||
? info.displayName
|
||||
: Component.literal(info.name),
|
||||
info.status,
|
||||
info.offlineSince,
|
||||
info.lastEntityGone
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
out.sort(Comparator.comparing((StaffEntry e) -> sortPriority(e.status))
|
||||
.thenComparing(e -> e.name.toLowerCase(Locale.ROOT)));
|
||||
return out;
|
||||
|
|
@ -91,14 +78,6 @@ public enum StaffData {
|
|||
return null;
|
||||
}
|
||||
|
||||
private static boolean containsIgnoreCase(Set<String> set, String value) {
|
||||
if (set == null || value == null) return false;
|
||||
for (String entry : set) {
|
||||
if (entry != null && entry.equalsIgnoreCase(value)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public record StaffEntry(
|
||||
String name,
|
||||
Component displayName,
|
||||
|
|
|
|||
|
|
@ -1,152 +0,0 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 pivosos2007.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.features.relations;
|
||||
|
||||
import dev.loki.lovisual.config.ConfigNameProvider;
|
||||
import dev.loki.lovisual.config.ConfigObject;
|
||||
import dev.loki.lovisual.config.ConfigSerializer;
|
||||
import dev.loki.lovisual.config.values.BooleanValue;
|
||||
import dev.loki.lovisual.config.values.ConfigValue;
|
||||
import dev.loki.lovisual.config.values.SetValue;
|
||||
import dev.loki.lovisual.util.text.LegacyTextUtil;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public final class StaffHeuristicsConfig implements ConfigObject, ConfigNameProvider {
|
||||
public static final StaffHeuristicsConfig INSTANCE = new StaffHeuristicsConfig();
|
||||
|
||||
private final BooleanValue enabled = new BooleanValue("enabled", false);
|
||||
private final SetValue prefixes = new SetValue("prefixes");
|
||||
private final SetValue suffixes = new SetValue("suffixes");
|
||||
private final SetValue contains = new SetValue("contains");
|
||||
|
||||
private StaffHeuristicsConfig() {
|
||||
ConfigSerializer.load(this);
|
||||
}
|
||||
|
||||
public static StaffHeuristicsConfig get() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public boolean enabled() {
|
||||
return enabled.get();
|
||||
}
|
||||
|
||||
public void setEnabled(boolean value) {
|
||||
enabled.set(value);
|
||||
save();
|
||||
}
|
||||
|
||||
public Set<String> prefixes() {
|
||||
return prefixes.get();
|
||||
}
|
||||
|
||||
public Set<String> suffixes() {
|
||||
return suffixes.get();
|
||||
}
|
||||
|
||||
public Set<String> contains() {
|
||||
return contains.get();
|
||||
}
|
||||
|
||||
public SetValue prefixesValue() {
|
||||
return prefixes;
|
||||
}
|
||||
|
||||
public SetValue suffixesValue() {
|
||||
return suffixes;
|
||||
}
|
||||
|
||||
public SetValue containsValue() {
|
||||
return contains;
|
||||
}
|
||||
|
||||
public boolean addPrefix(String value) {
|
||||
return add(prefixes, value);
|
||||
}
|
||||
|
||||
public boolean addSuffix(String value) {
|
||||
return add(suffixes, value);
|
||||
}
|
||||
|
||||
public boolean addContains(String value) {
|
||||
return add(contains, value);
|
||||
}
|
||||
|
||||
public boolean removePrefix(String value) {
|
||||
return remove(prefixes, value);
|
||||
}
|
||||
|
||||
public boolean removeSuffix(String value) {
|
||||
return remove(suffixes, value);
|
||||
}
|
||||
|
||||
public boolean removeContains(String value) {
|
||||
return remove(contains, value);
|
||||
}
|
||||
|
||||
public boolean matches(String name) {
|
||||
return matches(name, null);
|
||||
}
|
||||
|
||||
public boolean matches(String name, Component displayName) {
|
||||
if (!enabled()) return false;
|
||||
Component convertedDisplay = displayName == null ? null : LegacyTextUtil.convertLegacyCodes(displayName);
|
||||
String display = convertedDisplay == null ? "" : convertedDisplay.getString();
|
||||
return StaffHeuristicsMatcher.matches(name, display, prefixes.get(), suffixes.get(), contains.get());
|
||||
}
|
||||
|
||||
public void save() {
|
||||
ConfigSerializer.requestSave(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConfigName() {
|
||||
return "staffheuristics";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ConfigValue<?>> getConfigValues() {
|
||||
List<ConfigValue<?>> out = new ArrayList<>();
|
||||
out.add(enabled);
|
||||
out.add(prefixes);
|
||||
out.add(suffixes);
|
||||
out.add(contains);
|
||||
return out;
|
||||
}
|
||||
|
||||
private static boolean add(SetValue target, String raw) {
|
||||
String value = cleanRule(raw);
|
||||
if (value.isEmpty()) return false;
|
||||
boolean changed = target.get().add(value);
|
||||
if (changed) StaffHeuristicsConfig.get().save();
|
||||
return changed;
|
||||
}
|
||||
|
||||
private static boolean remove(SetValue target, String raw) {
|
||||
if (raw == null || raw.isBlank()) return false;
|
||||
String selected = null;
|
||||
for (String entry : target.get()) {
|
||||
if (entry != null && entry.equalsIgnoreCase(raw.trim())) {
|
||||
selected = entry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
boolean changed = selected != null && target.get().remove(selected);
|
||||
if (changed) StaffHeuristicsConfig.get().save();
|
||||
return changed;
|
||||
}
|
||||
|
||||
public static String cleanRule(String raw) {
|
||||
return raw == null ? "" : LegacyTextUtil.stripLegacy(raw).trim();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,154 +0,0 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 pivosos2007.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.features.relations;
|
||||
|
||||
import dev.loki.lovisual.util.text.LegacyTextUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
enum StaffHeuristicsMatcher {
|
||||
;
|
||||
|
||||
static boolean matches(String name,
|
||||
String displayName,
|
||||
Set<String> prefixes,
|
||||
Set<String> suffixes,
|
||||
Set<String> contains) {
|
||||
String plainName = normalizeText(name);
|
||||
String display = normalizeText(displayName);
|
||||
|
||||
if (matchesText(plainName, prefixes, suffixes, contains)) return true;
|
||||
if (display.isBlank()) return false;
|
||||
if (matchesText(display, prefixes, suffixes, contains)) return true;
|
||||
return matchesAffixesAroundName(plainName, display, prefixes, suffixes);
|
||||
}
|
||||
|
||||
private static boolean matchesText(String text,
|
||||
Set<String> prefixes,
|
||||
Set<String> suffixes,
|
||||
Set<String> contains) {
|
||||
if (text.isBlank()) return false;
|
||||
for (String prefix : prefixes) {
|
||||
String rule = normalizeText(prefix);
|
||||
if (!rule.isBlank() && startsWithRule(text, rule)) return true;
|
||||
}
|
||||
for (String suffix : suffixes) {
|
||||
String rule = normalizeText(suffix);
|
||||
if (!rule.isBlank() && endsWithRule(text, rule)) return true;
|
||||
}
|
||||
for (String needle : contains) {
|
||||
String rule = normalizeText(needle);
|
||||
if (!rule.isBlank() && containsRule(text, rule)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean matchesAffixesAroundName(String plainName,
|
||||
String display,
|
||||
Set<String> prefixes,
|
||||
Set<String> suffixes) {
|
||||
if (plainName.isBlank()) return false;
|
||||
|
||||
int from = 0;
|
||||
while (from < display.length()) {
|
||||
int idx = display.indexOf(plainName, from);
|
||||
if (idx < 0) return false;
|
||||
|
||||
String beforeName = display.substring(0, idx);
|
||||
String afterName = display.substring(idx + plainName.length());
|
||||
if (matchesRegion(beforeName, prefixes) || matchesRegion(afterName, suffixes)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
from = idx + Math.max(plainName.length(), 1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean matchesRegion(String text, Set<String> rules) {
|
||||
if (text.isBlank()) return false;
|
||||
for (String raw : rules) {
|
||||
String rule = normalizeText(raw);
|
||||
if (!rule.isBlank() && containsRule(text, rule)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean startsWithRule(String text, String rule) {
|
||||
if (text.startsWith(rule)) return true;
|
||||
String compactRule = compactKey(rule);
|
||||
return !compactRule.isBlank() && compactKey(text).startsWith(compactRule);
|
||||
}
|
||||
|
||||
private static boolean endsWithRule(String text, String rule) {
|
||||
if (text.endsWith(rule)) return true;
|
||||
String compactRule = compactKey(rule);
|
||||
return !compactRule.isBlank() && compactKey(text).endsWith(compactRule);
|
||||
}
|
||||
|
||||
private static boolean containsRule(String text, String rule) {
|
||||
return text.contains(rule) || containsCompactTokenSequence(text, rule);
|
||||
}
|
||||
|
||||
private static boolean containsCompactTokenSequence(String text, String rule) {
|
||||
String compactRule = compactKey(rule);
|
||||
if (compactRule.isBlank()) return false;
|
||||
|
||||
List<String> tokens = compactTokens(text);
|
||||
for (int start = 0; start < tokens.size(); start++) {
|
||||
StringBuilder joined = new StringBuilder();
|
||||
for (int i = start; i < tokens.size(); i++) {
|
||||
joined.append(tokens.get(i));
|
||||
if (joined.length() >= compactRule.length()) {
|
||||
if (joined.toString().equals(compactRule)) return true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String compactKey(String text) {
|
||||
if (text == null || text.isBlank()) return "";
|
||||
StringBuilder out = new StringBuilder(text.length());
|
||||
for (String token : compactTokens(text)) {
|
||||
out.append(token);
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
private static List<String> compactTokens(String text) {
|
||||
List<String> out = new ArrayList<>();
|
||||
if (text == null || text.isBlank()) return out;
|
||||
|
||||
StringBuilder token = new StringBuilder();
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char ch = text.charAt(i);
|
||||
if (Character.isLetterOrDigit(ch) || ch == '_') {
|
||||
token.append(ch);
|
||||
continue;
|
||||
}
|
||||
if (!token.isEmpty()) {
|
||||
out.add(token.toString());
|
||||
token.setLength(0);
|
||||
}
|
||||
}
|
||||
if (!token.isEmpty()) {
|
||||
out.add(token.toString());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static String normalizeText(String raw) {
|
||||
if (raw == null || raw.isBlank()) return "";
|
||||
return LegacyTextUtil.stripLegacy(raw).trim().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
|
|
@ -14,13 +14,17 @@ import java.util.HashMap;
|
|||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public enum StaffTracker {
|
||||
;
|
||||
public final class StaffTracker {
|
||||
|
||||
public static final StaffTracker INSTANCE = new StaffTracker();
|
||||
|
||||
private static final long TIMEOUT = 1500; // время ожидания TAB после исчезновения entity
|
||||
private static final Map<UUID, StaffInfo> map = new HashMap<>();
|
||||
private final Map<UUID, StaffInfo> map = new HashMap<>();
|
||||
|
||||
private static StaffInfo get(UUID id) {
|
||||
private StaffTracker() {
|
||||
}
|
||||
|
||||
private StaffInfo get(UUID id) {
|
||||
return map.computeIfAbsent(id, k -> {
|
||||
StaffInfo i = new StaffInfo();
|
||||
i.uuid = k;
|
||||
|
|
@ -29,7 +33,7 @@ public enum StaffTracker {
|
|||
});
|
||||
}
|
||||
|
||||
public static void onTabUpdate(UUID id, String name, Component displayName, Boolean listed, GameType gm) {
|
||||
public void onTabUpdate(UUID id, String name, Component displayName, Boolean listed, GameType gm) {
|
||||
StaffInfo i = get(id);
|
||||
if (displayName != null) i.displayName = displayName;
|
||||
|
||||
|
|
@ -40,7 +44,7 @@ public enum StaffTracker {
|
|||
recalc(i);
|
||||
}
|
||||
|
||||
public static void onEntityAppear(UUID id, String name) {
|
||||
public void onEntityAppear(UUID id, String name) {
|
||||
StaffInfo i = get(id);
|
||||
|
||||
i.entityPresent = true;
|
||||
|
|
@ -53,7 +57,7 @@ public enum StaffTracker {
|
|||
// PUBLIC EVENTS
|
||||
// ======================
|
||||
|
||||
public static void onEntityDisappear(UUID id) {
|
||||
public void onEntityDisappear(UUID id) {
|
||||
StaffInfo i = get(id);
|
||||
|
||||
i.entityPresent = false;
|
||||
|
|
@ -62,7 +66,7 @@ public enum StaffTracker {
|
|||
recalc(i);
|
||||
}
|
||||
|
||||
private static void recalc(StaffInfo i) {
|
||||
private void recalc(StaffInfo i) {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
// entity исчезла → показываем таймер ожидания TAB (WAITING)
|
||||
|
|
@ -98,7 +102,7 @@ public enum StaffTracker {
|
|||
i.status = Status.OFFLINE;
|
||||
}
|
||||
|
||||
public static Map<UUID, StaffInfo> all() {
|
||||
public Map<UUID, StaffInfo> all() {
|
||||
return map;
|
||||
}
|
||||
|
||||
|
|
@ -106,7 +110,7 @@ public enum StaffTracker {
|
|||
// LOGIC
|
||||
// ======================
|
||||
|
||||
public static void resetAll() {
|
||||
public void resetAll() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,34 +32,38 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public enum BackdoorProtection {
|
||||
;
|
||||
public final class BackdoorProtection {
|
||||
|
||||
private static final Map<String, Set<String>> TRANSLATION_KEY_PACKS = new ConcurrentHashMap<>();
|
||||
private static final Set<String> VANILLA_KEYBINDS = ConcurrentHashMap.newKeySet();
|
||||
public static final BackdoorProtection INSTANCE = new BackdoorProtection();
|
||||
|
||||
private static volatile String currentServerAddress;
|
||||
private static volatile boolean currentServerLocal;
|
||||
private final Map<String, Set<String>> translationKeyPacks = new ConcurrentHashMap<>();
|
||||
private final Set<String> vanillaKeybinds = ConcurrentHashMap.newKeySet();
|
||||
|
||||
public static void resetLoadedTranslations() {
|
||||
TRANSLATION_KEY_PACKS.clear();
|
||||
private volatile String currentServerAddress;
|
||||
private volatile boolean currentServerLocal;
|
||||
|
||||
private BackdoorProtection() {
|
||||
}
|
||||
|
||||
public static void registerTranslationKey(String key, String packId) {
|
||||
public void resetLoadedTranslations() {
|
||||
translationKeyPacks.clear();
|
||||
}
|
||||
|
||||
public void registerTranslationKey(String key, String packId) {
|
||||
if (key == null || key.isBlank() || packId == null || packId.isBlank()) return;
|
||||
TRANSLATION_KEY_PACKS.computeIfAbsent(key, ignored -> ConcurrentHashMap.newKeySet()).add(packId);
|
||||
translationKeyPacks.computeIfAbsent(key, ignored -> ConcurrentHashMap.newKeySet()).add(packId);
|
||||
}
|
||||
|
||||
public static void resetVanillaKeybinds() {
|
||||
VANILLA_KEYBINDS.clear();
|
||||
public void resetVanillaKeybinds() {
|
||||
vanillaKeybinds.clear();
|
||||
}
|
||||
|
||||
public static void registerVanillaKeybind(String key) {
|
||||
public void registerVanillaKeybind(String key) {
|
||||
if (key == null || key.isBlank()) return;
|
||||
VANILLA_KEYBINDS.add(key);
|
||||
vanillaKeybinds.add(key);
|
||||
}
|
||||
|
||||
public static void updateServerConnection(Connection connection) {
|
||||
public void updateServerConnection(Connection connection) {
|
||||
currentServerAddress = null;
|
||||
currentServerLocal = true;
|
||||
if (connection == null || connection.isMemoryConnection()) return;
|
||||
|
|
@ -71,7 +75,7 @@ public enum BackdoorProtection {
|
|||
currentServerLocal = isLocalAddressQuiet(host);
|
||||
}
|
||||
|
||||
public static Component filterText(Component input) {
|
||||
public Component filterText(Component input) {
|
||||
if (input == null || !MainConfig.get().isBackdoorTranslationFilterEnabled()) return input;
|
||||
|
||||
ComponentContents content = filterContent(input.getContents());
|
||||
|
|
@ -83,7 +87,7 @@ public enum BackdoorProtection {
|
|||
return filtered;
|
||||
}
|
||||
|
||||
private static ComponentContents filterContent(ComponentContents content) {
|
||||
private ComponentContents filterContent(ComponentContents content) {
|
||||
if (content instanceof FilteredTranslatableTextContent) {
|
||||
return content;
|
||||
}
|
||||
|
|
@ -105,14 +109,14 @@ public enum BackdoorProtection {
|
|||
return content;
|
||||
}
|
||||
|
||||
public static boolean isTranslationAllowed(String key) {
|
||||
public boolean isTranslationAllowed(String key) {
|
||||
MainConfig cfg = MainConfig.get();
|
||||
if (!cfg.isBackdoorTranslationFilterEnabled()) return true;
|
||||
if (key == null || key.isBlank()) return true;
|
||||
|
||||
Set<String> packIds = TRANSLATION_KEY_PACKS.get(key);
|
||||
Set<String> packIds = translationKeyPacks.get(key);
|
||||
if (packIds == null || packIds.isEmpty()) {
|
||||
return TRANSLATION_KEY_PACKS.isEmpty();
|
||||
return translationKeyPacks.isEmpty();
|
||||
}
|
||||
|
||||
Set<String> allowedPacks = cfg.getBackdoorAllowedTranslationPacks();
|
||||
|
|
@ -124,14 +128,14 @@ public enum BackdoorProtection {
|
|||
return false;
|
||||
}
|
||||
|
||||
public static boolean isKeybindAllowed(String key) {
|
||||
public boolean isKeybindAllowed(String key) {
|
||||
MainConfig cfg = MainConfig.get();
|
||||
if (!cfg.isBackdoorKeybindFilterEnabled()) return true;
|
||||
if (key == null || key.isBlank()) return true;
|
||||
return VANILLA_KEYBINDS.contains(key) || cfg.getBackdoorAllowedKeybinds().contains(key);
|
||||
return vanillaKeybinds.contains(key) || cfg.getBackdoorAllowedKeybinds().contains(key);
|
||||
}
|
||||
|
||||
public static void assertRemoteUrlAllowed(URL url) throws UnknownHostException {
|
||||
public void assertRemoteUrlAllowed(URL url) throws UnknownHostException {
|
||||
MainConfig cfg = MainConfig.get();
|
||||
if (!cfg.isBackdoorLocalHttpGuardEnabled() || url == null) return;
|
||||
String host = url.getHost();
|
||||
|
|
@ -143,7 +147,7 @@ public enum BackdoorProtection {
|
|||
throw new IllegalStateException("Blocked local/private HTTP request to " + host + " while connected to a remote server");
|
||||
}
|
||||
|
||||
public static java.nio.file.Path isolatePackCachePath(java.nio.file.Path original) {
|
||||
public java.nio.file.Path isolatePackCachePath(java.nio.file.Path original) {
|
||||
MainConfig cfg = MainConfig.get();
|
||||
if (!cfg.isBackdoorPackCacheIsolationEnabled() || original == null) return original;
|
||||
Minecraft client = Minecraft.getInstance();
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ public abstract class ClientPacketListenerMixin {
|
|||
Entity e = world.getEntity(id);
|
||||
if (!(e instanceof Player player)) continue;
|
||||
|
||||
StaffTracker.onEntityDisappear(player.getUUID());
|
||||
StaffTracker.INSTANCE.onEntityDisappear(player.getUUID());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -147,7 +147,7 @@ public abstract class ClientPacketListenerMixin {
|
|||
Entity entity = world.getEntity(packet.getId());
|
||||
if (!(entity instanceof Player player)) return;
|
||||
|
||||
StaffTracker.onEntityAppear(player.getUUID(), player.getGameProfile().name());
|
||||
StaffTracker.INSTANCE.onEntityAppear(player.getUUID(), player.getGameProfile().name());
|
||||
}
|
||||
|
||||
@Inject(method = "handlePlayerInfoUpdate", at = @At("TAIL"))
|
||||
|
|
@ -174,7 +174,7 @@ public abstract class ClientPacketListenerMixin {
|
|||
displayName = net.minecraft.network.chat.Component.literal(name);
|
||||
}
|
||||
|
||||
StaffTracker.onTabUpdate(uuid, name, displayName, listed, gm);
|
||||
StaffTracker.INSTANCE.onTabUpdate(uuid, name, displayName, listed, gm);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -184,10 +184,10 @@ public abstract class ClientPacketListenerMixin {
|
|||
for (UUID id : packet.profileIds()) {
|
||||
|
||||
// убрали из TAB
|
||||
StaffTracker.onTabUpdate(id, null, null, false, null);
|
||||
StaffTracker.INSTANCE.onTabUpdate(id, null, null, false, null);
|
||||
TotemPopCounter.onPlayerLogout(id);
|
||||
// ВАЖНО — фикс VANISH после кика
|
||||
StaffTracker.onEntityDisappear(id);
|
||||
StaffTracker.INSTANCE.onEntityDisappear(id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|||
import dev.loki.lovisual.features.module.Modules;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.BlockHighlight;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.ReimaginedVisual;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.ShaderSky;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.WorldTweaks;
|
||||
import dev.loki.lovisual.mixins.accessors.GameRendererAccessor;
|
||||
import dev.loki.lovisual.mixins.accessors.LevelRendererAccessor;
|
||||
|
|
@ -232,7 +233,10 @@ public abstract class LevelRendererMixin {
|
|||
}
|
||||
|
||||
ReimaginedVisual module = Modules.get(ReimaginedVisual.class);
|
||||
if (module == null || !module.isEnabled()) return;
|
||||
ShaderSky skyModule = Modules.get(ShaderSky.class);
|
||||
boolean needsCustomSky = (module != null && module.isEnabled())
|
||||
|| (skyModule != null && skyModule.isEnabled());
|
||||
if (!needsCustomSky) return;
|
||||
|
||||
Minecraft client = Minecraft.getInstance();
|
||||
net.minecraft.client.Camera camera = client != null && client.gameRenderer != null
|
||||
|
|
|
|||
|
|
@ -295,7 +295,7 @@ public class MinecraftMixin implements MinecraftGameConfigHolder {
|
|||
|
||||
@Inject(method = "clearDownloadedResourcePacks", at = @At("HEAD"))
|
||||
private void lovisual$onDisconnected(CallbackInfo ci) {
|
||||
StaffTracker.resetAll();
|
||||
StaffTracker.INSTANCE.resetAll();
|
||||
AntiBotTracker.INSTANCE.reset();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ public abstract class AbstractSignEditScreenMixin {
|
|||
)
|
||||
private void lovisual$backdoor$filterSignMessages(SignBlockEntity blockEntity, boolean front, boolean filtered, Component title, CallbackInfo ci) {
|
||||
for (int line = 0; line < this.messages.length; line++) {
|
||||
Component safeText = BackdoorProtection.filterText(this.text.getMessage(line, filtered));
|
||||
Component safeText = BackdoorProtection.INSTANCE.filterText(this.text.getMessage(line, filtered));
|
||||
this.messages[line] = safeText.getString();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,11 +31,11 @@ public abstract class AnvilScreenMixin {
|
|||
|
||||
@WrapOperation(method = "onNameChanged", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/ItemStack;getHoverName()Lnet/minecraft/network/chat/Component;"))
|
||||
private Component lovisual$backdoor$filterRenameName(ItemStack stack, Operation<Component> original) {
|
||||
return BackdoorProtection.filterText(original.call(stack));
|
||||
return BackdoorProtection.INSTANCE.filterText(original.call(stack));
|
||||
}
|
||||
|
||||
@WrapOperation(method = "slotChanged", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/ItemStack;getHoverName()Lnet/minecraft/network/chat/Component;"))
|
||||
private Component lovisual$backdoor$filterSlotUpdateName(ItemStack stack, Operation<Component> original) {
|
||||
return BackdoorProtection.filterText(original.call(stack));
|
||||
return BackdoorProtection.INSTANCE.filterText(original.call(stack));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,6 @@ public abstract class ClientLoginNetworkHandlerMixin {
|
|||
|
||||
@Inject(method = "handleLoginFinished", at = @At("HEAD"))
|
||||
private void lovisual$backdoor$captureServerConnection(ClientboundLoginFinishedPacket packet, CallbackInfo ci) {
|
||||
BackdoorProtection.updateServerConnection(connection);
|
||||
BackdoorProtection.INSTANCE.updateServerConnection(connection);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,9 +39,9 @@ public abstract class GameOptionsMixin {
|
|||
|
||||
@Inject(method = "<init>(Lnet/minecraft/client/Minecraft;Ljava/io/File;)V", at = @At("TAIL"))
|
||||
private void lovisual$backdoor$registerVanillaKeybinds(Minecraft client, File optionsFile, CallbackInfo ci) {
|
||||
BackdoorProtection.resetVanillaKeybinds();
|
||||
BackdoorProtection.INSTANCE.resetVanillaKeybinds();
|
||||
for (KeyMapping keyBinding : keyMappings) {
|
||||
BackdoorProtection.registerVanillaKeybind(keyBinding.getName());
|
||||
BackdoorProtection.INSTANCE.registerVanillaKeybind(keyBinding.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ public abstract class NetworkUtilsMixin {
|
|||
|
||||
@ModifyVariable(method = "downloadFile", at = @At("HEAD"), argsOnly = true, ordinal = 0)
|
||||
private static Path lovisual$backdoor$isolatePackCache(Path original) {
|
||||
return BackdoorProtection.isolatePackCachePath(original);
|
||||
return BackdoorProtection.INSTANCE.isolatePackCachePath(original);
|
||||
}
|
||||
|
||||
@WrapOperation(method = "downloadFile", at = @At(value = "INVOKE", target = "Ljava/net/HttpURLConnection;getInputStream()Ljava/io/InputStream;"))
|
||||
|
|
@ -53,7 +53,7 @@ public abstract class NetworkUtilsMixin {
|
|||
@Local LocalRef<HttpURLConnection> httpURLConnection
|
||||
) throws IOException {
|
||||
HttpURLConnection current = connection;
|
||||
BackdoorProtection.assertRemoteUrlAllowed(current.getURL());
|
||||
BackdoorProtection.INSTANCE.assertRemoteUrlAllowed(current.getURL());
|
||||
current.setInstanceFollowRedirects(false);
|
||||
|
||||
int redirects = 0;
|
||||
|
|
@ -64,7 +64,7 @@ public abstract class NetworkUtilsMixin {
|
|||
}
|
||||
|
||||
URL next = resolveRedirect(current);
|
||||
BackdoorProtection.assertRemoteUrlAllowed(next);
|
||||
BackdoorProtection.INSTANCE.assertRemoteUrlAllowed(next);
|
||||
|
||||
current = (HttpURLConnection) next.openConnection(proxy);
|
||||
current.setInstanceFollowRedirects(false);
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ public abstract class TranslationStorageMixin {
|
|||
|
||||
@Inject(method = "loadFrom(Lnet/minecraft/server/packs/resources/ResourceManager;Ljava/util/List;Z)Lnet/minecraft/client/resources/language/ClientLanguage;", at = @At("HEAD"))
|
||||
private static void lovisual$backdoor$resetLoadedTranslations(ResourceManager resourceManager, List<String> definitions, boolean rightToLeft, CallbackInfoReturnable<ClientLanguage> cir) {
|
||||
BackdoorProtection.resetLoadedTranslations();
|
||||
BackdoorProtection.INSTANCE.resetLoadedTranslations();
|
||||
}
|
||||
|
||||
@WrapOperation(
|
||||
|
|
@ -53,7 +53,7 @@ public abstract class TranslationStorageMixin {
|
|||
) {
|
||||
String packId = resource.sourcePackId();
|
||||
original.call(inputStream, (BiConsumer<String, String>) (key, value) -> {
|
||||
BackdoorProtection.registerTranslationKey(key, packId);
|
||||
BackdoorProtection.INSTANCE.registerTranslationKey(key, packId);
|
||||
consumer.accept(key, value);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ public enum LoVisualRenderPipelines {
|
|||
public static final Identifier SHADER_POS_TEX_COLOR_SKY_FOG_ADDITIVE_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/pos_tex_color_sky_fog_additive.frag");
|
||||
public static final Identifier SHADER_POS_COLOR_SKYBOX_SHADER_VERT = Identifier.fromNamespaceAndPath("lovisual", "shaders/pos_color_skybox_shader.vert");
|
||||
public static final Identifier SHADER_REIMAGINED_SKYBOX_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/reimagined_skybox.frag");
|
||||
public static final Identifier SHADER_SHADER_SKY_THUNDER_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/shader_sky_thunder.frag");
|
||||
public static final Identifier SHADER_SHADER_SKY_PULSAR_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/shader_sky_pulsar.frag");
|
||||
public static final Identifier SHADER_POS_TEX_COLOR_RECT_PARAMS_VERT = Identifier.fromNamespaceAndPath("lovisual", "shaders/pos_tex_color_rect_params.vert");
|
||||
public static final Identifier SHADER_POS_TEX_LOCAL_COLOR_RECT_PARAMS_VERT = Identifier.fromNamespaceAndPath("lovisual", "shaders/pos_tex_local_color_rect_params.vert");
|
||||
public static final Identifier SHADER_POS_TEX_LOCAL_COLOR_RECT_PARAMS2_VERT = Identifier.fromNamespaceAndPath("lovisual", "shaders/pos_tex_local_color_rect_params2.vert");
|
||||
|
|
@ -109,6 +111,13 @@ public enum LoVisualRenderPipelines {
|
|||
public static final Identifier SHADER_HAND_METALLIC_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/hand_metallic.frag");
|
||||
public static final Identifier SHADER_UI_BLUR_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/ui_blur.frag");
|
||||
public static final Identifier SHADER_HAND_GLASS_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/hand_glass.frag");
|
||||
public static final Identifier SHADER_HAND_CHAMS_SOLID_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/hand_chams_solid.frag");
|
||||
public static final Identifier SHADER_HAND_CHAMS_CHROMA_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/hand_chams_chroma.frag");
|
||||
public static final Identifier SHADER_HAND_CHAMS_STRIPES_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/hand_chams_stripes.frag");
|
||||
public static final Identifier SHADER_HAND_CHAMS_GLOW_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/hand_chams_glow.frag");
|
||||
public static final Identifier SHADER_HAND_CHAMS_SNOW_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/hand_chams_snow.frag");
|
||||
public static final Identifier SHADER_HAND_CHAMS_BALATRO_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/hand_chams_balatro.frag");
|
||||
public static final Identifier SHADER_HAND_CHAMS_BLEND_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/hand_chams_blend.frag");
|
||||
public static final Identifier SHADER_SKY_SUN_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/sky_sun.frag");
|
||||
public static final Identifier SHADER_MENU_BACKGROUND_WAVES_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/menu_background_waves.frag");
|
||||
public static final Identifier SHADER_MENU_BACKGROUND_AURORA_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/menu_background_aurora.frag");
|
||||
|
|
@ -970,6 +979,105 @@ public enum LoVisualRenderPipelines {
|
|||
.withCull(false)
|
||||
.build()
|
||||
);
|
||||
public static final RenderPipeline HAND_CHAMS_SOLID = add(new ExtendedRenderPipelineBuilder(MESH_UNIFORMS)
|
||||
.withLocation(Identifier.fromNamespaceAndPath("lovisual", "pipeline/hand_chams_solid"))
|
||||
.withVertexFormat(LoVisualVertexFormats.POS2, com.mojang.blaze3d.PrimitiveTopology.TRIANGLES)
|
||||
.withVertexShader(SHADER_DAMAGE_TINT_VERT)
|
||||
.withFragmentShader(SHADER_HAND_CHAMS_SOLID_FRAG)
|
||||
.withSampler("u_Src")
|
||||
.withSampler("u_Mask")
|
||||
.withUniform("HandChams", UniformType.UNIFORM_BUFFER)
|
||||
.withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST)
|
||||
.withDepthWrite(false)
|
||||
.withBlend(BlendFunction.TRANSLUCENT)
|
||||
.withCull(false)
|
||||
.build()
|
||||
);
|
||||
public static final RenderPipeline HAND_CHAMS_CHROMA = add(new ExtendedRenderPipelineBuilder(MESH_UNIFORMS)
|
||||
.withLocation(Identifier.fromNamespaceAndPath("lovisual", "pipeline/hand_chams_chroma"))
|
||||
.withVertexFormat(LoVisualVertexFormats.POS2, com.mojang.blaze3d.PrimitiveTopology.TRIANGLES)
|
||||
.withVertexShader(SHADER_DAMAGE_TINT_VERT)
|
||||
.withFragmentShader(SHADER_HAND_CHAMS_CHROMA_FRAG)
|
||||
.withSampler("u_Src")
|
||||
.withSampler("u_Mask")
|
||||
.withUniform("HandChams", UniformType.UNIFORM_BUFFER)
|
||||
.withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST)
|
||||
.withDepthWrite(false)
|
||||
.withBlend(BlendFunction.TRANSLUCENT)
|
||||
.withCull(false)
|
||||
.build()
|
||||
);
|
||||
public static final RenderPipeline HAND_CHAMS_STRIPES = add(new ExtendedRenderPipelineBuilder(MESH_UNIFORMS)
|
||||
.withLocation(Identifier.fromNamespaceAndPath("lovisual", "pipeline/hand_chams_stripes"))
|
||||
.withVertexFormat(LoVisualVertexFormats.POS2, com.mojang.blaze3d.PrimitiveTopology.TRIANGLES)
|
||||
.withVertexShader(SHADER_DAMAGE_TINT_VERT)
|
||||
.withFragmentShader(SHADER_HAND_CHAMS_STRIPES_FRAG)
|
||||
.withSampler("u_Src")
|
||||
.withSampler("u_Mask")
|
||||
.withUniform("HandChams", UniformType.UNIFORM_BUFFER)
|
||||
.withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST)
|
||||
.withDepthWrite(false)
|
||||
.withBlend(BlendFunction.TRANSLUCENT)
|
||||
.withCull(false)
|
||||
.build()
|
||||
);
|
||||
public static final RenderPipeline HAND_CHAMS_GLOW = add(new ExtendedRenderPipelineBuilder(MESH_UNIFORMS)
|
||||
.withLocation(Identifier.fromNamespaceAndPath("lovisual", "pipeline/hand_chams_glow"))
|
||||
.withVertexFormat(LoVisualVertexFormats.POS2, com.mojang.blaze3d.PrimitiveTopology.TRIANGLES)
|
||||
.withVertexShader(SHADER_DAMAGE_TINT_VERT)
|
||||
.withFragmentShader(SHADER_HAND_CHAMS_GLOW_FRAG)
|
||||
.withSampler("u_Src")
|
||||
.withSampler("u_Mask")
|
||||
.withUniform("HandChams", UniformType.UNIFORM_BUFFER)
|
||||
.withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST)
|
||||
.withDepthWrite(false)
|
||||
.withBlend(BlendFunction.TRANSLUCENT)
|
||||
.withCull(false)
|
||||
.build()
|
||||
);
|
||||
public static final RenderPipeline HAND_CHAMS_SNOW = add(new ExtendedRenderPipelineBuilder(MESH_UNIFORMS)
|
||||
.withLocation(Identifier.fromNamespaceAndPath("lovisual", "pipeline/hand_chams_snow"))
|
||||
.withVertexFormat(LoVisualVertexFormats.POS2, com.mojang.blaze3d.PrimitiveTopology.TRIANGLES)
|
||||
.withVertexShader(SHADER_DAMAGE_TINT_VERT)
|
||||
.withFragmentShader(SHADER_HAND_CHAMS_SNOW_FRAG)
|
||||
.withSampler("u_Src")
|
||||
.withSampler("u_Mask")
|
||||
.withUniform("HandChams", UniformType.UNIFORM_BUFFER)
|
||||
.withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST)
|
||||
.withDepthWrite(false)
|
||||
.withBlend(BlendFunction.TRANSLUCENT)
|
||||
.withCull(false)
|
||||
.build()
|
||||
);
|
||||
public static final RenderPipeline HAND_CHAMS_BALATRO = add(new ExtendedRenderPipelineBuilder(MESH_UNIFORMS)
|
||||
.withLocation(Identifier.fromNamespaceAndPath("lovisual", "pipeline/hand_chams_balatro"))
|
||||
.withVertexFormat(LoVisualVertexFormats.POS2, com.mojang.blaze3d.PrimitiveTopology.TRIANGLES)
|
||||
.withVertexShader(SHADER_DAMAGE_TINT_VERT)
|
||||
.withFragmentShader(SHADER_HAND_CHAMS_BALATRO_FRAG)
|
||||
.withSampler("u_Src")
|
||||
.withSampler("u_Mask")
|
||||
.withUniform("HandChams", UniformType.UNIFORM_BUFFER)
|
||||
.withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST)
|
||||
.withDepthWrite(false)
|
||||
.withBlend(BlendFunction.TRANSLUCENT)
|
||||
.withCull(false)
|
||||
.build()
|
||||
);
|
||||
public static final RenderPipeline HAND_CHAMS_BLEND = add(new ExtendedRenderPipelineBuilder(MESH_UNIFORMS)
|
||||
.withLocation(Identifier.fromNamespaceAndPath("lovisual", "pipeline/hand_chams_blend"))
|
||||
.withVertexFormat(LoVisualVertexFormats.POS2, com.mojang.blaze3d.PrimitiveTopology.TRIANGLES)
|
||||
.withVertexShader(SHADER_DAMAGE_TINT_VERT)
|
||||
.withFragmentShader(SHADER_HAND_CHAMS_BLEND_FRAG)
|
||||
.withSampler("u_Shader1")
|
||||
.withSampler("u_Shader2")
|
||||
.withSampler("u_Mask")
|
||||
.withUniform("HandBlend", UniformType.UNIFORM_BUFFER)
|
||||
.withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST)
|
||||
.withDepthWrite(false)
|
||||
.withBlend(BlendFunction.TRANSLUCENT)
|
||||
.withCull(false)
|
||||
.build()
|
||||
);
|
||||
private static final RenderPipeline.Snippet FOG_UNIFORMS = new ExtendedRenderPipelineBuilder()
|
||||
.withUniform("Fog", UniformType.UNIFORM_BUFFER)
|
||||
.buildSnippet();
|
||||
|
|
@ -1020,6 +1128,36 @@ public enum LoVisualRenderPipelines {
|
|||
.withCull(false)
|
||||
.build()
|
||||
);
|
||||
/**
|
||||
* Fullscreen procedural ShaderSky (Thunder) with sky/fog blending.
|
||||
*/
|
||||
public static final RenderPipeline WORLD_SHADER_SKY_THUNDER = add(new ExtendedRenderPipelineBuilder(MESH_UNIFORMS)
|
||||
.withLocation(Identifier.fromNamespaceAndPath("lovisual", "pipeline/world_shader_sky_thunder"))
|
||||
.withVertexFormat(LoVisualVertexFormats.POS2, com.mojang.blaze3d.PrimitiveTopology.TRIANGLES)
|
||||
.withVertexShader(SHADER_DAMAGE_TINT_VERT)
|
||||
.withFragmentShader(SHADER_SHADER_SKY_THUNDER_FRAG)
|
||||
.withUniform("ShaderSky", UniformType.UNIFORM_BUFFER)
|
||||
.withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST)
|
||||
.withDepthWrite(false)
|
||||
.withBlend(BlendFunction.TRANSLUCENT)
|
||||
.withCull(false)
|
||||
.build()
|
||||
);
|
||||
/**
|
||||
* Fullscreen procedural ShaderSky (Pulsar) with sky/fog blending.
|
||||
*/
|
||||
public static final RenderPipeline WORLD_SHADER_SKY_PULSAR = add(new ExtendedRenderPipelineBuilder(MESH_UNIFORMS)
|
||||
.withLocation(Identifier.fromNamespaceAndPath("lovisual", "pipeline/world_shader_sky_pulsar"))
|
||||
.withVertexFormat(LoVisualVertexFormats.POS2, com.mojang.blaze3d.PrimitiveTopology.TRIANGLES)
|
||||
.withVertexShader(SHADER_DAMAGE_TINT_VERT)
|
||||
.withFragmentShader(SHADER_SHADER_SKY_PULSAR_FRAG)
|
||||
.withUniform("ShaderSky", UniformType.UNIFORM_BUFFER)
|
||||
.withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST)
|
||||
.withDepthWrite(false)
|
||||
.withBlend(BlendFunction.TRANSLUCENT)
|
||||
.withCull(false)
|
||||
.build()
|
||||
);
|
||||
private static final RenderPipeline.Snippet UI_BATCH_UNIFORMS = new ExtendedRenderPipelineBuilder()
|
||||
.withUniform("UIBatch", UniformType.UNIFORM_BUFFER)
|
||||
.buildSnippet();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
package dev.loki.lovisual.render.engine.uniform.impl;
|
||||
|
||||
import com.mojang.blaze3d.buffers.GpuBufferSlice;
|
||||
import com.mojang.blaze3d.buffers.Std140Builder;
|
||||
import com.mojang.blaze3d.buffers.Std140SizeCalculator;
|
||||
import dev.loki.lovisual.render.engine.core.LoVisualRenderSystem;
|
||||
import dev.loki.lovisual.render.engine.rhi.uniform.LoVisualUniformAllocator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* UBO для dual-shader режима руки: смешивание двух шейдеров.
|
||||
*/
|
||||
public enum HandBlendUniforms {
|
||||
;
|
||||
public static final int SIZE = new Std140SizeCalculator()
|
||||
.putVec4()
|
||||
.get();
|
||||
|
||||
private static final Data DATA = new Data();
|
||||
private static final String UNIFORM_NAME = "LoVisual - HandBlend UBO";
|
||||
private static final int EXPECTED_WRITES_PER_FRAME = 1;
|
||||
|
||||
public static void update(int blendMode, float blendIntensity, float shader1Strength, float shader2Strength) {
|
||||
DATA.blend[0] = blendMode;
|
||||
DATA.blend[1] = Math.max(0.0f, Math.min(1.0f, blendIntensity));
|
||||
DATA.blend[2] = Math.max(0.0f, Math.min(1.0f, shader1Strength));
|
||||
DATA.blend[3] = Math.max(0.0f, Math.min(1.0f, shader2Strength));
|
||||
LoVisualRenderSystem.uniforms().writeCached(UNIFORM_NAME, SIZE, EXPECTED_WRITES_PER_FRAME, 1, true, DATA);
|
||||
}
|
||||
|
||||
public static GpuBufferSlice get() {
|
||||
return LoVisualRenderSystem.uniforms().current(UNIFORM_NAME);
|
||||
}
|
||||
|
||||
private static final class Data implements LoVisualUniformAllocator.UniformWriter {
|
||||
private final float[] blend = new float[4];
|
||||
|
||||
@Override
|
||||
public void write(ByteBuffer buffer) {
|
||||
Std140Builder.intoBuffer(buffer)
|
||||
.putFloat(blend[0]).putFloat(blend[1]).putFloat(blend[2]).putFloat(blend[3]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
package dev.loki.lovisual.render.engine.uniform.impl;
|
||||
|
||||
import com.mojang.blaze3d.buffers.GpuBufferSlice;
|
||||
import com.mojang.blaze3d.buffers.Std140Builder;
|
||||
import com.mojang.blaze3d.buffers.Std140SizeCalculator;
|
||||
import dev.loki.lovisual.render.engine.core.LoVisualRenderSystem;
|
||||
import dev.loki.lovisual.render.engine.rhi.uniform.LoVisualUniformAllocator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Единый UBO для дополнительных режимов руки Chams (Snow/Chroma/Solid/Stripes/Glow/Balatro).
|
||||
* Неиспользуемые режимом поля просто игнорируются шейдером.
|
||||
*/
|
||||
public enum HandChamsUniforms {
|
||||
;
|
||||
public static final int SIZE = new Std140SizeCalculator()
|
||||
.putVec4() // u_View
|
||||
.putVec4() // u_Mix
|
||||
.putVec4() // u_ColorA
|
||||
.putVec4() // u_ColorB
|
||||
.putVec4() // u_ColorC
|
||||
.putVec4() // u_ColorD
|
||||
.putVec4() // u_Common
|
||||
.putVec4() // u_Glow
|
||||
.putVec4() // u_Flags
|
||||
.get();
|
||||
|
||||
private static final Data DATA = new Data();
|
||||
private static final String UNIFORM_NAME = "LoVisual - HandChams UBO";
|
||||
private static final int EXPECTED_WRITES_PER_FRAME = 1;
|
||||
|
||||
public static void update(int colorA,
|
||||
int colorB,
|
||||
int colorC,
|
||||
int colorD,
|
||||
float resolutionX,
|
||||
float resolutionY,
|
||||
float time,
|
||||
float effectAlpha,
|
||||
float chromaSpeed,
|
||||
float chromaSaturation,
|
||||
float chromaBrightness,
|
||||
float snowIntensity,
|
||||
float snowSpeed,
|
||||
float stripesWidth,
|
||||
float stripesSpeed,
|
||||
boolean fillGlow,
|
||||
float glowRadius,
|
||||
float glowPower,
|
||||
float glowDispersion,
|
||||
boolean hideHand) {
|
||||
putRgb(DATA.colorA, colorA);
|
||||
putRgb(DATA.colorB, colorB);
|
||||
putRgb(DATA.colorC, colorC);
|
||||
putRgb(DATA.colorD, colorD);
|
||||
|
||||
DATA.view[0] = resolutionX;
|
||||
DATA.view[1] = resolutionY;
|
||||
DATA.view[2] = time;
|
||||
|
||||
DATA.mix[0] = clamp01(effectAlpha);
|
||||
DATA.mix[1] = chromaSpeed;
|
||||
DATA.mix[2] = clamp01(chromaSaturation);
|
||||
DATA.mix[3] = chromaBrightness;
|
||||
|
||||
DATA.common[0] = snowIntensity;
|
||||
DATA.common[1] = snowSpeed;
|
||||
DATA.common[2] = stripesWidth;
|
||||
DATA.common[3] = stripesSpeed;
|
||||
|
||||
DATA.glow[0] = fillGlow ? 1.0f : 0.0f;
|
||||
DATA.glow[1] = glowRadius;
|
||||
DATA.glow[2] = glowPower;
|
||||
DATA.glow[3] = glowDispersion;
|
||||
|
||||
DATA.flags[0] = hideHand ? 1.0f : 0.0f;
|
||||
|
||||
LoVisualRenderSystem.uniforms().writeCached(UNIFORM_NAME, SIZE, EXPECTED_WRITES_PER_FRAME, 1, true, DATA);
|
||||
}
|
||||
|
||||
public static GpuBufferSlice get() {
|
||||
return LoVisualRenderSystem.uniforms().current(UNIFORM_NAME);
|
||||
}
|
||||
|
||||
private static void putRgb(float[] out, int argb) {
|
||||
out[0] = ((argb >>> 16) & 0xFF) / 255f;
|
||||
out[1] = ((argb >>> 8) & 0xFF) / 255f;
|
||||
out[2] = (argb & 0xFF) / 255f;
|
||||
out[3] = 1.0f;
|
||||
}
|
||||
|
||||
private static float clamp01(float value) {
|
||||
return Math.max(0.0f, Math.min(1.0f, value));
|
||||
}
|
||||
|
||||
private static final class Data implements LoVisualUniformAllocator.UniformWriter {
|
||||
private final float[] view = new float[4];
|
||||
private final float[] mix = new float[4];
|
||||
private final float[] colorA = new float[4];
|
||||
private final float[] colorB = new float[4];
|
||||
private final float[] colorC = new float[4];
|
||||
private final float[] colorD = new float[4];
|
||||
private final float[] common = new float[4];
|
||||
private final float[] glow = new float[4];
|
||||
private final float[] flags = new float[4];
|
||||
|
||||
@Override
|
||||
public void write(ByteBuffer buffer) {
|
||||
Std140Builder.intoBuffer(buffer)
|
||||
.putFloat(view[0]).putFloat(view[1]).putFloat(view[2]).putFloat(view[3])
|
||||
.putFloat(mix[0]).putFloat(mix[1]).putFloat(mix[2]).putFloat(mix[3])
|
||||
.putFloat(colorA[0]).putFloat(colorA[1]).putFloat(colorA[2]).putFloat(colorA[3])
|
||||
.putFloat(colorB[0]).putFloat(colorB[1]).putFloat(colorB[2]).putFloat(colorB[3])
|
||||
.putFloat(colorC[0]).putFloat(colorC[1]).putFloat(colorC[2]).putFloat(colorC[3])
|
||||
.putFloat(colorD[0]).putFloat(colorD[1]).putFloat(colorD[2]).putFloat(colorD[3])
|
||||
.putFloat(common[0]).putFloat(common[1]).putFloat(common[2]).putFloat(common[3])
|
||||
.putFloat(glow[0]).putFloat(glow[1]).putFloat(glow[2]).putFloat(glow[3])
|
||||
.putFloat(flags[0]).putFloat(flags[1]).putFloat(flags[2]).putFloat(flags[3]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
package dev.loki.lovisual.render.engine.uniform.impl;
|
||||
|
||||
import com.mojang.blaze3d.buffers.GpuBufferSlice;
|
||||
import com.mojang.blaze3d.buffers.Std140Builder;
|
||||
import com.mojang.blaze3d.buffers.Std140SizeCalculator;
|
||||
import dev.loki.lovisual.render.engine.core.LoVisualRenderSystem;
|
||||
import dev.loki.lovisual.render.engine.rhi.uniform.LoVisualUniformAllocator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* UBO для модуля ShaderSky: общий набор параметров всех режимов неба.
|
||||
*/
|
||||
public enum ShaderSkyUniforms {
|
||||
;
|
||||
public static final int SIZE = new Std140SizeCalculator()
|
||||
.putVec4() // u_Color
|
||||
.putVec4() // u_Params
|
||||
.putVec4() // u_View
|
||||
.putVec4() // u_Cam
|
||||
.putVec4() // u_Thunder
|
||||
.get();
|
||||
|
||||
private static final Data DATA = new Data();
|
||||
private static final String UNIFORM_NAME = "LoVisual - ShaderSky UBO";
|
||||
private static final int EXPECTED_WRITES_PER_FRAME = 1;
|
||||
|
||||
public static void update(int colorRgb,
|
||||
float alpha,
|
||||
float time,
|
||||
float speed,
|
||||
float scale,
|
||||
float intensity,
|
||||
float resolutionX,
|
||||
float resolutionY,
|
||||
float yawRad,
|
||||
float pitchRad,
|
||||
float fov,
|
||||
float thunderInterval,
|
||||
float thunderChance,
|
||||
float thunderGlow) {
|
||||
DATA.color[0] = ((colorRgb >>> 16) & 0xFF) / 255f;
|
||||
DATA.color[1] = ((colorRgb >>> 8) & 0xFF) / 255f;
|
||||
DATA.color[2] = (colorRgb & 0xFF) / 255f;
|
||||
DATA.color[3] = clamp01(alpha);
|
||||
|
||||
DATA.params[0] = time;
|
||||
DATA.params[1] = speed;
|
||||
DATA.params[2] = Math.max(0.05f, scale);
|
||||
DATA.params[3] = intensity;
|
||||
|
||||
DATA.view[0] = resolutionX;
|
||||
DATA.view[1] = resolutionY;
|
||||
DATA.view[2] = fov;
|
||||
|
||||
DATA.cam[0] = yawRad;
|
||||
DATA.cam[1] = pitchRad;
|
||||
|
||||
DATA.thunder[0] = Math.max(1.0f, thunderInterval);
|
||||
DATA.thunder[1] = clamp01(thunderChance);
|
||||
DATA.thunder[2] = thunderGlow;
|
||||
|
||||
LoVisualRenderSystem.uniforms().writeCached(UNIFORM_NAME, SIZE, EXPECTED_WRITES_PER_FRAME, 1, true, DATA);
|
||||
}
|
||||
|
||||
public static GpuBufferSlice get() {
|
||||
return LoVisualRenderSystem.uniforms().current(UNIFORM_NAME);
|
||||
}
|
||||
|
||||
private static float clamp01(float value) {
|
||||
return Math.max(0.0f, Math.min(1.0f, value));
|
||||
}
|
||||
|
||||
private static final class Data implements LoVisualUniformAllocator.UniformWriter {
|
||||
private final float[] color = new float[4];
|
||||
private final float[] params = new float[4];
|
||||
private final float[] view = new float[4];
|
||||
private final float[] cam = new float[4];
|
||||
private final float[] thunder = new float[4];
|
||||
|
||||
@Override
|
||||
public void write(ByteBuffer buffer) {
|
||||
Std140Builder.intoBuffer(buffer)
|
||||
.putFloat(color[0]).putFloat(color[1]).putFloat(color[2]).putFloat(color[3])
|
||||
.putFloat(params[0]).putFloat(params[1]).putFloat(params[2]).putFloat(params[3])
|
||||
.putFloat(view[0]).putFloat(view[1]).putFloat(view[2]).putFloat(view[3])
|
||||
.putFloat(cam[0]).putFloat(cam[1]).putFloat(cam[2]).putFloat(cam[3])
|
||||
.putFloat(thunder[0]).putFloat(thunder[1]).putFloat(thunder[2]).putFloat(thunder[3]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,6 @@ import java.util.Deque;
|
|||
public enum ScissorFunction {
|
||||
;
|
||||
|
||||
private static final Minecraft mc = Minecraft.getInstance();
|
||||
private static final Deque<ScissorRect> STACK = new ArrayDeque<>();
|
||||
private static ScissorRect appliedScissor;
|
||||
|
||||
|
|
@ -37,6 +36,7 @@ public enum ScissorFunction {
|
|||
float width,
|
||||
float height
|
||||
) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.getWindow() == null) return false;
|
||||
|
||||
float uiScale = ViewportContext.getUiScale();
|
||||
|
|
@ -198,6 +198,7 @@ public enum ScissorFunction {
|
|||
ScissorRect raw,
|
||||
ScissorRect parent
|
||||
) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
int fbw = mc != null && mc.getWindow() != null ? mc.getWindow().getWidth() : -1;
|
||||
int fbh = mc != null && mc.getWindow() != null ? mc.getWindow().getHeight() : -1;
|
||||
return new IllegalStateException(
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
package dev.loki.lovisual.render.sky;
|
||||
|
||||
import com.mojang.blaze3d.buffers.GpuBufferSlice;
|
||||
import com.mojang.blaze3d.pipeline.RenderPipeline;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraft.client.Camera;
|
||||
|
|
@ -30,6 +31,8 @@ import dev.loki.lovisual.features.module.Modules;
|
|||
import dev.loki.lovisual.features.module.modules.visuals.Freecam;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.NoRender;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.ReimaginedVisual;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.ShaderSky;
|
||||
import dev.loki.lovisual.features.theme.Theme;
|
||||
import dev.loki.lovisual.mixins.accessors.GameRendererAccessor;
|
||||
import dev.loki.lovisual.mixins.accessors.LocalPlayerAccessor;
|
||||
import dev.loki.lovisual.render.engine.RenderState;
|
||||
|
|
@ -39,6 +42,7 @@ import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines;
|
|||
import dev.loki.lovisual.render.engine.renderer.FullScreenRenderer;
|
||||
import dev.loki.lovisual.render.engine.renderer.MeshRenderer;
|
||||
import dev.loki.lovisual.render.engine.uniform.MeshBuilder;
|
||||
import dev.loki.lovisual.render.engine.uniform.impl.ShaderSkyUniforms;
|
||||
import dev.loki.lovisual.render.engine.uniform.impl.SkySunUniforms;
|
||||
import dev.loki.lovisual.render.engine.uniform.impl.SkyboxShaderUniforms;
|
||||
|
||||
|
|
@ -86,9 +90,13 @@ public enum CustomSkyboxRenderer {
|
|||
float skyboxAlpha = Mth.lerp(twilightFade, 1.0f, MIN_TWILIGHT_SKYBOX_ALPHA);
|
||||
|
||||
ReimaginedVisual visual = Modules.get(ReimaginedVisual.class);
|
||||
boolean useShaderSkybox = visual != null && visual.isShaderSkyboxEnabled();
|
||||
ShaderSky skyModule = Modules.get(ShaderSky.class);
|
||||
boolean useShaderSky = skyModule != null && skyModule.isEnabled();
|
||||
boolean useShaderSkybox = !useShaderSky && visual != null && visual.isShaderSkyboxEnabled();
|
||||
|
||||
if (useShaderSkybox) {
|
||||
if (useShaderSky) {
|
||||
renderShaderSky(mc, mc.gameRenderer.mainRenderTarget(), camera, Theme.theme().accent() & 0x00FFFFFF, skyboxAlpha, tickDelta, fov, projection);
|
||||
} else if (useShaderSkybox) {
|
||||
renderShaderSkybox(mc, mc.gameRenderer.mainRenderTarget(), camera, fog, sky, skyboxAlpha, tickDelta, fov, projection);
|
||||
}
|
||||
if (shouldRenderShaderSun()) {
|
||||
|
|
@ -172,6 +180,69 @@ public enum CustomSkyboxRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
private static void renderShaderSky(Minecraft mc,
|
||||
com.mojang.blaze3d.pipeline.RenderTarget framebuffer,
|
||||
Camera camera,
|
||||
int skyColor,
|
||||
float alpha,
|
||||
float tickDelta,
|
||||
float fov,
|
||||
Matrix4f worldProjection) {
|
||||
if (alpha <= 0.001f) return;
|
||||
ShaderSky skyModule = Modules.get(ShaderSky.class);
|
||||
if (skyModule == null || !skyModule.isEnabled()) return;
|
||||
|
||||
RenderPipeline pipeline = pipelineForMode(skyModule.getMode());
|
||||
if (pipeline == null) return;
|
||||
|
||||
float time = AnimationUtility.time(0.001f);
|
||||
float width = mc.getWindow() != null ? mc.getWindow().getWidth() : 1.0f;
|
||||
float height = mc.getWindow() != null ? mc.getWindow().getHeight() : 1.0f;
|
||||
float yawRad = (float) Math.toRadians(-camera.yRot());
|
||||
float pitchRad = (float) Math.toRadians(camera.xRot());
|
||||
float shaderFov = extractProjectionFov(worldProjection, fov);
|
||||
ShaderSkyUniforms.update(
|
||||
skyColor,
|
||||
alpha * skyModule.getAlpha(),
|
||||
time,
|
||||
skyModule.getSpeed(),
|
||||
skyModule.getScale(),
|
||||
skyModule.getIntensity(),
|
||||
width,
|
||||
height,
|
||||
yawRad,
|
||||
pitchRad,
|
||||
shaderFov,
|
||||
skyModule.getThunderInterval(),
|
||||
skyModule.getThunderChance(),
|
||||
skyModule.getThunderGlow()
|
||||
);
|
||||
|
||||
FullScreenRenderer.ensureInit();
|
||||
Matrix4fStack mv = RenderSystem.getModelViewStack();
|
||||
mv.pushMatrix();
|
||||
mv.identity();
|
||||
MeshRenderer.setProjection(new Matrix4f());
|
||||
try {
|
||||
MeshRenderer.begin()
|
||||
.attachments(framebuffer)
|
||||
.pipeline(pipeline)
|
||||
.mesh(FullScreenRenderer.vbo, FullScreenRenderer.ibo)
|
||||
.uniform("ShaderSky", ShaderSkyUniforms.get())
|
||||
.end();
|
||||
} finally {
|
||||
MeshRenderer.setProjection(worldProjection);
|
||||
mv.popMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
private static RenderPipeline pipelineForMode(String mode) {
|
||||
return switch (mode) {
|
||||
case "Pulsar" -> LoVisualRenderPipelines.WORLD_SHADER_SKY_PULSAR;
|
||||
default -> LoVisualRenderPipelines.WORLD_SHADER_SKY_THUNDER;
|
||||
};
|
||||
}
|
||||
|
||||
private static float extractProjectionFov(Matrix4f projection, float fallbackFov) {
|
||||
if (projection == null) {
|
||||
return fallbackFov;
|
||||
|
|
|
|||
|
|
@ -12,15 +12,16 @@ import net.minecraft.network.chat.Component;
|
|||
|
||||
public enum LegacyHudNotifier {
|
||||
;
|
||||
private static final Minecraft client = Minecraft.getInstance();
|
||||
|
||||
public static void show(String message) {
|
||||
Minecraft client = Minecraft.getInstance();
|
||||
if (client.gui != null) {
|
||||
client.gui.hud.setOverlayMessage(Component.nullToEmpty(message), false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void show(Component text) {
|
||||
Minecraft client = Minecraft.getInstance();
|
||||
if (client.gui != null) {
|
||||
client.gui.hud.setOverlayMessage(text, false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
package dev.loki.lovisual.util.block;
|
||||
|
||||
import net.caffeinemc.mods.sodium.client.render.SodiumWorldRenderer;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
|
@ -23,24 +23,36 @@ public enum BlockObservationHub {
|
|||
if (client == null || client.level == null || client.player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
SodiumWorldRenderer renderer = SodiumWorldRenderer.instanceNullable();
|
||||
if (renderer == null) {
|
||||
if (!FabricLoader.getInstance().isModLoaded("sodium")) {
|
||||
return;
|
||||
}
|
||||
|
||||
int renderRadius = (client.options.getEffectiveRenderDistance() + 1) * 16;
|
||||
int radius = Math.max(1, Math.min(requestedRadius, renderRadius));
|
||||
BlockPos center = client.player.blockPosition();
|
||||
renderer.scheduleRebuildForBlockArea(
|
||||
center.getX() - radius,
|
||||
client.level.getMinY(),
|
||||
center.getZ() - radius,
|
||||
center.getX() + radius,
|
||||
client.level.getMaxY(),
|
||||
center.getZ() + radius,
|
||||
true
|
||||
);
|
||||
try {
|
||||
Class<?> cls = Class.forName("net.caffeinemc.mods.sodium.client.render.SodiumWorldRenderer");
|
||||
Object renderer = cls.getMethod("instanceNullable").invoke(null);
|
||||
if (renderer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int renderRadius = (client.options.getEffectiveRenderDistance() + 1) * 16;
|
||||
int radius = Math.max(1, Math.min(requestedRadius, renderRadius));
|
||||
BlockPos center = client.player.blockPosition();
|
||||
cls.getMethod(
|
||||
"scheduleRebuildForBlockArea",
|
||||
int.class, int.class, int.class, int.class, int.class, int.class, boolean.class
|
||||
).invoke(
|
||||
renderer,
|
||||
center.getX() - radius,
|
||||
client.level.getMinY(),
|
||||
center.getZ() - radius,
|
||||
center.getX() + radius,
|
||||
client.level.getMaxY(),
|
||||
center.getZ() + radius,
|
||||
true
|
||||
);
|
||||
} catch (Throwable ignored) {
|
||||
// Sodium отсутствует или изменился API — молча пропускаем
|
||||
}
|
||||
}
|
||||
|
||||
public static void observeSodiumBlock(int x, int y, int z, BlockState state) {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ public enum ProtocolUtil {
|
|||
|
||||
public static final ClientProtocolVersion DEFAULT_PROTOCOL_VERSION =
|
||||
new ClientProtocolVersion(SharedConstants.getCurrentVersion().name(), SharedConstants.getProtocolVersion());
|
||||
private static final Minecraft MC = Minecraft.getInstance();
|
||||
public static final boolean USES_VIAFABRICPLUS = detectViaFabricPlus();
|
||||
|
||||
private static boolean detectViaFabricPlus() {
|
||||
|
|
@ -48,8 +47,9 @@ public enum ProtocolUtil {
|
|||
|
||||
try {
|
||||
ViaFabricPlus.getImpl().registerOnChangeProtocolVersionCallback((oldVer, newVer) -> {
|
||||
// Update the window title if available
|
||||
MC.execute(MC::updateTitle);
|
||||
// Обновляем заголовок окна лениво — только когда сработает колбэк
|
||||
Minecraft client = Minecraft.getInstance();
|
||||
client.execute(client::updateTitle);
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
DebugLog.error("Failed to register ViaFabricPlus protocol callback", t);
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ final class RemotePlayerObservation {
|
|||
}
|
||||
}
|
||||
|
||||
StaffTracker.StaffInfo info = StaffTracker.all().get(player.getUUID());
|
||||
StaffTracker.StaffInfo info = StaffTracker.INSTANCE.all().get(player.getUUID());
|
||||
if (info != null && info.gm != null) {
|
||||
return info.gm;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ import java.util.EnumMap;
|
|||
public enum TargetManager {
|
||||
;
|
||||
|
||||
private static final long DEFAULT_ATTACK_HOLD_MS = 700L;
|
||||
// последняя цель удара запоминается на 10 секунд (боевая цель), пока не ударишь другого
|
||||
private static final long DEFAULT_ATTACK_HOLD_MS = 10_000L;
|
||||
private static final EnumMap<Source, TargetState> STATES = new EnumMap<>(Source.class);
|
||||
private static LivingEntity current;
|
||||
private static Source currentSource;
|
||||
|
|
@ -60,56 +61,6 @@ public enum TargetManager {
|
|||
}
|
||||
}
|
||||
|
||||
public static void setForcedTarget(LivingEntity target) {
|
||||
if (target == null) {
|
||||
clear(Source.FORCE);
|
||||
refreshCurrent();
|
||||
return;
|
||||
}
|
||||
push(Source.FORCE, target, 0L);
|
||||
refreshCurrent();
|
||||
}
|
||||
|
||||
public static void setModuleTarget(LivingEntity target) {
|
||||
if (target == null) {
|
||||
clear(Source.MODULE);
|
||||
refreshCurrent();
|
||||
return;
|
||||
}
|
||||
push(Source.MODULE, target, 0L);
|
||||
refreshCurrent();
|
||||
}
|
||||
|
||||
public static void setAutoCrystalTarget(LivingEntity target) {
|
||||
if (target == null) {
|
||||
clear(Source.AUTO_CRYSTAL);
|
||||
refreshCurrent();
|
||||
return;
|
||||
}
|
||||
push(Source.AUTO_CRYSTAL, target, 0L);
|
||||
refreshCurrent();
|
||||
}
|
||||
|
||||
public static void setAutoAnchorTarget(LivingEntity target) {
|
||||
if (target == null) {
|
||||
clear(Source.AUTO_ANCHOR);
|
||||
refreshCurrent();
|
||||
return;
|
||||
}
|
||||
push(Source.AUTO_ANCHOR, target, 0L);
|
||||
refreshCurrent();
|
||||
}
|
||||
|
||||
public static void setAutoBedTarget(LivingEntity target) {
|
||||
if (target == null) {
|
||||
clear(Source.AUTO_BED);
|
||||
refreshCurrent();
|
||||
return;
|
||||
}
|
||||
push(Source.AUTO_BED, target, 0L);
|
||||
refreshCurrent();
|
||||
}
|
||||
|
||||
public static void setPredictionTarget(LivingEntity target) {
|
||||
if (target == null) {
|
||||
clear(Source.PREDICTION);
|
||||
|
|
@ -120,16 +71,6 @@ public enum TargetManager {
|
|||
refreshCurrent();
|
||||
}
|
||||
|
||||
public static void setElytraTarget(LivingEntity target) {
|
||||
if (target == null) {
|
||||
clear(Source.ELYTRA);
|
||||
refreshCurrent();
|
||||
return;
|
||||
}
|
||||
push(Source.ELYTRA, target, 0L);
|
||||
refreshCurrent();
|
||||
}
|
||||
|
||||
public static void clear(Source source) {
|
||||
if (source == null) return;
|
||||
STATES.remove(source);
|
||||
|
|
@ -192,7 +133,7 @@ public enum TargetManager {
|
|||
}
|
||||
|
||||
private static TargetSelection selectCurrent(boolean includeCrosshair) {
|
||||
for (Source source : new Source[]{Source.FORCE, Source.ELYTRA, Source.AUTO_CRYSTAL, Source.AUTO_ANCHOR, Source.AUTO_BED, Source.MODULE, Source.PREDICTION, Source.ATTACK, Source.CROSSHAIR}) {
|
||||
for (Source source : new Source[]{Source.PREDICTION, Source.ATTACK, Source.CROSSHAIR}) {
|
||||
if (!includeCrosshair && source == Source.CROSSHAIR) continue;
|
||||
TargetState state = STATES.get(source);
|
||||
if (state != null && TargetingUtil.isValidCombatTarget(state.entity)) {
|
||||
|
|
@ -203,12 +144,6 @@ public enum TargetManager {
|
|||
}
|
||||
|
||||
public enum Source {
|
||||
FORCE,
|
||||
ELYTRA,
|
||||
AUTO_CRYSTAL,
|
||||
AUTO_ANCHOR,
|
||||
AUTO_BED,
|
||||
MODULE,
|
||||
PREDICTION,
|
||||
ATTACK,
|
||||
CROSSHAIR
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 pivosos2007.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.util.time;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
||||
/**
|
||||
* Игровое время (тики мира) в миллисекундах.
|
||||
* Вне мира (меню/загрузка) — откат на стенное время, чтобы не было NPE.
|
||||
*/
|
||||
public final class GameClock {
|
||||
|
||||
public static final long TICK_MS = 50L;
|
||||
|
||||
private GameClock() {
|
||||
}
|
||||
|
||||
public static long millis() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc != null && mc.level != null) {
|
||||
return mc.level.getGameTime() * TICK_MS;
|
||||
}
|
||||
return System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
#version 330 core
|
||||
|
||||
in vec2 v_TexCoord;
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform sampler2D u_Src;
|
||||
uniform sampler2D u_Mask;
|
||||
|
||||
layout (std140) uniform HandChams {
|
||||
vec4 u_View; // x = resX, y = resY, z = time
|
||||
vec4 u_Mix; // x = effectAlpha, y = chromaSpeed, z = chromaSaturation, w = chromaBrightness
|
||||
vec4 u_ColorA; // rgb = colorA
|
||||
vec4 u_ColorB; // rgb = colorB
|
||||
vec4 u_ColorC; // rgb = colorC
|
||||
vec4 u_ColorD; // rgb = colorD (r = contrast, g = lighting)
|
||||
vec4 u_Common; // x = snowIntensity, y = snowSpeed, z = stripesWidth, w = stripesSpeed
|
||||
vec4 u_Glow; // x = fillGlow, y = glowRadius, z = glowPower, w = glowDispersion
|
||||
vec4 u_Flags; // x = hideHand
|
||||
};
|
||||
|
||||
float handMaskAt(vec2 uv) {
|
||||
vec4 m = texture(u_Mask, uv);
|
||||
return max(m.a, max(m.r, max(m.g, m.b)));
|
||||
}
|
||||
|
||||
#define SPIN_ROTATION -2.0
|
||||
#define SPIN_SPEED 7.0
|
||||
#define OFFSET vec2(0.0)
|
||||
#define SPIN_AMOUNT 0.25
|
||||
#define PIXEL_FILTER 745.0
|
||||
#define SPIN_EASE 1.0
|
||||
#define PI 3.14159265359
|
||||
#define IS_ROTATE false
|
||||
|
||||
vec4 effect(vec2 screenSize, vec2 screen_coords, float contrast, float lighting) {
|
||||
float pixel_size = length(screenSize.xy) / PIXEL_FILTER;
|
||||
vec2 effectUv = (floor(screen_coords.xy*(1./pixel_size))*pixel_size - 0.5*screenSize.xy)/length(screenSize.xy) - OFFSET;
|
||||
float uv_len = length(effectUv);
|
||||
|
||||
float speed = (SPIN_ROTATION*SPIN_EASE*0.2);
|
||||
if(IS_ROTATE){
|
||||
speed = u_View.z * speed;
|
||||
}
|
||||
speed += 302.2;
|
||||
float new_pixel_angle = atan(effectUv.y, effectUv.x) + speed - SPIN_EASE*20.*(1.*SPIN_AMOUNT*uv_len + (1. - 1.*SPIN_AMOUNT));
|
||||
vec2 mid = (screenSize.xy/length(screenSize.xy))/2.;
|
||||
effectUv = (vec2((uv_len * cos(new_pixel_angle) + mid.x), (uv_len * sin(new_pixel_angle) + mid.y)) - mid);
|
||||
|
||||
effectUv *= 30.;
|
||||
speed = u_View.z*(SPIN_SPEED);
|
||||
vec2 uv2 = vec2(effectUv.x+effectUv.y);
|
||||
|
||||
for(int i=0; i < 5; i++) {
|
||||
uv2 += sin(max(effectUv.x, effectUv.y)) + effectUv;
|
||||
effectUv += 0.5*vec2(cos(5.1123314 + 0.353*uv2.y + speed*0.131121),sin(uv2.x - 0.113*speed));
|
||||
effectUv -= 1.0*cos(effectUv.x + effectUv.y) - 1.0*sin(effectUv.x*0.711 - effectUv.y);
|
||||
}
|
||||
|
||||
float contrast_mod = (0.25*contrast + 0.5*SPIN_AMOUNT + 1.2);
|
||||
float paint_res = min(2., max(0.,length(effectUv)*(0.035)*contrast_mod));
|
||||
float c1p = max(0.,1. - contrast_mod*abs(1.-paint_res));
|
||||
float c2p = max(0.,1. - contrast_mod*abs(paint_res));
|
||||
float c3p = 1. - min(1., c1p + c2p);
|
||||
float light = (lighting - 0.2)*max(c1p*5. - 4., 0.) + lighting*max(c2p*5. - 4., 0.);
|
||||
|
||||
vec4 colour1 = vec4(u_ColorA.rgb, 1.0);
|
||||
vec4 colour2 = vec4(u_ColorB.rgb, 1.0);
|
||||
vec4 colour3 = vec4(u_ColorC.rgb, 1.0);
|
||||
|
||||
return (0.3/contrast)*colour1 + (1. - 0.3/contrast)*(colour1*c1p + colour2*c2p + vec4(c3p*colour3.rgb, c3p*colour1.a)) + light;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 base = texture(u_Src, v_TexCoord);
|
||||
float mask = handMaskAt(v_TexCoord);
|
||||
if (mask <= 0.02) {
|
||||
fragColor = base;
|
||||
return;
|
||||
}
|
||||
|
||||
float contrast = clamp(u_ColorD.r, 0.1, 1.0) * 10.0;
|
||||
float lighting = clamp(u_ColorD.g, 0.0, 1.0);
|
||||
|
||||
vec4 effectColor = effect(u_View.xy, v_TexCoord * u_View.xy, contrast, lighting);
|
||||
|
||||
vec3 finalColor = mix(base.rgb, effectColor.rgb, u_Mix.x * smoothstep(0.02, 0.3, mask));
|
||||
fragColor = vec4(finalColor, 1.0);
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
#version 330 core
|
||||
|
||||
in vec2 v_TexCoord;
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform sampler2D u_Shader1;
|
||||
uniform sampler2D u_Shader2;
|
||||
uniform sampler2D u_Mask;
|
||||
|
||||
layout (std140) uniform HandBlend {
|
||||
vec4 u_Blend; // x = blendMode (0=Mix,1=Add,2=Multiply,3=Screen,4=Overlay,5=Difference), y = blendIntensity, z = shader1Strength, w = shader2Strength
|
||||
};
|
||||
|
||||
float maskAt(vec2 uv) {
|
||||
vec4 m = texture(u_Mask, uv);
|
||||
return max(m.a, max(m.r, max(m.g, m.b)));
|
||||
}
|
||||
|
||||
vec3 blendAdd(vec3 base, vec3 blend) {
|
||||
return min(base + blend, vec3(1.0));
|
||||
}
|
||||
|
||||
vec3 blendScreen(vec3 base, vec3 blend) {
|
||||
return vec3(1.0) - (vec3(1.0) - base) * (vec3(1.0) - blend);
|
||||
}
|
||||
|
||||
vec3 blendOverlay(vec3 base, vec3 blend) {
|
||||
vec3 result;
|
||||
result.r = base.r < 0.5 ? (2.0 * base.r * blend.r) : (1.0 - 2.0 * (1.0 - base.r) * (1.0 - blend.r));
|
||||
result.g = base.g < 0.5 ? (2.0 * base.g * blend.g) : (1.0 - 2.0 * (1.0 - base.g) * (1.0 - blend.g));
|
||||
result.b = base.b < 0.5 ? (2.0 * base.b * blend.b) : (1.0 - 2.0 * (1.0 - base.b) * (1.0 - blend.b));
|
||||
return result;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 shader1Color = texture(u_Shader1, v_TexCoord);
|
||||
vec4 shader2Color = texture(u_Shader2, v_TexCoord);
|
||||
|
||||
float mask = maskAt(v_TexCoord);
|
||||
if (mask <= 0.01) {
|
||||
fragColor = shader1Color;
|
||||
return;
|
||||
}
|
||||
|
||||
vec3 color1 = shader1Color.rgb * u_Blend.z;
|
||||
vec3 color2 = shader2Color.rgb * u_Blend.w;
|
||||
float intensity = u_Blend.y;
|
||||
|
||||
vec3 blendedColor;
|
||||
int mode = int(u_Blend.x);
|
||||
if (mode == 0) {
|
||||
blendedColor = mix(color1, color2, intensity);
|
||||
} else if (mode == 1) {
|
||||
blendedColor = mix(color1, blendAdd(color1, color2), intensity);
|
||||
} else if (mode == 2) {
|
||||
blendedColor = mix(color1, color1 * color2, intensity);
|
||||
} else if (mode == 3) {
|
||||
blendedColor = mix(color1, blendScreen(color1, color2), intensity);
|
||||
} else if (mode == 4) {
|
||||
blendedColor = mix(color1, blendOverlay(color1, color2), intensity);
|
||||
} else if (mode == 5) {
|
||||
blendedColor = mix(color1, abs(color1 - color2), intensity);
|
||||
} else {
|
||||
blendedColor = color1;
|
||||
}
|
||||
|
||||
fragColor = vec4(blendedColor, 1.0);
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
#version 330 core
|
||||
|
||||
in vec2 v_TexCoord;
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform sampler2D u_Src;
|
||||
uniform sampler2D u_Mask;
|
||||
|
||||
layout (std140) uniform HandChams {
|
||||
vec4 u_View; // x = resX, y = resY, z = time
|
||||
vec4 u_Mix; // x = effectAlpha, y = chromaSpeed, z = chromaSaturation, w = chromaBrightness
|
||||
vec4 u_ColorA; // rgb = colorA
|
||||
vec4 u_ColorB; // rgb = colorB
|
||||
vec4 u_ColorC; // rgb = colorC
|
||||
vec4 u_ColorD; // rgb = colorD
|
||||
vec4 u_Common; // x = snowIntensity, y = snowSpeed, z = stripesWidth, w = stripesSpeed
|
||||
vec4 u_Glow; // x = fillGlow, y = glowRadius, z = glowPower, w = glowDispersion
|
||||
vec4 u_Flags; // x = hideHand
|
||||
};
|
||||
|
||||
float handMaskAt(vec2 uv) {
|
||||
vec4 m = texture(u_Mask, uv);
|
||||
return max(m.a, max(m.r, max(m.g, m.b)));
|
||||
}
|
||||
|
||||
vec3 hsv2rgb(vec3 c) {
|
||||
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
|
||||
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
|
||||
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
|
||||
}
|
||||
|
||||
vec3 createChromaEffect(vec2 coord, float t) {
|
||||
float hue = fract(coord.y * 0.5 + coord.x * 0.3 + t * u_Mix.y);
|
||||
float wave = sin(coord.y * 10.0 + t * 3.0) * 0.05;
|
||||
hue += wave;
|
||||
return hsv2rgb(vec3(hue, u_Mix.z, u_Mix.w));
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 base = texture(u_Src, v_TexCoord);
|
||||
float mask = handMaskAt(v_TexCoord);
|
||||
if (mask <= 0.02) {
|
||||
fragColor = base;
|
||||
return;
|
||||
}
|
||||
|
||||
float time = u_View.z;
|
||||
float maskGain = smoothstep(0.02, 0.3, mask);
|
||||
vec3 chromaColor = createChromaEffect(v_TexCoord, time);
|
||||
float pulse = 0.9 + sin(time * 4.0) * 0.1;
|
||||
chromaColor *= pulse;
|
||||
|
||||
vec3 finalColor = mix(base.rgb, chromaColor, u_Mix.x * maskGain);
|
||||
fragColor = vec4(finalColor, 1.0);
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
#version 330 core
|
||||
|
||||
in vec2 v_TexCoord;
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform sampler2D u_Src;
|
||||
uniform sampler2D u_Mask;
|
||||
|
||||
layout (std140) uniform HandChams {
|
||||
vec4 u_View; // x = resX, y = resY, z = time
|
||||
vec4 u_Mix; // x = effectAlpha, y = chromaSpeed, z = chromaSaturation, w = chromaBrightness
|
||||
vec4 u_ColorA; // rgb = colorA
|
||||
vec4 u_ColorB; // rgb = colorB
|
||||
vec4 u_ColorC; // rgb = colorC
|
||||
vec4 u_ColorD; // rgb = colorD
|
||||
vec4 u_Common; // x = snowIntensity, y = snowSpeed, z = stripesWidth, w = stripesSpeed
|
||||
vec4 u_Glow; // x = fillGlow, y = glowRadius, z = glowPower, w = glowDispersion
|
||||
vec4 u_Flags; // x = hideHand
|
||||
};
|
||||
|
||||
float handMaskAt(vec2 uv) {
|
||||
vec4 m = texture(u_Mask, uv);
|
||||
return max(m.a, max(m.r, max(m.g, m.b)));
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 base = texture(u_Src, v_TexCoord);
|
||||
float mask = handMaskAt(v_TexCoord);
|
||||
if (mask <= 0.02 && u_Glow.w <= 1.0) {
|
||||
fragColor = base;
|
||||
return;
|
||||
}
|
||||
|
||||
float time = u_View.z;
|
||||
float maskGain = smoothstep(0.02, 0.3, mask);
|
||||
vec2 texelSize = 1.0 / max(u_View.xy, vec2(1.0));
|
||||
|
||||
// Маска: 0 = фон, >0 = рука. Инвертируем в depth-семантику (1 = фон).
|
||||
float centerDepth = 1.0 - mask;
|
||||
|
||||
float edgeStrength = 0.0;
|
||||
float outwardGlow = 0.0;
|
||||
|
||||
int disperseRadius = int(clamp(u_Glow.w, 1.0, 12.0));
|
||||
|
||||
for (int y = -disperseRadius; y <= disperseRadius; y++) {
|
||||
for (int x = -disperseRadius; x <= disperseRadius; x++) {
|
||||
if (x == 0 && y == 0) continue;
|
||||
|
||||
vec2 offset = vec2(x, y) * texelSize;
|
||||
float neighborMask = handMaskAt(v_TexCoord + offset);
|
||||
float neighborDepth = 1.0 - neighborMask;
|
||||
|
||||
float dist = length(vec2(x, y));
|
||||
|
||||
// Сосед — фон, а мы — рука: край руки
|
||||
if (neighborDepth > 0.9 && centerDepth < 0.9) {
|
||||
edgeStrength += (1.0 / (dist + 0.1));
|
||||
}
|
||||
|
||||
// Мы — фон рядом с рукой: внешнее свечение
|
||||
if (centerDepth > 0.9 && neighborDepth < 0.9) {
|
||||
float falloff = 1.0 - (dist / float(disperseRadius));
|
||||
falloff = pow(max(falloff, 0.0), 2.5);
|
||||
outwardGlow += falloff * 0.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
edgeStrength = clamp(edgeStrength * 0.15, 0.0, 1.0) * maskGain;
|
||||
outwardGlow = clamp(outwardGlow, 0.0, 1.0);
|
||||
|
||||
float pulse = 0.9 + sin(time * 2.0) * 0.1;
|
||||
float fill = u_Glow.x;
|
||||
float glowRadius = max(u_Glow.y, 0.01);
|
||||
float glowPower = max(u_Glow.z, 0.0);
|
||||
|
||||
// Фон рядом с рукой: внешнее свечение
|
||||
if (centerDepth > 0.9 && outwardGlow > 0.01) {
|
||||
vec3 dispersedGlow = u_ColorC.rgb * outwardGlow * glowPower * pulse * glowRadius * 10.0;
|
||||
fragColor = vec4(dispersedGlow, outwardGlow * 0.8);
|
||||
return;
|
||||
}
|
||||
|
||||
vec3 finalColor;
|
||||
if (fill > 0.5) {
|
||||
vec3 handGlow = u_ColorC.rgb * (glowRadius * 10.0);
|
||||
vec3 edgeGlow = u_ColorC.rgb * edgeStrength * glowPower * pulse * 2.5;
|
||||
float glowMix = u_Flags.x > 0.5 ? 1.0 : 0.75;
|
||||
finalColor = mix(base.rgb, handGlow + edgeGlow, glowMix);
|
||||
} else {
|
||||
vec3 edgeGlow = u_ColorC.rgb * edgeStrength * glowPower * pulse * glowRadius * 25.0;
|
||||
finalColor = base.rgb + edgeGlow;
|
||||
}
|
||||
|
||||
fragColor = vec4(finalColor, 1.0);
|
||||
}
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
#version 330 core
|
||||
|
||||
in vec2 v_TexCoord;
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform sampler2D u_Src;
|
||||
uniform sampler2D u_Mask;
|
||||
|
||||
layout (std140) uniform HandChams {
|
||||
vec4 u_View; // x = resX, y = resY, z = time
|
||||
vec4 u_Mix; // x = effectAlpha, y = chromaSpeed, z = chromaSaturation, w = chromaBrightness
|
||||
vec4 u_ColorA; // rgb = colorA
|
||||
vec4 u_ColorB; // rgb = colorB
|
||||
vec4 u_ColorC; // rgb = colorC
|
||||
vec4 u_ColorD; // rgb = colorD
|
||||
vec4 u_Common; // x = snowIntensity, y = snowSpeed, z = stripesWidth, w = stripesSpeed
|
||||
vec4 u_Glow; // x = fillGlow, y = glowRadius, z = glowPower, w = glowDispersion
|
||||
vec4 u_Flags; // x = hideHand
|
||||
};
|
||||
|
||||
float handMaskAt(vec2 uv) {
|
||||
vec4 m = texture(u_Mask, uv);
|
||||
return max(m.a, max(m.r, max(m.g, m.b)));
|
||||
}
|
||||
|
||||
#define pi 3.14159265359
|
||||
|
||||
const int nbFlakes = 500;
|
||||
const vec3 flakedomain = vec3(2.5, 3.0, 2.5);
|
||||
const float flakeMinSpeed = 0.8;
|
||||
const float flakeMaxSpeed = 2.0;
|
||||
const float flakeMinSinVariation = 0.015;
|
||||
const float flakeMaxSinVariation = 0.035;
|
||||
const float flakeMinFreq = 3.0;
|
||||
const float flakeMaxFreq = 8.0;
|
||||
const vec2 flakeWindFact = vec2(0.25, 0.05);
|
||||
|
||||
const float starNbBranches = 6.0;
|
||||
const float starPow = 1.5;
|
||||
const float starStrength = 1.0;
|
||||
|
||||
vec2 rotateVec(vec2 vect, float angle) {
|
||||
vec2 rv;
|
||||
rv.x = vect.x * cos(angle) - vect.y * sin(angle);
|
||||
rv.y = vect.x * sin(angle) + vect.y * cos(angle);
|
||||
return rv;
|
||||
}
|
||||
|
||||
float hash(float n) {
|
||||
return fract(sin(n) * 753.5453123);
|
||||
}
|
||||
|
||||
float rand(float minVal, float maxVal, float seed) {
|
||||
return minVal + (maxVal - minVal) * hash(seed);
|
||||
}
|
||||
|
||||
vec3 getFlakePosition(int flakeNr, float t) {
|
||||
float fn = float(flakeNr);
|
||||
float s = rand(flakeMinSpeed, flakeMaxSpeed, fn * 348.0 + 173.0) * u_Common.y;
|
||||
float posY = mod(-(t + 15.0 * hash(fn * 1613.0 + 1354.0)) * s, flakedomain.y * 2.0) - flakedomain.y;
|
||||
float posX = rand(-flakedomain.x, flakedomain.x, fn * 743.0 + 514.0) + posY * flakeWindFact.x;
|
||||
float posZ = rand(-flakedomain.z, flakedomain.z, fn * 284.0 + 483.0) + posY * flakeWindFact.y;
|
||||
|
||||
float sinvar = rand(flakeMinSinVariation, flakeMaxSinVariation, fn * 842.0 + 951.0);
|
||||
float sinfreq = rand(flakeMinFreq, flakeMaxFreq, fn * 348.0 + 173.0);
|
||||
float dd = hash(fn * 235.0 + 934.0);
|
||||
posX += sinvar * sin(t * sinfreq) * dd;
|
||||
posZ += sinvar * sin(t * sinfreq) * sqrt(1.0 - dd * dd);
|
||||
|
||||
return vec3(posX, posY, posZ);
|
||||
}
|
||||
|
||||
float nppow(float x, float p) {
|
||||
return sign(x) * pow(abs(x), p);
|
||||
}
|
||||
|
||||
float getSnowProfile(float val, float dist, vec3 fpos, vec3 ray, vec3 campos, int flakeNr) {
|
||||
float val2 = -log(1.0 - val);
|
||||
|
||||
if (dist < 1.5) {
|
||||
vec3 v3 = (fpos - campos) - dot((fpos - campos), ray) * ray;
|
||||
vec3 vx = vec3(1.0, 0.0, 0.0);
|
||||
vx.xy = rotateVec(vx.xy, 2.0 * float(flakeNr) * 152.5 + u_View.z * 0.4);
|
||||
vx = normalize(vx - dot(vx, ray) * ray);
|
||||
vec3 vy = vec3(ray.y * vx.z - ray.z * vx.y, ray.z * vx.x - ray.x * vx.z, ray.x * vx.y - ray.y * vx.x);
|
||||
|
||||
float a = atan(dot(v3, vx), dot(v3, vy));
|
||||
|
||||
float spp = 1.0 + starStrength * nppow(sin(a * starNbBranches), starPow);
|
||||
val2 += 1.3 * spp * pow(smoothstep(1.6, 0.1, dist), 2.0);
|
||||
}
|
||||
|
||||
float delta = 1.5 - 0.9 / pow(dist + 1.0, 0.3);
|
||||
float midpoint = 10.0 / pow(dist + 0.1, 0.3);
|
||||
float pr = smoothstep(midpoint - delta * 0.5, midpoint + delta * 0.5, val2);
|
||||
|
||||
float d = 1.0 - pow(abs(1.0 - 2.0 * pr), 2.0);
|
||||
float f = 1.3 / pow(dist + 0.8, 2.5);
|
||||
|
||||
if (val2 < 8.0) {
|
||||
pr += 32.0 * pow(f, 1.5) * max(0.0, dist - 2.0) * d * (0.5 + sin(val2 * 230.0 / (3.8 + dist) - midpoint * 90.0) * 0.5);
|
||||
}
|
||||
|
||||
return pr * f;
|
||||
}
|
||||
|
||||
vec3 getFlakes(vec3 ray, vec3 campos) {
|
||||
vec3 rc1 = vec3(0.0);
|
||||
vec3 rc2 = vec3(0.0);
|
||||
vec3 fpos;
|
||||
float lp;
|
||||
|
||||
for (int l = 0; l < nbFlakes; l++) {
|
||||
fpos = getFlakePosition(l, u_View.z);
|
||||
|
||||
float val = max(0.0, dot(ray, normalize(fpos - campos)));
|
||||
if (val > 0.996) {
|
||||
vec3 camtarget = vec3(0.0, 0.0, 0.0);
|
||||
float dist1 = distance(camtarget, fpos);
|
||||
float dist2 = distance(campos, fpos);
|
||||
float dist = max(5.2 * pow(dist1 / dist2, 1.7), 0.32);
|
||||
lp = getSnowProfile(val, dist, fpos, ray, campos, l);
|
||||
|
||||
const float fogdens = 0.08;
|
||||
lp *= clamp(exp(-pow(fogdens * dist2, 2.0)), 0.0, 1.0);
|
||||
|
||||
lp *= smoothstep(-flakedomain.y, -flakedomain.y * 0.75, fpos.y);
|
||||
lp *= smoothstep(flakedomain.y, flakedomain.y * 0.75, fpos.y);
|
||||
|
||||
rc1 += clamp(normalize(mix(u_ColorA.rgb, vec3(1.0), 0.55 * lp)) * lp, 0.0, 1.0);
|
||||
rc2 = max(rc2, clamp(normalize(mix(u_ColorA.rgb, vec3(1.0), 0.55 * lp)) * lp, 0.0, 1.0));
|
||||
}
|
||||
}
|
||||
return mix(rc1, rc2, 0.7);
|
||||
}
|
||||
|
||||
vec3 getCameraRayDir(vec2 vWindow) {
|
||||
float fov = 3.8;
|
||||
vec3 vForward = normalize(vec3(0.0, 0.0, -1.0));
|
||||
vec3 vRight = normalize(cross(vec3(0.0, 1.0, 0.0), vForward));
|
||||
vec3 vUp = normalize(cross(vForward, vRight));
|
||||
|
||||
return normalize(vWindow.x * vRight + vWindow.y * vUp + vForward * fov);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 base = texture(u_Src, v_TexCoord);
|
||||
float mask = handMaskAt(v_TexCoord);
|
||||
if (mask <= 0.02) {
|
||||
fragColor = base;
|
||||
return;
|
||||
}
|
||||
|
||||
vec2 screenUv = v_TexCoord * 2.0 - 1.0;
|
||||
screenUv.x *= u_View.x / max(u_View.y, 1.0);
|
||||
vec3 ray = getCameraRayDir(screenUv);
|
||||
vec3 campos = vec3(0.0, 0.0, 3.5);
|
||||
|
||||
vec3 flakes = getFlakes(ray, campos);
|
||||
flakes *= u_Common.x * 1.8 * smoothstep(0.02, 0.3, mask);
|
||||
|
||||
vec3 finalColor = u_Flags.x > 0.5 ? flakes : base.rgb + flakes;
|
||||
fragColor = vec4(finalColor, 1.0);
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
#version 330 core
|
||||
|
||||
in vec2 v_TexCoord;
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform sampler2D u_Src;
|
||||
uniform sampler2D u_Mask;
|
||||
|
||||
layout (std140) uniform HandChams {
|
||||
vec4 u_View; // x = resX, y = resY, z = time
|
||||
vec4 u_Mix; // x = effectAlpha, y = chromaSpeed, z = chromaSaturation, w = chromaBrightness
|
||||
vec4 u_ColorA; // rgb = colorA
|
||||
vec4 u_ColorB; // rgb = colorB
|
||||
vec4 u_ColorC; // rgb = colorC
|
||||
vec4 u_ColorD; // rgb = colorD
|
||||
vec4 u_Common; // x = snowIntensity, y = snowSpeed, z = stripesWidth, w = stripesSpeed
|
||||
vec4 u_Glow; // x = fillGlow, y = glowRadius, z = glowPower, w = glowDispersion
|
||||
vec4 u_Flags; // x = hideHand
|
||||
};
|
||||
|
||||
float handMaskAt(vec2 uv) {
|
||||
vec4 m = texture(u_Mask, uv);
|
||||
return max(m.a, max(m.r, max(m.g, m.b)));
|
||||
}
|
||||
|
||||
vec3 createVerticalGradient(vec2 coord, vec3 color1, vec3 color2, float t) {
|
||||
float factor = coord.y + t;
|
||||
factor = sin(factor * 3.14159 * 2.0) * 0.5 + 0.5;
|
||||
return mix(color1, color2, factor);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 base = texture(u_Src, v_TexCoord);
|
||||
float mask = handMaskAt(v_TexCoord);
|
||||
if (mask <= 0.02) {
|
||||
fragColor = base;
|
||||
return;
|
||||
}
|
||||
|
||||
float time = u_View.z;
|
||||
float maskGain = smoothstep(0.02, 0.3, mask);
|
||||
vec3 gradientColor = createVerticalGradient(v_TexCoord, u_ColorA.rgb, u_ColorB.rgb, time);
|
||||
float brightness = 0.85 + sin(time * 6.28318) * 0.15;
|
||||
gradientColor *= brightness;
|
||||
|
||||
vec3 finalColor = mix(base.rgb, gradientColor, u_Mix.x * maskGain);
|
||||
fragColor = vec4(finalColor, 1.0);
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
#version 330 core
|
||||
|
||||
in vec2 v_TexCoord;
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform sampler2D u_Src;
|
||||
uniform sampler2D u_Mask;
|
||||
|
||||
layout (std140) uniform HandChams {
|
||||
vec4 u_View; // x = resX, y = resY, z = time
|
||||
vec4 u_Mix; // x = effectAlpha, y = chromaSpeed, z = chromaSaturation, w = chromaBrightness
|
||||
vec4 u_ColorA; // rgb = colorA
|
||||
vec4 u_ColorB; // rgb = colorB
|
||||
vec4 u_ColorC; // rgb = colorC
|
||||
vec4 u_ColorD; // rgb = colorD
|
||||
vec4 u_Common; // x = snowIntensity, y = snowSpeed, z = stripesWidth, w = stripesSpeed
|
||||
vec4 u_Glow; // x = fillGlow, y = glowRadius, z = glowPower, w = glowDispersion
|
||||
vec4 u_Flags; // x = hideHand
|
||||
};
|
||||
|
||||
float handMaskAt(vec2 uv) {
|
||||
vec4 m = texture(u_Mask, uv);
|
||||
return max(m.a, max(m.r, max(m.g, m.b)));
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 base = texture(u_Src, v_TexCoord);
|
||||
float mask = handMaskAt(v_TexCoord);
|
||||
if (mask <= 0.02) {
|
||||
fragColor = base;
|
||||
return;
|
||||
}
|
||||
|
||||
vec2 effectUv = v_TexCoord;
|
||||
effectUv.x *= u_View.x / max(u_View.y, 1.0);
|
||||
|
||||
// Плавные анимированные диагональные полосы с мягкими краями
|
||||
float angle = 0.65;
|
||||
float d = (effectUv.x * cos(angle) + effectUv.y * sin(angle)) + u_View.z * u_Common.w;
|
||||
float stripe = abs(sin(d * 3.14159265 / max(u_Common.z, 0.001)));
|
||||
stripe = pow(stripe, 1.5);
|
||||
|
||||
vec3 col = mix(u_ColorB.rgb, u_ColorA.rgb, stripe);
|
||||
col += u_ColorA.rgb * (0.12 + 0.10 * sin(d * 6.0 + u_View.z * 2.0));
|
||||
|
||||
float strength = u_Mix.x * (0.35 + 0.65 * stripe) * smoothstep(0.02, 0.3, mask);
|
||||
vec3 finalColor = mix(base.rgb, col, strength);
|
||||
fragColor = vec4(finalColor, 1.0);
|
||||
}
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
#version 330 core
|
||||
|
||||
layout (std140) uniform ShaderSky {
|
||||
vec4 u_Color; // rgb = tint, a = alpha
|
||||
vec4 u_Params; // x = time, y = speed, z = scale, w = intensity
|
||||
vec4 u_View; // x/y = resolution, z = fov
|
||||
vec4 u_Cam; // x = yaw, y = pitch
|
||||
vec4 u_Thunder; // x = interval, y = chance, z = glow
|
||||
};
|
||||
|
||||
out vec4 color;
|
||||
|
||||
mat3 rotX(float a) {
|
||||
float c = cos(a), s = sin(a);
|
||||
return mat3(1.0, 0.0, 0.0,
|
||||
0.0, c, s,
|
||||
0.0, -s, c);
|
||||
}
|
||||
mat3 rotY(float a) {
|
||||
float c = cos(a), s = sin(a);
|
||||
return mat3( c, 0.0, s,
|
||||
0.0, 1.0, 0.0,
|
||||
-s, 0.0, c);
|
||||
}
|
||||
|
||||
// Optimized fast hash function
|
||||
float hash(vec3 p) {
|
||||
return fract(sin(dot(p, vec3(127.1, 311.7, 74.7))) * 43758.5453123);
|
||||
}
|
||||
|
||||
float hash(float n) {
|
||||
return fract(sin(n) * 43758.5453123);
|
||||
}
|
||||
|
||||
// Vectorized 3D noise for FBM nebulae
|
||||
float noise(vec3 p) {
|
||||
vec3 i = floor(p);
|
||||
vec3 f = fract(p);
|
||||
f = f * f * (3.0 - 2.0 * f);
|
||||
|
||||
float n000 = hash(i + vec3(0.0, 0.0, 0.0));
|
||||
float n100 = hash(i + vec3(1.0, 0.0, 0.0));
|
||||
float n010 = hash(i + vec3(0.0, 1.0, 0.0));
|
||||
float n110 = hash(i + vec3(1.0, 1.0, 0.0));
|
||||
float n001 = hash(i + vec3(0.0, 0.0, 1.0));
|
||||
float n101 = hash(i + vec3(1.0, 0.0, 1.0));
|
||||
float n011 = hash(i + vec3(0.0, 1.0, 1.0));
|
||||
float n111 = hash(i + vec3(1.0, 1.0, 1.0));
|
||||
|
||||
vec4 a = mix(vec4(n000, n010, n001, n011),
|
||||
vec4(n100, n110, n101, n111), f.x);
|
||||
vec2 b = mix(a.xz, a.yw, f.y);
|
||||
return mix(b.x, b.y, f.z);
|
||||
}
|
||||
|
||||
float fbm(vec3 p) {
|
||||
float v = 0.0;
|
||||
float a = 0.5;
|
||||
vec3 shift = vec3(100.0);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
v += a * noise(p);
|
||||
p = p * 2.0 + shift;
|
||||
a *= 0.5;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
// Realistic twinkling star field with varied star colors
|
||||
vec3 starField(vec3 rd) {
|
||||
// Reduced density (130.0 grid) and higher threshold (0.992) for sharper background stars
|
||||
vec3 gridPos = floor(rd * 130.0);
|
||||
float starHash = hash(gridPos);
|
||||
|
||||
vec3 starColor = vec3(0.0);
|
||||
if (starHash > 0.992) {
|
||||
vec3 starOffset = vec3(hash(gridPos + 1.1), hash(gridPos + 2.2), hash(gridPos + 3.3)) - 0.5;
|
||||
vec3 starPos = (gridPos + 0.5 + starOffset * 0.85) / 130.0;
|
||||
|
||||
float d = length(rd - normalize(starPos));
|
||||
|
||||
// Twinkling effect
|
||||
float twinkle = 0.55 + 0.45 * sin(u_Params.x * 3.5 + starHash * 120.0);
|
||||
float starIntensity = exp(-d * 600.0) * twinkle * (0.3 + 0.7 * hash(gridPos + 5.5));
|
||||
|
||||
// Varied color temperatures for stars
|
||||
vec3 col = vec3(1.0);
|
||||
float colorHash = hash(gridPos + 7.7);
|
||||
if (colorHash < 0.22) {
|
||||
col = vec3(0.65, 0.8, 1.0); // Hot blue star
|
||||
} else if (colorHash < 0.38) {
|
||||
col = vec3(1.0, 0.82, 0.6); // Warm orange star
|
||||
} else if (colorHash < 0.43) {
|
||||
col = vec3(1.0, 0.55, 0.55); // Cool red star
|
||||
}
|
||||
|
||||
starColor = col * starIntensity;
|
||||
}
|
||||
return starColor;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = gl_FragCoord.xy / u_View.xy;
|
||||
vec2 sp = uv * 2.0 - 1.0;
|
||||
float aspect = u_View.x / u_View.y;
|
||||
|
||||
float tanV = tan(radians(u_View.z) * 0.5);
|
||||
vec3 rayV = normalize(vec3(sp.x * tanV * aspect, sp.y * tanV, 1.0));
|
||||
vec3 rayW = rotY(u_Cam.x) * rotX(u_Cam.y) * rayV;
|
||||
|
||||
// 1. Cosmic background with stars and detailed dust lane nebulae
|
||||
float n1 = fbm(rayW * u_Params.z * 0.4 + vec3(u_Params.x * 0.003, 0.0, 0.0));
|
||||
float n2 = fbm(rayW * u_Params.z * 0.9 - vec3(0.0, u_Params.x * 0.002, 0.0));
|
||||
|
||||
// Glowing active gas clouds
|
||||
vec3 gasColor = mix(vec3(0.001, 0.003, 0.012), vec3(0.035, 0.01, 0.055), n1);
|
||||
gasColor = mix(gasColor, vec3(0.008, 0.025, 0.035), n2 * n1);
|
||||
|
||||
// Dark silhouette dust lanes blocking the gas
|
||||
float dust = smoothstep(0.3, 0.7, fbm(rayW * u_Params.z * 1.2 + vec3(0.1)));
|
||||
vec3 skyBackground = mix(gasColor, vec3(0.0005, 0.0005, 0.0015), dust * 0.85);
|
||||
|
||||
vec3 col = skyBackground + starField(rayW);
|
||||
|
||||
// 2. Precessing Pulsar math
|
||||
vec3 pulsarCenter = normalize(vec3(0.55, 0.65, -0.45));
|
||||
|
||||
// Only render pulsar on the front-facing hemisphere
|
||||
float frontMask = smoothstep(0.0, 0.4, dot(rayW, pulsarCenter));
|
||||
|
||||
if (frontMask > 0.0) {
|
||||
// Orthogonal tangents relative to the pulsar direction
|
||||
vec3 tangent1 = normalize(cross(pulsarCenter, vec3(0.0, 1.0, 0.0)));
|
||||
vec3 tangent2 = cross(tangent1, pulsarCenter);
|
||||
|
||||
// Local 2D coordinates centered on the pulsar core
|
||||
vec2 p2d = vec2(dot(rayW, tangent1), dot(rayW, tangent2));
|
||||
float dCore = length(p2d);
|
||||
|
||||
// Core (Neutron star) - Pulsating size
|
||||
float pulse = 0.95 + 0.05 * sin(u_Params.x * u_Params.y * 8.0);
|
||||
float coreSize = 0.058 * pulse;
|
||||
float core = smoothstep(coreSize, 0.0, dCore);
|
||||
|
||||
// Detailed solar corona/flares on the halo
|
||||
float flare = fbm(rayW * 12.0 + vec3(0.0, u_Params.x * 0.25, 0.0));
|
||||
float halo = exp(-dCore * 16.0) * 3.2 + exp(-dCore * 3.5) * 0.9 * (0.7 + 0.3 * flare);
|
||||
|
||||
// 3. Wobbling vertical plasma jets (shakes back and forth)
|
||||
float wobbleAngle = sin(u_Params.x * u_Params.y * 6.0) * 0.22;
|
||||
vec2 jetDir2d = vec2(sin(wobbleAngle), cos(wobbleAngle));
|
||||
|
||||
float distAlong = dot(p2d, jetDir2d);
|
||||
|
||||
// Outward propagating plasma displacement waves
|
||||
float wave = sin(abs(distAlong) * 75.0 - u_Params.x * u_Params.y * 32.0) * 0.0055 * abs(distAlong);
|
||||
float distToJet = length(p2d - distAlong * jetDir2d) - wave;
|
||||
|
||||
// Collimated core jet + soft glowing outer shell, fading along the distance
|
||||
float jetIntensity = (exp(-distToJet * 320.0) * 3.0 + exp(-distToJet * 38.0) * 0.8);
|
||||
jetIntensity *= exp(-abs(distAlong) * 0.85);
|
||||
|
||||
// High frequency electrical flicker/sparkle of the jets
|
||||
float jetFlicker = 0.82 + 0.18 * sin(u_Params.x * 115.0) * cos(u_Params.x * 145.0);
|
||||
|
||||
// 4. Combine Pulsar Layers
|
||||
vec3 coreColor = vec3(1.0, 1.0, 1.0);
|
||||
vec3 haloColor = vec3(0.18, 0.45, 1.0) * u_Color.rgb;
|
||||
vec3 jetColor = vec3(0.35, 0.68, 1.0) * u_Color.rgb;
|
||||
|
||||
vec3 pulsarCol = mix(vec3(0.0), coreColor, core);
|
||||
pulsarCol += haloColor * halo;
|
||||
pulsarCol += jetColor * jetIntensity * jetFlicker;
|
||||
|
||||
// Apply front mask to ensure smooth fade at horizon/boundaries
|
||||
col += pulsarCol * frontMask;
|
||||
}
|
||||
|
||||
// Apply global intensity adjustment and alpha transparency
|
||||
col *= (1.0 + u_Params.w * 10.0);
|
||||
|
||||
color = vec4(col, u_Color.a);
|
||||
}
|
||||
|
|
@ -0,0 +1,211 @@
|
|||
#version 330 core
|
||||
|
||||
layout (std140) uniform ShaderSky {
|
||||
vec4 u_Color; // rgb = tint, a = alpha
|
||||
vec4 u_Params; // x = time, y = speed, z = scale, w = intensity
|
||||
vec4 u_View; // x/y = resolution, z = fov
|
||||
vec4 u_Cam; // x = yaw, y = pitch
|
||||
vec4 u_Thunder; // x = interval, y = chance, z = glow
|
||||
};
|
||||
|
||||
out vec4 color;
|
||||
|
||||
mat3 rotX(float a) {
|
||||
float c = cos(a), s = sin(a);
|
||||
return mat3(1.0, 0.0, 0.0,
|
||||
0.0, c, s,
|
||||
0.0, -s, c);
|
||||
}
|
||||
mat3 rotY(float a) {
|
||||
float c = cos(a), s = sin(a);
|
||||
return mat3( c, 0.0, s,
|
||||
0.0, 1.0, 0.0,
|
||||
-s, 0.0, c);
|
||||
}
|
||||
|
||||
// Highly optimized fast hash function using GPU hardware dot & sin
|
||||
float hash(vec3 p) {
|
||||
return fract(sin(dot(p, vec3(127.1, 311.7, 74.7))) * 43758.5453123);
|
||||
}
|
||||
|
||||
float hash(float n) {
|
||||
return fract(sin(n) * 43758.5453123);
|
||||
}
|
||||
|
||||
// Vectorized 3D noise for maximum GPU throughput
|
||||
float noise(vec3 p) {
|
||||
vec3 i = floor(p);
|
||||
vec3 f = fract(p);
|
||||
f = f * f * (3.0 - 2.0 * f);
|
||||
|
||||
float n000 = hash(i + vec3(0.0, 0.0, 0.0));
|
||||
float n100 = hash(i + vec3(1.0, 0.0, 0.0));
|
||||
float n010 = hash(i + vec3(0.0, 1.0, 0.0));
|
||||
float n110 = hash(i + vec3(1.0, 1.0, 0.0));
|
||||
float n001 = hash(i + vec3(0.0, 0.0, 1.0));
|
||||
float n101 = hash(i + vec3(1.0, 0.0, 1.0));
|
||||
float n011 = hash(i + vec3(0.0, 1.0, 1.0));
|
||||
float n111 = hash(i + vec3(1.0, 1.0, 1.0));
|
||||
|
||||
vec4 a = mix(vec4(n000, n010, n001, n011),
|
||||
vec4(n100, n110, n101, n111), f.x);
|
||||
vec2 b = mix(a.xz, a.yw, f.y);
|
||||
return mix(b.x, b.y, f.z);
|
||||
}
|
||||
|
||||
float fbm(vec3 p) {
|
||||
float v = 0.0;
|
||||
float a = 0.5;
|
||||
vec3 shift = vec3(100.0);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
v += a * noise(p);
|
||||
p = p * 2.0 + shift;
|
||||
a *= 0.5;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = gl_FragCoord.xy / u_View.xy;
|
||||
vec2 sp = uv * 2.0 - 1.0;
|
||||
float aspect = u_View.x / u_View.y;
|
||||
|
||||
float tanV = tan(radians(u_View.z) * 0.5);
|
||||
vec3 rayV = normalize(vec3(sp.x * tanV * aspect, sp.y * tanV, 1.0));
|
||||
vec3 rayW = rotY(u_Cam.x) * rotX(u_Cam.y) * rayV;
|
||||
|
||||
// 1. Storm clouds overhead
|
||||
float cloudDensity = 0.0;
|
||||
if (rayW.y > -0.15) {
|
||||
// FBM 3D noise for cloud density, scrolled over time
|
||||
vec3 p = rayW * u_Params.z;
|
||||
p.x += u_Params.x * u_Params.y * 0.08;
|
||||
p.z += u_Params.x * u_Params.y * 0.04;
|
||||
|
||||
cloudDensity = fbm(p);
|
||||
// Fade clouds near and below horizon
|
||||
cloudDensity *= smoothstep(-0.15, 0.3, rayW.y);
|
||||
}
|
||||
|
||||
// 2. Procedural lightning generator
|
||||
float interval = max(1.0, u_Thunder.x); // lightning triggers every interval seconds
|
||||
float timeIndex = floor(u_Params.x / interval);
|
||||
float timeOffset = fract(u_Params.x / interval);
|
||||
|
||||
// Decisions based on time index
|
||||
float strikeHash = hash(timeIndex * 12.34);
|
||||
bool hasStrike = strikeHash < u_Thunder.y; // Custom chance of strike
|
||||
|
||||
float flash = 0.0;
|
||||
float boltGlow = 0.0;
|
||||
|
||||
if (hasStrike) {
|
||||
// Random horizontal angle for the lightning bolt
|
||||
float strikeAngle = hash(timeIndex * 45.67) * 6.28318;
|
||||
|
||||
// Define bolt start (high sky) and end (near horizon)
|
||||
vec3 boltStart = normalize(vec3(cos(strikeAngle), 1.0, sin(strikeAngle)));
|
||||
vec3 boltEnd = normalize(vec3(cos(strikeAngle + (hash(timeIndex * 8.3) - 0.5) * 0.2), -0.2, sin(strikeAngle + (hash(timeIndex * 8.3) - 0.5) * 0.2)));
|
||||
|
||||
float t = timeOffset;
|
||||
if (t < 0.55) {
|
||||
// Electrical flicker overlay (high frequency hum)
|
||||
float flicker = 0.8 + 0.2 * sin(u_Params.x * 95.0) * cos(u_Params.x * 135.0);
|
||||
|
||||
// Realistic double-flash lighting pattern
|
||||
flash = (exp(-t * 22.0) * 1.5 + exp(-abs(t - 0.15) * 25.0) * 1.0 + exp(-abs(t - 0.35) * 15.0) * 0.4) * flicker;
|
||||
|
||||
// Build the lightning bolt
|
||||
float boltIntensity = max(exp(-t * 18.0), exp(-abs(t - 0.15) * 22.0));
|
||||
if (boltIntensity > 0.02) {
|
||||
vec3 segment = boltEnd - boltStart;
|
||||
float segLen = length(segment);
|
||||
vec3 segDir = segment / segLen;
|
||||
|
||||
// Project ray onto the segment
|
||||
float h = dot(rayW - boltStart, segDir);
|
||||
h = clamp(h, 0.0, segLen);
|
||||
vec3 projection = boltStart + segDir * h;
|
||||
|
||||
// Generate detailed jagged offsets using multiple octaves of sine waves
|
||||
float noiseSeed = timeIndex * 73.19;
|
||||
float jagged = sin(h * 30.0 + noiseSeed) * 0.035;
|
||||
jagged += cos(h * 70.0 - noiseSeed) * 0.015;
|
||||
jagged += sin(h * 150.0 + noiseSeed * 1.5) * 0.007;
|
||||
jagged += cos(h * 350.0 - noiseSeed * 3.0) * 0.003; // extra high frequency details
|
||||
|
||||
vec3 offsetProj = projection + vec3(jagged, 0.0, jagged * 0.6);
|
||||
|
||||
// Distance from camera ray to the jagged bolt
|
||||
float distToBolt = length(rayW - normalize(offsetProj));
|
||||
|
||||
// Hot white core + soft blue/purple glow
|
||||
boltGlow = (exp(-distToBolt * 280.0) * 2.5 + exp(-distToBolt * 30.0) * 0.6) * boltIntensity * flicker;
|
||||
|
||||
// Branch 1 (going right)
|
||||
if (hash(timeIndex * 29.5) > 0.3 && h > segLen * 0.2) {
|
||||
vec3 bStart = projection;
|
||||
vec3 bEnd = normalize(boltEnd + vec3(sin(strikeAngle + 0.8), -0.15, cos(strikeAngle + 0.8)) * 0.4);
|
||||
vec3 bSeg = bEnd - bStart;
|
||||
float bLen = length(bSeg);
|
||||
if (bLen > 0.0) {
|
||||
vec3 bDir = bSeg / bLen;
|
||||
float bh = dot(rayW - bStart, bDir);
|
||||
bh = clamp(bh, 0.0, bLen);
|
||||
vec3 bProj = bStart + bDir * bh;
|
||||
float bJagged = sin(bh * 45.0 + noiseSeed) * 0.015 + cos(bh * 110.0) * 0.006;
|
||||
vec3 bOffsetProj = bProj + vec3(bJagged, 0.0, bJagged);
|
||||
float distToBranch = length(rayW - normalize(bOffsetProj));
|
||||
|
||||
boltGlow += (exp(-distToBranch * 220.0) * 1.2 + exp(-distToBranch * 30.0) * 0.3) * boltIntensity * (1.0 - bh/bLen) * flicker;
|
||||
}
|
||||
}
|
||||
|
||||
// Branch 2 (going left, from a different position)
|
||||
if (hash(timeIndex * 83.2) > 0.5 && h > segLen * 0.4) {
|
||||
vec3 bStart = projection;
|
||||
vec3 bEnd = normalize(boltEnd + vec3(sin(strikeAngle - 0.8), -0.2, cos(strikeAngle - 0.8)) * 0.45);
|
||||
vec3 bSeg = bEnd - bStart;
|
||||
float bLen = length(bSeg);
|
||||
if (bLen > 0.0) {
|
||||
vec3 bDir = bSeg / bLen;
|
||||
float bh = dot(rayW - bStart, bDir);
|
||||
bh = clamp(bh, 0.0, bLen);
|
||||
vec3 bProj = bStart + bDir * bh;
|
||||
float bJagged = sin(bh * 50.0 - noiseSeed) * 0.015 + cos(bh * 120.0) * 0.006;
|
||||
vec3 bOffsetProj = bProj + vec3(bJagged, 0.0, bJagged);
|
||||
float distToBranch = length(rayW - normalize(bOffsetProj));
|
||||
|
||||
boltGlow += (exp(-distToBranch * 220.0) * 1.2 + exp(-distToBranch * 30.0) * 0.3) * boltIntensity * (1.0 - bh/bLen) * flicker;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Compose Colors
|
||||
// Theme color input is blended with a dark storm color scheme
|
||||
vec3 stormBase = mix(vec3(0.005, 0.005, 0.015), u_Color.rgb * 0.05, 0.5);
|
||||
|
||||
// Background sky flashes scaled by custom glow multiplier
|
||||
vec3 flashColor = vec3(0.75, 0.82, 1.0) * flash * u_Thunder.z;
|
||||
vec3 finalSky = stormBase + flashColor * 0.25;
|
||||
|
||||
// Ambient cloud lighting from within scaled by custom glow multiplier
|
||||
vec3 cloudBaseColor = mix(vec3(0.01, 0.01, 0.02), vec3(0.06, 0.07, 0.12), cloudDensity);
|
||||
vec3 cloudLitColor = mix(vec3(0.02, 0.025, 0.05), vec3(0.8, 0.85, 1.0), cloudDensity * flash * u_Thunder.z * 0.7);
|
||||
|
||||
vec3 finalCloud = mix(cloudBaseColor, cloudLitColor, min(flash * u_Thunder.z, 1.0));
|
||||
|
||||
// Blend background sky and storm clouds
|
||||
float cloudMix = smoothstep(0.18, 0.48, cloudDensity);
|
||||
vec3 finalColor = mix(finalSky, finalCloud, cloudMix);
|
||||
|
||||
// Layer the bright lightning bolt on top scaled by custom glow multiplier
|
||||
finalColor += vec3(0.85, 0.92, 1.0) * boltGlow * u_Thunder.z;
|
||||
|
||||
// Apply global intensity adjustment and alpha transparency
|
||||
finalColor *= (1.0 + u_Params.w * 10.0);
|
||||
|
||||
color = vec4(finalColor, u_Color.a);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue