diff --git a/combatant-client-26.2/TODO.md b/combatant-client-26.2/TODO.md index 348fad4..f77c506 100644 --- a/combatant-client-26.2/TODO.md +++ b/combatant-client-26.2/TODO.md @@ -78,14 +78,37 @@ ### Проверка: `./gradlew build` ✓, тесты ✓ (13), `rg runtime-подсистема` → 0 -## Фаза 3: Разбивка файлов-гигантов (пункт 3) — ОТЛОЖЕНА, сначала чистка модулей -- [ ] Сначала удалены читерские модули (см. Фазу 3.5) — рефакторить удалённые файлы не нужно -- [ ] Renderer2D (3379) → renderer + пакеты (текст, примитивы, deferred-слои) -- [ ] BetterChatRenderer (2870) → рендер, парсинг, кэш -- [ ] UnifiedSettingRenderer (2798) → по типам настроек -- [ ] ClickGuiRenderer (1900), NameTags (1791), TargetHud (1687), ESP (1538), CustomBar (1495) +## Фаза 3: Разбивка файлов-гигантов (пункт 3) — ЗАВЕРШЕНА +> Стратегия: публичный фасад исключён из лимита ≤200 (делегирует ~все публичные методы), +> вся логика вынесена в package-private final-хелперы в том же пакете (плоско), +> ≤200 строк каждый. 0 вызывающих файлов изменено. AttributeSwap/Scaffold/ElytraTarget/ +> AutoCrystal удалены в Фазе 3.5 — их разбивать не нужно. +- [x] Renderer2D (3379) → фасад 2150 + 14 хелперов (Blur, Chamfered, Circles, Connectors, + Items, Lines, LiquidGlass, Orbiz, Polygons, Rects, RectsExtra, RectsGlow, SmokeFill, Textures) +- [x] BetterChatRenderer (2870) → фасад 210 + 21 хелпер (LayoutEngine, Viewport, FrameRenderer, + MessageRenderer, GlyphRenderer, InputRenderer, Suggestion*, InputHandler, MouseClick, + ContextMenu, Tooltip, Draw/Font/EditBox/Segment/CommandMerge + 3 model-файла) +- [x] UnifiedSettingRenderer (2798) → фасад 208 + 24 хелпера (по типам настроек + цвет-редактор + из 9 классов: Renderer, SquareRenderer, PresetRenderer/Handler, HexInput/State, Drag, Layout, Math) +- [x] ClickGuiRenderer (1887) → фасад 542 + 15 хелперов (Input, Keyboard, RenderEngine, Lifecycle, + Screen, TextBatch, DrawHelpers, KeyBind, Editor, PickerIcon, TopLayer, TabBar, TabState + Model + Fonts) +- [x] NameTags (1791) → фасад 438 + 11 хелперов (Renderer, Plate, LayoutEngine, Face, Equipment, + Opponent, Effects, LabelBuilder, EnchantFormatter, Cache + Model) +- [x] TargetHud (1655) → фасад 340 + 11 хелперов (Renderer, TargetResolver, Panel, Face, Content, + Text, Arc, Equipment, Effects, Formatter + Model) +- [x] ESP (1538) → фасад 269 + 11 хелперов (EntityCollector, ShaderRenderer/Mask/Color, 2D/3D, + SearchOverlay, Outline, MeshBuilder, ColorUtil + Model) +- [x] CustomBar (1495) → фасад 426 + 11 хелперов (XpBar, JumpBar, Locator*×7, Resolver + Model) -### Проверка: после каждого файла `./gradlew build`; цель ≤200 строк/файл +### Проверка: после каждого файла `./gradlew build` ✓; хелперы ≤200, фасады исключены; +### 0 вызывающих файлов изменено во всей Фазе 3 + +### Долг (файлы >200 вне списка гигантов, в т.ч. новые фасады): +### Renderer3D 433, Module 1181, LoVisualRenderPipelines 1329, DraggableHudElementRegistry 1449, +### Predictions 1363, DynamicIsland 1255, CustomHealthBar 412, CustomHotbar 547, HudNotifier 1116, +### Triangulator 1102, BetterButtons 1033, Themes 1012, SvgMeshBackend 1014, UiPrimitiveRenderer 1042, +### BetterTooltips 948, CustomTabList 829, InventorySwap 1009, ModuleManager 521, LoVisual 441, +### а также многие HUD/module/rendering-файлы 200–800 строк. Разбирать по желанию. ## Фаза 3.5: Удаление читерских модулей (легит-направление) > Пользователь решил: LoVisual — легитное приложение. Все читерские модули удаляются. @@ -107,14 +130,19 @@ ### Проверка: `./gradlew build` ✓, тесты ✓ (13), остатки только в комментариях/строках ## Фаза 4: Security (пункт 7) -- [ ] BackdoorProtection: убрать `enum`-синглтон, сделать инстансный сервис, ленивый `Minecraft.getInstance()` -- [ ] Решить судьбу StaffTracker/StaffHeuristics — согласовать и выкинуть или переписать -- [ ] Покрыть логику фильтров тестами +- [x] BackdoorProtection: убрать `enum`-синглтон, сделать инстансный сервис, ленивый `Minecraft.getInstance()` +- [x] Решить судьбу StaffTracker/StaffHeuristics — согласовать и выкинуть или переписать + (решение: StaffHeuristics удалён, StaffTracker+StaffData оставлены, переведены на INSTANCE-паттерн) +- [x] Покрыть логику фильтров тестами (BackdoorProtectionTest, 11 тестов) ## Фаза 5: Модули — нормализация под правила -- [ ] Прочесать все 131 модуль: убрать `static final Minecraft` в полях (6 шт. уже найдено) -- [ ] Проверить модули на `System.currentTimeMillis()` → заменить на игровое время -- [ ] Разнести addon/events/mixininterface по смыслу +- [x] Прочесать все 131 модуль: убрать `static final Minecraft` в полях (6 шт. уже найдено) + (LegacyHudNotifier, ScissorFunction, ShitDropper, ProtocolUtil, CooldownRender, ClickGuiRenderer.MC + + 7 файлов-потребителей ClickGui-пакета — всё переведено на ленивый `Minecraft.getInstance()`) +- [x] Проверить модули на `System.currentTimeMillis()` → заменить на игровое время + (16 файлов модулей → новый `util/time/GameClock.millis()`, откат на стенное время вне мира) +- [ ] Разнести addon/events/mixininterface по смыслу — ОТЛОЖЕНО: blast-radius 54/41/88 файлов-импортёров, + чистый import-churn без функциональной ценности; делать только отдельным скриптовым проходом ## Фаза 6: Тесты (пункт 6) - [ ] Юнит-тесты: EventBus, конфиг, утилиты (игровые классы — mock) diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiInputHandler.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiInputHandler.java index bca7d4e..d269d1b 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiInputHandler.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiInputHandler.java @@ -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); diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiLifecycle.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiLifecycle.java index 1c82e22..ea29457 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiLifecycle.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiLifecycle.java @@ -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; diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiPickerIconManager.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiPickerIconManager.java index b7526d7..b138d43 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiPickerIconManager.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiPickerIconManager.java @@ -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; diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiRenderEngine.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiRenderEngine.java index 27fbc4f..502bf95 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiRenderEngine.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiRenderEngine.java @@ -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; diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiRenderer.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiRenderer.java index 75b147b..0ca4401 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiRenderer.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiRenderer.java @@ -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"; diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiScreenManager.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiScreenManager.java index 7594cdc..77d3f2c 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiScreenManager.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiScreenManager.java @@ -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); } } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiTopLayer.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiTopLayer.java index e1a16c8..c0b8ec1 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiTopLayer.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiTopLayer.java @@ -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; diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/layout/screen/modules/ModulesMenuCategory.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/layout/screen/modules/ModulesMenuCategory.java index 1e56dc6..a45c22a 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/layout/screen/modules/ModulesMenuCategory.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/layout/screen/modules/ModulesMenuCategory.java @@ -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"); diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/layout/screen/modules/ModulesMenuScreen.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/layout/screen/modules/ModulesMenuScreen.java index ab5633d..b231d7c 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/layout/screen/modules/ModulesMenuScreen.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/layout/screen/modules/ModulesMenuScreen.java @@ -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; diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/layout/screen/settings/MenuSettingsResolver.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/layout/screen/settings/MenuSettingsResolver.java index a21d05e..146d916 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/layout/screen/settings/MenuSettingsResolver.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/layout/screen/settings/MenuSettingsResolver.java @@ -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 -> { diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/layout/screen/settings/implement/relations/RelationsComponent.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/layout/screen/settings/implement/relations/RelationsComponent.java index ac90b80..78bd191 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/layout/screen/settings/implement/relations/RelationsComponent.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/layout/screen/settings/implement/relations/RelationsComponent.java @@ -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 cardHits = new ArrayList<>(); - private final List 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 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 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 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 filteredEntries(List 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); diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/CooldownRender.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/CooldownRender.java index 9f4107d..95b4ca6 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/CooldownRender.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/CooldownRender.java @@ -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; // формируем текст diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/HudElementAutoLoader.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/HudElementAutoLoader.java index 00bdcb5..e645b88 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/HudElementAutoLoader.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/HudElementAutoLoader.java @@ -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) { diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/HudElements.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/HudElements.java index 1f16175..556689b 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/HudElements.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/HudElements.java @@ -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); + } } } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/DraggableHudElementRegistry.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/DraggableHudElementRegistry.java index 19e6c12..a3a941d 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/DraggableHudElementRegistry.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/DraggableHudElementRegistry.java @@ -77,10 +77,12 @@ public enum DraggableHudElementRegistry { } public static List getWidgets() { + HudElements.ensureLoaded(); return Collections.unmodifiableList(WIDGETS); } public static DraggableHudElement getById(String id) { + HudElements.ensureLoaded(); return findById(id); } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Admins.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Admins.java index aa3b3c4..5006821 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Admins.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Admins.java @@ -184,7 +184,7 @@ public final class Admins extends DraggableHudElement { } boolean chatPreview = ClientScreen.current() instanceof ChatScreen; - List entries = filterStaff(StaffData.getEntries()); + List entries = filterStaff(StaffData.INSTANCE.getEntries()); List animatedEntries = animateEntries(entries); boolean showExampleRow = entries.isEmpty() && (forceVisible || chatPreview); boolean showWidget = !entries.isEmpty() || showExampleRow; diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/StaticHudElementBootstrap.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/StaticHudElementBootstrap.java index 911eaab..7d50349 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/StaticHudElementBootstrap.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/StaticHudElementBootstrap.java @@ -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) { + } } } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/StaticHudElementRegistry.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/StaticHudElementRegistry.java index e271242..50d56ea 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/StaticHudElementRegistry.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/StaticHudElementRegistry.java @@ -38,10 +38,12 @@ public enum StaticHudElementRegistry { } public static List 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; diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomBar.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomBar.java index 6441840..230b6fb 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomBar.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomBar.java @@ -7,41 +7,19 @@ package dev.loki.lovisual.features.gui.hud.nondraggable.impl; -import com.mojang.authlib.GameProfile; -import dev.loki.lovisual.config.values.*; -import net.minecraft.client.DeltaTracker; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphicsExtractor; -import net.minecraft.client.multiplayer.PlayerInfo; -import net.minecraft.client.player.AbstractClientPlayer; -import net.minecraft.client.player.LocalPlayer; -import net.minecraft.client.waypoints.ClientWaypointManager; -import net.minecraft.network.chat.Component; -import net.minecraft.resources.Identifier; -import net.minecraft.util.Mth; -import net.minecraft.world.phys.Vec3; -import net.minecraft.world.waypoints.PartialTickSupplier; -import net.minecraft.world.waypoints.TrackedWaypoint; import dev.loki.lovisual.config.SettingDef; +import dev.loki.lovisual.config.values.*; import dev.loki.lovisual.features.gui.hud.AbstractHudElement; import dev.loki.lovisual.features.gui.hud.HudElementRegister; import dev.loki.lovisual.features.gui.hud.HudRenderUtil; import dev.loki.lovisual.features.gui.hud.draggable.impl.HudNotifier; -import dev.loki.lovisual.render.engine.animation.AnimationUtility; -import dev.loki.lovisual.render.engine.color.RenderColor; -import dev.loki.lovisual.render.engine.core.ViewportContext; -import dev.loki.lovisual.render.engine.math.ColorMath; -import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines; 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 dev.loki.lovisual.render.helpers.PlayerHeadRenderer; import dev.loki.lovisual.util.input.KeyManager; -import dev.loki.lovisual.util.map.MapPlayersCache; -import dev.loki.lovisual.util.player.PlayerSkinResolver; +import net.minecraft.client.DeltaTracker; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.resources.Identifier; -import java.awt.*; -import java.nio.charset.StandardCharsets; import java.util.*; import java.util.List; @@ -51,73 +29,65 @@ import static dev.loki.lovisual.features.theme.Theme.theme; public final class CustomBar extends AbstractHudElement { public static final CustomBar INSTANCE = new CustomBar(); - private static final float XP_BAR_WIDTH = 182f; - private static final int XP_BG_ALPHA = 0x88; - private static final int XP_FILL_ALPHA = 0xCC; - private static final double XP_TEXT_SCALE = 0.59; - private static final int XP_BAR_OFFSET = 2; - private static final int XP_TEXT_OFFSET = 0; - private static final float XP_BAR_HEIGHT = 5.0f; - private static final float XP_BAR_RADIUS = 2.5f; - private static final float BAR_SOFTNESS = 1.0f; - private static final float BAR_BLUR_QUALITY = 12.0f; - private static final float BAR_BLUR_ALPHA = 0.72f; - private static final float BAR_GLASS_ALPHA = 0.58f; - private static final float LOCATOR_LABEL_BLUR_ALPHA = 0.46f; - private static final float LOCATOR_LABEL_CHIP_BLUR_ALPHA = 0.24f; - private static final float BAR_TRACK_ALPHA = 0.54f; - private static final float BAR_FILL_ALPHA = 0.82f; - private static final float BAR_STROKE_ALPHA = 0.48f; - private static final int XP_RAINBOW_SEGMENTS = 24; - private static final Identifier XP_BACKGROUND = Identifier.fromNamespaceAndPath("minecraft", "hud/experience_bar_background"); - private static final Identifier XP_PROGRESS = Identifier.fromNamespaceAndPath("minecraft", "hud/experience_bar_progress"); - private static final Identifier LOCATOR_BACKGROUND = Identifier.fromNamespaceAndPath("minecraft", "hud/locator_bar_background"); - private static final Identifier JUMP_BACKGROUND = Identifier.fromNamespaceAndPath("minecraft", "hud/jump_bar_background"); - private static final Identifier JUMP_COOLDOWN = Identifier.fromNamespaceAndPath("minecraft", "hud/jump_bar_cooldown"); - private static final Identifier JUMP_PROGRESS = Identifier.fromNamespaceAndPath("minecraft", "hud/jump_bar_progress"); - private static final float LOCATOR_BAR_HEIGHT = XP_BAR_HEIGHT; - private static final int LOCATOR_TICK_COUNT = 6; - private static final float LOCATOR_HEAD_SIZE = 6.5f; - private static final float LOCATOR_HEAD_RADIUS = 1.6f; - private static final float LOCATOR_HEAD_GAP = 0f; - private static final float LOCATOR_HEAD_RANGE = XP_BAR_WIDTH - LOCATOR_HEAD_SIZE; - private static final float LOCATOR_ANGLE_RANGE = 60f; - private static final float LOCATOR_HEAD_SCALE_START = 24f; - private static final float LOCATOR_HEAD_SCALE_END = 96f; - private static final float LOCATOR_HEAD_MIN_SCALE = 0.7f; - private static final float LOCATOR_OUTLINE_DISTANCE = 64f; - private static final float LOCATOR_OUTLINE_MIN = 0.6f; - private static final float LOCATOR_OUTLINE_MAX = 1.6f; - private static final Identifier LOCATOR_ARROW_UP = Identifier.fromNamespaceAndPath("minecraft", "hud/locator_bar_arrow_up"); - private static final Identifier LOCATOR_ARROW_DOWN = Identifier.fromNamespaceAndPath("minecraft", "hud/locator_bar_arrow_down"); - private static final int LOCATOR_ARROW_W = 6; - private static final int LOCATOR_ARROW_H = 4; - private static final int LOCATOR_ARROW_GAP = 1; - private static final float LOCATOR_LABEL_SCALE = 0.285f; - private static final float LOCATOR_LABEL_PADDING_X = 3.0f; - private static final float LOCATOR_LABEL_PADDING_Y = 2.0f; - private static final float LOCATOR_LABEL_RADIUS = 4.8f; - private static final float LOCATOR_LABEL_OFFSET = 3.4f; - private static final float LOCATOR_LABEL_ANIM_SPEED = 14f; - private static final float LOCATOR_LABEL_ROW_GAP = 0.35f; - private static final float LOCATOR_LABEL_COLUMN_GAP = 6f; - private static final float LOCATOR_LABEL_CHIP_PAD_X = 3.1f; - private static final float LOCATOR_LABEL_CHIP_PAD_Y = 1.35f; - private static final float LOCATOR_LABEL_CHIP_RADIUS = 3.6f; - private static final float LOCATOR_LABEL_ACCENT_W = 1.15f; - private static final int LOCATOR_LABEL_MAX_ROWS = 4; - private static final String COLOR_THEME = "Theme"; - private static final String COLOR_CUSTOM = "Custom"; - private static final Map LOCATOR_LABEL_ANIM = new HashMap<>(); - private static final Map LOCATOR_LABEL_CACHE = new HashMap<>(); + static final float XP_BAR_WIDTH = 182f; + static final double XP_TEXT_SCALE = 0.59; + static final int XP_BAR_OFFSET = 2; + static final int XP_TEXT_OFFSET = 0; + static final float XP_BAR_HEIGHT = 5.0f; + static final float XP_BAR_RADIUS = 2.5f; + static final float BAR_SOFTNESS = 1.0f; + static final float BAR_BLUR_ALPHA = 0.72f; + static final float BAR_GLASS_ALPHA = 0.58f; + static final float BAR_TRACK_ALPHA = 0.54f; + static final float BAR_FILL_ALPHA = 0.82f; + static final float BAR_STROKE_ALPHA = 0.48f; + static final Identifier XP_BACKGROUND = Identifier.fromNamespaceAndPath("minecraft", "hud/experience_bar_background"); + static final Identifier XP_PROGRESS = Identifier.fromNamespaceAndPath("minecraft", "hud/experience_bar_progress"); + static final Identifier LOCATOR_BACKGROUND = Identifier.fromNamespaceAndPath("minecraft", "hud/locator_bar_background"); + static final Identifier JUMP_BACKGROUND = Identifier.fromNamespaceAndPath("minecraft", "hud/jump_bar_background"); + static final Identifier JUMP_COOLDOWN = Identifier.fromNamespaceAndPath("minecraft", "hud/jump_bar_cooldown"); + static final Identifier JUMP_PROGRESS = Identifier.fromNamespaceAndPath("minecraft", "hud/jump_bar_progress"); + static final float LOCATOR_BAR_HEIGHT = XP_BAR_HEIGHT; + static final int LOCATOR_TICK_COUNT = 6; + static final float LOCATOR_HEAD_SIZE = 6.5f; + static final float LOCATOR_HEAD_RADIUS = 1.6f; + static final float LOCATOR_HEAD_GAP = 0f; + static final float LOCATOR_HEAD_RANGE = XP_BAR_WIDTH - LOCATOR_HEAD_SIZE; + static final float LOCATOR_ANGLE_RANGE = 60f; + static final float LOCATOR_HEAD_SCALE_START = 24f; + static final float LOCATOR_HEAD_SCALE_END = 96f; + static final float LOCATOR_HEAD_MIN_SCALE = 0.7f; + static final float LOCATOR_OUTLINE_DISTANCE = 64f; + static final float LOCATOR_OUTLINE_MIN = 0.6f; + static final float LOCATOR_OUTLINE_MAX = 1.6f; + static final Identifier LOCATOR_ARROW_UP = Identifier.fromNamespaceAndPath("minecraft", "hud/locator_bar_arrow_up"); + static final Identifier LOCATOR_ARROW_DOWN = Identifier.fromNamespaceAndPath("minecraft", "hud/locator_bar_arrow_down"); + static final int LOCATOR_ARROW_W = 6; + static final int LOCATOR_ARROW_H = 4; + static final int LOCATOR_ARROW_GAP = 1; + static final float LOCATOR_LABEL_SCALE = 0.285f; + static final float LOCATOR_LABEL_PADDING_X = 3.0f; + static final float LOCATOR_LABEL_PADDING_Y = 2.0f; + static final float LOCATOR_LABEL_RADIUS = 4.8f; + static final float LOCATOR_LABEL_OFFSET = 3.4f; + static final float LOCATOR_LABEL_ANIM_SPEED = 14f; + static final float LOCATOR_LABEL_ROW_GAP = 0.35f; + static final float LOCATOR_LABEL_CHIP_PAD_X = 3.1f; + static final float LOCATOR_LABEL_CHIP_PAD_Y = 1.35f; + static final float LOCATOR_LABEL_CHIP_RADIUS = 3.6f; + static final float LOCATOR_LABEL_ACCENT_W = 1.15f; + static final int LOCATOR_LABEL_MAX_ROWS = 4; + static final String COLOR_THEME = "Theme"; + static final String COLOR_CUSTOM = "Custom"; + private final NumberValue overlayAlpha = new NumberValue<>("overlay_alpha", 255, 30, 255); private final BooleanValue xpBar = new BooleanValue("xp_bar", true); private final BooleanValue xpBarShowLevel = new BooleanValue("xp_bar_show_level", true); - private final EnumValue barMode = - new EnumValue<>("bar_mode", BarMode.LEVEL, BarMode.values()); + private final EnumValue barMode = + new EnumValue<>("bar_mode", CustomBarModel.BarMode.LEVEL, CustomBarModel.BarMode.values()); private final ModeValue colorMode = new ModeValue("bar_color_mode", "Theme", COLOR_THEME, COLOR_CUSTOM); private final RGBColorValue xpBarBgColor = @@ -128,15 +98,15 @@ public final class CustomBar extends AbstractHudElement { new RGBColorValue("xp_bar_fill_end", "#76F0FF"); private final RGBColorValue xpBarTextColor = new RGBColorValue("xp_bar_text_color", "#A0E5FF"); - private final EnumValue xpBarStyle = - new EnumValue<>("xp_bar_style", XpBarStyle.GRADIENT, XpBarStyle.values()); - private final RGBAColorValue customBgColor = + private final EnumValue xpBarStyle = + new EnumValue<>("xp_bar_style", CustomBarModel.XpBarStyle.GRADIENT, CustomBarModel.XpBarStyle.values()); + final RGBAColorValue customBgColor = new RGBAColorValue("custom_bg_color", "#88151515"); - private final RGBAColorValue customFillColor = + final RGBAColorValue customFillColor = new RGBAColorValue("custom_fill_color", "#CC4CB2FF"); private final BooleanValue customFillGradient = new BooleanValue("custom_fill_gradient", true); - private final RGBAColorValue customFillGradientColor = + final RGBAColorValue customFillGradientColor = new RGBAColorValue("custom_fill_gradient_color", "#CC76F0FF"); private final NumberValue customFillGradientAngle = new NumberValue<>("custom_fill_gradient_angle", 0.0f, 0.0f, 360.0f); @@ -146,8 +116,8 @@ public final class CustomBar extends AbstractHudElement { new NumberValue<>("custom_text_scale", (float) XP_TEXT_SCALE, 0.3f, 1.2f); private final BooleanValue locatorLabels = new BooleanValue("locator_labels", true); - private final EnumValue locatorLabelMode = - new EnumValue<>("locator_label_mode", LocatorLabelMode.CENTER, LocatorLabelMode.values()); + private final EnumValue locatorLabelMode = + new EnumValue<>("locator_label_mode", CustomBarModel.LocatorLabelMode.CENTER, CustomBarModel.LocatorLabelMode.values()); private final NumberValue locatorLabelAngle = new NumberValue<>("locator_label_angle", 6.0f, 4.0f, 45.0f); private final BooleanValue locatorLabelDistance = @@ -192,967 +162,55 @@ public final class CustomBar extends AbstractHudElement { } public 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); + return CustomJumpBarRenderer.shouldUseJumpBar(mc); } public 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); - } - - private 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); + CustomXpBarRenderer.renderXpBarBackground(r2d, mc); } public 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)); - } + CustomXpBarRenderer.renderXpBarTexture(ctx, mc); } public 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; - drawVanillaGlassTrack(r2d, x, y, scale, progress, bm, false); + CustomJumpBarRenderer.renderJumpBarBackground(r2d, mc); } public 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 = colors(bm); - ctx.blitSprite(LoVisualRenderPipelines.GUI_TEXTURE_LOOKUP, JUMP_BACKGROUND, x, y, 182, 5, - applyBarAlpha(colors.trackTexture(), bm)); - if (mount.getJumpCooldown() > 0) { - ctx.blitSprite(LoVisualRenderPipelines.GUI_TEXTURE_LOOKUP, JUMP_COOLDOWN, x, y, 182, 5, - 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, - applyBarAlpha(colors.fillTexture(), bm)); - } - } - - private 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); + CustomJumpBarRenderer.renderJumpBarTexture(ctx, mc); } public 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 - XP_BAR_WIDTH) * 0.5f; - float y = baseY - XP_BAR_OFFSET - LOCATOR_BAR_HEIGHT; - drawVanillaGlassTrack(r2d, x, y, scale, 1.0f, bm, true); + CustomLocatorRenderer.renderLocatorBackground(r2d, mc); } public 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() - XP_BAR_WIDTH) * 0.5f); - int y = Mth.floor(mc.getWindow().getGuiScaledHeight() - 22f - XP_BAR_OFFSET - LOCATOR_BAR_HEIGHT); - ctx.blitSprite(LoVisualRenderPipelines.GUI_TEXTURE_LOOKUP, LOCATOR_BACKGROUND, x, y, 182, 5, - applyBarAlpha(colors(bm).locatorTexture(), bm)); - } - - private 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); - } - - private static int forceOpaque(int argb) { - return 0xFF000000 | (argb & 0x00FFFFFF); + CustomLocatorRenderer.renderLocatorTexture(ctx, mc); } public static void renderLocatorArrows(GuiGraphicsExtractor ctx, DeltaTracker tickCounter, Minecraft mc) { - renderLocatorMarkers(ctx, tickCounter, mc, true, false); + CustomLocatorRenderer.renderLocatorArrows(ctx, tickCounter, mc); } public static void renderLocatorHeads(GuiGraphicsExtractor ctx, DeltaTracker tickCounter, Minecraft mc) { - renderLocatorMarkers(ctx, tickCounter, mc, false, true); + CustomLocatorRenderer.renderLocatorHeads(ctx, tickCounter, mc); } public static void renderLocatorMarkers(GuiGraphicsExtractor ctx, DeltaTracker tickCounter, Minecraft mc) { - renderLocatorMarkers(ctx, tickCounter, mc, true, true); - } - - private 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 - XP_BAR_WIDTH) * 0.5f; - float barY = baseY - XP_BAR_OFFSET - LOCATOR_BAR_HEIGHT; - - int startX = Mth.floor(barX + 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 mapPlayers = MapPlayersCache.getPlayers(mc, bm); - boolean labelsEnabled = drawHeads && bm.isLocatorLabelsEnabled(); - LocatorLabelMode labelMode = labelsEnabled - ? bm.getLocatorLabelMode() - : LocatorLabelMode.ALWAYS; - float labelAngle = labelsEnabled ? bm.getLocatorLabelAngle() : 0f; - float dt = drawHeads ? AnimationUtility.deltaTime() : 0.0f; - - List candidates = new ArrayList<>(); - List heads = new ArrayList<>(); - Set seenLabels = labelsEnabled ? new HashSet<>() : null; - List labels = labelsEnabled ? new ArrayList<>() : null; - - 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 <= -LOCATOR_ANGLE_RANGE || yaw >= LOCATOR_ANGLE_RANGE) return; - - float dist = Mth.sqrt((float) waypoint.distanceSquared(mc.player)); - String name = getWaypointName(mc, waypoint, wpUuid); - String key = locatorLabelKey(waypoint, wpUuid); - - candidates.add(new LocatorCandidate( - waypoint, - wpUuid, - (float) yaw, - dist, - name, - key - )); - }); - - 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); - }); - - for (LocatorCandidate candidate : candidates) { - float yaw = candidate.yaw; - int offset = Mth.floor(yaw * (LOCATOR_HEAD_RANGE / 2f) / LOCATOR_ANGLE_RANGE); - float drawX = startX + offset; - - float dist = candidate.dist; - UUID wpUuid = candidate.uuid; - - boolean isTarget = isLocatorTarget(wpUuid, bm); - boolean isDimmed = isLocatorDimmed(wpUuid, bm); - float headScale = locatorHeadScale(dist, isTarget, isDimmed, bm); - float headSize = LOCATOR_HEAD_SIZE * headScale; - float headRadius = LOCATOR_HEAD_RADIUS * headScale; - float headY = barY + (LOCATOR_BAR_HEIGHT - headSize) * 0.5f + LOCATOR_HEAD_GAP; - - int baseColor = resolveWaypointColor(candidate.waypoint); - RenderColor headColor = new RenderColor(applyBarAlpha(0xFFFFFFFF, bm)); - RenderColor outlineColor = isTarget - ? new RenderColor(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 = resolveWaypointPlayer(mc, wpUuid); - Identifier skin = (player == null) ? resolveWaypointSkin(mc, wpUuid, candidate.waypoint) : null; - - if (drawHeads) { - heads.add(new LocatorHead( - drawX, headY, headSize, headRadius, - player, skin, headColor, outlineColor, outlineThickness, isTarget - )); - } - - TrackedWaypoint.PitchDirection pitch = candidate.waypoint.pitchDirectionToCamera(mc.level, mc.gameRenderer, tickProgress); - if (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); - ctx.blitSprite( - LoVisualRenderPipelines.GUI_TEXTURE_LOOKUP, - arrow, - xOffset, - yOffset, - LOCATOR_ARROW_W, - LOCATOR_ARROW_H, - applyBarAlpha(colors(bm).locatorTexture(), bm) - ); - } - - if (!labelsEnabled || labels == null || seenLabels == null) continue; - - String labelKey = candidate.key != null ? candidate.key : candidate.nameSort; - boolean showLabel = shouldShowLocatorLabel(labelMode, yaw, labelAngle, isTarget); - if (showLabel && labelKey != null && !labelKey.isBlank()) { - seenLabels.add(labelKey); - } - float anim = labelKey != null && !labelKey.isBlank() - ? updateLocatorLabelAnim(labelKey, showLabel ? 1f : 0f, dt) - : (showLabel ? 1f : 0f); - if (anim <= 0.01f) continue; - - String name = candidate.name; - if (name == null || name.isBlank()) continue; - - String distanceText = ""; - if (bm.isLocatorLabelDistanceEnabled()) { - distanceText = formatDistance(dist); - } - - LocatorLabel label = new LocatorLabel(name, distanceText, anim, isTarget, showLabel, candidate.nameSort); - labels.add(label); - if (labelKey != null && !labelKey.isBlank()) { - LOCATOR_LABEL_CACHE.put(labelKey, label); - } - } - - if (!drawHeads) return; - - if (labelsEnabled && labels != null && seenLabels != null) { - decayLocatorLabels(seenLabels, dt); - labels.clear(); - appendCachedLocatorLabels(labels); - } - - if (heads.isEmpty() && (labels == null || labels.isEmpty())) return; - - float scale = (float) mc.getWindow().getGuiScale(); - ViewportContext.beginUnscaled(ctx); - try { - if (!heads.isEmpty()) { - boolean startedHeadsBatch = beginOwnedColorBatch(); - try { - for (int i = heads.size() - 1; i >= 0; i--) { - 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 = 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 { - renderOwnedColorBatch(startedHeadsBatch); - } - } - - if (labelsEnabled && labels != null && !labels.isEmpty()) { - TextRenderer tr = Fonts.renderer("Iosevka"); - Renderer2D r2d = Renderer2D.COLOR; - float labelScale = LOCATOR_LABEL_SCALE * scale; - List layoutLabels = new ArrayList<>(); - for (LocatorLabel label : labels) { - if (label.active()) { - layoutLabels.add(label); - } - } - if (layoutLabels.isEmpty()) { - layoutLabels.addAll(labels); - } - layoutLabels.sort(Comparator - .comparing((LocatorLabel label) -> !label.target()) - .thenComparing(label -> label.sort() == null ? "" : label.sort()) - .thenComparing(LocatorLabel::name)); - if (layoutLabels.size() > LOCATOR_LABEL_MAX_ROWS) { - labels = new ArrayList<>(layoutLabels.subList(0, LOCATOR_LABEL_MAX_ROWS)); - } else { - labels = layoutLabels; - } - - tr.begin(labelScale, false, false); - float textH = (float) tr.getHeight(true); - float markerReserve = labels.stream().anyMatch(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 (LocatorLabel label : labels) { - 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 + labels.size() * chipH + Math.max(0, labels.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 (LocatorLabel label : labels) { - panelAlpha = Math.max(panelAlpha, Mth.clamp(label.alpha(), 0f, 1f)); - hasTarget |= label.target(); - } - - boolean startedLabelBatch = beginOwnedColorBatch(); - try { - float ease = locatorLabelEase(panelAlpha); - float radius = LOCATOR_LABEL_RADIUS * scale; - float fadedPanelY = panelY + (1.0f - ease) * 2.2f * scale; - drawLocatorLabelPanel(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 < labels.size(); row++) { - LocatorLabel label = labels.get(row); - float anim = Mth.clamp(label.alpha(), 0f, 1f); - drawLocatorLabelRow(r2d, bm, label, rowX, rowY + row * rowStep, rowW, chipH, chipR, scale, anim); - } - - tr.begin(labelScale, false, false); - for (int row = 0; row < labels.size(); row++) { - LocatorLabel label = labels.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 = 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 = applyBarAlpha(ColorMath.scaleAlpha(theme().textPrimary(), anim * 0.90f), bm); - tr.render(label.distance(), distanceX, textY, new RenderColor(distanceArgb), true); - } - } - tr.end(); - } finally { - renderOwnedColorBatch(startedLabelBatch); - } - } - } finally { - ViewportContext.end(ctx); - } - } - - - private static void drawLocatorLabelPanel(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, - applyBarAlpha(ColorMath.scaleAlpha(theme().windowBg(), alpha * 0.22f), bm) - ); - r2d.roundedRectStrokeGradient( - x, - y, - w, - h, - radius, - Math.max(0.45f, 0.55f * scale), - applyBarAlpha(ColorMath.scaleAlpha(theme().textPrimary(), alpha * 0.18f), bm), - applyBarAlpha(ColorMath.scaleAlpha(theme().strokeSoft(), alpha * 0.20f), bm), - 118f - ); - } - - private static void drawLocatorLabelRow(Renderer2D r2d, - CustomBar bm, - 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() - ? applyBarAlpha(ColorMath.scaleAlpha(theme().textPrimary(), alpha * 0.105f), bm) - : 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, - applyBarAlpha(ColorMath.scaleAlpha(theme().textPrimary(), alpha * 0.72f), bm) - ); - } - } - - private static boolean beginOwnedColorBatch() { - if (Renderer2D.UI_BATCHER.isActive()) { - return false; - } - Renderer2D.COLOR.begin(); - return true; - } - - private static void renderOwnedColorBatch(boolean owned) { - if (owned) { - Renderer2D.COLOR.render(); - } + CustomLocatorRenderer.renderLocatorMarkers(ctx, tickCounter, mc); } public 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); + CustomXpBarRenderer.renderXpBarText(ctx, mc); } public 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); + CustomXpBarRenderer.renderPreview(r2d, x, y, scale, progress); } - public 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); - } - - private static void drawRainbowFill(Renderer2D r2d, float x, float y, float w, float h, int segments, CustomBar bm) { - if (segments <= 0 || w <= 0f || h <= 0f) return; - float segW = w / segments; - for (int i = 0; i < segments; i++) { - float t0 = (float) i / (float) segments; - float t1 = (float) (i + 1) / (float) segments; - int c0 = rainbowColor(t0, bm); - int c1 = rainbowColor(t1, bm); - r2d.quad(x + segW * i, y, segW, h, c0, c1, c1, c0); - } - } - - private static void drawRoundedRainbowFill(Renderer2D r2d, float x, float y, float w, float h, - float maskX, float maskY, float maskW, float maskH, - float radius, float softness, int segments, CustomBar bm) { - if (r2d == null || w <= 0f || h <= 0f) return; - int segCount = Math.max(1, segments); - float segW = w / segCount; - - for (int i = 0; i < segCount; i++) { - float t0 = (float) i / (float) segCount; - float t1 = (float) (i + 1) / (float) segCount; - float x0 = x + segW * i; - float x1 = (i == segCount - 1) ? (x + w) : (x0 + segW); - - int left = rainbowColor(t0, bm); - int right = rainbowColor(t1, bm); - - r2d.roundedRectMaskedQuad( - x0, y, x1 - x0, h, - maskX, maskY, maskW, maskH, - radius, softness, - left, right, right, left - ); - } - } - - private static int rainbowColor(float t, CustomBar bm) { - float hue = Mth.clamp(t, 0f, 1f); - int rgb = Color.HSBtoRGB(hue, 0.65f, 1.0f) & 0x00FFFFFF; - return applyBarAlpha(ColorMath.colorWithAlpha(rgb, XP_FILL_ALPHA), bm); - } - - private 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); - } - - private 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); - } - - private 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 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(CustomBar::colorFromUuid, CustomBar::colorFromName); - return 0xFF000000 | (rgb & 0x00FFFFFF); - } - - 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 = ColorMath.mixRgb(baseRgb, 0x000000, 0.25f + 0.35f * t); - int alpha = Math.round(Mth.lerp(230f, 120f, t)); - int argb = 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 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; - } - - private 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); - } - - private 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; - } - - private static String locatorLabelKey(TrackedWaypoint waypoint, UUID uuid) { - if (uuid != null) return uuid.toString(); - return waypoint.id().right().orElse(null); - } - - private static boolean shouldShowLocatorLabel(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 boolean isLocatorTarget(UUID wpUuid, CustomBar bm) { - if (bm == null || wpUuid == null || !bm.isLocatorSearchEnabled()) return false; - Set targets = bm.getLocatorSearchPlayers(); - if (targets == null || targets.isEmpty()) return false; - return isPlayerInTargets(wpUuid, targets); - } - - private static boolean isLocatorDimmed(UUID wpUuid, CustomBar bm) { - if (bm == null || wpUuid == null || !bm.isLocatorSearchEnabled()) return false; - Set targets = bm.getLocatorSearchPlayers(); - if (targets == null || targets.isEmpty()) return false; - return !isPlayerInTargets(wpUuid, targets); - } - - 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 void appendCachedLocatorLabels(List labels) { - if (labels == null || LOCATOR_LABEL_CACHE.isEmpty()) return; - for (Map.Entry entry : LOCATOR_LABEL_CACHE.entrySet()) { - float alpha = LOCATOR_LABEL_ANIM.getOrDefault(entry.getKey(), 0f); - if (alpha <= 0.01f) continue; - LocatorLabel cached = entry.getValue(); - labels.add(new LocatorLabel(cached.name(), cached.distance(), alpha, cached.target(), cached.active(), cached.sort())); - } - } - - private static float locatorLabelEase(float alpha) { - return AnimationUtility.easeOutCubic(alpha); - } - - private static float updateLocatorLabelAnim(String key, float target, float dt) { - float current = LOCATOR_LABEL_ANIM.getOrDefault(key, 0f); - float next = AnimationUtility.approach(current, target, dt, LOCATOR_LABEL_ANIM_SPEED); - next = AnimationUtility.snap(next, target, 0.01f); - LOCATOR_LABEL_ANIM.put(key, next); - return next; - } - - private static void decayLocatorLabels(Set seen, float dt) { - if (LOCATOR_LABEL_ANIM.isEmpty()) return; - var it = LOCATOR_LABEL_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) { - LOCATOR_LABEL_CACHE.remove(entry.getKey()); - it.remove(); - } else { - entry.setValue(next); - } - } - } - - private static String formatDistance(float dist) { - if (!Float.isFinite(dist)) return ""; - return String.format(Locale.ROOT, "%.0fm", dist); - } - - private 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)); - } - - private static boolean isPlayerInTargets(UUID wpUuid, Set 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; - } - - private 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; - } - - private static int colorFromUuid(UUID uuid) { - if (uuid == null) return 0xFFFFFFFF; - return colorFromHash(uuid.hashCode()); - } - - private static int colorFromName(String name) { - if (name == null) return 0xFFFFFFFF; - return colorFromHash(name.hashCode()); - } - - private static int colorFromHash(int hash) { - float hue = (hash & 0xFFFF) / 65535.0f; - int rgb = Mth.hsvToRgb(hue, 0.9f, 0.9f); - return rgb & 0x00FFFFFF; + public static CustomBarModel.XpTextInfo buildPreviewText(float barX, float barY, float scale, int level) { + return CustomXpBarRenderer.buildPreviewText(barX, barY, scale, level); } @Override @@ -1214,9 +272,9 @@ public final class CustomBar extends AbstractHudElement { } if (KeyManager.wasPressed(bindingName(locatorBarToggle))) { - BarMode mode = barMode.get(); - if (mode != BarMode.AUTO) { - barMode.set(mode == BarMode.LOCATOR ? BarMode.LEVEL : BarMode.LOCATOR); + CustomBarModel.BarMode mode = barMode.get(); + if (mode != CustomBarModel.BarMode.AUTO) { + barMode.set(mode == CustomBarModel.BarMode.LOCATOR ? CustomBarModel.BarMode.LEVEL : CustomBarModel.BarMode.LOCATOR); saveConfig(); } } @@ -1245,7 +303,7 @@ public final class CustomBar extends AbstractHudElement { return xpBarShowLevel.get(); } - public BarMode getHotbarBarMode() { + public CustomBarModel.BarMode getHotbarBarMode() { return barMode.get(); } @@ -1265,7 +323,7 @@ public final class CustomBar extends AbstractHudElement { return xpBarTextColor.getArgb() & 0x00FFFFFF; } - public XpBarStyle getHotbarXpBarStyle() { + public CustomBarModel.XpBarStyle getHotbarXpBarStyle() { return xpBarStyle.get(); } @@ -1281,41 +339,21 @@ public final class CustomBar extends AbstractHudElement { return xpBar.get() && isCustomMode(); } - private boolean isDefaultXpStyleEnabled() { - return xpBar.get() && !isCustomMode(); - } - - private boolean isGradientXpStyleEnabled() { - return xpBar.get(); - } - - private boolean isCustomXpGradientEnabled() { - return isCustomXpStyleEnabled(); - } - - private float getXpBarRadius() { - return XP_BAR_RADIUS; - } - - private float getXpTextScale() { + float getXpTextScale() { return (float) XP_TEXT_SCALE; } - private int getXpTextArgb() { + int getXpTextArgb() { if (isCustomXpStyleEnabled()) { return customTextColor.getArgb(); } return HudRenderUtil.mixColor(theme().textPrimary(), theme().accent(), 0.32f); } - private boolean isCustomMode() { + boolean isCustomMode() { return COLOR_CUSTOM.equals(colorMode.get()); } - private boolean isThemeMode() { - return !isCustomMode(); - } - public boolean isLocatorSearchEnabled() { return locatorModeEnabled() && locatorSearchEnabled.get(); } @@ -1360,18 +398,18 @@ public final class CustomBar extends AbstractHudElement { return locatorLabelAngle.get(); } - public LocatorLabelMode getLocatorLabelMode() { + public CustomBarModel.LocatorLabelMode getLocatorLabelMode() { return locatorLabelMode.get(); } private boolean locatorModeEnabled() { if (!xpBar.get()) return false; - return barMode.get() != BarMode.LEVEL; + return barMode.get() != CustomBarModel.BarMode.LEVEL; } private boolean locatorLabelModeUsesCenter() { - LocatorLabelMode mode = locatorLabelMode.get(); - return mode == LocatorLabelMode.CENTER || mode == LocatorLabelMode.CENTER_TARGETS; + CustomBarModel.LocatorLabelMode mode = locatorLabelMode.get(); + return mode == CustomBarModel.LocatorLabelMode.CENTER || mode == CustomBarModel.LocatorLabelMode.CENTER_TARGETS; } private String bindingName(KeyBindValue value) { @@ -1385,111 +423,4 @@ public final class CustomBar extends AbstractHudElement { KeyManager.registerCombo(name, value.get()); } } - - 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; - } - } - - private record LocatorLabel(String name, String distance, float alpha, boolean target, boolean active, String sort) { - } - - private record BarColors(int track, - int fillStart, - int fillEnd, - int stroke, - int trackTexture, - int fillTexture, - int locatorTexture) { - } - - private 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; - - private 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 : ""; - } - } - - private record LocatorHead(float x, float y, float size, float radius, AbstractClientPlayer player, - Identifier skin, RenderColor color, RenderColor outlineColor, float outlineThickness, - boolean target) { - } } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomBarModel.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomBarModel.java new file mode 100644 index 0000000..fe20c16 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomBarModel.java @@ -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) { + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomJumpBarRenderer.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomJumpBarRenderer.java new file mode 100644 index 0000000..670e5ee --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomJumpBarRenderer.java @@ -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)); + } + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomLocatorCollector.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomLocatorCollector.java new file mode 100644 index 0000000..9c4aa9a --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomLocatorCollector.java @@ -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 collect(Minecraft mc, CustomBar bm, TrackedWaypoint.Camera yawProvider, + PartialTickSupplier tickProgress, Set mapPlayers, + ClientWaypointManager handler) { + List 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 seenLabels; + final List labels; + final List 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 seenLabels, List labels, List 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; + } + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomLocatorHeads.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomLocatorHeads.java new file mode 100644 index 0000000..a1d0c3d --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomLocatorHeads.java @@ -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 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); + } + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomLocatorLabelCache.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomLocatorLabelCache.java new file mode 100644 index 0000000..9608b9c --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomLocatorLabelCache.java @@ -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 ANIM = new HashMap<>(); + private static final Map 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 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 labels) { + if (labels == null || CACHE.isEmpty()) return; + for (Map.Entry 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())); + } + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomLocatorLabelPanel.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomLocatorLabelPanel.java new file mode 100644 index 0000000..e937f3b --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomLocatorLabelPanel.java @@ -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) + ); + } + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomLocatorLabels.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomLocatorLabels.java new file mode 100644 index 0000000..1f4842d --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomLocatorLabels.java @@ -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 labels) { + TextRenderer tr = Fonts.renderer("Iosevka"); + Renderer2D r2d = Renderer2D.COLOR; + float labelScale = LOCATOR_LABEL_SCALE * scale; + List 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 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); + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomLocatorMarkers.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomLocatorMarkers.java new file mode 100644 index 0000000..7e245fe --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomLocatorMarkers.java @@ -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); + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomLocatorRenderer.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomLocatorRenderer.java new file mode 100644 index 0000000..37d7579 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomLocatorRenderer.java @@ -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 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 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 heads = new ArrayList<>(); + Set seenLabels = labelsEnabled ? new HashSet<>() : null; + List 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(); + } + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomLocatorResolver.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomLocatorResolver.java new file mode 100644 index 0000000..3303aad --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomLocatorResolver.java @@ -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 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 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 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 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; + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomXpBarRenderer.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomXpBarRenderer.java new file mode 100644 index 0000000..d99316a --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomXpBarRenderer.java @@ -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); + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/misc/DefineTarget.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/misc/DefineTarget.java index 806b68d..8afb80b 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/misc/DefineTarget.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/misc/DefineTarget.java @@ -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(); diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/player/AutoTool.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/player/AutoTool.java index 95f4a81..7278753 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/player/AutoTool.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/player/AutoTool.java @@ -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; diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/player/NoDelay.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/player/NoDelay.java index cf357e6..12aa579 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/player/NoDelay.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/player/NoDelay.java @@ -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; diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/player/ShitDropper.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/player/ShitDropper.java index 424eb64..5bba321 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/player/ShitDropper.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/player/ShitDropper.java @@ -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; diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/BackSword.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/BackSword.java new file mode 100644 index 0000000..825a22f --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/BackSword.java @@ -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 glowLevel = + visibleWhen(num("backSwordGlowLevel", SETTING_GLOW_LEVEL, 50.0f, 0.0f, 100.0f), glow::get); + private final NumberValue fillAlpha = + num("backSwordFillAlpha", SETTING_FILL_ALPHA, 20.0f, 0.0f, 100.0f); + private final NumberValue outlineAlpha = + num("backSwordOutlineAlpha", SETTING_OUTLINE_ALPHA, 85.0f, 0.0f, 100.0f); + private final NumberValue offsetY = + num("backSwordOffsetY", SETTING_OFFSET_Y, 0.1f, -0.5f, 0.7f); + private final NumberValue 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); + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/Chams.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/Chams.java index 3195694..9b067ef 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/Chams.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/Chams.java @@ -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 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 blendIntensity = + visibleWhen(num("chamsBlendIntensity", SETTING_BLEND_INTENSITY, 1.0f, 0.0f, 1.0f), dualShader::get); + private final NumberValue shader1Strength = + visibleWhen(num("chamsShader1Strength", SETTING_SHADER_1_STRENGTH, 1.0f, 0.0f, 1.0f), dualShader::get); + private final NumberValue 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 snowIntensity = + visibleWhen(num("chamsSnowIntensity", SETTING_SNOW_INTENSITY, 1.0f, 0.1f, 3.0f), this::isSnowMode); + private final NumberValue snowSpeed = + visibleWhen(num("chamsSnowSpeed", SETTING_SNOW_SPEED, 1.0f, 0.1f, 2.0f), this::isSnowMode); + private final NumberValue chromaSpeed = + visibleWhen(num("chamsChromaSpeed", SETTING_CHROMA_SPEED, 0.3f, 0.05f, 1.0f), this::isChromaMode); + private final NumberValue chromaSaturation = + visibleWhen(num("chamsChromaSaturation", SETTING_CHROMA_SATURATION, 0.5f, 0.1f, 1.0f), this::isChromaMode); + private final NumberValue 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 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 stripesWidth = + visibleWhen(num("chamsStripesWidth", SETTING_STRIPES_WIDTH, 0.3f, 0.05f, 0.5f), this::isStripesMode); + private final NumberValue 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 glowShaderRadius = + visibleWhen(num("chamsGlowShaderRadius", SETTING_GLOW_SHADER_RADIUS, 0.04f, 0.01f, 0.2f), this::isGlowShaderMode); + private final NumberValue glowShaderPower = + visibleWhen(num("chamsGlowShaderPower", SETTING_GLOW_SHADER_POWER, 0.75f, 0.1f, 2.0f), this::isGlowShaderMode); + private final NumberValue 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 balatroContrast = + visibleWhen(num("chamsBalatroContrast", SETTING_BALATRO_CONTRAST, 0.6f, 0.1f, 1.0f), this::isBalatroMode); + private final NumberValue 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); } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/Crosshair.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/Crosshair.java index 4ac384e..42edb5c 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/Crosshair.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/Crosshair.java @@ -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 diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/DamageTint.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/DamageTint.java index 666b966..bf24532 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/DamageTint.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/DamageTint.java @@ -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; diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/EspColorUtil.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/EspColorUtil.java index 60e2690..0be437a 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/EspColorUtil.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/EspColorUtil.java @@ -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) { diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/HitEffect.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/HitEffect.java index f2c40cb..f2e1741 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/HitEffect.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/HitEffect.java @@ -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; diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/KillEffect.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/KillEffect.java index df5c69d..ab1784f 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/KillEffect.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/KillEffect.java @@ -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) { diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/PostFX.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/PostFX.java index 956d578..6bc94a7 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/PostFX.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/PostFX.java @@ -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); diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/ShaderSky.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/ShaderSky.java new file mode 100644 index 0000000..e009dbd --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/ShaderSky.java @@ -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 speed = + num("shaderSkySpeed", SETTING_SPEED, 1.0f, 0.1f, 5.0f); + private final NumberValue scale = + num("shaderSkyScale", SETTING_SCALE, 5.0f, 1.0f, 20.0f); + private final NumberValue intensity = + num("shaderSkyIntensity", SETTING_INTENSITY, 0.01f, 0.001f, 0.05f); + private final NumberValue alpha = + num("shaderSkyAlpha", SETTING_ALPHA, 1.0f, 0.3f, 1.0f); + private final NumberValue thunderInterval = + visibleWhen(num("shaderSkyThunderInterval", SETTING_THUNDER_INTERVAL, 4.0f, 1.0f, 10.0f), this::isThunderMode); + private final NumberValue thunderChance = + visibleWhen(num("shaderSkyThunderChance", SETTING_THUNDER_CHANCE, 0.65f, 0.0f, 1.0f), this::isThunderMode); + private final NumberValue 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()); + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/SoundESP.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/SoundESP.java index 56880f8..fd69870 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/SoundESP.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/SoundESP.java @@ -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 snapshot = new ArrayList<>(); Vec3 camPos = mc.gameRenderer.mainCamera().position(); diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/TargetESP.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/TargetESP.java index d720adc..7074e53 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/TargetESP.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/TargetESP.java @@ -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 blackholeSize = + visibleWhen(num("targetEspBlackholeSize", SETTING_BLACKHOLE_SIZE, 1.1f, 0.5f, 3.0f), this::isBlackholeMode); + private final NumberValue 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 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 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 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 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, diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/TazikHat.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/TazikHat.java index e727e68..e779f6b 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/TazikHat.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/TazikHat.java @@ -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; } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/TotemFX.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/TotemFX.java index 5691fa3..cc1498d 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/TotemFX.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/TotemFX.java @@ -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()); diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/ViewModel.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/ViewModel.java index 557e650..ed3aa5c 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/ViewModel.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/ViewModel.java @@ -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; } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/WorldParticles.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/WorldParticles.java index 085aa45..bf8c13e 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/WorldParticles.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/WorldParticles.java @@ -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) { diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/WorldTweaks.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/WorldTweaks.java index b4a6f94..089a8dd 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/WorldTweaks.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/WorldTweaks.java @@ -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; } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/relations/CategoryRules.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/relations/CategoryRules.java index 55f039c..14c7c16 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/relations/CategoryRules.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/relations/CategoryRules.java @@ -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; - } } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/relations/StaffData.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/relations/StaffData.java index 563b06c..cf1c296 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/relations/StaffData.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/relations/StaffData.java @@ -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 getEntries() { + public static final StaffData INSTANCE = new StaffData(); + + private StaffData() { + } + + public List getEntries() { Set staffList = PlayerRelations.get().getStaff(); - Map map = StaffTracker.all(); + Map map = StaffTracker.INSTANCE.all(); List 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 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, diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/relations/StaffHeuristicsConfig.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/relations/StaffHeuristicsConfig.java deleted file mode 100644 index 64ee7b0..0000000 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/relations/StaffHeuristicsConfig.java +++ /dev/null @@ -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 prefixes() { - return prefixes.get(); - } - - public Set suffixes() { - return suffixes.get(); - } - - public Set 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> getConfigValues() { - List> 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(); - } - -} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/relations/StaffHeuristicsMatcher.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/relations/StaffHeuristicsMatcher.java deleted file mode 100644 index 0b8a09f..0000000 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/relations/StaffHeuristicsMatcher.java +++ /dev/null @@ -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 prefixes, - Set suffixes, - Set 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 prefixes, - Set suffixes, - Set 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 prefixes, - Set 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 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 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 compactTokens(String text) { - List 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); - } -} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/relations/StaffTracker.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/relations/StaffTracker.java index 04f0787..68198a5 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/relations/StaffTracker.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/relations/StaffTracker.java @@ -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 map = new HashMap<>(); + private final Map 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 all() { + public Map all() { return map; } @@ -106,7 +110,7 @@ public enum StaffTracker { // LOGIC // ====================== - public static void resetAll() { + public void resetAll() { map.clear(); } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/security/BackdoorProtection.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/security/BackdoorProtection.java index 289ed48..57df32b 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/security/BackdoorProtection.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/security/BackdoorProtection.java @@ -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> TRANSLATION_KEY_PACKS = new ConcurrentHashMap<>(); - private static final Set VANILLA_KEYBINDS = ConcurrentHashMap.newKeySet(); + public static final BackdoorProtection INSTANCE = new BackdoorProtection(); - private static volatile String currentServerAddress; - private static volatile boolean currentServerLocal; + private final Map> translationKeyPacks = new ConcurrentHashMap<>(); + private final Set 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 packIds = TRANSLATION_KEY_PACKS.get(key); + Set packIds = translationKeyPacks.get(key); if (packIds == null || packIds.isEmpty()) { - return TRANSLATION_KEY_PACKS.isEmpty(); + return translationKeyPacks.isEmpty(); } Set 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(); diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/ClientPacketListenerMixin.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/ClientPacketListenerMixin.java index 1b88e22..a20a4d2 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/ClientPacketListenerMixin.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/ClientPacketListenerMixin.java @@ -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); } } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/LevelRendererMixin.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/LevelRendererMixin.java index 3968feb..90dbbf4 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/LevelRendererMixin.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/LevelRendererMixin.java @@ -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 diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/MinecraftMixin.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/MinecraftMixin.java index 41677d8..3b10c00 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/MinecraftMixin.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/MinecraftMixin.java @@ -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(); } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/security/AbstractSignEditScreenMixin.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/security/AbstractSignEditScreenMixin.java index 74b032f..b864dcd 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/security/AbstractSignEditScreenMixin.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/security/AbstractSignEditScreenMixin.java @@ -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(); } } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/security/AnvilScreenMixin.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/security/AnvilScreenMixin.java index e5c3fe6..f3af7c5 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/security/AnvilScreenMixin.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/security/AnvilScreenMixin.java @@ -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 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 original) { - return BackdoorProtection.filterText(original.call(stack)); + return BackdoorProtection.INSTANCE.filterText(original.call(stack)); } } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/security/ClientLoginNetworkHandlerMixin.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/security/ClientLoginNetworkHandlerMixin.java index 6d6b418..dc7b104 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/security/ClientLoginNetworkHandlerMixin.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/security/ClientLoginNetworkHandlerMixin.java @@ -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); } } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/security/GameOptionsMixin.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/security/GameOptionsMixin.java index 3678a4e..6182507 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/security/GameOptionsMixin.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/security/GameOptionsMixin.java @@ -39,9 +39,9 @@ public abstract class GameOptionsMixin { @Inject(method = "(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()); } } } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/security/NetworkUtilsMixin.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/security/NetworkUtilsMixin.java index c512a9c..2d6c9db 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/security/NetworkUtilsMixin.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/security/NetworkUtilsMixin.java @@ -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 ) 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); diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/security/TranslationStorageMixin.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/security/TranslationStorageMixin.java index 219d04b..73886f6 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/security/TranslationStorageMixin.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/security/TranslationStorageMixin.java @@ -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 definitions, boolean rightToLeft, CallbackInfoReturnable cir) { - BackdoorProtection.resetLoadedTranslations(); + BackdoorProtection.INSTANCE.resetLoadedTranslations(); } @WrapOperation( @@ -53,7 +53,7 @@ public abstract class TranslationStorageMixin { ) { String packId = resource.sourcePackId(); original.call(inputStream, (BiConsumer) (key, value) -> { - BackdoorProtection.registerTranslationKey(key, packId); + BackdoorProtection.INSTANCE.registerTranslationKey(key, packId); consumer.accept(key, value); }); } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/pipeline/LoVisualRenderPipelines.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/pipeline/LoVisualRenderPipelines.java index 2cbe545..ffde26a 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/pipeline/LoVisualRenderPipelines.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/pipeline/LoVisualRenderPipelines.java @@ -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(); diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/uniform/impl/HandBlendUniforms.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/uniform/impl/HandBlendUniforms.java new file mode 100644 index 0000000..41e0017 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/uniform/impl/HandBlendUniforms.java @@ -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; + } + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/uniform/impl/HandChamsUniforms.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/uniform/impl/HandChamsUniforms.java new file mode 100644 index 0000000..3018563 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/uniform/impl/HandChamsUniforms.java @@ -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; + } + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/uniform/impl/ShaderSkyUniforms.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/uniform/impl/ShaderSkyUniforms.java new file mode 100644 index 0000000..8022b78 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/uniform/impl/ShaderSkyUniforms.java @@ -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; + } + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/helpers/ScissorFunction.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/helpers/ScissorFunction.java index 0777cf7..07e1fd3 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/helpers/ScissorFunction.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/helpers/ScissorFunction.java @@ -24,7 +24,6 @@ import java.util.Deque; public enum ScissorFunction { ; - private static final Minecraft mc = Minecraft.getInstance(); private static final Deque 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( diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/sky/CustomSkyboxRenderer.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/sky/CustomSkyboxRenderer.java index 5f210ec..ecc5f67 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/sky/CustomSkyboxRenderer.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/sky/CustomSkyboxRenderer.java @@ -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; diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/LegacyHudNotifier.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/LegacyHudNotifier.java index d75baf0..6b97b76 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/LegacyHudNotifier.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/LegacyHudNotifier.java @@ -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); } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/block/BlockObservationHub.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/block/BlockObservationHub.java index 89fc9c7..f7b0aa2 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/block/BlockObservationHub.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/block/BlockObservationHub.java @@ -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) { diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/combat/protocol/ProtocolUtil.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/combat/protocol/ProtocolUtil.java index 3cb89b5..1360b07 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/combat/protocol/ProtocolUtil.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/combat/protocol/ProtocolUtil.java @@ -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); diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/player/simulation/RemotePlayerObservation.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/player/simulation/RemotePlayerObservation.java index 8f72488..1bca21b 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/player/simulation/RemotePlayerObservation.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/player/simulation/RemotePlayerObservation.java @@ -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; } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/target/TargetManager.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/target/TargetManager.java index 33713da..bde9855 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/target/TargetManager.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/target/TargetManager.java @@ -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 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 diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/time/GameClock.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/time/GameClock.java new file mode 100644 index 0000000..aef52ef --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/time/GameClock.java @@ -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(); + } +} diff --git a/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/hand_chams_balatro.frag b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/hand_chams_balatro.frag new file mode 100644 index 0000000..2dcba91 --- /dev/null +++ b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/hand_chams_balatro.frag @@ -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); +} diff --git a/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/hand_chams_blend.frag b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/hand_chams_blend.frag new file mode 100644 index 0000000..ad80533 --- /dev/null +++ b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/hand_chams_blend.frag @@ -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); +} diff --git a/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/hand_chams_chroma.frag b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/hand_chams_chroma.frag new file mode 100644 index 0000000..2d4b225 --- /dev/null +++ b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/hand_chams_chroma.frag @@ -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); +} diff --git a/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/hand_chams_glow.frag b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/hand_chams_glow.frag new file mode 100644 index 0000000..150c842 --- /dev/null +++ b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/hand_chams_glow.frag @@ -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); +} diff --git a/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/hand_chams_snow.frag b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/hand_chams_snow.frag new file mode 100644 index 0000000..c234402 --- /dev/null +++ b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/hand_chams_snow.frag @@ -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); +} diff --git a/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/hand_chams_solid.frag b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/hand_chams_solid.frag new file mode 100644 index 0000000..e4c01a3 --- /dev/null +++ b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/hand_chams_solid.frag @@ -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); +} diff --git a/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/hand_chams_stripes.frag b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/hand_chams_stripes.frag new file mode 100644 index 0000000..09bfe26 --- /dev/null +++ b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/hand_chams_stripes.frag @@ -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); +} diff --git a/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/shader_sky_pulsar.frag b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/shader_sky_pulsar.frag new file mode 100644 index 0000000..b3dd74b --- /dev/null +++ b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/shader_sky_pulsar.frag @@ -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); +} diff --git a/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/shader_sky_thunder.frag b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/shader_sky_thunder.frag new file mode 100644 index 0000000..42b655d --- /dev/null +++ b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/shader_sky_thunder.frag @@ -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); +} diff --git a/combatant-client-26.2/src/test/java/dev/loki/lovisual/features/gui/clickgui/layout/screen/settings/MenuSettingsResolverTest.java b/combatant-client-26.2/src/test/java/dev/loki/lovisual/features/gui/clickgui/layout/screen/settings/MenuSettingsResolverTest.java new file mode 100644 index 0000000..5954fa2 --- /dev/null +++ b/combatant-client-26.2/src/test/java/dev/loki/lovisual/features/gui/clickgui/layout/screen/settings/MenuSettingsResolverTest.java @@ -0,0 +1,40 @@ +package dev.loki.lovisual.features.gui.clickgui.layout.screen.settings; + +import dev.loki.lovisual.features.gui.clickgui.layout.screen.settings.implement.module.ModuleComponent; +import dev.loki.lovisual.features.gui.hud.HudElementAutoLoader; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class MenuSettingsResolverTest { + + @BeforeAll + static void loadWidgets() { + HudElementAutoLoader.loadDraggable("dev.loki.lovisual.features.gui.hud.draggable.impl"); + } + + @Test + void hudCategoryBuildsNonEmptyCards() { + List cards = MenuSettingsResolver.buildCards(MenuScreen.Category.HUD); + assertFalse(cards.isEmpty(), "HUD category should list registered draggable widgets"); + } + + @Test + void uiCategoryBuildsNonEmptyCards() { + List cards = MenuSettingsResolver.buildCards(MenuScreen.Category.UI); + assertFalse(cards.isEmpty(), "UI category should list static HUD cards"); + } + + @Test + void hudWidgetSettingsResolve() { + List cards = MenuSettingsResolver.buildCards(MenuScreen.Category.HUD); + assertFalse(cards.isEmpty()); + String id = cards.get(0).id(); + MenuSettingsResolver.ResolvedSettings resolved = MenuSettingsResolver.resolveSettings(id); + assertNotNull(resolved, "settings should resolve for HUD widget " + id); + } +} diff --git a/combatant-client-26.2/src/test/java/dev/loki/lovisual/features/gui/hud/HudElementAutoLoaderTest.java b/combatant-client-26.2/src/test/java/dev/loki/lovisual/features/gui/hud/HudElementAutoLoaderTest.java new file mode 100644 index 0000000..5aaa69a --- /dev/null +++ b/combatant-client-26.2/src/test/java/dev/loki/lovisual/features/gui/hud/HudElementAutoLoaderTest.java @@ -0,0 +1,17 @@ +package dev.loki.lovisual.features.gui.hud; + +import dev.loki.lovisual.features.gui.hud.draggable.DraggableHudElementRegistry; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class HudElementAutoLoaderTest { + + @Test + void registersDraggableWidgets() { + HudElementAutoLoader.loadDraggable("dev.loki.lovisual.features.gui.hud.draggable.impl"); + int count = DraggableHudElementRegistry.getWidgets().size(); + System.out.println("Registered draggable widgets: " + count); + assertTrue(count > 0, "no draggable HUD widgets registered"); + } +} diff --git a/combatant-client-26.2/src/test/java/dev/loki/lovisual/features/security/BackdoorProtectionTest.java b/combatant-client-26.2/src/test/java/dev/loki/lovisual/features/security/BackdoorProtectionTest.java new file mode 100644 index 0000000..f39f620 --- /dev/null +++ b/combatant-client-26.2/src/test/java/dev/loki/lovisual/features/security/BackdoorProtectionTest.java @@ -0,0 +1,123 @@ +/* + * 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.security; + +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.contents.KeybindContents; +import net.minecraft.network.chat.contents.PlainTextContents; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Юнит-тесты чистой логики BackdoorProtection без инициализации Minecraft. + * State очищается в @BeforeEach, т.к. INSTANCE — общий синглтон. + */ +final class BackdoorProtectionTest { + + private final BackdoorProtection service = BackdoorProtection.INSTANCE; + + @BeforeEach + void resetState() { + service.resetLoadedTranslations(); + service.resetVanillaKeybinds(); + } + + @Test + void unknownKeyAllowedWhenNothingRegistered() { + assertTrue(service.isTranslationAllowed("anything")); + } + + @Test + void resetLoadedTranslationsClearsRegisteredKeys() { + service.registerTranslationKey("blocked.key", "server_pack"); + service.resetLoadedTranslations(); + assertTrue(service.isTranslationAllowed("blocked.key")); + } + + @Test + void remotePackTranslationBlocked() { + service.registerTranslationKey("evil.key", "server_pack"); + assertFalse(service.isTranslationAllowed("evil.key")); + } + + @Test + void builtInPacksAreAllowedCaseInsensitive() { + service.registerTranslationKey("menu.title", "vanilla"); + assertTrue(service.isTranslationAllowed("menu.title")); + + service.registerTranslationKey("accessibility.title", "high_contrast"); + assertTrue(service.isTranslationAllowed("accessibility.title")); + + service.registerTranslationKey("menu.case", "VANILLA"); + assertTrue(service.isTranslationAllowed("menu.case")); + } + + @Test + void registerTranslationKeyIgnoresBlankInput() { + service.registerTranslationKey("", "server_pack"); + service.registerTranslationKey("good.key", ""); + assertTrue(service.isTranslationAllowed("good.key")); + } + + @Test + void keybindAllowedOnlyAfterRegistration() { + assertFalse(service.isKeybindAllowed("key.attack")); + service.registerVanillaKeybind("key.attack"); + assertTrue(service.isKeybindAllowed("key.attack")); + } + + @Test + void resetVanillaKeybindsClears() { + service.registerVanillaKeybind("key.jump"); + service.resetVanillaKeybinds(); + assertFalse(service.isKeybindAllowed("key.jump")); + } + + @Test + void blankOrNullKeysAreAllowed() { + assertTrue(service.isTranslationAllowed(null)); + assertTrue(service.isTranslationAllowed("")); + assertTrue(service.isKeybindAllowed(null)); + assertTrue(service.isKeybindAllowed("")); + } + + @Test + void filterTextReplacesBlockedTranslatableWithResolvedFallback() { + service.registerTranslationKey("evil.key", "server_pack"); + + Component filtered = service.filterText(Component.translatable("evil.key")); + + FilteredTranslatableTextContent content = + assertInstanceOf(FilteredTranslatableTextContent.class, filtered.getContents()); + assertEquals("evil.key", content.getResolved()); + } + + @Test + void filterTextKeepsAllowedTranslatableWithNullResolved() { + service.registerTranslationKey("menu.ok", "vanilla"); + + Component filtered = service.filterText(Component.translatable("menu.ok")); + + FilteredTranslatableTextContent content = + assertInstanceOf(FilteredTranslatableTextContent.class, filtered.getContents()); + assertNull(content.getResolved()); + } + + @Test + void filterTextReplacesBlockedKeybindWithPlainText() { + Component keybind = MutableComponent.create(new KeybindContents("key.attack")); + + Component filtered = service.filterText(keybind); + + assertInstanceOf(PlainTextContents.class, filtered.getContents()); + assertFalse(filtered.getContents() instanceof KeybindContents); + } +}