From 53ff8f8f8ef2cc349994a77abdb48e04402b0775 Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Thu, 3 Sep 2026 19:52:03 +0200 Subject: [PATCH] feat: add KineticLyrics 3D lyrics rendering system --- .../config/lovisual/hud/draggable/admins.json | 28 + .../config/lovisual/hud/draggable/armor.json | 17 + .../lovisual/hud/draggable/better_chat.json | 27 + .../config/lovisual/hud/draggable/fps.json | 18 + .../lovisual/hud/draggable/game_time.json | 16 + .../lovisual/hud/draggable/hud_notifier.json | 9 + .../lovisual/hud/draggable/inventory.json | 25 + .../lovisual/hud/draggable/itemizer.json | 22 + .../lovisual/hud/draggable/keybinds.json | 33 + .../lovisual/hud/draggable/media_player.json | 19 + .../config/lovisual/hud/draggable/memory.json | 17 + .../lovisual/hud/draggable/module_list.json | 16 + .../config/lovisual/hud/draggable/ping.json | 16 + .../lovisual/hud/draggable/potions.json | 24 + .../config/lovisual/hud/draggable/radar.json | 24 + .../lovisual/hud/draggable/scoreboard.json | 31 + .../lovisual/hud/draggable/speed_bps.json | 18 + .../lovisual/hud/draggable/system_time.json | 16 + .../lovisual/hud/draggable/target_hud.json | 41 + .../hud/draggable/timer_indicator.json | 21 + .../config/lovisual/hud/draggable/tps.json | 18 + .../lovisual/hud/draggable/triangulator.json | 29 + .../config/lovisual/hud/draggable/xyz.json | 18 + .../hud_element_dynamic_island.json | 11 + .../hud_element_swap_tooltip.json | 7 + .../nondraggable/hud_element_tab_list.json | 10 + .../nondraggable/hud_element_vanilla_bar.json | 27 + .../hud_element_vanilla_buttons.json | 25 + .../hud_element_vanilla_health.json | 8 + .../hud_element_vanilla_hotbar.json | 14 + .../hud_element_vanilla_tooltips.json | 22 + .../config/lovisual/mainconfig.json5 | 30 + .../main/java/dev/loki/lovisual/LoVisual.java | 2 - .../CosmeticAttachmentTransforms.java | 55 ++ .../cosmetic/CosmeticBbAnimation.java | 223 +++++ .../features/cosmetic/CosmeticBone.java | 66 ++ .../features/cosmetic/CosmeticCube.java | 42 + .../features/cosmetic/CosmeticManager.java | 74 ++ .../features/cosmetic/CosmeticModel.java | 659 +++++++++++++ .../features/cosmetic/CosmeticRenderer.java | 204 ++++ .../features/cosmetic/CosmeticRepository.java | 97 ++ .../cosmetic/CosmeticTextureSlot.java | 119 +++ .../features/cosmetic/CosmeticType.java | 32 + .../gui/clickgui/ClickGuiInputHandler.java | 12 +- .../gui/clickgui/ClickGuiKeyBindManager.java | 44 +- .../gui/clickgui/ClickGuiKeyboardHandler.java | 10 +- .../hud/nondraggable/impl/BetterTooltips.java | 10 +- .../gui/hud/nondraggable/impl/CustomBar.java | 4 +- .../nondraggable/impl/CustomHealthBar.java | 6 +- .../hud/nondraggable/impl/CustomHotbar.java | 10 +- .../hud/nondraggable/impl/DynamicIsland.java | 3 +- .../features/module/modules/combat/Reach.java | 258 ------ .../module/modules/combat/TargetESP.java | 80 +- .../module/modules/visuals/BlockESP.java | 276 ++++++ .../modules/visuals/CosmeticDisplay.java | 77 ++ .../module/modules/visuals/Crosshair.java | 151 --- .../module/modules/visuals/HoldMyItems.java | 61 ++ .../module/modules/visuals/InvisEsp.java | 13 +- .../modules/visuals/LightningTargetESP.java | 315 +++++++ .../features/module/modules/visuals/Rain.java | 869 ++++++++++++++++++ .../module/modules/visuals/ShaderSky.java | 47 +- .../modules/visuals/lyrics/KineticLyrics.java | 432 +++++++++ .../modules/visuals/lyrics/LrcLibClient.java | 184 ++++ .../modules/visuals/lyrics/LrcParser.java | 58 ++ .../modules/visuals/lyrics/LyricLine.java | 8 + .../visuals/lyrics/LyricLineSplitter.java | 68 ++ .../visuals/lyrics/LyricParticle3D.java | 147 +++ .../visuals/trollface/TrollfaceMask.java | 163 ++++ .../mixins/ItemInHandRendererMixin.java | 15 + .../lovisual/mixins/LoVisualMixinPlugin.java | 1 - .../loki/lovisual/mixins/MinecraftMixin.java | 25 - .../mixins/MultiPlayerGameModeMixin.java | 6 +- .../worldmap/WorldMapWaypointAccessor.java | 30 - .../render/engine/TextureStorage.java | 21 + .../pipeline/LoVisualRenderPipelines.java | 60 ++ .../engine/postprocess/SkyShaderRenderer.java | 115 +++ .../uniform/impl/SkyShaderUniforms.java | 81 ++ .../uniform/impl/WetSurfaceUniforms.java | 86 ++ .../loki/lovisual/util/combat/AttackUtil.java | 6 +- .../util/combat/VulcanReachController.java | 721 --------------- .../resources/assets/lovisual/lang/en_us.json | 5 + .../resources/assets/lovisual/lang/ru_ru.json | 5 + .../lovisual/shaders/sky_shader_caustic.frag | 91 ++ .../lovisual/shaders/sky_shader_sky.frag | 94 ++ .../lovisual/shaders/sky_shader_water.frag | 91 ++ .../assets/lovisual/shaders/wet_surface.frag | 174 ++++ .../textures/trollface/trollface_1.png | Bin 0 -> 56539 bytes .../textures/trollface/trollface_2.png | Bin 0 -> 71484 bytes .../textures/trollface/trollface_3.png | Bin 0 -> 46267 bytes .../textures/trollface/trollface_4.png | Bin 0 -> 43753 bytes .../textures/trollface/trollface_5.png | Bin 0 -> 46136 bytes .../textures/trollface/trollface_6.png | Bin 0 -> 46112 bytes .../textures/trollface/trollface_classic.png | Bin 0 -> 46256 bytes .../src/main/resources/lovisual.mixins.json | 3 +- .../visuals/lyrics/LrcLibClientTest.java | 57 ++ .../modules/visuals/lyrics/LrcParserTest.java | 136 +++ .../visuals/lyrics/LyricLineSplitterTest.java | 99 ++ 97 files changed, 6238 insertions(+), 1220 deletions(-) create mode 100644 combatant-client-26.2/config/lovisual/hud/draggable/admins.json create mode 100644 combatant-client-26.2/config/lovisual/hud/draggable/armor.json create mode 100644 combatant-client-26.2/config/lovisual/hud/draggable/better_chat.json create mode 100644 combatant-client-26.2/config/lovisual/hud/draggable/fps.json create mode 100644 combatant-client-26.2/config/lovisual/hud/draggable/game_time.json create mode 100644 combatant-client-26.2/config/lovisual/hud/draggable/hud_notifier.json create mode 100644 combatant-client-26.2/config/lovisual/hud/draggable/inventory.json create mode 100644 combatant-client-26.2/config/lovisual/hud/draggable/itemizer.json create mode 100644 combatant-client-26.2/config/lovisual/hud/draggable/keybinds.json create mode 100644 combatant-client-26.2/config/lovisual/hud/draggable/media_player.json create mode 100644 combatant-client-26.2/config/lovisual/hud/draggable/memory.json create mode 100644 combatant-client-26.2/config/lovisual/hud/draggable/module_list.json create mode 100644 combatant-client-26.2/config/lovisual/hud/draggable/ping.json create mode 100644 combatant-client-26.2/config/lovisual/hud/draggable/potions.json create mode 100644 combatant-client-26.2/config/lovisual/hud/draggable/radar.json create mode 100644 combatant-client-26.2/config/lovisual/hud/draggable/scoreboard.json create mode 100644 combatant-client-26.2/config/lovisual/hud/draggable/speed_bps.json create mode 100644 combatant-client-26.2/config/lovisual/hud/draggable/system_time.json create mode 100644 combatant-client-26.2/config/lovisual/hud/draggable/target_hud.json create mode 100644 combatant-client-26.2/config/lovisual/hud/draggable/timer_indicator.json create mode 100644 combatant-client-26.2/config/lovisual/hud/draggable/tps.json create mode 100644 combatant-client-26.2/config/lovisual/hud/draggable/triangulator.json create mode 100644 combatant-client-26.2/config/lovisual/hud/draggable/xyz.json create mode 100644 combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_dynamic_island.json create mode 100644 combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_swap_tooltip.json create mode 100644 combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_tab_list.json create mode 100644 combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_vanilla_bar.json create mode 100644 combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_vanilla_buttons.json create mode 100644 combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_vanilla_health.json create mode 100644 combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_vanilla_hotbar.json create mode 100644 combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_vanilla_tooltips.json create mode 100644 combatant-client-26.2/config/lovisual/mainconfig.json5 create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticAttachmentTransforms.java create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticBbAnimation.java create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticBone.java create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticCube.java create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticManager.java create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticModel.java create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticRenderer.java create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticRepository.java create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticTextureSlot.java create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticType.java delete mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/combat/Reach.java create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/BlockESP.java create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/CosmeticDisplay.java create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/HoldMyItems.java create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/LightningTargetESP.java create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/Rain.java create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/KineticLyrics.java create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LrcLibClient.java create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LrcParser.java create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LyricLine.java create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LyricLineSplitter.java create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LyricParticle3D.java create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/trollface/TrollfaceMask.java delete mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/xaero/worldmap/WorldMapWaypointAccessor.java create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/postprocess/SkyShaderRenderer.java create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/uniform/impl/SkyShaderUniforms.java create mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/uniform/impl/WetSurfaceUniforms.java delete mode 100644 combatant-client-26.2/src/main/java/dev/loki/lovisual/util/combat/VulcanReachController.java create mode 100644 combatant-client-26.2/src/main/resources/assets/lovisual/shaders/sky_shader_caustic.frag create mode 100644 combatant-client-26.2/src/main/resources/assets/lovisual/shaders/sky_shader_sky.frag create mode 100644 combatant-client-26.2/src/main/resources/assets/lovisual/shaders/sky_shader_water.frag create mode 100644 combatant-client-26.2/src/main/resources/assets/lovisual/shaders/wet_surface.frag create mode 100644 combatant-client-26.2/src/main/resources/assets/lovisual/textures/trollface/trollface_1.png create mode 100644 combatant-client-26.2/src/main/resources/assets/lovisual/textures/trollface/trollface_2.png create mode 100644 combatant-client-26.2/src/main/resources/assets/lovisual/textures/trollface/trollface_3.png create mode 100644 combatant-client-26.2/src/main/resources/assets/lovisual/textures/trollface/trollface_4.png create mode 100644 combatant-client-26.2/src/main/resources/assets/lovisual/textures/trollface/trollface_5.png create mode 100644 combatant-client-26.2/src/main/resources/assets/lovisual/textures/trollface/trollface_6.png create mode 100644 combatant-client-26.2/src/main/resources/assets/lovisual/textures/trollface/trollface_classic.png create mode 100644 combatant-client-26.2/src/test/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LrcLibClientTest.java create mode 100644 combatant-client-26.2/src/test/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LrcParserTest.java create mode 100644 combatant-client-26.2/src/test/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LyricLineSplitterTest.java diff --git a/combatant-client-26.2/config/lovisual/hud/draggable/admins.json b/combatant-client-26.2/config/lovisual/hud/draggable/admins.json new file mode 100644 index 0000000..458676e --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/draggable/admins.json @@ -0,0 +1,28 @@ +{ + "enabled": true, + "layout": { + "pos": [ + 16.0, + 212.0 + ], + "space": "logical", + "parent": "keybinds", + "side": "RIGHT", + "pox": 0.0, + "poy": 0.0 + }, + "admins_scale": 1.0, + "admins_layout": "Split Header", + "admins_color_mode": "Theme", + "admins_bg": "#EB111318", + "admins_bg_secondary": "#F7161616", + "admins_stroke": "#5A5A5A", + "admins_bg_alpha": 131.0, + "admins_text": "#FFFFFF", + "admins_muted": "#A5A5A5", + "admins_bg_effect": "Blur", + "admins_blur_alpha": 255.0, + "admins_header_icon_pulse": true, + "admins_header_icon_pulse_speed": 18.0, + "admins_header_icon_pulse_intensity": 100.0 +} diff --git a/combatant-client-26.2/config/lovisual/hud/draggable/armor.json b/combatant-client-26.2/config/lovisual/hud/draggable/armor.json new file mode 100644 index 0000000..a4ee59f --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/draggable/armor.json @@ -0,0 +1,17 @@ +{ + "enabled": false, + "layout": { + "pos": [ + 1616.315, + 1003.6977 + ], + "space": "logical" + }, + "armor_scale": 3.68, + "armor_layout": "Horizontal", + "armor_reverse": false, + "armor_state_mode": "Text", + "armor_state_bar_threshold": 70.0, + "armor_state_threshold": 70.0, + "armor_state_color_threshold": 70.0 +} diff --git a/combatant-client-26.2/config/lovisual/hud/draggable/better_chat.json b/combatant-client-26.2/config/lovisual/hud/draggable/better_chat.json new file mode 100644 index 0000000..06fa1a2 --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/draggable/better_chat.json @@ -0,0 +1,27 @@ +{ + "enabled": true, + "layout": { + "pos": [ + 16.0, + 485.12 + ], + "space": "logical" + }, + "width_ratio": 0.54, + "font_scale": 1.36, + "lines": 12.0, + "fade_seconds": 14.98, + "liquid_glass_alpha": 230.0, + "hide_vanilla": true, + "history_enabled": true, + "history_limit": 32000.0, + "timestamps_enabled": true, + "timestamp_color": "#C6D2CD", + "timestamp_toggles": { + "with_seconds": false, + "with_brackets": true, + "active_only": false, + "hover_unix": false, + "hover_date": true + } +} diff --git a/combatant-client-26.2/config/lovisual/hud/draggable/fps.json b/combatant-client-26.2/config/lovisual/hud/draggable/fps.json new file mode 100644 index 0000000..40704de --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/draggable/fps.json @@ -0,0 +1,18 @@ +{ + "enabled": true, + "fps_scale": 2.37, + "fps_color_mode": "Theme", + "fps_icon_color": "#FFFFFF", + "fps_value_color": "#FFFFFF", + "fps_meta_color": "#9B9B9B", + "fps_bg_effect": "Blur", + "fps_bg": "#F7343434", + "fps_bg_secondary": "#F7161616", + "fps_stroke": "#5A5A5A", + "fps_bg_alpha": 133.0, + "fps_blur_alpha": 255.0, + "fps_label_effect": "Flow", + "fps_label_effect_speed": 18.0, + "fps_digit_animation": false, + "fps_digit_poll_ms": 495.0 +} diff --git a/combatant-client-26.2/config/lovisual/hud/draggable/game_time.json b/combatant-client-26.2/config/lovisual/hud/draggable/game_time.json new file mode 100644 index 0000000..8876e66 --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/draggable/game_time.json @@ -0,0 +1,16 @@ +{ + "enabled": false, + "game_time_weather": true, + "game_time_scale": 2.37, + "game_time_color_mode": "Theme", + "game_time_icon_color": "#FFFFFF", + "game_time_value_color": "#FFFFFF", + "game_time_bg_effect": "None", + "game_time_bg": "#EB111318", + "game_time_bg_secondary": "#F7161616", + "game_time_stroke": "#5A5A5A", + "game_time_bg_alpha": 235.0, + "game_time_blur_alpha": 140.0, + "game_time_icon_effect": "Mix", + "game_time_icon_effect_speed": 18.0 +} diff --git a/combatant-client-26.2/config/lovisual/hud/draggable/hud_notifier.json b/combatant-client-26.2/config/lovisual/hud/draggable/hud_notifier.json new file mode 100644 index 0000000..a79a71c --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/draggable/hud_notifier.json @@ -0,0 +1,9 @@ +{ + "enabled": true, + "duration_ms": 2000.0, + "fade_ms": 250.0, + "scale": 1.35, + "sound": true, + "sound_mode": "2", + "sound_volume": 0.47 +} diff --git a/combatant-client-26.2/config/lovisual/hud/draggable/inventory.json b/combatant-client-26.2/config/lovisual/hud/draggable/inventory.json new file mode 100644 index 0000000..7e726dc --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/draggable/inventory.json @@ -0,0 +1,25 @@ +{ + "enabled": true, + "layout": { + "pos": [ + 1255.352, + 658.36005 + ], + "space": "logical" + }, + "toggle_bind": "I", + "inventory_scale": 1.32, + "inventory_layout": "Unified Divider", + "inventory_color_mode": "Theme", + "inventory_bg": "#F7343434", + "inventory_bg_secondary": "#F7161616", + "inventory_stroke": "#5A5A5A", + "inventory_bg_alpha": 194.0, + "inventory_text": "#FFFFFF", + "inventory_muted": "#A5A5A5", + "inventory_bg_effect": "Blur", + "inventory_blur_alpha": 255.0, + "inventory_header_icon_pulse": true, + "inventory_header_icon_pulse_speed": 15.0, + "inventory_header_icon_pulse_intensity": 100.0 +} diff --git a/combatant-client-26.2/config/lovisual/hud/draggable/itemizer.json b/combatant-client-26.2/config/lovisual/hud/draggable/itemizer.json new file mode 100644 index 0000000..e9b105f --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/draggable/itemizer.json @@ -0,0 +1,22 @@ +{ + "enabled": true, + "layout": { + "pos": [ + -16.0, + 48.0 + ], + "space": "logical", + "ax": "CENTER", + "ay": "CENTER" + }, + "itemizer_scale": 1.0, + "itemizer_display_mode": "icons", + "itemizer_direction": "horizontal", + "itemizer_duration": 1.15, + "itemizer_max_entries": 3.0, + "itemizer_auto_eat": true, + "itemizer_auto_totem": true, + "itemizer_elytra_swap": true, + "itemizer_blur": true, + "itemizer_bg_alpha": 168.0 +} diff --git a/combatant-client-26.2/config/lovisual/hud/draggable/keybinds.json b/combatant-client-26.2/config/lovisual/hud/draggable/keybinds.json new file mode 100644 index 0000000..d893bd3 --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/draggable/keybinds.json @@ -0,0 +1,33 @@ +{ + "enabled": true, + "layout": { + "pos": [ + 456.6228, + 248.0 + ], + "space": "logical", + "parent": "potions", + "side": "RIGHT", + "pox": 0.0, + "poy": 0.0 + }, + "keybinds_scale": 1.0, + "keybinds_layout": "Unified Divider", + "keybinds_color_mode": "Theme", + "keybinds_bg": "#F7343434", + "keybinds_bg_secondary": "#F7161616", + "keybinds_stroke": "#5A5A5A", + "keybinds_bg_alpha": 225.0, + "keybinds_text": "#FFFFFF", + "keybinds_muted": "#A5A5A5", + "keybinds_bg_effect": "None", + "keybinds_blur_alpha": 140.0, + "keybinds_local_actions": true, + "keybinds_local_action_modes": { + "Press": true, + "Hold": true + }, + "keybinds_header_icon_pulse": true, + "keybinds_header_icon_pulse_speed": 18.0, + "keybinds_header_icon_pulse_intensity": 100.0 +} diff --git a/combatant-client-26.2/config/lovisual/hud/draggable/media_player.json b/combatant-client-26.2/config/lovisual/hud/draggable/media_player.json new file mode 100644 index 0000000..464b2d6 --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/draggable/media_player.json @@ -0,0 +1,19 @@ +{ + "enabled": false, + "layout": { + "pos": [ + 12.0, + 82.30222 + ], + "space": "logical" + }, + "media_player_scale": 1.51, + "media_player_sync_theme": true, + "media_player_bg": "#60000000", + "media_player_bg_alpha": 124.0, + "media_player_accent": "#FF2F2F", + "media_player_text": "#FFFFFF", + "media_player_bg_effect": "None", + "media_player_blur_alpha": 255.0, + "media_player_gradient": true +} diff --git a/combatant-client-26.2/config/lovisual/hud/draggable/memory.json b/combatant-client-26.2/config/lovisual/hud/draggable/memory.json new file mode 100644 index 0000000..4354390 --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/draggable/memory.json @@ -0,0 +1,17 @@ +{ + "enabled": true, + "memory_scale": 2.37, + "memory_color_mode": "Theme", + "memory_icon_color": "#FFFFFF", + "memory_value_color": "#FFFFFF", + "memory_meta_color": "#9B9B9B", + "memory_extra_color": "#BEBEBE", + "memory_bg_effect": "Blur", + "memory_bg": "#F7343434", + "memory_bg_secondary": "#F7161616", + "memory_stroke": "#5A5A5A", + "memory_bg_alpha": 135.0, + "memory_blur_alpha": 255.0, + "memory_label_effect": "None", + "memory_label_effect_speed": 18.0 +} diff --git a/combatant-client-26.2/config/lovisual/hud/draggable/module_list.json b/combatant-client-26.2/config/lovisual/hud/draggable/module_list.json new file mode 100644 index 0000000..2dc8dfd --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/draggable/module_list.json @@ -0,0 +1,16 @@ +{ + "enabled": false, + "module_list_scale": 3.67, + "module_list_color_mode": "Custom", + "module_list_bg_effect": "Blur", + "module_list_bg_alpha": 226.0, + "module_list_blur_alpha": 255.0, + "module_list_bg": "#FF444242", + "module_list_bg_secondary": "#22000000", + "module_list_text": "RNB#E87171", + "module_list_text_secondary": "#93118C", + "module_list_text_gradient_mode": "rainbow", + "module_list_text_gradient_speed": 18.0, + "module_list_text_effect": "None", + "module_list_text_effect_speed": 30.0 +} diff --git a/combatant-client-26.2/config/lovisual/hud/draggable/ping.json b/combatant-client-26.2/config/lovisual/hud/draggable/ping.json new file mode 100644 index 0000000..759a3a5 --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/draggable/ping.json @@ -0,0 +1,16 @@ +{ + "enabled": false, + "ping_scale": 2.58, + "ping_color_mode": "Theme", + "ping_icon_color": "#FFFFFF", + "ping_value_color": "#FFFFFF", + "ping_meta_color": "#9B9B9B", + "ping_bg_effect": "None", + "ping_bg": "#F7343434", + "ping_bg_secondary": "#F7161616", + "ping_stroke": "#5A5A5A", + "ping_bg_alpha": 225.0, + "ping_blur_alpha": 140.0, + "ping_label_effect": "None", + "ping_label_effect_speed": 18.0 +} diff --git a/combatant-client-26.2/config/lovisual/hud/draggable/potions.json b/combatant-client-26.2/config/lovisual/hud/draggable/potions.json new file mode 100644 index 0000000..63c9332 --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/draggable/potions.json @@ -0,0 +1,24 @@ +{ + "enabled": true, + "layout": { + "pos": [ + 31.729004, + 182.0 + ], + "space": "logical" + }, + "potions_scale": 1.05, + "potions_layout": "Unified Divider", + "potions_color_mode": "Theme", + "potions_bg": "#EB784F4F", + "potions_bg_secondary": "#F7161616", + "potions_stroke": "#5A5A5A", + "potions_bg_alpha": 200.0, + "potions_text": "#FFFFFF", + "potions_muted": "#A5A5A5", + "potions_bg_effect": "Blur", + "potions_blur_alpha": 159.0, + "potions_header_icon_pulse": true, + "potions_header_icon_pulse_speed": 18.0, + "potions_header_icon_pulse_intensity": 100.0 +} diff --git a/combatant-client-26.2/config/lovisual/hud/draggable/radar.json b/combatant-client-26.2/config/lovisual/hud/draggable/radar.json new file mode 100644 index 0000000..9bc5b3e --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/draggable/radar.json @@ -0,0 +1,24 @@ +{ + "enabled": false, + "layout": { + "pos": [ + 1666.0, + 17.915222 + ], + "space": "logical" + }, + "radar_mode": "Rect", + "radar_color_mode": "Sync", + "radar_size": 217.27, + "radar_bg_color": "#CC101010", + "radar_bg_alpha": 109.0, + "radar_text_color": "#FFFFFF", + "radar_field_1": "Name", + "radar_field_2": "Hp", + "radar_field_3": "Ping", + "radar_field_4": "None", + "radar_field_5": "None", + "radar_blur": true, + "radar_blur_alpha": 255.0, + "radar_gradient": false +} diff --git a/combatant-client-26.2/config/lovisual/hud/draggable/scoreboard.json b/combatant-client-26.2/config/lovisual/hud/draggable/scoreboard.json new file mode 100644 index 0000000..8356f0d --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/draggable/scoreboard.json @@ -0,0 +1,31 @@ +{ + "enabled": false, + "layout": { + "pos": [ + 1721.69, + 158.0 + ], + "space": "logical" + }, + "toggle_bind": "Э", + "scoreboard_scale": 1.93, + "scoreboard_color_mode": "Theme", + "scoreboard_bg_effect": "Blur", + "scoreboard_bg_alpha": 182.0, + "scoreboard_blur_alpha": 255.0, + "scoreboard_bg": "#820A0A0A", + "scoreboard_bg_secondary": "#B8141414", + "scoreboard_stroke": "#4A4A4A", + "scoreboard_title_color": "#FFCC00", + "scoreboard_label_color": "#FFFFFF", + "scoreboard_value_color": "#FF4A4A", + "scoreboard_footer_color": "#FFD44A", + "scoreboard_text_effect": "Stripe", + "scoreboard_text_effect_speed": 18.0, + "scoreboard_text_effect_sections": { + "header": true, + "labels": false, + "values": false, + "footer": false + } +} diff --git a/combatant-client-26.2/config/lovisual/hud/draggable/speed_bps.json b/combatant-client-26.2/config/lovisual/hud/draggable/speed_bps.json new file mode 100644 index 0000000..8bb89c0 --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/draggable/speed_bps.json @@ -0,0 +1,18 @@ +{ + "enabled": true, + "speed_bps_bps": true, + "speed_bps_average": false, + "speed_bps_scale": 2.37, + "speed_bps_color_mode": "Theme", + "speed_bps_icon_color": "#FFFFFF", + "speed_bps_value_color": "#FFFFFF", + "speed_bps_meta_color": "#9B9B9B", + "speed_bps_bg_effect": "Blur", + "speed_bps_bg": "#F7343434", + "speed_bps_bg_secondary": "#F7161616", + "speed_bps_stroke": "#5A5A5A", + "speed_bps_bg_alpha": 135.0, + "speed_bps_blur_alpha": 255.0, + "speed_bps_label_effect": "Mix", + "speed_bps_label_effect_speed": 60.0 +} diff --git a/combatant-client-26.2/config/lovisual/hud/draggable/system_time.json b/combatant-client-26.2/config/lovisual/hud/draggable/system_time.json new file mode 100644 index 0000000..5637c67 --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/draggable/system_time.json @@ -0,0 +1,16 @@ +{ + "enabled": false, + "system_time_seconds": false, + "system_time_scale": 2.37, + "system_time_color_mode": "Theme", + "system_time_icon_color": "#FFFFFF", + "system_time_value_color": "#FFFFFF", + "system_time_bg_effect": "Blur", + "system_time_bg": "#EB111318", + "system_time_bg_secondary": "#F7161616", + "system_time_stroke": "#5A5A5A", + "system_time_bg_alpha": 235.0, + "system_time_blur_alpha": 140.0, + "system_time_icon_effect": "None", + "system_time_icon_effect_speed": 18.0 +} diff --git a/combatant-client-26.2/config/lovisual/hud/draggable/target_hud.json b/combatant-client-26.2/config/lovisual/hud/draggable/target_hud.json new file mode 100644 index 0000000..b28151d --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/draggable/target_hud.json @@ -0,0 +1,41 @@ +{ + "enabled": true, + "layout": { + "pos": [ + 7.6799927, + 721.12 + ], + "space": "logical", + "ax": "CENTER" + }, + "target_scale": 3.28, + "target_hold_ms": 1000.0, + "totem_pop_counter": true, + "totem_pop_reset_seconds": 300.0, + "target_totem_pop_text_color": "RNB#FFFFFF", + "target_marquee": true, + "target_players_only": false, + "target_effects_mode": "Off", + "target_effects_self": true, + "target_equipment_mode": "Off", + "target_hp_mode": "Arc", + "target_color_mode": "Theme", + "target_bg": "#F7343434", + "target_bg_secondary": "#F7161616", + "target_stroke": "#5A5A5A", + "target_bg_alpha": 162.0, + "target_text": "#FFFFFF", + "target_text_secondary": "#D7D7D7", + "target_bar_bg": "#1E1E1E", + "target_health_start": "#F3F3F3", + "target_health_end": "#9E9E9E", + "target_trail": "#373737", + "target_absorption_start": "#FFC300", + "target_absorption_end": "#FFD96B", + "target_hit_outer": "#FF5A5A", + "target_hit_inner": "#FF7373", + "target_bg_effect": "Glass", + "target_blur_alpha": 255.0, + "target_hit_anim": true, + "target_hit_pulse": 1.0 +} diff --git a/combatant-client-26.2/config/lovisual/hud/draggable/timer_indicator.json b/combatant-client-26.2/config/lovisual/hud/draggable/timer_indicator.json new file mode 100644 index 0000000..237e18e --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/draggable/timer_indicator.json @@ -0,0 +1,21 @@ +{ + "enabled": false, + "layout": { + "pos": [ + 1242.2065, + 889.5111 + ], + "space": "logical" + }, + "scale": 1.0, + "always_show": true, + "sync_theme": true, + "blur": false, + "blur_alpha": 140.0, + "blur_bg_alpha": 128.0, + "gradient": true, + "bg": "#B012151B", + "bar_start": "#5CC8E7", + "bar_end": "#55FFFF", + "text_color": "#FFFFFF" +} diff --git a/combatant-client-26.2/config/lovisual/hud/draggable/tps.json b/combatant-client-26.2/config/lovisual/hud/draggable/tps.json new file mode 100644 index 0000000..4e0dec9 --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/draggable/tps.json @@ -0,0 +1,18 @@ +{ + "enabled": false, + "tps_extra": false, + "tps_scale": 2.37, + "tps_color_mode": "Theme", + "tps_icon_color": "#FFFFFF", + "tps_value_color": "#FFFFFF", + "tps_meta_color": "#9B9B9B", + "tps_extra_color": "#BEBEBE", + "tps_bg_effect": "Blur", + "tps_bg": "#F7343434", + "tps_bg_secondary": "#F7161616", + "tps_stroke": "#5A5A5A", + "tps_bg_alpha": 133.0, + "tps_blur_alpha": 255.0, + "tps_label_effect": "None", + "tps_label_effect_speed": 18.0 +} diff --git a/combatant-client-26.2/config/lovisual/hud/draggable/triangulator.json b/combatant-client-26.2/config/lovisual/hud/draggable/triangulator.json new file mode 100644 index 0000000..7a202d7 --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/draggable/triangulator.json @@ -0,0 +1,29 @@ +{ + "enabled": false, + "layout": { + "pos": [ + 1302.04, + 330.69208 + ], + "space": "logical" + }, + "triangulator_scale": 1.68, + "triangulator_color_mode": "Theme", + "triangulator_bg": "#F7343434", + "triangulator_bg_secondary": "#F7161616", + "triangulator_stroke": "#5A5A5A", + "triangulator_bg_alpha": 225.0, + "triangulator_text": "#FFFFFF", + "triangulator_muted": "#A5A5A5", + "triangulator_bg_effect": "Blur", + "triangulator_blur_alpha": 140.0, + "triangulator_icon_effect": "None", + "triangulator_icon_effect_speed": 18.0, + "triangulator_min_observation_ticks": 6.0, + "triangulator_stability_threshold": 0.99, + "triangulator_min_pair_angle": 6.0, + "triangulator_auto_reset": true, + "triangulator_auto_reset_distance": 96.0, + "triangulator_max_cached_throws": 6.0, + "triangulator_cache_lifetime": 180.0 +} diff --git a/combatant-client-26.2/config/lovisual/hud/draggable/xyz.json b/combatant-client-26.2/config/lovisual/hud/draggable/xyz.json new file mode 100644 index 0000000..25d7d5d --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/draggable/xyz.json @@ -0,0 +1,18 @@ +{ + "enabled": true, + "xyz_nether": "On", + "xyz_scale": 2.37, + "xyz_color_mode": "Theme", + "xyz_icon_color": "#FFFFFF", + "xyz_label_color": "#9B9B9B", + "xyz_value_color": "#FFFFFF", + "xyz_extra_color": "#BEBEBE", + "xyz_bg_effect": "Blur", + "xyz_bg": "#F7343434", + "xyz_bg_secondary": "#F7161616", + "xyz_stroke": "#5A5A5A", + "xyz_bg_alpha": 184.0, + "xyz_blur_alpha": 255.0, + "xyz_label_effect": "Flow", + "xyz_label_effect_speed": 18.0 +} diff --git a/combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_dynamic_island.json b/combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_dynamic_island.json new file mode 100644 index 0000000..7e3a186 --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_dynamic_island.json @@ -0,0 +1,11 @@ +{ + "enabled": true, + "dynamic_island_sync_theme": true, + "dynamic_island_theme_alpha": 200.0, + "dynamic_island_blur": false, + "dynamic_island_blur_alpha": 120.0, + "dynamic_island_bg": "#E614171D", + "dynamic_island_accent": "#6E8DFF", + "dynamic_island_text": "#F5F7FA", + "dynamic_island_muted": "#A9B1BC" +} diff --git a/combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_swap_tooltip.json b/combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_swap_tooltip.json new file mode 100644 index 0000000..d1970f6 --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_swap_tooltip.json @@ -0,0 +1,7 @@ +{ + "enabled": true, + "swap_scale": 0.24, + "swap_offset_y": 66.6, + "swap_text_effect": "Mix", + "swap_text_effect_speed": 18.0 +} diff --git a/combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_tab_list.json b/combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_tab_list.json new file mode 100644 index 0000000..3e44f56 --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_tab_list.json @@ -0,0 +1,10 @@ +{ + "enabled": true, + "tab_list_max_width": 1680.0, + "tab_list_sync_theme": true, + "tab_list_theme_alpha": 225.0, + "tab_list_bg": "#E50B1018", + "tab_list_accent": "#6E8DFF", + "tab_list_text": "#F4F7FB", + "tab_list_muted": "#9AA5B5" +} diff --git a/combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_vanilla_bar.json b/combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_vanilla_bar.json new file mode 100644 index 0000000..0afcb10 --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_vanilla_bar.json @@ -0,0 +1,27 @@ +{ + "enabled": true, + "xp_bar": true, + "xp_bar_show_level": true, + "bar_mode": "level", + "bar_color_mode": "Theme", + "custom_bg_color": "#88151515", + "custom_fill_color": "#CC4CB2FF", + "custom_fill_gradient_color": "#CC76F0FF", + "custom_text_color": "#FFA0E5FF", + "locator_labels": true, + "locator_label_mode": "center", + "locator_label_angle": 6.0, + "locator_label_distance": true, + "locator_search_enabled": true, + "locator_search_list": [ + "imhorny" + ], + "locator_dimmed_color": "#777777", + "locator_search_color": "#FFAA00", + "locator_dimmed_scale_only": true, + "locator_map_filter_enabled": false, + "locator_map_server": "", + "locator_map_url": "example", + "locator_target_toggle": "NONE", + "locator_bar_toggle": "U" +} diff --git a/combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_vanilla_buttons.json b/combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_vanilla_buttons.json new file mode 100644 index 0000000..fc9af17 --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_vanilla_buttons.json @@ -0,0 +1,25 @@ +{ + "enabled": true, + "style": "Default", + "gradient_base_color": "#FF2F2929", + "gradient_color": "#FF902F2F", + "gradient_angle": 74.25, + "gradient_intensity": 0.6, + "custom_bg": "#FF1B1B1B", + "custom_bg_hover": "#FF2A2A2A", + "custom_bg_disabled": "#FF141414", + "custom_stroke": "#FF9B5DB8", + "custom_stroke_hover": "#FFA710B2", + "custom_stroke_disabled": "#FF2A2A2A", + "custom_text": "#FFECECEC", + "custom_text_disabled": "#FF7A7A7A", + "custom_radius": 6.0, + "custom_softness": 1.2, + "custom_stroke_thickness": 0.7, + "custom_gradient": true, + "custom_gradient_color": "#73521BA7", + "custom_gradient_angle": 90.0, + "custom_gradient_intensity": 0.5, + "custom_text_scale": 1.0, + "custom_text_pad_x": 6.0 +} diff --git a/combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_vanilla_health.json b/combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_vanilla_health.json new file mode 100644 index 0000000..b9b489b --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_vanilla_health.json @@ -0,0 +1,8 @@ +{ + "enabled": true, + "health_alpha": 255.0, + "health_bar_style": "TargetHud", + "custom_bar_radius": 5.0, + "custom_text_scale": 0.24, + "custom_text_padding": 3.0 +} diff --git a/combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_vanilla_hotbar.json b/combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_vanilla_hotbar.json new file mode 100644 index 0000000..1390e4a --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_vanilla_hotbar.json @@ -0,0 +1,14 @@ +{ + "enabled": true, + "offhand_mode": "merged", + "hotbar_color_mode": "Custom", + "bg_color": "#7A191414", + "hotbar_bg_alpha": 167.0, + "custom_select_color": false, + "select_color": "#2F6277", + "custom_divider_color": false, + "divider_color": "#C13434", + "accent_alpha": 255.0, + "bg_effect": "Glass", + "blur_alpha": 255.0 +} diff --git a/combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_vanilla_tooltips.json b/combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_vanilla_tooltips.json new file mode 100644 index 0000000..4ce8e27 --- /dev/null +++ b/combatant-client-26.2/config/lovisual/hud/nondraggable/hud_element_vanilla_tooltips.json @@ -0,0 +1,22 @@ +{ + "enabled": true, + "item_tooltip": true, + "shulker_preview": true, + "item_info_colorize": true, + "tooltip_font": "Iosevka", + "tooltip_text_size": 1.0, + "top_enchant_ignore": [ + "minecraft:binding_curse", + "minecraft:vanishing_curse", + "minecraft:mending", + "minecraft:infinity", + "minecraft:silk_touch", + "minecraft:aqua_affinity", + "minecraft:flame", + "minecraft:channeling", + "minecraft:multishot", + "minecraft:unbreaking" + ], + "shulker_slot_size": 18.0, + "shulker_preview_hold": "LEFT_SHIFT" +} diff --git a/combatant-client-26.2/config/lovisual/mainconfig.json5 b/combatant-client-26.2/config/lovisual/mainconfig.json5 new file mode 100644 index 0000000..fdf4c2c --- /dev/null +++ b/combatant-client-26.2/config/lovisual/mainconfig.json5 @@ -0,0 +1,30 @@ +{ + 'debug': 'off', + 'forcePvp': false, + 'menuBg': 'off', + 'menuClockShowSeconds': false, + 'menuBgUseTheme': false, + 'msaa3d': 'off', + 'clickGuiModulesHints': true, + 'clickGuiHudEditorHints': true, + 'inventorySwapPolicy': 'none', + 'inventorySwapScope': 'full', + 'inventorySwapVisibility': 'silent', + 'inventorySwapRestore': true, + 'inventorySwapPreferHotbar': true, + 'inventorySwapLegitWaitTicks': 2.0, + 'inventorySwapStrictInventoryWaitTicks': 1.0, + 'inventorySwapStrictMovementLockTicks': 2.0, + 'disableNarrator': true, + 'backdoorProtection': true, + 'backdoorTranslationFilter': true, + 'backdoorKeybindFilter': true, + 'backdoorLocalHttpGuard': true, + 'backdoorPackCacheIsolation': true, + 'backdoorAllowLocalHttpWhenServerLocal': true, + 'backdoorAllowedTranslationPacks': [ + 'vanilla', + ], + 'backdoorAllowedKeybinds': [ + ], +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/LoVisual.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/LoVisual.java index 1fe3b4a..7c0eccf 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/LoVisual.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/LoVisual.java @@ -60,7 +60,6 @@ import dev.loki.lovisual.util.click.AttackPressing; import dev.loki.lovisual.util.combat.AntiBotTracker; import dev.loki.lovisual.util.combat.protocol.CombatProtocolHeuristics; import dev.loki.lovisual.util.combat.SprintController; -import dev.loki.lovisual.util.combat.VulcanReachController; import dev.loki.lovisual.util.entity.simulation.BoatSimulationCache; import dev.loki.lovisual.util.media.MediaSessionService; import dev.loki.lovisual.util.network.BacktrackController; @@ -318,7 +317,6 @@ public class LoVisual implements ClientModInitializer { Events.BUS.register(InventorySwap.INSTANCE); Events.BUS.register(SprintController.INSTANCE); Events.BUS.register(AntiBotTracker.INSTANCE); - Events.BUS.register(VulcanReachController.INSTANCE); Events.BUS.register(CombatProtocolHeuristics.INSTANCE); Events.BUS.register(PvpTracker.INSTANCE); Events.BUS.register(PvpTargetTracker.INSTANCE); diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticAttachmentTransforms.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticAttachmentTransforms.java new file mode 100644 index 0000000..72acb1f --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticAttachmentTransforms.java @@ -0,0 +1,55 @@ +package dev.loki.lovisual.features.cosmetic; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Axis; + +public final class CosmeticAttachmentTransforms { + private CosmeticAttachmentTransforms() { + } + + public static void apply(PoseStack matrices, CosmeticType type, CosmeticModel model) { + if (type == CosmeticType.PET) { + // TODO: replace with CosmeticAnimator.getPetHoverOffset() once the animator is ported. + float bob = 0f; + matrices.mulPose(Axis.YP.rotationDegrees(180f)); + matrices.translate(0.45f, 2.15f + bob, 0.12f); + matrices.scale(0.55f, 0.55f, 0.55f); + return; + } + if (type == CosmeticType.ACCESSORY && model != null) { + String folder = model.getFolderName().toLowerCase(); + if (folder.contains("witch") || folder.contains("hat")) { + matrices.translate(0f, 1.72f, 0f); + matrices.scale(0.52f, 0.52f, 0.52f); + } else if (folder.contains("wing")) { + matrices.translate(0f, 0.1f, -0.3f); + matrices.mulPose(Axis.YP.rotationDegrees(180f)); + matrices.scale(0.85f, 0.85f, 0.85f); + } else if (folder.contains("wheel")) { + matrices.translate(0f, 0.55f, 0.25f); + matrices.scale(0.55f, 0.55f, 0.55f); + } else { + matrices.translate(0f, 1.3f, 0f); + matrices.scale(0.5f, 0.5f, 0.5f); + } + } + } + + public static void applyPreview(PoseStack matrices, CosmeticModel model) { + if (model == null) return; + String folder = model.getFolderName().toLowerCase(); + if (model.getType() == CosmeticType.PET) { + matrices.translate(6f, 22f, 1f); + matrices.scale(1.8f, 1.8f, 1.8f); + } else if (model.getType() == CosmeticType.ACCESSORY) { + if (folder.contains("witch") || folder.contains("hat")) { + matrices.translate(0f, 20f, 0f); + } else if (folder.contains("wing")) { + matrices.translate(0f, 2f, -4f); + matrices.scale(1.3f, 1.3f, 1.3f); + } else if (folder.contains("wheel")) { + matrices.translate(0f, 6f, 3f); + } + } + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticBbAnimation.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticBbAnimation.java new file mode 100644 index 0000000..8468df6 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticBbAnimation.java @@ -0,0 +1,223 @@ +package dev.loki.lovisual.features.cosmetic; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.joml.Vector3f; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public final class CosmeticBbAnimation { + private final String name; + private final float length; + private final boolean loop; + private final Map tracksByBoneUuid = new java.util.HashMap<>(); + + public CosmeticBbAnimation(String name, float length, boolean loop) { + this.name = name; + this.length = Math.max(length, 1f / 20f); + this.loop = loop; + } + + public String getName() { + return name; + } + + public float getLength() { + return length; + } + + public boolean isLoop() { + return loop; + } + + public Map getTracks() { + return tracksByBoneUuid; + } + + public static Map parseAll(JsonObject bbmodel) { + Map map = new LinkedHashMap<>(); + if (!bbmodel.has("animations")) return map; + + for (JsonElement el : bbmodel.getAsJsonArray("animations")) { + if (!el.isJsonObject()) continue; + JsonObject obj = el.getAsJsonObject(); + String name = obj.has("name") ? obj.get("name").getAsString() : "anim"; + if (name.startsWith("--") || name.isBlank()) continue; + + float length = obj.has("length") ? obj.get("length").getAsFloat() : 1f; + boolean loop = !obj.has("loop") || obj.get("loop").getAsBoolean(); + CosmeticBbAnimation anim = new CosmeticBbAnimation(name, length, loop); + + if (obj.has("animators")) { + JsonObject animators = obj.getAsJsonObject("animators"); + for (Map.Entry entry : animators.entrySet()) { + if (!entry.getValue().isJsonObject()) continue; + JsonObject boneAnim = entry.getValue().getAsJsonObject(); + String boneUuid = entry.getKey(); + if (boneAnim.has("uuid")) { + boneUuid = boneAnim.get("uuid").getAsString(); + } + BoneTrack track = BoneTrack.parse(boneAnim); + if (!track.isEmpty()) { + anim.tracksByBoneUuid.put(boneUuid, track); + } + } + } + + if (anim.tracksByBoneUuid.isEmpty()) continue; + map.put(name.toLowerCase(), anim); + } + return map; + } + + public static void apply(CosmeticModel model, CosmeticBbAnimation animation, float time) { + java.util.Set filter = model.getAnimationBoneFilter(); + for (Map.Entry entry : animation.tracksByBoneUuid.entrySet()) { + if (filter != null && !filter.contains(entry.getKey())) continue; + CosmeticBone bone = model.getBoneByUuid(entry.getKey()); + if (bone == null) continue; + + BoneTrack track = entry.getValue(); + Vector3f rot = track.sampleRotation(time, animation.length, animation.loop); + Vector3f pos = track.samplePosition(time, animation.length, animation.loop); + + if (rot != null) { + bone.setAnimRotation(new Vector3f( + rot.x - bone.getDefaultRotation().x, + rot.y - bone.getDefaultRotation().y, + rot.z - bone.getDefaultRotation().z + )); + } + if (pos != null) { + bone.setAnimPosition(pos); + } + } + } + + public static final class BoneTrack { + private final List rotations = new ArrayList<>(); + private final List positions = new ArrayList<>(); + + static BoneTrack parse(JsonObject boneAnim) { + BoneTrack track = new BoneTrack(); + if (!boneAnim.has("keyframes")) return track; + + for (JsonElement el : boneAnim.getAsJsonArray("keyframes")) { + if (!el.isJsonObject()) continue; + JsonObject kf = el.getAsJsonObject(); + String channel = kf.has("channel") ? kf.get("channel").getAsString() : ""; + float t = kf.has("time") ? kf.get("time").getAsFloat() : 0f; + Vector3f value = readPoint(kf); + String interp = kf.has("interpolation") ? kf.get("interpolation").getAsString() : "linear"; + Keyframe keyframe = new Keyframe(t, value, interp); + + switch (channel) { + case "rotation" -> track.rotations.add(keyframe); + case "position" -> track.positions.add(keyframe); + default -> { + } + } + } + + track.rotations.sort(Comparator.comparing(Keyframe::time)); + track.positions.sort(Comparator.comparing(Keyframe::time)); + return track; + } + + boolean isEmpty() { + return rotations.isEmpty() && positions.isEmpty(); + } + + Vector3f sampleRotation(float time, float length, boolean loop) { + return sample(rotations, time, length, loop); + } + + Vector3f samplePosition(float time, float length, boolean loop) { + return sample(positions, time, length, loop); + } + + private static Vector3f sample(List frames, float time, float length, boolean loop) { + if (frames.isEmpty()) return null; + if (frames.size() == 1) return new Vector3f(frames.get(0).value); + + float t = loop ? wrapTime(time, length) : Math.min(time, length); + Keyframe prev = frames.get(0); + Keyframe next = frames.get(frames.size() - 1); + + for (int i = 0; i < frames.size() - 1; i++) { + Keyframe a = frames.get(i); + Keyframe b = frames.get(i + 1); + if (t >= a.time && t <= b.time) { + prev = a; + next = b; + break; + } + } + if (t >= frames.get(frames.size() - 1).time) { + prev = frames.get(frames.size() - 1); + next = prev; + } + + if (prev == next || Math.abs(next.time - prev.time) < 0.0001f) { + return new Vector3f(prev.value); + } + + float alpha = (t - prev.time) / (next.time - prev.time); + if ("step".equals(prev.interpolation)) { + return new Vector3f(prev.value); + } + return lerp(prev.value, next.value, clamp01(alpha)); + } + + private static float wrapTime(float time, float length) { + if (length <= 0f) return time; + float wrapped = time % length; + return wrapped < 0f ? wrapped + length : wrapped; + } + + private static float clamp01(float v) { + return Math.max(0f, Math.min(1f, v)); + } + + private static Vector3f lerp(Vector3f a, Vector3f b, float t) { + return new Vector3f( + a.x + (b.x - a.x) * t, + a.y + (b.y - a.y) * t, + a.z + (b.z - a.z) * t + ); + } + + private static Vector3f readPoint(JsonObject kf) { + if (!kf.has("data_points")) return new Vector3f(); + JsonArray points = kf.getAsJsonArray("data_points"); + if (points.isEmpty()) return new Vector3f(); + JsonObject point = points.get(0).getAsJsonObject(); + return new Vector3f( + parseNum(point, "x"), + parseNum(point, "y"), + parseNum(point, "z") + ); + } + + private static float parseNum(JsonObject obj, String key) { + if (!obj.has(key)) return 0f; + JsonElement el = obj.get(key); + if (el.isJsonPrimitive() && el.getAsJsonPrimitive().isNumber()) { + return el.getAsFloat(); + } + try { + return Float.parseFloat(el.getAsString().trim()); + } catch (Exception ignored) { + return 0f; + } + } + } + + private record Keyframe(float time, Vector3f value, String interpolation) { + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticBone.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticBone.java new file mode 100644 index 0000000..5a17efe --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticBone.java @@ -0,0 +1,66 @@ +package dev.loki.lovisual.features.cosmetic; + +import lombok.Getter; +import lombok.Setter; +import org.joml.Matrix4f; +import org.joml.Vector3f; + +import java.util.ArrayList; +import java.util.List; + +@Getter +public class CosmeticBone { + private final String name; + private final String uuid; + private final Vector3f pivot; + private final Vector3f defaultRotation; + private final List cubes = new ArrayList<>(); + private final List children = new ArrayList<>(); + + @Setter private Vector3f animRotation = new Vector3f(0, 0, 0); + @Setter private Vector3f animPosition = new Vector3f(0, 0, 0); + + public CosmeticBone(String name, String uuid, Vector3f pivot, Vector3f defaultRotation) { + this.name = name; + this.uuid = uuid; + this.pivot = pivot; + this.defaultRotation = defaultRotation; + } + + public Matrix4f buildMatrix() { + Matrix4f mat = new Matrix4f(); + + mat.translate(pivot.x / 16f, pivot.y / 16f, pivot.z / 16f); + + if (defaultRotation.x != 0 || defaultRotation.y != 0 || defaultRotation.z != 0 + || animRotation.x != 0 || animRotation.y != 0 || animRotation.z != 0) { + float rx = (float) Math.toRadians(defaultRotation.x + animRotation.x); + float ry = (float) Math.toRadians(defaultRotation.y + animRotation.y); + float rz = (float) Math.toRadians(defaultRotation.z + animRotation.z); + mat.rotateZYX(rz, ry, rx); + } + + mat.translate(-pivot.x / 16f, -pivot.y / 16f, -pivot.z / 16f); + + if (animPosition.x != 0 || animPosition.y != 0 || animPosition.z != 0) { + mat.translate(animPosition.x / 16f, animPosition.y / 16f, animPosition.z / 16f); + } + + return mat; + } + + public void resetAnim() { + animRotation = new Vector3f(0, 0, 0); + animPosition = new Vector3f(0, 0, 0); + for (CosmeticBone child : children) child.resetAnim(); + } + + public CosmeticBone deepClone() { + CosmeticBone copy = new CosmeticBone(name, uuid, new Vector3f(pivot), new Vector3f(defaultRotation)); + copy.getCubes().addAll(cubes); + for (CosmeticBone child : children) { + copy.getChildren().add(child.deepClone()); + } + return copy; + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticCube.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticCube.java new file mode 100644 index 0000000..ca1a35c --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticCube.java @@ -0,0 +1,42 @@ +package dev.loki.lovisual.features.cosmetic; + +import lombok.Getter; +import org.joml.Vector3f; + +@Getter +public class CosmeticCube { + private final Vector3f from; + private final Vector3f to; + private final Vector3f rotation; + private final Vector3f origin; + private final float inflate; + private final CubeFace[] faces; + private final int textureWidth; + private final int textureHeight; + + public CosmeticCube(Vector3f from, Vector3f to, Vector3f rotation, Vector3f origin, + float inflate, CubeFace[] faces, int textureWidth, int textureHeight) { + this.from = from; + this.to = to; + this.rotation = rotation; + this.origin = origin; + this.inflate = inflate; + this.faces = faces; + this.textureWidth = textureWidth; + this.textureHeight = textureHeight; + } + + @Getter + public static class CubeFace { + private final float u1, v1, u2, v2; + private final int textureIndex; + + public CubeFace(float u1, float v1, float u2, float v2, int textureIndex) { + this.u1 = u1; + this.v1 = v1; + this.u2 = u2; + this.v2 = v2; + this.textureIndex = textureIndex; + } + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticManager.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticManager.java new file mode 100644 index 0000000..f2053e8 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticManager.java @@ -0,0 +1,74 @@ +package dev.loki.lovisual.features.cosmetic; + +/** + * Holds which cosmetic (model/pet/accessory) the local user currently has equipped. + * Rendering and animation live in separate classes (not part of this core pass). + */ +public final class CosmeticManager { + private static final CosmeticManager INSTANCE = new CosmeticManager(); + + private CosmeticModel activeModel; + private CosmeticModel activePet; + private CosmeticModel activeAccessory; + + private CosmeticManager() { + } + + public static CosmeticManager get() { + return INSTANCE; + } + + /** + * Reloads the catalog from disk, re-resolving the currently equipped cosmetics + * (by id) against the freshly loaded models. + */ + public void init() { + String modelId = activeModel != null ? activeModel.getId() : null; + String petId = activePet != null ? activePet.getId() : null; + String accessoryId = activeAccessory != null ? activeAccessory.getId() : null; + + for (CosmeticModel model : CosmeticRepository.get().all()) { + model.resetTexture(); + } + CosmeticRepository.get().reload(); + + CosmeticRepository repo = CosmeticRepository.get(); + activeModel = modelId != null ? repo.get(modelId) : null; + activePet = petId != null ? repo.get(petId) : null; + activeAccessory = accessoryId != null ? repo.get(accessoryId) : null; + } + + public CosmeticModel getActive(CosmeticType type) { + return switch (type) { + case PET -> activePet; + case ACCESSORY -> activeAccessory; + case MODEL -> activeModel; + }; + } + + public void setActive(CosmeticType type, CosmeticModel model) { + if (model != null) { + CosmeticModel resolved = CosmeticRepository.get().get(model.getId()); + if (resolved != null) { + model = resolved; + } + } + switch (type) { + case PET -> activePet = model; + case ACCESSORY -> activeAccessory = model; + case MODEL -> activeModel = model; + } + } + + public void clear(CosmeticType type) { + setActive(type, null); + } + + public boolean hasAnyActive() { + return activeModel != null || activePet != null || activeAccessory != null; + } + + public boolean isActive() { + return activeModel != null; + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticModel.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticModel.java new file mode 100644 index 0000000..168bae4 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticModel.java @@ -0,0 +1,659 @@ +package dev.loki.lovisual.features.cosmetic; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.mojang.blaze3d.platform.NativeImage; +import lombok.Getter; +import lombok.Setter; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.texture.DynamicTexture; +import net.minecraft.resources.Identifier; +import org.joml.Vector3f; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +/** + * A single cosmetic asset: a Blockbench (.bbmodel) bone/cube tree plus its texture(s), + * loaded from a user-provided folder (avatar.json + model.bbmodel + textures). + */ +@Getter +public class CosmeticModel { + private final String id; + private final String name; + private final String description; + private final String accentColor; + private final String folderName; + private final CosmeticType type; + private final File externalDirectory; + @Setter private String source = "Своя"; + + private final List rootBones = new ArrayList<>(); + private final List renderRoots = new ArrayList<>(); + private boolean customLegsPresent; + private boolean bowReplacementAccessory; + private boolean swordReplacementAccessory; + private boolean hatOnlyAccessory; + private boolean wingsOnlyAccessory; + private Set visibleBoneUuids; + @Setter private Set animationBoneFilter; + private final Map boneMap = new HashMap<>(); + private final Map boneMapByUuid = new HashMap<>(); + private final Map bbAnimations = new LinkedHashMap<>(); + private final List textureSlots = new ArrayList<>(); + private int textureWidth = 64; + private int textureHeight = 64; + private Identifier textureId; + private boolean textureLoaded = false; + private CosmeticTextureSlot fallbackSlot; + + private CosmeticModel(String id, String name, String description, String accentColor, + String folderName, CosmeticType type, File externalDirectory) { + this.id = id; + this.name = name; + this.description = description; + this.accentColor = accentColor; + this.folderName = folderName; + this.type = type; + this.externalDirectory = externalDirectory; + } + + public CosmeticModel withType(CosmeticType newType) { + CosmeticModel copy = new CosmeticModel(id, name, description, accentColor, folderName, newType, externalDirectory); + copy.rootBones.addAll(rootBones); + copy.renderRoots.addAll(renderRoots); + copy.boneMap.putAll(boneMap); + copy.boneMapByUuid.putAll(boneMapByUuid); + copy.bbAnimations.putAll(bbAnimations); + copy.textureSlots.addAll(textureSlots); + copy.textureWidth = textureWidth; + copy.textureHeight = textureHeight; + copy.textureId = textureId; + copy.textureLoaded = textureLoaded; + copy.source = source; + copy.postProcess(); + return copy; + } + + public void resetTexture() { + textureLoaded = false; + textureId = null; + for (CosmeticTextureSlot slot : textureSlots) { + slot.reset(); + } + } + + public CosmeticTextureSlot getTextureSlot(int index) { + if (!textureSlots.isEmpty()) { + if (index >= 0 && index < textureSlots.size()) { + return textureSlots.get(index); + } + return textureSlots.get(0); + } + if (textureId == null) return null; + if (fallbackSlot == null) { + fallbackSlot = new CosmeticTextureSlot(0, textureId, textureWidth, textureHeight, "fallback"); + if (textureLoaded) fallbackSlot.markLoaded(); + } + return fallbackSlot; + } + + /** + * Loads a cosmetic from a user folder: {@code avatar.json} (metadata) plus a + * {@code .bbmodel} (geometry/animation) and its texture(s). + */ + public static CosmeticModel loadExternal(File folder, String sourceTag) { + if (folder == null || !folder.isDirectory()) return null; + String key = sourceTag + ":" + folder.getName().toLowerCase(); + return load(folder.getName(), folder, key, null); + } + + private static CosmeticModel load(String folderName, File externalDir, String catalogId, CosmeticType forcedType) { + try { + JsonObject avatar = new JsonObject(); + File avatarFile = new File(externalDir, "avatar.json"); + if (avatarFile.exists()) { + try (InputStreamReader reader = new InputStreamReader(new FileInputStream(avatarFile), StandardCharsets.UTF_8)) { + avatar = JsonParser.parseReader(reader).getAsJsonObject(); + } + } + + String name = avatar.has("name") ? avatar.get("name").getAsString() : folderName; + String desc = avatar.has("description") ? avatar.get("description").getAsString() : ""; + String color = avatar.has("color") ? avatar.get("color").getAsString() : "#ffffff"; + CosmeticType type = forcedType != null ? forcedType : CosmeticType.fromJson( + avatar.has("category") ? avatar.get("category").getAsString() + : avatar.has("type") ? avatar.get("type").getAsString() : null + ); + + CosmeticModel model = new CosmeticModel(catalogId, name, desc, color, folderName, type, externalDir); + + InputStream modelStream = openModelStream(externalDir, folderName); + if (modelStream == null) { + return model; + } + + JsonObject bbmodel; + try (InputStreamReader reader = new InputStreamReader(modelStream, StandardCharsets.UTF_8)) { + bbmodel = JsonParser.parseReader(reader).getAsJsonObject(); + } + + if (bbmodel.has("resolution")) { + JsonObject res = bbmodel.getAsJsonObject("resolution"); + model.textureWidth = res.get("width").getAsInt(); + model.textureHeight = res.get("height").getAsInt(); + } + + model.textureId = Identifier.fromNamespaceAndPath( + "lovisual", + "dynamic/cosmetic/" + catalogId.replace(':', '_').replace(' ', '_') + ); + model.loadTextureSlots(bbmodel); + + Map elementMap = new HashMap<>(); + if (bbmodel.has("elements")) { + for (JsonElement el : bbmodel.getAsJsonArray("elements")) { + JsonObject obj = el.getAsJsonObject(); + if (obj.has("uuid")) { + elementMap.put(obj.get("uuid").getAsString(), obj); + } + } + } + + Map groupMap = buildGroupMap(bbmodel); + + if (bbmodel.has("outliner")) { + for (JsonElement el : bbmodel.getAsJsonArray("outliner")) { + CosmeticBone bone = parseOutliner(el, elementMap, groupMap, model.textureWidth, model.textureHeight); + if (bone != null) { + model.rootBones.add(bone); + model.indexBone(bone); + } + } + } + + model.bbAnimations.putAll(CosmeticBbAnimation.parseAll(bbmodel)); + model.postProcess(); + + return model; + } catch (Exception e) { + return null; + } + } + + private static InputStream openModelStream(File externalDir, String folderName) throws IOException { + File model = new File(externalDir, "model.bbmodel"); + if (!model.exists()) { + model = new File(externalDir, folderName + ".bbmodel"); + } + if (model.exists()) return new FileInputStream(model); + File[] bbmodels = externalDir.listFiles((dir, name) -> name.endsWith(".bbmodel")); + if (bbmodels != null && bbmodels.length > 0) { + return new FileInputStream(bbmodels[0]); + } + return null; + } + + private void loadTextureSlots(JsonObject bbmodel) { + textureSlots.clear(); + if (!bbmodel.has("textures")) return; + + String modelKey = id.replace(':', '_'); + JsonArray textures = bbmodel.getAsJsonArray("textures"); + for (int i = 0; i < textures.size(); i++) { + if (!textures.get(i).isJsonObject()) continue; + JsonObject tex = textures.get(i).getAsJsonObject(); + textureSlots.add(CosmeticTextureSlot.fromJson(i, modelKey, tex)); + } + } + + public void ensureTextureSlotLoaded(CosmeticTextureSlot slot) { + if (slot == null || slot.isLoaded()) return; + Minecraft mc = Minecraft.getInstance(); + if (mc == null) return; + slot.load(mc, externalDirectory, folderName); + } + + /** + * Loads whichever textures this model needs (per-slot bbmodel textures, or a single + * fallback {@code texture.png}/{@code avatar.png} in the cosmetic's folder). + */ + public void loadTexture() { + if (textureLoaded) return; + try { + Minecraft mc = Minecraft.getInstance(); + if (mc == null || mc.getTextureManager() == null) return; + + boolean anyLoaded = false; + for (CosmeticTextureSlot slot : textureSlots) { + slot.load(mc, externalDirectory, folderName); + if (slot.isLoaded()) anyLoaded = true; + } + + if (!anyLoaded && externalDirectory != null) { + String[] names = {"outfits/texture.png", "texture.png", folderName + ".png", "avatar.png"}; + for (String rel : names) { + File tex = new File(externalDirectory, rel); + if (!tex.exists()) continue; + try (InputStream is = new FileInputStream(tex)) { + registerTexture(mc, textureId, is); + anyLoaded = true; + break; + } catch (Exception ignored) { + } + } + } else if (!textureSlots.isEmpty() && textureSlots.get(0).isLoaded()) { + textureId = textureSlots.get(0).getId(); + textureWidth = textureSlots.get(0).getWidth(); + textureHeight = textureSlots.get(0).getHeight(); + } + + textureLoaded = anyLoaded; + if (!anyLoaded) { + textureId = null; + } + } catch (Exception ignored) { + } + } + + private void registerTexture(Minecraft mc, Identifier rawId, InputStream is) throws IOException { + NativeImage img = NativeImage.read(is); + DynamicTexture tex = new DynamicTexture(() -> "lovisual_cosmetic", img); + mc.getTextureManager().register(rawId, tex); + textureId = rawId; + textureLoaded = true; + } + + private void indexBone(CosmeticBone bone) { + boneMap.put(bone.getName().toLowerCase(), bone); + boneMapByUuid.put(bone.getUuid(), bone); + for (CosmeticBone child : bone.getChildren()) indexBone(child); + } + + public CosmeticBone getBone(String name) { + return boneMap.get(name.toLowerCase()); + } + + public CosmeticBone getBoneByUuid(String uuid) { + return boneMapByUuid.get(uuid); + } + + public boolean hasBbAnimations() { + return !bbAnimations.isEmpty(); + } + + public Collection getBbAnimations() { + return bbAnimations.values(); + } + + public CosmeticBbAnimation findBbAnimation(String... candidates) { + for (String candidate : candidates) { + if (candidate == null) continue; + CosmeticBbAnimation anim = bbAnimations.get(candidate.toLowerCase()); + if (anim != null) return anim; + } + for (String candidate : candidates) { + if (candidate == null) continue; + String lower = candidate.toLowerCase(); + for (Map.Entry entry : bbAnimations.entrySet()) { + if (entry.getKey().contains(lower) || lower.contains(entry.getKey())) { + return entry.getValue(); + } + } + } + return null; + } + + public CosmeticBbAnimation getFirstBbAnimation() { + for (String preferred : List.of( + "idling", "idle", "idle_arms", "idlemonsbob", "defaultstate", + "walking", "yap", "painting", "pose", "pose2" + )) { + CosmeticBbAnimation anim = findBbAnimation(preferred); + if (anim != null) return anim; + } + if (bbAnimations.isEmpty()) return null; + return bbAnimations.values().iterator().next(); + } + + public List getRenderRoots() { + return renderRoots.isEmpty() ? rootBones : renderRoots; + } + + public boolean hasCustomLegs() { + return customLegsPresent; + } + + public boolean isBowReplacementAccessory() { + return bowReplacementAccessory; + } + + public boolean isSwordReplacementAccessory() { + return swordReplacementAccessory; + } + + public boolean isBodyOnlyAccessory() { + return type == CosmeticType.ACCESSORY && !bowReplacementAccessory && !swordReplacementAccessory; + } + + public boolean hasRestPoseAnimation() { + return findBbAnimation( + "idling", "idle", "idle_arms", "idlemonsbob", "defaultstate", + "walking", "walkingback", "flywalk" + ) != null; + } + + public boolean shouldSkipBone(CosmeticBone bone) { + if (visibleBoneUuids != null) { + return !visibleBoneUuids.contains(bone.getUuid()); + } + String lower = bone.getName().toLowerCase(); + if (customLegsPresent && lower.contains("vanilla") && (lower.contains("leg") || lower.contains("foot"))) { + return true; + } + if (lower.contains("rightarm_fp") || lower.contains("leftarm_fp")) { + return true; + } + if (hatOnlyAccessory) { + return lower.equals("body") || lower.equals("leftarm") || lower.equals("rightarm") + || lower.equals("leftleg") || lower.equals("rightleg") || lower.equals("root"); + } + return false; + } + + /** + * Derives a standalone pet cosmetic from a single named bone subtree of a parent model + * (e.g. turning a model's "head" bone into its own pet cosmetic). + */ + public static CosmeticModel derivePet(CosmeticModel parent, String boneName, String petFolder, String petName) { + if (parent == null) return null; + CosmeticBone source = parent.findBoneDeep(boneName); + if (source == null) return null; + + CosmeticModel pet = new CosmeticModel( + "derived:" + petFolder.replace('/', '_'), + petName, + "", + parent.getAccentColor(), + petFolder, + CosmeticType.PET, + null + ); + pet.source = parent.source; + pet.textureWidth = parent.textureWidth; + pet.textureHeight = parent.textureHeight; + pet.textureId = parent.textureId; + pet.textureSlots.addAll(parent.textureSlots); + pet.textureLoaded = parent.textureLoaded; + + CosmeticBone root = source.deepClone(); + pet.rootBones.add(root); + pet.renderRoots.add(root); + pet.indexBone(root); + + Set uuids = new HashSet<>(); + pet.collectUuids(root, uuids); + pet.animationBoneFilter = uuids; + + for (Map.Entry entry : parent.bbAnimations.entrySet()) { + String key = entry.getKey().toLowerCase(); + if (key.contains("mons") || key.contains("mini")) { + pet.bbAnimations.put(entry.getKey(), entry.getValue()); + } + } + if (pet.bbAnimations.isEmpty()) { + pet.bbAnimations.putAll(parent.bbAnimations); + } + pet.postProcess(); + return pet; + } + + private void collectUuids(CosmeticBone bone, Set uuids) { + uuids.add(bone.getUuid()); + for (CosmeticBone child : bone.getChildren()) collectUuids(child, uuids); + } + + private CosmeticBone findBoneDeep(String name) { + String lower = name.toLowerCase(); + for (CosmeticBone root : rootBones) { + CosmeticBone found = findBoneDeep(root, lower); + if (found != null) return found; + } + return null; + } + + private static CosmeticBone findBoneDeep(CosmeticBone bone, String lower) { + if (bone.getName().toLowerCase().equals(lower) || bone.getName().toLowerCase().contains(lower)) { + return bone; + } + for (CosmeticBone child : bone.getChildren()) { + CosmeticBone found = findBoneDeep(child, lower); + if (found != null) return found; + } + return null; + } + + private void postProcess() { + customLegsPresent = boneMap.keySet().stream().anyMatch(n -> + n.contains("leftleg") || n.contains("rightleg") || n.contains("leftpaw") || n.contains("rightpaw")) + && boneMap.keySet().stream().noneMatch(n -> n.equals("leftlegvanilla") || n.equals("rightlegvanilla") + || (n.contains("vanilla") && n.contains("leg"))); + + String folder = folderName.toLowerCase(); + bowReplacementAccessory = type == CosmeticType.ACCESSORY && folder.contains("bow"); + swordReplacementAccessory = false; + hatOnlyAccessory = type == CosmeticType.ACCESSORY + && (folder.contains("witch") || folder.contains("hat")) && !bowReplacementAccessory; + wingsOnlyAccessory = type == CosmeticType.ACCESSORY && folder.contains("wing"); + visibleBoneUuids = null; + if (wingsOnlyAccessory) { + visibleBoneUuids = buildWingsBoneChainUuids(); + } + + renderRoots.clear(); + for (CosmeticBone root : rootBones) { + String n = root.getName().toLowerCase(); + if (type == CosmeticType.MODEL) { + if (n.equals("monst3r") || n.equals("skull") || n.endsWith("_fp")) continue; + } + if (type == CosmeticType.PET) { + if (n.equals("arrow") || n.equals("skull")) continue; + } + renderRoots.add(root); + } + if (renderRoots.isEmpty()) { + renderRoots.addAll(rootBones); + } + + applyAccessorySubtreeFilter(); + } + + private void applyAccessorySubtreeFilter() { + if (hatOnlyAccessory) { + CosmeticBone head = findBoneDeep("head"); + CosmeticBone hatBone = findBoneDeep("bone"); + if (head != null && hatBone != null) { + CosmeticBone mount = new CosmeticBone( + head.getName(), head.getUuid(), + new Vector3f(head.getPivot()), new Vector3f(head.getDefaultRotation())); + mount.getChildren().add(hatBone.deepClone()); + renderRoots.clear(); + renderRoots.add(mount); + } + } + } + + private Set buildWingsBoneChainUuids() { + Set keep = new HashSet<>(); + CosmeticBone wings = findBoneDeep("wings"); + if (wings == null) return keep; + collectBoneTreeUuids(wings, keep); + for (CosmeticBone root : rootBones) { + markBonePathToUuid(root, wings.getUuid(), keep); + } + return keep; + } + + private static void collectBoneTreeUuids(CosmeticBone bone, Set uuids) { + uuids.add(bone.getUuid()); + for (CosmeticBone child : bone.getChildren()) { + collectBoneTreeUuids(child, uuids); + } + } + + private static boolean markBonePathToUuid(CosmeticBone bone, String targetUuid, Set keep) { + keep.add(bone.getUuid()); + if (bone.getUuid().equals(targetUuid)) { + return true; + } + for (CosmeticBone child : bone.getChildren()) { + if (markBonePathToUuid(child, targetUuid, keep)) { + return true; + } + } + keep.remove(bone.getUuid()); + return false; + } + + private static Map buildGroupMap(JsonObject bbmodel) { + Map map = new HashMap<>(); + if (!bbmodel.has("groups")) return map; + for (JsonElement el : bbmodel.getAsJsonArray("groups")) { + if (!el.isJsonObject()) continue; + JsonObject group = el.getAsJsonObject(); + if (group.has("uuid")) { + map.put(group.get("uuid").getAsString(), group); + } + } + return map; + } + + private static Vector3f readVec3(JsonObject obj, String key, Vector3f fallback) { + if (obj == null || !obj.has(key)) return new Vector3f(fallback); + JsonArray arr = obj.getAsJsonArray(key); + return new Vector3f(arr.get(0).getAsFloat(), arr.get(1).getAsFloat(), arr.get(2).getAsFloat()); + } + + private static CosmeticBone parseOutliner(JsonElement element, Map elementMap, + Map groupMap, int texW, int texH) { + if (element.isJsonPrimitive() || !element.isJsonObject()) return null; + JsonObject obj = element.getAsJsonObject(); + + String uuid = obj.has("uuid") ? obj.get("uuid").getAsString() : UUID.randomUUID().toString(); + JsonObject group = groupMap.get(uuid); + + String name = "bone"; + if (obj.has("name")) { + name = obj.get("name").getAsString(); + } else if (group != null && group.has("name")) { + name = group.get("name").getAsString(); + } + + Vector3f pivot = obj.has("origin") + ? readVec3(obj, "origin", new Vector3f()) + : readVec3(group, "origin", new Vector3f()); + + Vector3f rotation = obj.has("rotation") + ? readVec3(obj, "rotation", new Vector3f()) + : readVec3(group, "rotation", new Vector3f()); + + CosmeticBone bone = new CosmeticBone(name, uuid, pivot, rotation); + + if (obj.has("children")) { + for (JsonElement child : obj.getAsJsonArray("children")) { + if (child.isJsonPrimitive()) { + String childUuid = child.getAsString(); + JsonObject cubeObj = elementMap.get(childUuid); + if (cubeObj != null) { + CosmeticCube cube = parseCube(cubeObj, texW, texH); + if (cube != null) bone.getCubes().add(cube); + } + } else { + CosmeticBone childBone = parseOutliner(child, elementMap, groupMap, texW, texH); + if (childBone != null) bone.getChildren().add(childBone); + } + } + } + + return bone; + } + + private static CosmeticCube parseCube(JsonObject obj, int texW, int texH) { + try { + JsonArray fromArr = obj.getAsJsonArray("from"); + JsonArray toArr = obj.getAsJsonArray("to"); + Vector3f from = new Vector3f(fromArr.get(0).getAsFloat(), fromArr.get(1).getAsFloat(), fromArr.get(2).getAsFloat()); + Vector3f to = new Vector3f(toArr.get(0).getAsFloat(), toArr.get(1).getAsFloat(), toArr.get(2).getAsFloat()); + + Vector3f rot = new Vector3f(0, 0, 0); + if (obj.has("rotation")) { + JsonArray r = obj.getAsJsonArray("rotation"); + rot.set(r.get(0).getAsFloat(), r.get(1).getAsFloat(), r.get(2).getAsFloat()); + } + + Vector3f origin = new Vector3f(0, 0, 0); + if (obj.has("origin")) { + JsonArray o = obj.getAsJsonArray("origin"); + origin.set(o.get(0).getAsFloat(), o.get(1).getAsFloat(), o.get(2).getAsFloat()); + } + + float inflate = obj.has("inflate") ? obj.get("inflate").getAsFloat() : 0f; + + CosmeticCube.CubeFace[] faces = new CosmeticCube.CubeFace[6]; + String[] faceNames = {"north", "east", "south", "west", "up", "down"}; + + if (obj.has("faces")) { + JsonObject facesObj = obj.getAsJsonObject("faces"); + for (int i = 0; i < faceNames.length; i++) { + String fn = faceNames[i]; + if (!facesObj.has(fn)) continue; + JsonObject face = facesObj.getAsJsonObject(fn); + if (!face.has("uv") || !face.get("uv").isJsonArray()) continue; + JsonArray uv = face.getAsJsonArray("uv"); + if (uv.size() < 4) continue; + int tex = 0; + if (face.has("texture") && !face.get("texture").isJsonNull()) { + JsonElement texEl = face.get("texture"); + if (texEl.isJsonPrimitive() && texEl.getAsJsonPrimitive().isNumber()) { + tex = texEl.getAsInt(); + } + } + float u1 = uv.get(0).getAsFloat(); + float v1 = uv.get(1).getAsFloat(); + float u2 = uv.get(2).getAsFloat(); + float v2 = uv.get(3).getAsFloat(); + if (u2 < u1) { + float t = u1; + u1 = u2; + u2 = t; + } + if (v2 < v1) { + float t = v1; + v1 = v2; + v2 = t; + } + faces[i] = new CosmeticCube.CubeFace(u1, v1, u2, v2, tex); + } + } + + return new CosmeticCube(from, to, rot, origin, inflate, faces, texW, texH); + } catch (Exception e) { + return null; + } + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticRenderer.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticRenderer.java new file mode 100644 index 0000000..58c42da --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticRenderer.java @@ -0,0 +1,204 @@ +package dev.loki.lovisual.features.cosmetic; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Axis; +import dev.loki.lovisual.render.engine.RenderState; +import dev.loki.lovisual.render.engine.math.RenderMath; +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 net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.texture.AbstractTexture; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.phys.Vec3; +import org.joml.Matrix4f; +import org.joml.Vector3f; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Renders a rest-pose (no bb-animation) {@link CosmeticModel} bone/cube tree attached to a + * player, as camera-relative textured quads through the engine's {@link Renderer3D}/{@link MeshBuilder}. + */ +public final class CosmeticRenderer { + private static final float UNITS_PER_BLOCK = 16.0f; + + private CosmeticRenderer() { + } + + public static void renderOnPlayer(Renderer3D renderer, Player player, CosmeticType type, float tickDelta) { + if (renderer == null || player == null) return; + + CosmeticModel model = CosmeticManager.get().getActive(type); + if (model == null) return; + + model.loadTexture(); + List roots = model.getRenderRoots(); + if (roots.isEmpty()) return; + + Vec3 pos = RenderMath.getLerpedPos(player, tickDelta); + float bodyYaw = Mth.rotLerp(tickDelta, player.yBodyRotO, player.yBodyRot); + + PoseStack matrices = new PoseStack(); + matrices.translate(pos.x, pos.y, pos.z); + CosmeticAttachmentTransforms.apply(matrices, type, model); + matrices.mulPose(Axis.YP.rotationDegrees(180f - bodyYaw)); + + Map> byTexture = new HashMap<>(); + for (CosmeticBone bone : roots) { + collectBone(matrices, model, bone, byTexture); + } + if (byTexture.isEmpty()) return; + + Minecraft mc = Minecraft.getInstance(); + Vec3 cam = RenderState.cameraPos; + + for (Map.Entry> entry : byTexture.entrySet()) { + CosmeticTextureSlot slot = model.getTextureSlot(entry.getKey()); + if (slot == null) continue; + if (!slot.isLoaded()) { + model.ensureTextureSlotLoaded(slot); + } + if (!slot.isLoaded()) continue; + + AbstractTexture texture = mc.getTextureManager().getTexture(slot.getId()); + if (texture == null || texture.getTextureView() == null || texture.getSampler() == null) continue; + + Renderer3D.BatchBindings bindings = Renderer3D.BatchBindings.none() + .withSampler("u_Texture", texture.getTextureView(), texture.getSampler()); + MeshBuilder mesh = renderer.batch(LoVisualRenderPipelines.WORLD_TEXTURED, Renderer3D.DepthMode.MAIN, bindings); + if (mesh == null) continue; + + for (PendingFace face : entry.getValue()) { + mesh.ensureQuadCapacity(); + + int i1 = mesh.vec3(face.xs[0] - cam.x, face.ys[0] - cam.y, face.zs[0] - cam.z) + .raw2(face.us[0], face.vs[0]).color(255, 255, 255, 255).next(); + int i2 = mesh.vec3(face.xs[1] - cam.x, face.ys[1] - cam.y, face.zs[1] - cam.z) + .raw2(face.us[1], face.vs[1]).color(255, 255, 255, 255).next(); + int i3 = mesh.vec3(face.xs[2] - cam.x, face.ys[2] - cam.y, face.zs[2] - cam.z) + .raw2(face.us[2], face.vs[2]).color(255, 255, 255, 255).next(); + int i4 = mesh.vec3(face.xs[3] - cam.x, face.ys[3] - cam.y, face.zs[3] - cam.z) + .raw2(face.us[3], face.vs[3]).color(255, 255, 255, 255).next(); + + mesh.quad(i1, i2, i3, i4); + } + } + } + + private static void collectBone(PoseStack matrices, CosmeticModel model, CosmeticBone bone, + Map> out) { + if (model.shouldSkipBone(bone)) return; + + matrices.pushPose(); + matrices.last().pose().mul(bone.buildMatrix()); + + for (CosmeticCube cube : bone.getCubes()) { + collectCube(matrices, cube, out); + } + for (CosmeticBone child : bone.getChildren()) { + collectBone(matrices, model, child, out); + } + + matrices.popPose(); + } + + private static void collectCube(PoseStack matrices, CosmeticCube cube, Map> out) { + matrices.pushPose(); + + Vector3f origin = cube.getOrigin(); + Vector3f rotation = cube.getRotation(); + if (rotation.x != 0f || rotation.y != 0f || rotation.z != 0f) { + matrices.translate(origin.x / UNITS_PER_BLOCK, origin.y / UNITS_PER_BLOCK, origin.z / UNITS_PER_BLOCK); + matrices.mulPose(Axis.XP.rotationDegrees(rotation.x)); + matrices.mulPose(Axis.YP.rotationDegrees(rotation.y)); + matrices.mulPose(Axis.ZP.rotationDegrees(rotation.z)); + matrices.translate(-origin.x / UNITS_PER_BLOCK, -origin.y / UNITS_PER_BLOCK, -origin.z / UNITS_PER_BLOCK); + } + + float inflate = cube.getInflate(); + float x1 = (cube.getFrom().x - inflate) / UNITS_PER_BLOCK; + float y1 = (cube.getFrom().y - inflate) / UNITS_PER_BLOCK; + float z1 = (cube.getFrom().z - inflate) / UNITS_PER_BLOCK; + float x2 = (cube.getTo().x + inflate) / UNITS_PER_BLOCK; + float y2 = (cube.getTo().y + inflate) / UNITS_PER_BLOCK; + float z2 = (cube.getTo().z + inflate) / UNITS_PER_BLOCK; + + Matrix4f m = new Matrix4f(matrices.last().pose()); + CosmeticCube.CubeFace[] faces = cube.getFaces(); + int tw = cube.getTextureWidth(); + int th = cube.getTextureHeight(); + + for (int side = 0; side < 6; side++) { + CosmeticCube.CubeFace face = faces[side]; + if (face == null) continue; + addFace(m, face, tw, th, x1, y1, z1, x2, y2, z2, side, out); + } + + matrices.popPose(); + } + + private static void addFace(Matrix4f m, CosmeticCube.CubeFace face, float tw, float th, + float x1, float y1, float z1, float x2, float y2, float z2, + int side, Map> out) { + PendingFace quad = switch (side) { + case 0 -> quad(m, + x2, y2, z1, x1, y2, z1, x1, y1, z1, x2, y1, z1); + case 1 -> quad(m, + x2, y2, z2, x2, y2, z1, x2, y1, z1, x2, y1, z2); + case 2 -> quad(m, + x1, y2, z2, x2, y2, z2, x2, y1, z2, x1, y1, z2); + case 3 -> quad(m, + x1, y2, z1, x1, y2, z2, x1, y1, z2, x1, y1, z1); + case 4 -> quad(m, + x1, y2, z2, x2, y2, z2, x2, y2, z1, x1, y2, z1); + case 5 -> quad(m, + x1, y1, z1, x2, y1, z1, x2, y1, z2, x1, y1, z2); + default -> null; + }; + if (quad == null) return; + + float u1 = face.getU1() / tw; + float v1 = face.getV1() / th; + float u2 = face.getU2() / tw; + float v2 = face.getV2() / th; + quad.us[0] = u1; + quad.vs[0] = v1; + quad.us[1] = u2; + quad.vs[1] = v1; + quad.us[2] = u2; + quad.vs[2] = v2; + quad.us[3] = u1; + quad.vs[3] = v2; + + out.computeIfAbsent(face.getTextureIndex(), k -> new ArrayList<>()).add(quad); + } + + private static PendingFace quad(Matrix4f m, + float x1, float y1, float z1, + float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) { + PendingFace face = new PendingFace(); + Vector3f p1 = m.transformPosition(new Vector3f(x1, y1, z1)); + Vector3f p2 = m.transformPosition(new Vector3f(x2, y2, z2)); + Vector3f p3 = m.transformPosition(new Vector3f(x3, y3, z3)); + Vector3f p4 = m.transformPosition(new Vector3f(x4, y4, z4)); + face.xs = new float[]{p1.x, p2.x, p3.x, p4.x}; + face.ys = new float[]{p1.y, p2.y, p3.y, p4.y}; + face.zs = new float[]{p1.z, p2.z, p3.z, p4.z}; + return face; + } + + private static final class PendingFace { + float[] xs; + float[] ys; + float[] zs; + final float[] us = new float[4]; + final float[] vs = new float[4]; + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticRepository.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticRepository.java new file mode 100644 index 0000000..1ddf345 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticRepository.java @@ -0,0 +1,97 @@ +package dev.loki.lovisual.features.cosmetic; + +import dev.loki.lovisual.config.ConfigPaths; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +/** + * Scans {@code config/lovisual/cosmetics/<type>/<name>} (and the flat root as a + * fallback) for user-provided cosmetic folders (avatar.json + .bbmodel) and keeps the + * loaded catalog in memory. + */ +public final class CosmeticRepository { + private static final CosmeticRepository INSTANCE = new CosmeticRepository(); + + private final Map catalog = new LinkedHashMap<>(); + + public static CosmeticRepository get() { + return INSTANCE; + } + + public Path root() { + return ConfigPaths.root().resolve("cosmetics"); + } + + public void reload() { + catalog.clear(); + discoverUserFolder(root()); + } + + public List all() { + return new ArrayList<>(catalog.values()); + } + + public List byType(CosmeticType type) { + return catalog.values().stream().filter(m -> m.getType() == type).toList(); + } + + public CosmeticModel get(String id) { + return catalog.get(id); + } + + private void register(CosmeticModel model) { + if (model != null && !catalog.containsKey(model.getId())) { + catalog.put(model.getId(), model); + } + } + + private void discoverUserFolder(Path root) { + if (!Files.isDirectory(root)) { + try { + Files.createDirectories(root); + for (CosmeticType type : CosmeticType.values()) { + Files.createDirectories(root.resolve(type.getId())); + } + } catch (Exception ignored) { + } + return; + } + + for (CosmeticType type : CosmeticType.values()) { + Path typeDir = root.resolve(type.getId()); + scanDirectory(typeDir, type); + } + scanDirectory(root, null); + } + + private void scanDirectory(Path dir, CosmeticType forcedType) { + if (!Files.isDirectory(dir)) return; + try (Stream stream = Files.list(dir)) { + stream.filter(Files::isDirectory).forEach(sub -> { + File folder = sub.toFile(); + if (new File(folder, "avatar.json").exists() || hasBbmodel(folder)) { + CosmeticModel model = CosmeticModel.loadExternal(folder, "user"); + if (model != null) { + if (forcedType != null && model.getType() == CosmeticType.MODEL) { + model = model.withType(forcedType); + } + register(model); + } + } + }); + } catch (Exception ignored) { + } + } + + private static boolean hasBbmodel(File folder) { + return new File(folder, "model.bbmodel").exists() + || new File(folder, folder.getName() + ".bbmodel").exists(); + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticTextureSlot.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticTextureSlot.java new file mode 100644 index 0000000..ef6d390 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticTextureSlot.java @@ -0,0 +1,119 @@ +package dev.loki.lovisual.features.cosmetic; + +import com.google.gson.JsonObject; +import com.mojang.blaze3d.platform.NativeImage; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.texture.DynamicTexture; +import net.minecraft.resources.Identifier; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Base64; + +public final class CosmeticTextureSlot { + private final int index; + private final Identifier id; + private int width; + private int height; + private final String fileName; + + private boolean loaded; + private JsonObject textureJson; + + CosmeticTextureSlot(int index, Identifier id, int width, int height, String fileName) { + this.index = index; + this.id = id; + this.width = Math.max(width, 1); + this.height = Math.max(height, 1); + this.fileName = fileName; + } + + public int getIndex() { + return index; + } + + public Identifier getId() { + return id; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public boolean isLoaded() { + return loaded; + } + + void markLoaded() { + loaded = true; + } + + static CosmeticTextureSlot fromJson(int index, String modelKey, JsonObject texture) { + int w = texture.has("width") ? texture.get("width").getAsInt() : 64; + int h = texture.has("height") ? texture.get("height").getAsInt() : 64; + String name = texture.has("name") ? texture.get("name").getAsString() : "tex" + index; + Identifier id = Identifier.fromNamespaceAndPath( + "lovisual", + "dynamic/cosmetic/" + modelKey + "/tex/" + index + ); + CosmeticTextureSlot slot = new CosmeticTextureSlot(index, id, w, h, name); + slot.textureJson = texture; + return slot; + } + + void reset() { + loaded = false; + } + + /** + * Attempts to load this texture slot from, in order: the cosmetic's external folder + * (file name, then outfits/<file>, then a generic texture.png fallback), or an + * inline base64 "source" embedded in the .bbmodel JSON. + */ + void load(Minecraft mc, File externalDir, String resourcePath) { + if (loaded || mc == null || mc.getTextureManager() == null) return; + + try { + if (externalDir != null) { + if (tryFile(mc, new File(externalDir, fileName))) return; + if (tryFile(mc, new File(externalDir, "outfits/" + fileName))) return; + if (tryFile(mc, new File(externalDir, "texture.png"))) return; + } + + if (textureJson != null && textureJson.has("source")) { + String source = textureJson.get("source").getAsString(); + if (source.startsWith("data:image") && source.contains(",")) { + byte[] bytes = Base64.getDecoder().decode(source.substring(source.indexOf(',') + 1)); + try (InputStream is = new ByteArrayInputStream(bytes)) { + register(mc, is); + } + } + } + } catch (Exception ignored) { + } + } + + private boolean tryFile(Minecraft mc, File file) throws IOException { + if (!file.exists()) return false; + try (InputStream is = new FileInputStream(file)) { + register(mc, is); + return true; + } + } + + private void register(Minecraft mc, InputStream is) throws IOException { + NativeImage image = NativeImage.read(is); + width = Math.max(image.getWidth(), 1); + height = Math.max(image.getHeight(), 1); + DynamicTexture texture = new DynamicTexture(() -> "lovisual_cosmetic_" + index, image); + mc.getTextureManager().register(id, texture); + loaded = true; + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticType.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticType.java new file mode 100644 index 0000000..8c632de --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/cosmetic/CosmeticType.java @@ -0,0 +1,32 @@ +package dev.loki.lovisual.features.cosmetic; + +public enum CosmeticType { + MODEL("model", "Модель"), + PET("pet", "Питомец"), + ACCESSORY("accessory", "Аксессуар"); + + private final String id; + private final String displayName; + + CosmeticType(String id, String displayName) { + this.id = id; + this.displayName = displayName; + } + + public String getId() { + return id; + } + + public String getDisplayName() { + return displayName; + } + + public static CosmeticType fromJson(String raw) { + if (raw == null || raw.isBlank()) return MODEL; + return switch (raw.toLowerCase()) { + case "pet", "pets", "питомец", "питомцы" -> PET; + case "accessory", "accessories", "item", "items", "аксессуар", "аксессуары" -> ACCESSORY; + default -> MODEL; + }; + } +} 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 d269d1b..e59310f 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 @@ -63,7 +63,11 @@ final class ClickGuiInputHandler { static void onEditorMouseButtonScaled(double x, double y, int button, boolean pressed) { updateMouse(x, y, true); if (ClickGuiRenderer.waitingForKey) { - if (pressed) ClickGuiKeyBindManager.addPendingMouseButton(button); + if (pressed) { + ClickGuiKeyBindManager.addPendingMouseButton(button); + } else { + ClickGuiKeyBindManager.onMouseReleased(button); + } return; } if (ClickGuiRenderer.textEditor != null) { @@ -96,7 +100,11 @@ final class ClickGuiInputHandler { private static void handleMouseButton(int button, boolean pressed) { if (!ClickGuiLifecycle.isInputReady()) return; if (ClickGuiRenderer.waitingForKey) { - if (pressed) ClickGuiKeyBindManager.addPendingMouseButton(button); + if (pressed) { + ClickGuiKeyBindManager.addPendingMouseButton(button); + } else { + ClickGuiKeyBindManager.onMouseReleased(button); + } return; } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiKeyBindManager.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiKeyBindManager.java index 70f7493..fbead76 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiKeyBindManager.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiKeyBindManager.java @@ -6,6 +6,7 @@ import dev.loki.lovisual.util.input.KeyUtil; import dev.loki.lovisual.util.wav.ClickGuiSounds; import org.lwjgl.glfw.GLFW; +import java.util.LinkedHashSet; import java.util.Set; final class ClickGuiKeyBindManager { @@ -13,11 +14,18 @@ final class ClickGuiKeyBindManager { private ClickGuiKeyBindManager() { } + // Keys/buttons currently physically held down while capturing a bind. + // A single tap (press+release with nothing else pressed meanwhile) commits + // instantly on release; holding it while pressing another key builds a combo, + // which commits once every held key in it has been released. + private static final Set heldKeys = new LinkedHashSet<>(); + static void beginKeyBind(Object owner) { ClickGuiEditorManager.clearInlineText(); ClickGuiRenderer.waitingForKey = true; ClickGuiRenderer.keyBindOwner = owner; ClickGuiRenderer.pendingBindKeys.clear(); + heldKeys.clear(); ClickGuiSearch.deactivate(); ClickGuiSounds.bindingStart(); } @@ -26,17 +34,51 @@ final class ClickGuiKeyBindManager { ClickGuiRenderer.waitingForKey = false; ClickGuiRenderer.keyBindOwner = null; ClickGuiRenderer.pendingBindKeys.clear(); + heldKeys.clear(); + } + + /** Cancels bind capture without touching the previously saved value. */ + static void cancelKeyBind() { + if (!ClickGuiRenderer.waitingForKey) return; + ClickGuiRenderer.waitingForKey = false; + ClickGuiRenderer.keyBindOwner = null; + ClickGuiRenderer.pendingBindKeys.clear(); + heldKeys.clear(); + ClickGuiSounds.bindingStart(); } static void addPendingKey(int keyCode, int scancode) { int code = KeyUtil.fromKeyInput(keyCode, scancode); if (code == KeyUtil.NONE) return; ClickGuiRenderer.pendingBindKeys.add(code); + heldKeys.add(code); } static void addPendingMouseButton(int button) { if (button < GLFW.GLFW_MOUSE_BUTTON_1 || button > GLFW.GLFW_MOUSE_BUTTON_LAST) return; - ClickGuiRenderer.pendingBindKeys.add(KeyUtil.mouseButtonToCode(button)); + int code = KeyUtil.mouseButtonToCode(button); + ClickGuiRenderer.pendingBindKeys.add(code); + heldKeys.add(code); + } + + static void onKeyReleased(int keyCode, int scancode) { + handleRelease(KeyUtil.fromKeyInput(keyCode, scancode)); + } + + static void onMouseReleased(int button) { + if (button < GLFW.GLFW_MOUSE_BUTTON_1 || button > GLFW.GLFW_MOUSE_BUTTON_LAST) return; + handleRelease(KeyUtil.mouseButtonToCode(button)); + } + + private static void handleRelease(int code) { + if (!ClickGuiRenderer.waitingForKey) return; + if (code == KeyUtil.NONE) return; + if (!heldKeys.remove(code)) return; + if (!heldKeys.isEmpty()) return; + if (ClickGuiRenderer.pendingBindKeys.isEmpty()) return; + + submitKeySelection(ClickGuiRenderer.pendingBindKeys); + finishKeyBind(); } static void submitKeySelection(Set keys) { diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiKeyboardHandler.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiKeyboardHandler.java index 3a8e6e9..07b7743 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiKeyboardHandler.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/clickgui/ClickGuiKeyboardHandler.java @@ -122,8 +122,9 @@ final class ClickGuiKeyboardHandler { if (ClickGuiRenderer.waitingForKey) { if (key == GLFW.GLFW_KEY_ESCAPE) { - ClickGuiKeyBindManager.submitKeySelection((String) null); - ClickGuiKeyBindManager.finishKeyBind(); + // Escape always exits the bind capture immediately without + // touching the previously saved bind. + ClickGuiKeyBindManager.cancelKeyBind(); return true; } @@ -135,6 +136,11 @@ final class ClickGuiKeyboardHandler { } ClickGuiKeyBindManager.addPendingKey(key, scancode); + } else if (action == GLFW.GLFW_RELEASE) { + // A tap (press+release with no other key held) commits instantly; + // holding this key while pressing another builds a combo, which + // commits once every held key in it has been released. + ClickGuiKeyBindManager.onKeyReleased(key, scancode); } return true; diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/BetterTooltips.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/BetterTooltips.java index e1f78cf..2bc3387 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/BetterTooltips.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/BetterTooltips.java @@ -53,6 +53,9 @@ import java.util.Map; @HudElementRegister(order = 20) public final class BetterTooltips extends AbstractHudElement { + private static final String[] TOOLTIP_FONT_OPTIONS = { + "Onest", "OnestMedium", "OnestLight", "Inter", "InterMedium", "Comfortaa", "Iosevka", "ProFont", "Monsterrat" + }; public static final BetterTooltips INSTANCE = new BetterTooltips(); public static final String GROUP_TOOLTIP_GUI = "vanilla_tooltip_gui"; public static final String GROUP_TOOLTIP_ITEMS = "vanilla_tooltip_items"; @@ -95,6 +98,9 @@ public final class BetterTooltips extends AbstractHudElement { private final BooleanValue itemTooltipEnabled = bool("item_tooltip", true); private final BooleanValue shulkerPreviewEnabled = bool("shulker_preview", true); private final BooleanValue itemInfoColorize = bool("item_info_colorize", true); + private final ModeValue tooltipFont = mode("tooltip_font", "Iosevka", TOOLTIP_FONT_OPTIONS); + private final NumberValue tooltipTextSize = + num("tooltip_text_size", 1.0f, 0.5f, 2.0f); private final ItemIdSetValue topIgnore = registerTopIgnore(); private final NumberValue shulkerPreviewSlotSize = visibleWhen(num("shulker_slot_size", 18.0f, 12.0f, 24.0f), this::isShulkerPreviewEnabled); @@ -197,14 +203,14 @@ public final class BetterTooltips extends AbstractHudElement { Minecraft mc = Minecraft.getInstance(); if (mc == null) return; - TextRenderer tr = Fonts.renderer("Iosevka", FontInfo.Type.Regular, TextRenderer.get()); + TextRenderer tr = Fonts.renderer(INSTANCE.tooltipFont.get(), FontInfo.Type.Regular, TextRenderer.get()); if (tr == null) return; PreviewLayout preview = buildShulkerPreviewLayout(); boolean previewOpen = preview != null; List finalLines = prepareTooltipLines(lines, previewOpen); - float textScale = TOOLTIP_TEXT_SCALE * TOOLTIP_SCALE_OVERRIDE; + float textScale = TOOLTIP_TEXT_SCALE * TOOLTIP_SCALE_OVERRIDE * INSTANCE.tooltipTextSize.get(); tr.begin(textScale, false, false); int maxWidth = 0; for (TooltipLine line : finalLines) { 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 230b6fb..695d589 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 @@ -28,6 +28,8 @@ import static dev.loki.lovisual.features.theme.Theme.theme; @HudElementRegister(order = 40) public final class CustomBar extends AbstractHudElement { + static final String COLOR_THEME = "Theme"; + static final String COLOR_CUSTOM = "Custom"; public static final CustomBar INSTANCE = new CustomBar(); static final float XP_BAR_WIDTH = 182f; static final double XP_TEXT_SCALE = 0.59; @@ -77,8 +79,6 @@ public final class CustomBar extends AbstractHudElement { 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); diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomHealthBar.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomHealthBar.java index 13117b4..8cb4260 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomHealthBar.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomHealthBar.java @@ -39,8 +39,10 @@ import static dev.loki.lovisual.features.theme.Theme.theme; @HudElementRegister(order = 50) public final class CustomHealthBar extends AbstractHudElement { - public static final CustomHealthBar INSTANCE = new CustomHealthBar(); private static final String LAYOUT_ID = "lovisual:api/hud/static/custom_health_bar"; + private static final String STYLE_TARGET_HUD = "TargetHud"; + private static final String STYLE_DYNAMIC = "Dynamic"; + public static final CustomHealthBar INSTANCE = new CustomHealthBar(); private static final float HEALTH_BAR_WIDTH = 81f; private static final float HEALTH_BAR_HEIGHT = 9f; private static final float HEALTH_BAR_RADIUS = 5.0f; @@ -50,8 +52,6 @@ public final class CustomHealthBar extends AbstractHudElement { private static final int HEALTH_BAR_X_OFFSET = 0; private static final int HEALTH_BAR_Y_OFFSET = 0; private static final float HEALTH_ANIM_SPEED = 10.0f; - private static final String STYLE_TARGET_HUD = "TargetHud"; - private static final String STYLE_DYNAMIC = "Dynamic"; private static float animatedHealth = -1.0f; private static float animatedAbsorb = -1.0f; private static long lastHealthAnimMs = -1L; diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomHotbar.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomHotbar.java index a6ec7ef..b9cb723 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomHotbar.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/CustomHotbar.java @@ -33,6 +33,11 @@ import static dev.loki.lovisual.features.theme.Theme.theme; @HudElementRegister(order = 30) public final class CustomHotbar extends AbstractHudElement { + private static final String EFFECT_NONE = "None"; + private static final String EFFECT_BLUR = "Blur"; + private static final String EFFECT_GLASS = "Glass"; + private static final String COLOR_THEME = "Theme"; + private static final String COLOR_CUSTOM = "Custom"; public static final CustomHotbar INSTANCE = new CustomHotbar(); private static final int BG_ALPHA = 0xAA; private static final float RADIUS = 6.0f; @@ -49,11 +54,6 @@ public final class CustomHotbar extends AbstractHudElement { private static final float SELECT_SLOT_STEP = SLOT_STEP; private static final float SELECT_ANIMATION_MS = 145.0f; private static final float SELECT_ANIMATION_MIN_PROGRESS = 0.015f; - private static final String EFFECT_NONE = "None"; - private static final String EFFECT_BLUR = "Blur"; - private static final String EFFECT_GLASS = "Glass"; - private static final String COLOR_THEME = "Theme"; - private static final String COLOR_CUSTOM = "Custom"; private static int animatedSelectedSlot = -1; private static float selectionStartSlot = 0.0f; private static float selectionTargetSlot = 0.0f; diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/DynamicIsland.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/DynamicIsland.java index aef9505..566ec56 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/DynamicIsland.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/nondraggable/impl/DynamicIsland.java @@ -69,9 +69,8 @@ import static dev.loki.lovisual.features.theme.Theme.theme; @HudElementRegister(order = 60) public final class DynamicIsland extends AbstractHudElement { - public static final DynamicIsland INSTANCE = new DynamicIsland(); - private static final String LAYOUT_ID = "lovisual:api/hud/static/dynamic_island"; + public static final DynamicIsland INSTANCE = new DynamicIsland(); private static final float TOP_Y = 18f; private static final float CLOSED_HEIGHT = 35f; diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/combat/Reach.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/combat/Reach.java deleted file mode 100644 index ab4afd5..0000000 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/combat/Reach.java +++ /dev/null @@ -1,258 +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.module.modules.combat; - -import net.minecraft.client.Minecraft; -import net.minecraft.client.player.LocalPlayer; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.level.ClipContext; -import net.minecraft.world.phys.*; -import dev.loki.lovisual.config.common.CommonSettingSchemas; -import dev.loki.lovisual.config.values.EnumValue; -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; -import dev.loki.lovisual.util.combat.VulcanReachController; - -import java.util.List; -import java.util.Optional; - -//todo Description -@ModuleInfo( - id = "reach", - displayName = "Reach", - category = ModuleCategory.COMBAT -) -public class Reach extends Module { - - public enum RaycastMode { - STRICT, - THROUGH_WALLS, - NONE - } - - public final EnumValue raycast = - enumCommon( - "reachContext", - "raycast", - CommonSettingSchemas.COMBAT_RAYCAST, - RaycastMode.STRICT, - RaycastMode.values() - ); - private final Minecraft mc = Minecraft.getInstance(); - - private final EnumValue mode = - enumCommon( - "reachMode", - "mode", - CommonSettingSchemas.ANTICHEAT_MODE, - Mode.NORMAL, - Mode.values() - ); - - private final NumberValue distance = - numCommon( - "reachDistance", - "range", - CommonSettingSchemas.COMBAT_RANGE, - 4.5, - 3.0, - 6.0 - ); - private final NumberValue wallDistance = - visibleWhen(numCommon( - "reachWallDistance", - "wall_range", - CommonSettingSchemas.COMBAT_WALL_RANGE, - 3.0, - 0.0, - 6.0 - ), () -> raycast.get() == RaycastMode.THROUGH_WALLS); - - private static double boxDistanceSq(AABB box, Vec3 p) { - double dx = 0.0; - if (p.x < box.minX) dx = box.minX - p.x; - else if (p.x > box.maxX) dx = p.x - box.maxX; - - double dy = 0.0; - if (p.y < box.minY) dy = box.minY - p.y; - else if (p.y > box.maxY) dy = p.y - box.maxY; - - double dz = 0.0; - if (p.z < box.minZ) dz = box.minZ - p.z; - else if (p.z > box.maxZ) dz = p.z - box.maxZ; - - return dx * dx + dy * dy + dz * dz; - } - - public EntityHitResult raycastEntities(LocalPlayer player) { - if (!isEnabled()) return null; - if (player == null || mc.level == null) return null; - - double visibleRange = distance.get(); - double throughWallsRange = raycast.get() == RaycastMode.THROUGH_WALLS - ? wallDistance.get() - : 0.0; - double searchRange = Math.max(visibleRange, throughWallsRange); - - Vec3 start = player.getEyePosition(1.0F); - Vec3 direction = player.getViewVector(1.0F).normalize(); - Vec3 end = start.add(direction.scale(searchRange)); - - double blockLimitSq = resolveBlockLimitSq(player, start, end, visibleRange, throughWallsRange); - - AABB search = new AABB(start, end).inflate(1.0); - - List candidates = mc.level.getEntities( - player, - search, - e -> e instanceof LivingEntity && e.isAlive() - ); - - return findBestHit(player, start, end, candidates, blockLimitSq, searchRange); - } - - public EntityHitResult raycastEntities(LocalPlayer player, Vec3 start, Vec3 direction) { - if (!isEnabled()) return null; - if (player == null || mc.level == null) return null; - - double visibleRange = distance.get(); - double throughWallsRange = raycast.get() == RaycastMode.THROUGH_WALLS - ? wallDistance.get() - : 0.0; - double searchRange = Math.max(visibleRange, throughWallsRange); - - Vec3 dir = direction.normalize(); - Vec3 end = start.add(dir.scale(searchRange)); - - double blockLimitSq = resolveBlockLimitSq(player, start, end, visibleRange, throughWallsRange); - - AABB search = new AABB(start, end).inflate(1.0); - - List candidates = mc.level.getEntities( - player, - search, - e -> e instanceof LivingEntity && e.isAlive() - ); - - return findBestHit(player, start, end, candidates, blockLimitSq, searchRange); - } - - private EntityHitResult findBestHit(LocalPlayer player, - Vec3 start, - Vec3 end, - List candidates, - double blockLimitSq, - double requestedReach) { - EntityHitResult bestHit = null; - double bestBoxDistSq = Double.MAX_VALUE; - double bestHitDistSq = Double.MAX_VALUE; - - for (Entity e : candidates) { - AABB baseBox = e.getBoundingBox(); - AABB bb = baseBox; - - Optional hit = bb.contains(start) - ? Optional.of(start) - : bb.clip(start, end); - - if (hit.isEmpty()) continue; - - double boxDistSq = boxDistanceSq(baseBox, start); - double entityReach = resolveEntityReach(e, requestedReach); - double entityReachSq = entityReach * entityReach; - if (boxDistSq > entityReachSq) continue; - if (boxDistSq > blockLimitSq) continue; - - double hitDistSq = start.distanceToSqr(hit.get()); - - if (boxDistSq < bestBoxDistSq - || (boxDistSq == bestBoxDistSq && hitDistSq < bestHitDistSq)) { - bestBoxDistSq = boxDistSq; - bestHitDistSq = hitDistSq; - bestHit = new EntityHitResult(e, hit.get()); - } - } - - return bestHit; - } - - private double resolveEntityReach(Entity entity, double requestedReach) { - if (mode.get() == Mode.VULCAN_297 && entity instanceof Player) { - return VulcanReachController.INSTANCE.clampReach(entity, requestedReach); - } - return requestedReach; - } - - public boolean isVulcan297Mode() { - return mode.get() == Mode.VULCAN_297; - } - - public double getConfiguredDistance() { - return distance.get(); - } - - private double resolveBlockLimitSq(LocalPlayer player, - Vec3 start, - Vec3 end, - double visibleRange, - double throughWallsRange) { - RaycastMode mode = raycast.get(); - - if (mode == RaycastMode.NONE) { - double maxRange = Math.max(visibleRange, throughWallsRange); - return maxRange * maxRange; - } - - if (mode == RaycastMode.THROUGH_WALLS) { - double maxAllowed = Math.max(visibleRange, throughWallsRange); - - HitResult blockHit = mc.level.clip(new ClipContext( - start, - end, - ClipContext.Block.OUTLINE, - ClipContext.Fluid.NONE, - player - )); - - if (!(blockHit instanceof BlockHitResult bhr)) { - return maxAllowed * maxAllowed; - } - - double blockDistSq = bhr.getLocation().distanceToSqr(start); - - if (throughWallsRange <= 0.0) { - return blockDistSq; - } - - return Math.max(blockDistSq, throughWallsRange * throughWallsRange); - } - - HitResult blockHit = mc.level.clip(new ClipContext( - start, - end, - ClipContext.Block.OUTLINE, - ClipContext.Fluid.NONE, - player - )); - - if (blockHit instanceof BlockHitResult bhr) { - return bhr.getLocation().distanceToSqr(start); - } - - return visibleRange * visibleRange; - } - - public enum Mode { - NORMAL, - VULCAN_297 - } -} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/combat/TargetESP.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/combat/TargetESP.java index e0fa413..bb486e9 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/combat/TargetESP.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/combat/TargetESP.java @@ -68,6 +68,7 @@ public class TargetESP extends Module { 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 String SETTING_VORTEX_SPEED = "vortex_speed"; 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; @@ -87,7 +88,7 @@ public class TargetESP extends Module { "targetEspMode", SETTING_MODE, "ghosts", - "ghosts", "ring", "capture_mark", "crystals", "blackhole" + "ghosts", "ring", "capture_mark", "crystals", "blackhole", "vortex" ); private final RGBAColorValue colorValue = color("targetEspColor", SETTING_COLOR, "#FFFF5555"); private final ModeValue colorMode = visibleWhen(modeSetting( @@ -130,6 +131,8 @@ public class TargetESP extends Module { 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 NumberValue vortexSpeed = + visibleWhen(num("targetEspVortexSpeed", SETTING_VORTEX_SPEED, 1.2f, 0.2f, 4.0f), this::isVortexMode); private final List crystalList = new ArrayList<>(); private LivingEntity target; private LivingEntity renderTarget; @@ -237,6 +240,75 @@ public class TargetESP extends Module { } } + private static final int VORTEX_HELIX_STEPS = 28; + private static final int VORTEX_RING_SEGMENTS = 24; + + private static void renderVortex(Renderer3D renderer, + LivingEntity target, + float tickDelta, + IntFunction palette, + float spinSpeed) { + if (renderer == null || target == null) return; + + Vec3 pos = RenderMath.getLerpedPos(target, tickDelta); + float radius = target.getBbWidth() * 0.7f; + float height = target.getBbHeight(); + double time = (GameClock.millis() / 1000.0) * spinSpeed; + + float prevWidth = RenderState.lineWidth; + RenderState.lineWidth = 1.4f; + MeshBuilder mesh; + try { + mesh = renderer.batch(LoVisualRenderPipelines.WORLD_COLORED_LINES, Renderer3D.DepthMode.NONE); + } finally { + RenderState.lineWidth = prevWidth; + } + if (mesh == null) return; + + for (int strand = 0; strand < 2; strand++) { + double strandOffset = strand * Math.PI; + int prevIndex = -1; + for (int i = 0; i <= VORTEX_HELIX_STEPS; i++) { + double t = (double) i / VORTEX_HELIX_STEPS; + double angle = time * 2.4 + t * Math.PI * 3.0 + strandOffset; + double r = radius * (0.85 + 0.15 * Math.sin(t * Math.PI)); + double x = pos.x + Math.cos(angle) * r; + double y = pos.y + t * height; + double z = pos.z + Math.sin(angle) * r; + int color = applyOpacity(palette.apply((int) (t * 180)), (float) (1.0 - t * 0.35)); + + int index = addVertex(mesh, x, y, z, color); + if (prevIndex >= 0) { + mesh.line(prevIndex, index); + } + prevIndex = index; + } + } + + float[] ringHeights = {0.05f, height * 0.5f, height * 0.95f}; + float[] ringSpeeds = {0.4f, -0.55f, 0.35f}; + for (int ring = 0; ring < ringHeights.length; ring++) { + double ringY = pos.y + ringHeights[ring]; + double ringRotation = time * ringSpeeds[ring]; + int ringColor = applyOpacity(palette.apply(60), 0.4f); + int prevIndex = -1; + int firstIndex = -1; + for (int i = 0; i <= VORTEX_RING_SEGMENTS; i++) { + double angle = ringRotation + (i % VORTEX_RING_SEGMENTS) * 2.0 * Math.PI / VORTEX_RING_SEGMENTS; + double x = pos.x + Math.cos(angle) * radius; + double z = pos.z + Math.sin(angle) * radius; + int index = addVertex(mesh, x, ringY, z, ringColor); + if (firstIndex < 0) { + firstIndex = index; + } + if (prevIndex >= 0) { + mesh.line(prevIndex, index); + } + prevIndex = index; + } + } + } + private static void renderCaptureMark(Renderer3D renderer, Entity target, float tickDelta, @@ -730,6 +802,8 @@ public class TargetESP extends Module { } else if ("blackhole".equals(mode)) { renderBlackhole(renderer, renderTarget, tickDelta, palette, blackholeSize.get(), blackholeSpinSpeed.get(), targetAlpha, blackholeStyle.get()); + } else if ("vortex".equals(mode)) { + renderVortex(renderer, renderTarget, tickDelta, palette, vortexSpeed.get()); } } @@ -900,6 +974,10 @@ public class TargetESP extends Module { return "blackhole".equals(espMode.get()); } + private boolean isVortexMode() { + return "vortex".equals(espMode.get()); + } + private boolean usesSecondaryColor() { return AnimatedRenderColors.usesSecondary(animatedColorMode()); } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/BlockESP.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/BlockESP.java new file mode 100644 index 0000000..ac3a08e --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/BlockESP.java @@ -0,0 +1,276 @@ +package dev.loki.lovisual.features.module.modules.visuals; + +import net.minecraft.client.Minecraft; +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.MapColor; +import net.minecraft.world.phys.AABB; +import dev.loki.lovisual.config.values.EnumValue; +import dev.loki.lovisual.config.values.ItemIdSetValue; +import dev.loki.lovisual.config.values.NumberValue; +import dev.loki.lovisual.config.values.RGBAColorValue; +import dev.loki.lovisual.features.gui.clickgui.settings.TextListSetting; +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.render.engine.animation.AnimatedRenderColors; +import dev.loki.lovisual.render.engine.renderer.Renderer3D; +import dev.loki.lovisual.util.time.Timer; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; + +//todo Description +@ModuleInfo( + id = "blockesp", + displayName = "BlockESP", + category = ModuleCategory.VISUALS, + description = "Подсвечивает выбранные блоки сквозь стены" +) +public final class BlockESP extends Module { + + private final Minecraft mc = Minecraft.getInstance(); + + private final ItemIdSetValue blockList = + itemList("block_list", TextListSetting.PickerMode.BLOCKS); + + private final NumberValue range = + num("range", 64, 8, 160); + + private final NumberValue scanInterval = + num("scan_interval", 2.0f, 0.5f, 10.0f); + + private final RGBAColorValue fillColor = + color("fill_color", "#4000FFC8"); + + private final RGBAColorValue fillColor2 = + visibleWhen(color("fill_color2", "#40FFAA00"), this::usesSecondaryColor); + + private final EnumValue colorMode = + enumMode("color_mode", ColorMode.PER_BLOCK, ColorMode.values()); + + private final NumberValue colorSpeed = + visibleWhen(num("color_speed", 18, 2, 54), () -> colorMode.get() != ColorMode.PER_BLOCK && colorMode.get() != ColorMode.STATIC); + + private final Timer scanTimer = new Timer(); + private final ExecutorService executor = Executors.newSingleThreadExecutor(r -> { + Thread thread = new Thread(r, "BlockESP-Scanner"); + thread.setDaemon(true); + return thread; + }); + private final AtomicBoolean scanning = new AtomicBoolean(false); + private final AtomicBoolean scanInProgress = new AtomicBoolean(false); + private volatile List foundBlocks = new ArrayList<>(); + + public BlockESP() { + } + + @Override + public void onEnable() { + scanTimer.setMs(0); + scanning.set(true); + } + + @Override + public void onDisable() { + scanning.set(false); + foundBlocks = new ArrayList<>(); + } + + @Override + public void onTick() { + if (mc.level == null || mc.player == null) return; + + if (scanTimer.passedS(scanInterval.get())) { + scanTimer.reset(); + requestScan(); + } + } + + @Override + public WorldPhase getWorldPhase() { + return WorldPhase.BEFORE_TRANSLUCENT; + } + + @Override + public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) { + if (!isEnabled() || mc.level == null || mc.player == null) return; + if (blockList.get().isEmpty()) return; + + List snapshot = foundBlocks; + for (BlockPos pos : snapshot) { + BlockState state = mc.level.getBlockState(pos); + if (state.isAir()) continue; + if (!blockList.get().contains(blockId(state.getBlock()))) continue; + + int argb = resolveColor(state.getBlock(), pos); + addFilledBox(renderer, new AABB(pos), argb); + } + } + + private void requestScan() { + if (mc.level == null || mc.player == null) return; + if (!scanning.get()) return; + if (executor.isShutdown()) return; + if (!scanInProgress.compareAndSet(false, true)) return; + + executor.submit(() -> { + try { + scan(); + } finally { + scanInProgress.set(false); + } + }); + } + + private void scan() { + if (mc.level == null || mc.player == null) return; + + List result = new ArrayList<>(); + BlockPos center = mc.player.blockPosition(); + int r = range.get(); + + int minX = center.getX() - r; + int maxX = center.getX() + r; + int minY = Math.max(mc.level.getMinY(), center.getY() - r); + int maxY = Math.min(mc.level.getMaxY(), center.getY() + r); + int minZ = center.getZ() - r; + int maxZ = center.getZ() + r; + + int minChunkX = minX >> 4; + int maxChunkX = maxX >> 4; + int minChunkZ = minZ >> 4; + int maxChunkZ = maxZ >> 4; + + for (int cx = minChunkX; cx <= maxChunkX; cx++) { + for (int cz = minChunkZ; cz <= maxChunkZ; cz++) { + if (!scanning.get()) return; + if (!mc.level.hasChunk(cx, cz)) continue; + + int chunkMinX = Math.max(minX, cx << 4); + int chunkMaxX = Math.min(maxX, (cx << 4) + 15); + int chunkMinZ = Math.max(minZ, cz << 4); + int chunkMaxZ = Math.min(maxZ, (cz << 4) + 15); + + for (int x = chunkMinX; x <= chunkMaxX; x++) { + for (int y = minY; y <= maxY; y++) { + for (int z = chunkMinZ; z <= chunkMaxZ; z++) { + if (!scanning.get()) return; + + BlockPos pos = new BlockPos(x, y, z); + BlockState state = mc.level.getBlockState(pos); + if (state.isAir()) continue; + + if (blockList.get().contains(blockId(state.getBlock()))) { + result.add(pos.immutable()); + } + } + } + } + } + } + + foundBlocks = result; + } + + private String blockId(Block block) { + return BuiltInRegistries.BLOCK.getKey(block).toString(); + } + + private int resolveColor(Block block, BlockPos pos) { + if (colorMode.get() == ColorMode.PER_BLOCK) { + MapColor mapColor = block.defaultMapColor(); + return withAlpha(mapColor.col, alpha(fillColor.getArgb())); + } + + return AnimatedRenderColors.resolve( + animatedColorMode(), + colorSpeed.get(), + spatialIndex(pos), + fillColor.getArgb(), + fillColor2.getArgb(), + true + ); + } + + private AnimatedRenderColors.Mode animatedColorMode() { + return switch (colorMode.get()) { + case PER_BLOCK, STATIC -> AnimatedRenderColors.Mode.STATIC; + case RAINBOW -> AnimatedRenderColors.Mode.RAINBOW; + case FADE -> AnimatedRenderColors.Mode.FADE; + }; + } + + private boolean usesSecondaryColor() { + return AnimatedRenderColors.usesSecondary(animatedColorMode()); + } + + private int spatialIndex(BlockPos pos) { + return Math.floorMod(pos.getX() * 13 + pos.getY() * 7 + pos.getZ() * 17, 3600); + } + + private static void addFilledBox(Renderer3D renderer, AABB box, int argb) { + int a = alpha(argb); + int r = red(argb); + int g = green(argb); + int b = blue(argb); + + double minX = box.minX; + double minY = box.minY; + double minZ = box.minZ; + double maxX = box.maxX; + double maxY = box.maxY; + double maxZ = box.maxZ; + + renderer.quad(minX, minY, minZ, maxX, minY, minZ, maxX, minY, maxZ, minX, minY, maxZ, r, g, b, a); + renderer.quad(minX, maxY, minZ, minX, maxY, maxZ, maxX, maxY, maxZ, maxX, maxY, minZ, r, g, b, a); + renderer.quad(minX, minY, maxZ, maxX, minY, maxZ, maxX, maxY, maxZ, minX, maxY, maxZ, r, g, b, a); + renderer.quad(minX, minY, minZ, minX, maxY, minZ, maxX, maxY, minZ, maxX, minY, minZ, r, g, b, a); + renderer.quad(maxX, minY, minZ, maxX, maxY, minZ, maxX, maxY, maxZ, maxX, minY, maxZ, r, g, b, a); + renderer.quad(minX, minY, minZ, minX, minY, maxZ, minX, maxY, maxZ, minX, maxY, minZ, r, g, b, a); + } + + private static int withAlpha(int argb, int alpha) { + return (alpha << 24) | (argb & 0x00FFFFFF); + } + + private static int red(int argb) { + return (argb >>> 16) & 255; + } + + private static int green(int argb) { + return (argb >>> 8) & 255; + } + + private static int blue(int argb) { + return argb & 255; + } + + private static int alpha(int argb) { + return (argb >>> 24) & 255; + } + + public enum ColorMode implements EnumValue.IdProvider { + PER_BLOCK("per_block"), + STATIC("static"), + RAINBOW("rainbow"), + FADE("fade"); + + private final String id; + + ColorMode(String id) { + this.id = id; + } + + @Override + public String id() { + return id; + } + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/CosmeticDisplay.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/CosmeticDisplay.java new file mode 100644 index 0000000..4490b5f --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/CosmeticDisplay.java @@ -0,0 +1,77 @@ +package dev.loki.lovisual.features.module.modules.visuals; + +import dev.loki.lovisual.features.cosmetic.CosmeticManager; +import dev.loki.lovisual.features.cosmetic.CosmeticModel; +import dev.loki.lovisual.features.cosmetic.CosmeticRenderer; +import dev.loki.lovisual.features.cosmetic.CosmeticRepository; +import dev.loki.lovisual.features.cosmetic.CosmeticType; +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.render.engine.renderer.Renderer3D; +import net.minecraft.client.Minecraft; + +import java.util.List; + +/** + * Renders the locally equipped cosmetic (model/pet/accessory) attached to the local player. + * Equip picking is a minimal next/prev keybind for now; a proper catalog screen is a follow-up. + */ +@ModuleInfo( + id = "cosmeticdisplay", + displayName = "Cosmetics", + aliases = {"cosmetics"}, + category = ModuleCategory.VISUALS, + description = "Отображает надетую косметику (модель/питомец/аксессуар)" +) +public final class CosmeticDisplay extends Module { + + private final Minecraft mc = Minecraft.getInstance(); + + { + action("Next Cosmetic", "NONE"); + action("Prev Cosmetic", "NONE"); + } + + @Override + public void onEnable() { + CosmeticRepository.get().reload(); + } + + @Override + public void onTick() { + if (!isEnabled()) return; + + if (isActionPressedOnce("Next Cosmetic")) { + cycle(1); + } else if (isActionPressedOnce("Prev Cosmetic")) { + cycle(-1); + } + } + + private void cycle(int direction) { + List models = CosmeticRepository.get().byType(CosmeticType.MODEL); + if (models.isEmpty()) return; + + CosmeticModel current = CosmeticManager.get().getActive(CosmeticType.MODEL); + int index = current == null ? -1 : models.indexOf(current); + int next = Math.floorMod(index + direction, models.size()); + + CosmeticManager.get().setActive(CosmeticType.MODEL, models.get(next)); + } + + @Override + public WorldPhase getWorldPhase() { + return WorldPhase.AFTER_POST_PROCESS; + } + + @Override + public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) { + if (!isEnabled() || mc.level == null || mc.player == null) return; + + CosmeticRenderer.renderOnPlayer(renderer, mc.player, CosmeticType.MODEL, tickDelta); + CosmeticRenderer.renderOnPlayer(renderer, mc.player, CosmeticType.PET, tickDelta); + CosmeticRenderer.renderOnPlayer(renderer, mc.player, CosmeticType.ACCESSORY, tickDelta); + } +} 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 42edb5c..6609295 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,7 +7,6 @@ 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; @@ -18,7 +17,6 @@ import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.core.BlockPos; import net.minecraft.core.component.DataComponents; import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.network.protocol.game.ServerboundInteractPacket; import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EquipmentSlot; @@ -37,14 +35,8 @@ import dev.loki.lovisual.config.values.EnumValue; import dev.loki.lovisual.config.values.NumberValue; import dev.loki.lovisual.config.values.RGBAColorValue; import dev.loki.lovisual.config.common.CommonSettingSchemas; -import dev.loki.lovisual.events.EventHandler; -import dev.loki.lovisual.events.impl.AttackEntityEvent; -import dev.loki.lovisual.events.impl.PacketEvent; import dev.loki.lovisual.features.gui.hud.HudRenderUtil; import dev.loki.lovisual.features.gui.hud.draggable.impl.Itemizer; -import dev.loki.lovisual.features.module.*; -import dev.loki.lovisual.features.module.Module; -import dev.loki.lovisual.features.module.modules.combat.Reach; import dev.loki.lovisual.features.relations.CategoryService; import dev.loki.lovisual.features.relations.EntityFilters; import dev.loki.lovisual.features.relations.PlayerRelations; @@ -57,10 +49,8 @@ import dev.loki.lovisual.render.engine.renderer.Renderer2D; import dev.loki.lovisual.render.engine.text.FontInfo; import dev.loki.lovisual.render.engine.text.Fonts; import dev.loki.lovisual.render.engine.text.TextRenderer; -import dev.loki.lovisual.util.combat.VulcanReachController; import java.util.List; -import java.util.Locale; //todo Description @ModuleInfo( @@ -79,9 +69,6 @@ public class Crosshair extends Module { private static final float ORBIZ_PROGRESS_SMOOTH_SPEED = 22.0f; private static final float COOLDOWN_LEAD = 0.06f; private static final float RIPTIDE_CROSS_LENGTH_FACTOR = 0.65f; - private static final float VULCAN_REACH_FONT_SIZE = 14.0f; - private static final int VULCAN_REACH_PRIMARY = 0xF2FFFFFF; - private static final int VULCAN_REACH_SECONDARY = 0xD8D7DEE8; private final Minecraft mc = Minecraft.getInstance(); private final EnumValue crosshairType = enumSetting("crosshairType", "type", CrosshairType.CLASSIC); @@ -199,15 +186,6 @@ public class Crosshair extends Module { num("crosshairOrbizMotionSmoothing", "orbiz_motion_smoothing", 12.0, 1.0, 30.0), () -> isOrbiz() && isOrbizMotionDynamic() ); - private final EnumValue vulcanReachDisplay = - enumSetting("crosshairVulcanReachDisplay", "vulcan_reach_display", - VulcanReachDisplay.TIMED, VulcanReachDisplay.values()); - private final NumberValue vulcanReachLookMs = - visibleWhen(num("crosshairVulcanReachLookMs", "vulcan_reach_look_ms", 1200, 0, 3000), - () -> vulcanReachDisplay.get() == VulcanReachDisplay.TIMED); - private final NumberValue vulcanReachAttackMs = - visibleWhen(num("crosshairVulcanReachAttackMs", "vulcan_reach_attack_ms", 2500, 0, 6000), - () -> vulcanReachDisplay.get() == VulcanReachDisplay.TIMED); private float animatedGap; private int animatedColor = DEFAULT_COLOR; private float animatedOrbizProgress = 1.0f; @@ -221,9 +199,6 @@ public class Crosshair extends Module { private boolean lastSwapCandidate; private boolean lastChestWasElytra; private ItemStack lastChestStack = ItemStack.EMPTY; - private long lastVulcanReachLookMs; - private long lastVulcanReachAttackMs; - private Player lastVulcanReachTarget; private static int colorDistance(int a, int b) { int dr = Math.abs(((a >>> 16) & 0xFF) - ((b >>> 16) & 0xFF)); @@ -322,7 +297,6 @@ public class Crosshair extends Module { renderOrbiz(renderer, centerX, centerY, scale); float infoGapPx = (float) (orbizRadius.get() * scale); float infoLengthPx = (float) ((orbizGlow.get() + orbizThickness.get()) * scale); - renderVulcanReachInfo(textRenderer, centerX, centerY, infoGapPx, infoLengthPx); return; } @@ -344,7 +318,6 @@ public class Crosshair extends Module { double stemH = lengthPx + gapPx; drawSegment(renderer, barX, barY, barW, thicknessPx, color, outline, outlinePx); drawSegment(renderer, stemX, centerY, thicknessPx, stemH, color, outline, outlinePx); - renderVulcanReachInfo(textRenderer, centerX, centerY, gapPx, lengthPx); return; } @@ -361,35 +334,6 @@ public class Crosshair extends Module { drawSegment(renderer, bottomX, bottomY, thicknessPx, lengthPx, color, outline, outlinePx); drawSegment(renderer, leftX, leftY, lengthPx, thicknessPx, color, outline, outlinePx); drawSegment(renderer, rightX, rightY, lengthPx, thicknessPx, color, outline, outlinePx); - renderVulcanReachInfo(textRenderer, centerX, centerY, gapPx, lengthPx); - } - - @EventHandler - private void onAttackEntity(AttackEntityEvent event) { - if (!isVulcanReachActive()) { - return; - } - if (event.getPlayer() != mc.player || !(event.getTarget() instanceof Player player)) { - return; - } - lastVulcanReachAttackMs = GameClock.millis(); - lastVulcanReachTarget = player; - } - - @EventHandler - private void onPacketSendPost(PacketEvent.SendPost event) { - if (!isVulcanReachActive() || mc.level == null) { - return; - } - if (!(event.getPacket() instanceof ServerboundInteractPacket packet) || !isAttackPacket(packet)) { - return; - } - - Entity target = mc.level.getEntity(packet.entityId()); - if (target instanceof Player player) { - lastVulcanReachAttackMs = GameClock.millis(); - lastVulcanReachTarget = player; - } } private float getAttackCooldownProgress() { @@ -555,85 +499,6 @@ public class Crosshair extends Module { return resolveEntityColor(entity); } - private void renderVulcanReachInfo(TextRenderer fallback, float centerX, float centerY, float gapPx, float lengthPx) { - if (!isVulcanReachActive()) { - return; - } - - Player target = resolveVulcanReachTarget(); - double requestedReach = resolveVulcanRequestedReach(target); - VulcanReachController.HudSnapshot snapshot = target != null && target.isAlive() - ? VulcanReachController.INSTANCE.hudSnapshot(target, requestedReach) - : vulcanReachDisplay.get() == VulcanReachDisplay.ALWAYS - ? VulcanReachController.INSTANCE.hudSnapshot(requestedReach) - : null; - if (snapshot == null) { - return; - } - - String reachText = String.format(Locale.ROOT, "%.1f", snapshot.displayReach()); - String hitsText = Integer.toString(snapshot.longHitsRemaining()); - - TextRenderer font = Fonts.renderer("Montserrat", FontInfo.Type.Regular, fallback); - float textScale = VULCAN_REACH_FONT_SIZE / 18.0f; - float x = centerX + gapPx + lengthPx + 7.0f; - float y = centerY - VULCAN_REACH_FONT_SIZE * 0.95f; - float lineHeight = VULCAN_REACH_FONT_SIZE * 0.92f; - - font.begin(textScale, false, false); - font.render(reachText, x, y, new RenderColor(VULCAN_REACH_PRIMARY), true); - font.render(hitsText, x, y + lineHeight, new RenderColor(VULCAN_REACH_SECONDARY), true); - font.end(); - } - - private boolean isVulcanReachActive() { - Reach reach = Modules.get(Reach.class); - return reach != null && reach.isEnabled() && reach.isVulcan297Mode(); - } - - private double resolveVulcanRequestedReach(Player target) { - Reach reach = Modules.get(Reach.class); - if (reach != null && reach.isEnabled() && reach.isVulcan297Mode()) { - return reach.getConfiguredDistance(); - } - - return 3.0; - } - - private Player resolveVulcanReachTarget() { - long now = GameClock.millis(); - HitResult hit = mc.hitResult; - if (hit instanceof EntityHitResult entityHit - && entityHit.getEntity() instanceof Player player - && player.isAlive()) { - lastVulcanReachLookMs = now; - lastVulcanReachTarget = player; - return player; - } - - if (lastVulcanReachTarget != null && !lastVulcanReachTarget.isAlive()) { - lastVulcanReachTarget = null; - } - - if (vulcanReachDisplay.get() == VulcanReachDisplay.ALWAYS) { - return lastVulcanReachTarget; - } - - if (lastVulcanReachTarget == null) { - return null; - } - - long lookWindow = Math.max(0, vulcanReachLookMs.get()); - long attackWindow = Math.max(0, vulcanReachAttackMs.get()); - boolean recentLook = lookWindow > 0 && now - lastVulcanReachLookMs <= lookWindow; - boolean recentAttack = attackWindow > 0 && now - lastVulcanReachAttackMs <= attackWindow; - return recentLook || recentAttack ? lastVulcanReachTarget : null; - } - - private boolean isAttackPacket(ServerboundInteractPacket packet) { - return packet.hand() == null && packet.location() == null; - } - private int smoothCrosshairColor(int targetColor) { if (!colorInitialized) { animatedColor = targetColor; @@ -780,22 +645,6 @@ public class Crosshair extends Module { renderer.quad(x, y, w, h, color); } - public enum VulcanReachDisplay implements EnumValue.IdProvider { - TIMED("timed"), - ALWAYS("always"); - - private final String id; - - VulcanReachDisplay(String id) { - this.id = id; - } - - @Override - public String id() { - return id; - } - } - private enum CrosshairType implements EnumValue.AliasProvider { CLASSIC, T_CLASSIC, diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/HoldMyItems.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/HoldMyItems.java new file mode 100644 index 0000000..c64aaa3 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/HoldMyItems.java @@ -0,0 +1,61 @@ +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 pivosos2007. + * + * Portions of this file are based on, adapted from, or implemented + * with reference to a "Hold My Items" mixin distributed for another + * Minecraft client. The original attribution was not preserved. + * This implementation has since been substantially modified for LoVisual. + * + * Licensed under the GNU General Public License v3.0. + */ + +package dev.loki.lovisual.features.module.modules.visuals; + +import dev.loki.lovisual.config.values.BooleanValue; +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; +import net.minecraft.client.Minecraft; + +/** + * Smooths and adds a subtle hand-held sway to first-person item rendering, so weapons and tools + * settle into place with an ease-out-back curve instead of vanilla's linear equip motion. + */ +@ModuleInfo( + id = "holdmyitems", + displayName = "Hold My Items", + aliases = {"hmi"}, + category = ModuleCategory.VISUALS, + description = "Плавная анимация удержания предмета в руке вместо линейного ваниллового equip" +) +public final class HoldMyItems extends Module { + + public final Minecraft mc = Minecraft.getInstance(); + + public final NumberValue swayIntensity = num("sway_intensity", 1.0f, 0.0f, 2.0f); + public final NumberValue easeStrength = num("ease_strength", 1.6f, 0.5f, 3.5f); + public final BooleanValue offhandToo = bool("offhand", true); + + /** + * Ease-out-back: overshoots slightly past 1.0 before settling, giving the equip motion weight. + * Ported from the original "Hold My Items" easing curve, generalized with a configurable strength. + */ + public float easeOutBack(float x) { + float c1 = easeStrength.get(); + float c3 = c1 + 1.0f; + float t = x - 1.0f; + return 1.0f + c3 * t * t * t + c1 * t * t; + } + + /** + * Reshapes the raw [0,1] equip progress with the ease-out-back curve, then blends by intensity + * so a intensity of 0 falls back to vanilla's linear motion. + */ + public float shapeEquipProgress(float linear) { + float eased = easeOutBack(linear); + float amount = swayIntensity.get(); + return linear + (eased - linear) * amount; + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/InvisEsp.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/InvisEsp.java index 2c75e68..10e7f33 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/InvisEsp.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/InvisEsp.java @@ -7,12 +7,14 @@ package dev.loki.lovisual.features.module.modules.visuals; +import dev.loki.lovisual.config.values.BooleanValue; import dev.loki.lovisual.config.values.NumberValue; import dev.loki.lovisual.config.values.RGBAColorValue; 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.relations.PlayerRelations; import dev.loki.lovisual.render.engine.RenderState; import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines; import dev.loki.lovisual.render.engine.renderer.Renderer3D; @@ -35,6 +37,10 @@ public final class InvisEsp extends Module { private final RGBAColorValue outlineColor = color("invisesp_color", "color", "#FF00E5FF"); + private final BooleanValue highlightFriends = + bool("invisesp_highlight_friends", "highlight_friends", true); + private final RGBAColorValue friendColor = + visibleWhen(color("invisesp_friend_color", "friend_color", "#FF00FF00"), highlightFriends::get); private final NumberValue lineWidth = num("invisesp_line_width", "line_width", 1.5f, 0.5f, 6.0f); @@ -60,11 +66,16 @@ public final class InvisEsp extends Module { RenderState.lineWidth = previousLineWidth; } - int color = outlineColor.getArgb(); + int defaultColor = outlineColor.getArgb(); + boolean highlightFriendsEnabled = highlightFriends.get(); for (Player player : mc.level.players()) { if (player == mc.player) continue; if (!isInvisible(player)) continue; + int color = highlightFriendsEnabled && PlayerRelations.get().isFriendOrStaff(player.getGameProfile().name()) + ? friendColor.getArgb() + : defaultColor; + AABB box = Esp2DRenderer.resolveWorldBox(player, tickDelta); EspMeshBuilder.addOutlineBox(lines, box, color, color, color, color, color, color, color, color); } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/LightningTargetESP.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/LightningTargetESP.java new file mode 100644 index 0000000..1a64e11 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/LightningTargetESP.java @@ -0,0 +1,315 @@ +/* + * 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.module.modules.visuals; + +import dev.loki.lovisual.config.values.BooleanValue; +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; +import dev.loki.lovisual.events.EventHandler; +import dev.loki.lovisual.events.impl.AttackEntityEvent; +import dev.loki.lovisual.render.engine.renderer.Renderer3D; +import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines; +import dev.loki.lovisual.render.engine.uniform.MeshBuilder; +import dev.loki.lovisual.util.target.TargetManager; +import net.minecraft.client.Minecraft; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.phys.Vec3; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +@ModuleInfo( + id = "lightning_target_esp", + displayName = "Lightning Target ESP", + category = ModuleCategory.VISUALS +) +public class LightningTargetESP extends Module { + + private final Minecraft mc = Minecraft.getInstance(); + + private final NumberValue lightningCount = num("lightningCount", "count", 4.0f, 1.0f, 10.0f); + private final NumberValue lightningWidth = num("lightningWidth", "width", 1.0f, 0.1f, 3.0f); + private final BooleanValue hurtColor = bool("hurtColor", "hurt_color", true); + + private float appearValue = 0f; + private Vec3 lastTargetPos = null; + private long lastAttackTime = 0L; + private LivingEntity lastAttackedEntity = null; + + private final ArrayList bolts = new ArrayList<>(); + private long lastBoltSpawn = 0L; + + @EventHandler + private void onAttack(AttackEntityEvent event) { + if (event.getTarget() instanceof LivingEntity entity) { + this.lastAttackTime = System.currentTimeMillis(); + this.lastAttackedEntity = entity; + } + } + + @Override + public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) { + if (mc.player == null || mc.level == null) return; + + LivingEntity currentTarget = TargetManager.getTarget(); + boolean hasTarget = currentTarget != null && currentTarget.isAlive(); + + LivingEntity activeTarget = hasTarget ? currentTarget : lastAttackedEntity; + boolean hasActiveTarget = activeTarget != null && activeTarget.isAlive(); + + appearValue = animateTo(appearValue, hasActiveTarget ? 1f : 0f, 0.05f); + + if (hasActiveTarget) { + this.lastTargetPos = new Vec3( + activeTarget.getX(), + activeTarget.getY(), + activeTarget.getZ() + ); + } + + if (appearValue <= 0.001f) { + if (!hasActiveTarget) { + lastTargetPos = null; + } + return; + } + + renderLightning(renderer, tickDelta, hasActiveTarget, activeTarget); + } + + private void renderLightning(Renderer3D renderer, float partialTicks, boolean hasActiveTarget, LivingEntity activeTarget) { + if (lastTargetPos == null) return; + + long now = System.currentTimeMillis(); + int maxBolts = Math.max(4, Math.round(lightningCount.get()) * 2); + long spawnInterval = (long) Math.max(15, 65.0f / (lightningCount.get() / 3.0f)); + + if (hasActiveTarget && activeTarget != null && activeTarget.isAlive()) { + if (bolts.size() < maxBolts) { + if (now - lastBoltSpawn > spawnInterval) { + bolts.add(spawnBolt(activeTarget)); + lastBoltSpawn = now; + } + } + } + bolts.removeIf(b -> now - b.spawn > b.life); + + if (bolts.isEmpty()) return; + + Vec3 basePos = lastTargetPos; + Vec3 cam = mc.gameRenderer.mainCamera().position(); + float hurtPC = getHurtPC(); + int baseColor = 0xFF55FFFF; + int redColor = 0xFFFF3C3C; + int glowColor = blendColor(baseColor, redColor, hurtPC); + int coreColor = blendColor(0xFFFFFFFF, redColor, hurtPC); + + float widthScale = lightningWidth.get(); + + renderRibbonLayer(renderer, basePos, cam, bolts, + 0.045f * widthScale, setAlpha(glowColor, (int) (appearValue * 45)), now, appearValue); + renderRibbonLayer(renderer, basePos, cam, bolts, + 0.018f * widthScale, setAlpha(glowColor, (int) (appearValue * 95)), now, appearValue); + renderRibbonLayer(renderer, basePos, cam, bolts, + 0.006f * widthScale, setAlpha(coreColor, (int) (appearValue * 235)), now, appearValue); + } + + private void renderRibbonLayer(Renderer3D renderer, Vec3 base, Vec3 cam, + ArrayList bolts, float width, int color, long now, float appearValue) { + MeshBuilder mesh = renderer.batch(LoVisualRenderPipelines.WORLD_COLORED, Renderer3D.DepthMode.NONE); + if (mesh == null) return; + + for (Bolt bolt : bolts) { + float alpha = boltAlpha(bolt, now) * appearValue; + if (alpha <= 0.02f) continue; + int a = (color >> 24) & 0xFF; + int layerA = (int) (a * alpha); + if (layerA <= 0 || bolt.points.size() < 2) continue; + int r = (color >> 16) & 0xFF; + int g = (color >> 8) & 0xFF; + int b = color & 0xFF; + appendRibbonPoints(mesh, base, cam, bolt.points, width, r, g, b, layerA); + for (List branch : bolt.branches) { + appendRibbonPoints(mesh, base, cam, branch, width * 0.6f, r, g, b, (int) (layerA * 0.85f)); + } + } + } + + private Bolt spawnBolt(LivingEntity target) { + ThreadLocalRandom random = ThreadLocalRandom.current(); + float radius = target.getBbWidth() * 0.5f; + float height = target.getBbHeight(); + + double startAngle = random.nextDouble() * Math.PI * 2.0; + double startY = 0.15 + random.nextDouble() * Math.max(0.2, height - 0.4); + Vec3 start = new Vec3( + Math.cos(startAngle) * radius * 0.7, + startY, + Math.sin(startAngle) * radius * 0.7 + ); + + double theta = random.nextDouble() * Math.PI * 2.0; + double phi = Math.toRadians((random.nextDouble() - 0.5) * 150.0); + Vec3 dir = new Vec3( + Math.cos(phi) * Math.cos(theta), + Math.sin(phi) * 0.8 + 0.25, + Math.cos(phi) * Math.sin(theta) + ).normalize(); + + double length = 0.35 + random.nextDouble() * 0.45; + Vec3 end = start.add(dir.scale(length)); + + ArrayList points = new ArrayList<>(List.of(start, end)); + points = displacePoints(points, 0.09, 3); + + ArrayList> branches = new ArrayList<>(); + int branchCount = 1 + random.nextInt(3); + for (int i = 0; i < branchCount; i++) { + int anchorIndex = 1 + random.nextInt(Math.max(1, points.size() - 2)); + Vec3 anchor = points.get(anchorIndex); + Vec3 branchDir = randomPerpendicular(dir); + if (random.nextBoolean()) { + branchDir = branchDir.scale(-1.0); + } + double branchLength = 0.12 + random.nextDouble() * 0.18; + Vec3 branchEnd = anchor.add(branchDir.scale(branchLength)); + ArrayList branchPoints = new ArrayList<>(List.of(anchor, branchEnd)); + branches.add(displacePoints(branchPoints, 0.05, 2)); + } + + return new Bolt(points, branches, 140L + random.nextLong(160L), random.nextFloat() * 6.28f); + } + + private ArrayList displacePoints(ArrayList input, double amount, int passes) { + ArrayList points = new ArrayList<>(input); + double amt = amount; + for (int pass = 0; pass < passes; pass++) { + ArrayList next = new ArrayList<>(points.size() * 2); + for (int i = 0; i < points.size() - 1; i++) { + Vec3 a = points.get(i); + Vec3 b = points.get(i + 1); + next.add(a); + Vec3 dir = b.subtract(a); + if (dir.lengthSqr() < 1.0E-7) { + next.add(a.add(b.scale(0.5))); + continue; + } + Vec3 perp = randomPerpendicular(dir.normalize()); + double offset = (ThreadLocalRandom.current().nextDouble() - 0.5) * 2.0 * amt; + next.add(a.add(b.scale(0.5)).add(perp.scale(offset))); + } + next.add(points.get(points.size() - 1)); + points = next; + amt *= 0.5; + } + return points; + } + + private Vec3 randomPerpendicular(Vec3 dir) { + Vec3 arbitrary = Math.abs(dir.y) < 0.9 ? new Vec3(0, 1, 0) : new Vec3(1, 0, 0); + Vec3 perp1 = dir.cross(arbitrary).normalize(); + Vec3 perp2 = dir.cross(perp1).normalize(); + double angle = ThreadLocalRandom.current().nextDouble() * Math.PI * 2.0; + return perp1.scale(Math.cos(angle)).add(perp2.scale(Math.sin(angle))); + } + + private void appendRibbonPoints(MeshBuilder mesh, Vec3 base, Vec3 cam, + List points, float width, int r, int g, int b, int a) { + if (a <= 0 || points.size() < 2) return; + float half = width * 0.5f; + + for (int i = 0; i < points.size() - 1; i++) { + Vec3 p1 = base.add(points.get(i)); + Vec3 p2 = base.add(points.get(i + 1)); + Vec3 segDir = p2.subtract(p1); + if (segDir.lengthSqr() < 1.0E-8) continue; + Vec3 viewDir = p1.add(p2).scale(0.5).subtract(cam); + if (viewDir.lengthSqr() < 1.0E-8) continue; + Vec3 side = segDir.cross(viewDir); + double sideLen = side.length(); + if (sideLen < 1.0E-8 || !Double.isFinite(sideLen)) continue; + side = side.scale(1.0 / sideLen); + + float x1 = (float) (p1.x - cam.x); + float y1 = (float) (p1.y - cam.y); + float z1 = (float) (p1.z - cam.z); + float x2 = (float) (p2.x - cam.x); + float y2 = (float) (p2.y - cam.y); + float z2 = (float) (p2.z - cam.z); + float sx = (float) side.x * half; + float sy = (float) side.y * half; + float sz = (float) side.z * half; + + int i1 = mesh.vec3(x1 - sx, y1 - sy, z1 - sz).color(r, g, b, a).next(); + int i2 = mesh.vec3(x1 + sx, y1 + sy, z1 + sz).color(r, g, b, a).next(); + int i3 = mesh.vec3(x2 + sx, y2 + sy, z2 + sz).color(r, g, b, a).next(); + int i4 = mesh.vec3(x2 - sx, y2 - sy, z2 - sz).color(r, g, b, a).next(); + mesh.quad(i1, i2, i3, i4); + } + } + + private float boltAlpha(Bolt bolt, long now) { + float life = Math.max(0.0f, Math.min(1.0f, (now - bolt.spawn) / (float) bolt.life)); + float fade = life < 0.18f ? life / 0.18f : 1.0f - (life - 0.18f) / 0.82f; + float flicker = 0.78f + 0.22f * (float) Math.sin(now * 0.045f + bolt.phase); + return Math.max(0.0f, Math.min(1.0f, fade)) * flicker; + } + + private float getHurtPC() { + if (!hurtColor.get()) return 0f; + float fromAttack = 0f; + long elapsed = System.currentTimeMillis() - lastAttackTime; + if (elapsed < 420L && lastAttackedEntity != null && lastAttackedEntity.isAlive()) { + fromAttack = 1.0f - (float) elapsed / 420.0f; + } + float progress = Math.max(0f, fromAttack); + return progress * progress * (3.0f - 2.0f * progress); + } + + private int blendColor(int c1, int c2, float factor) { + factor = Math.max(0f, Math.min(1f, factor)); + int r1 = (c1 >> 16) & 0xFF, g1 = (c1 >> 8) & 0xFF, b1 = c1 & 0xFF, a1 = (c1 >> 24) & 0xFF; + int r2 = (c2 >> 16) & 0xFF, g2 = (c2 >> 8) & 0xFF, b2 = c2 & 0xFF, a2 = (c2 >> 24) & 0xFF; + int r = (int) (r1 + (r2 - r1) * factor); + int g = (int) (g1 + (g2 - g1) * factor); + int b = (int) (b1 + (b2 - b1) * factor); + int a = (int) (a1 + (a2 - a1) * factor); + return (a << 24) | (r << 16) | (g << 8) | b; + } + + private int setAlpha(int color, int alpha) { + alpha = Math.max(0, Math.min(255, alpha)); + return (alpha << 24) | (color & 0x00FFFFFF); + } + + private float animateTo(float current, float target, float delta) { + if (current < target) return Math.min(current + delta, target); + if (current > target) return Math.max(current - delta, target); + return current; + } + + private static class Bolt { + final List points; + final List> branches; + final long spawn; + final long life; + final float phase; + + Bolt(List points, List> branches, long life, float phase) { + this.points = points; + this.branches = branches; + this.spawn = System.currentTimeMillis(); + this.life = life; + this.phase = phase; + } + } +} \ No newline at end of file diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/Rain.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/Rain.java new file mode 100644 index 0000000..24587cb --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/Rain.java @@ -0,0 +1,869 @@ +package dev.loki.lovisual.features.module.modules.visuals; + +import dev.loki.lovisual.config.values.BooleanValue; +import dev.loki.lovisual.config.values.EnumValue; +import dev.loki.lovisual.config.values.NumberValue; +import dev.loki.lovisual.config.values.RGBAColorValue; +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.RenderState; +import dev.loki.lovisual.render.engine.core.LoVisualWorldMatrices; +import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines; +import dev.loki.lovisual.render.engine.postprocess.PostProcessContext; +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.renderer.Renderer3D; +import dev.loki.lovisual.render.engine.uniform.MeshBuilder; +import dev.loki.lovisual.render.engine.uniform.impl.WetSurfaceUniforms; +import com.mojang.blaze3d.textures.GpuTextureView; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.core.BlockPos; +import net.minecraft.util.Mth; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.phys.Vec3; +import org.joml.Matrix4f; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; + +/** + * Volumetric rain: falling drop streaks, ground splashes with debris droplets, ground mist and lightning flashes. + * Wet-ground screen-space reflections are a separate follow-up (see {@link #renderWetSurfaceFrame}). + */ +@ModuleInfo( + id = "rain", + displayName = "Rain", + category = ModuleCategory.VISUALS, + description = "Реалистичный объёмный дождь с брызгами, туманом и молниями" +) +public final class Rain extends Module implements PostProcessPass { + + private final Matrix4f wetSurfaceProjection = new Matrix4f(); + private final Matrix4f wetSurfaceInvView = new Matrix4f(); + private boolean wetSurfaceDepthSupported = true; + + { + PostProcessManager.register(this); + } + + private static final double GRAVITY = 22.0; + private static final double TERMINAL_VELOCITY = 34.0; + private static final int RIPPLE_SEGMENTS = 14; + private static final int MAX_DROPS = 4200; + private static final int MAX_MIST = 90; + + private final Minecraft mc = Minecraft.getInstance(); + + private final EnumValue preset = enumMode("preset", Preset.RAIN, Preset.values()); + private final NumberValue density = num("density", 1.0f, 0.1f, 3.0f); + private final NumberValue radius = num("radius", 26.0f, 8.0f, 64.0f); + private final NumberValue altitude = num("altitude", 16.0f, 6.0f, 40.0f); + private final NumberValue dropSize = num("drop_size", 1.0f, 0.3f, 2.5f); + private final NumberValue fallSpeed = num("fall_speed", 1.0f, 0.3f, 2.5f); + private final NumberValue wind = num("wind", 1.0f, 0.0f, 3.0f); + private final NumberValue opacity = num("opacity", 1.0f, 0.1f, 2.0f); + + private final BooleanValue splashes = bool("splashes", true); + private final BooleanValue droplets = visibleWhen(bool("splash_droplets", true), splashes::get); + private final BooleanValue groundMist = bool("ground_mist", true); + private final BooleanValue lightning = bool("lightning", false); + private final BooleanValue skyCheck = bool("sky_check", true); + private final BooleanValue onlyWhenRaining = bool("only_when_raining", false); + + private final EnumValue colorMode = enumMode("color_mode", ColorMode.REALISTIC, ColorMode.values()); + private final RGBAColorValue customColor = visibleWhen(color("color", "#FFBFD8E6"), () -> colorMode.get() == ColorMode.CUSTOM); + + private final BooleanValue wetGround = bool("wet_ground", true); + private final NumberValue wetness = visibleWhen(num("wetness", 1.25f, 0.1f, 2.0f), wetGround::get); + private final NumberValue puddleCoverage = visibleWhen(num("puddle_coverage", 0.90f, 0.05f, 1.0f), wetGround::get); + private final NumberValue reflectionStrength = visibleWhen(num("reflection_strength", 1.55f, 0.1f, 2.0f), wetGround::get); + private final NumberValue reflectionDistance = visibleWhen(num("reflection_distance", 56.0f, 12.0f, 96.0f), wetGround::get); + private final EnumValue reflectionQuality = visibleWhen(enumMode("reflection_quality", ReflectionQuality.BALANCED, ReflectionQuality.values()), wetGround::get); + private final BooleanValue puddleRipples = visibleWhen(bool("puddle_ripples", true), wetGround::get); + private final NumberValue rippleStrength = visibleWhen(num("ripple_strength", 0.8f, 0.0f, 1.6f), () -> wetGround.get() && puddleRipples.get()); + + private final List drops = new ArrayList<>(); + private final List splashList = new ArrayList<>(); + private final List dropletList = new ArrayList<>(); + private final List mistList = new ArrayList<>(); + private final Map groundCache = new HashMap<>(); + private final BlockPos.MutableBlockPos scratchPos = new BlockPos.MutableBlockPos(); + private final Random random = new Random(); + + private long lastFrameNanos; + private float clock; + private float flash; + private float nextFlashIn = 9.0f; + private int cacheTicks; + + @Override + public void onEnable() { + lastFrameNanos = 0L; + wetSurfaceDepthSupported = true; + clearAll(); + } + + @Override + public void onDisable() { + clearAll(); + } + + private void clearAll() { + drops.clear(); + splashList.clear(); + dropletList.clear(); + mistList.clear(); + groundCache.clear(); + flash = 0.0f; + } + + @Override + public void onTick() { + if (mc.player == null || mc.level == null) { + clearAll(); + return; + } + if (++cacheTicks > 30) { + cacheTicks = 0; + groundCache.clear(); + } + } + + private float presetDensity() { + return switch (preset.get()) { + case DRIZZLE -> 0.45f; + case DOWNPOUR -> 1.9f; + case STORM -> 2.8f; + case RAIN -> 1.0f; + }; + } + + private float presetWind() { + return switch (preset.get()) { + case DRIZZLE -> 0.5f; + case DOWNPOUR -> 1.35f; + case STORM -> 2.4f; + case RAIN -> 1.0f; + }; + } + + private float presetMass() { + return switch (preset.get()) { + case DRIZZLE -> 0.55f; + case DOWNPOUR -> 1.25f; + case STORM -> 1.5f; + case RAIN -> 1.0f; + }; + } + + private int targetDropCount() { + float r = radius.get(); + float area = r * r * 0.0155f; + int count = (int) (area * density.get() * presetDensity() * 26.0f); + return Mth.clamp(count, 40, MAX_DROPS); + } + + private int resolveColorArgb() { + return switch (colorMode.get()) { + case CUSTOM -> customColor.getArgb(); + case THEME -> Theme.theme().accent(); + case REALISTIC -> 0xFFC8DCE8; + }; + } + + private float random(float min, float max) { + return min + random.nextFloat() * (max - min); + } + + private int surfaceY(int x, int z) { + long key = (((long) x) << 32) ^ (z & 0xFFFFFFFFL); + Integer cached = groundCache.get(key); + if (cached != null) return cached; + + int top; + try { + top = mc.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, new BlockPos(x, 0, z)).getY(); + } catch (Throwable ignored) { + top = mc.level.getMinY(); + } + if (groundCache.size() < 20000) groundCache.put(key, top); + return top; + } + + private boolean isBlocking(double x, double y, double z) { + if (mc.level == null) return false; + scratchPos.set(Mth.floor(x), Mth.floor(y), Mth.floor(z)); + + BlockState state = mc.level.getBlockState(scratchPos); + if (!state.getFluidState().isEmpty()) return true; + if (state.isAir()) return false; + try { + return !state.getCollisionShape(mc.level, scratchPos).isEmpty(); + } catch (Throwable ignored) { + return true; + } + } + + private boolean isWater(double x, double y, double z) { + if (mc.level == null) return false; + scratchPos.set(Mth.floor(x), Mth.floor(y), Mth.floor(z)); + try { + return !mc.level.getBlockState(scratchPos).getFluidState().isEmpty(); + } catch (Throwable ignored) { + return false; + } + } + + private boolean skyVisible(double x, double y, double z) { + if (!skyCheck.get() || mc.level == null) return true; + scratchPos.set(Mth.floor(x), Mth.floor(y), Mth.floor(z)); + try { + return mc.level.canSeeSky(scratchPos); + } catch (Throwable ignored) { + return true; + } + } + + private void spawnDrop(Vec3 center, boolean fromTop) { + float r = radius.get(); + double angle = random.nextDouble() * Math.PI * 2.0; + double dist = Math.sqrt(random.nextDouble()) * r; + + double x = center.x + Math.cos(angle) * dist; + double z = center.z + Math.sin(angle) * dist; + double y = fromTop + ? center.y + altitude.get() * random(0.75f, 1.0f) + : center.y + random(-4.0f, altitude.get()); + + if (!skyVisible(x, y, z)) return; + + Drop drop = new Drop(); + drop.x = x; + drop.y = y; + drop.z = z; + drop.mass = random(0.6f, 1.4f) * presetMass(); + drop.vy = -random(6.0f, 11.0f) * drop.mass; + drop.phase = random(0.0f, 62.8f); + drop.brightness = random(0.55f, 1.0f); + drop.ground = surfaceY(Mth.floor(x), Mth.floor(z)); + drops.add(drop); + } + + private void updateDrops(Vec3 center, float dt) { + int target = targetDropCount(); + + drops.removeIf(d -> { + double dx = d.x - center.x; + double dz = d.z - center.z; + float r = radius.get() + 6.0f; + return d.dead || dx * dx + dz * dz > r * r || d.y < center.y - altitude.get() - 12.0; + }); + + boolean fill = drops.size() < target / 2; + int spawnBudget = fill ? Math.min(target - drops.size(), 900) : Math.min(target - drops.size(), 260); + for (int i = 0; i < spawnBudget; i++) { + spawnDrop(center, !fill); + } + + float speedMul = fallSpeed.get(); + float windMul = wind.get() * presetWind(); + + for (int i = 0, size = drops.size(); i < size; i++) { + Drop d = drops.get(i); + + double gust = 0.65 + 0.35 * Math.sin(clock * 0.55 + d.phase * 0.15) + + 0.18 * Math.sin(clock * 1.7 + d.phase); + double windX = windMul * 3.1 * gust; + double windZ = windMul * 1.7 * Math.sin(clock * 0.31 + d.phase * 0.05) * gust; + + double drag = 1.0 - Math.min(0.92, Math.abs(d.vy) / (TERMINAL_VELOCITY * d.mass)); + d.vy -= GRAVITY * d.mass * drag * dt; + if (d.vy < -TERMINAL_VELOCITY * d.mass) d.vy = -TERMINAL_VELOCITY * d.mass; + + d.vx += (windX - d.vx) * Math.min(1.0, dt * 3.2); + d.vz += (windZ - d.vz) * Math.min(1.0, dt * 3.2); + + double nx = d.x + d.vx * dt * speedMul; + double ny = d.y + d.vy * dt * speedMul; + double nz = d.z + d.vz * dt * speedMul; + + boolean hit = false; + if (ny <= d.ground + 2.5) { + if (isBlocking(nx, ny, nz)) { + hit = true; + } else if (ny <= d.ground - 3.0) { + d.dead = true; + } + } + + if (hit) { + if (splashes.get()) addSplash(nx, ny, nz, d); + d.dead = true; + continue; + } + + d.x = nx; + d.y = ny; + d.z = nz; + } + } + + private void addSplash(double x, double y, double z, Drop d) { + if (splashList.size() > 700) return; + + double impactY = Math.floor(y) + 1.0005; + Splash splash = new Splash(); + splash.x = x; + splash.y = impactY; + splash.z = z; + splash.life = 0.0f; + splash.maxLife = random(0.30f, 0.52f); + splash.scale = (float) (0.32 + 0.22 * d.mass); + splash.water = isWater(x, y - 0.15, z); + splashList.add(splash); + + if (!droplets.get() || dropletList.size() > 1400) return; + + int shards = splash.water ? 4 : 3; + for (int i = 0; i < shards; i++) { + double a = random.nextDouble() * Math.PI * 2.0; + double sp = random(1.1f, 2.6f) * d.mass; + Droplet drop = new Droplet(); + drop.x = x; + drop.y = impactY + 0.02; + drop.z = z; + drop.vx = Math.cos(a) * sp * 0.45 + d.vx * 0.08; + drop.vz = Math.sin(a) * sp * 0.45 + d.vz * 0.08; + drop.vy = random(1.9f, 4.1f); + drop.maxLife = random(0.28f, 0.5f); + dropletList.add(drop); + } + } + + private void updateDroplets(float dt) { + for (int i = dropletList.size() - 1; i >= 0; i--) { + Droplet dr = dropletList.get(i); + dr.life += dt; + if (dr.life >= dr.maxLife) { + dropletList.remove(i); + continue; + } + dr.vy -= GRAVITY * 0.55 * dt; + dr.x += dr.vx * dt; + dr.y += dr.vy * dt; + dr.z += dr.vz * dt; + } + } + + private void updateSplashes(float dt) { + for (int i = splashList.size() - 1; i >= 0; i--) { + Splash s = splashList.get(i); + s.life += dt; + if (s.life >= s.maxLife) splashList.remove(i); + } + } + + private void updateMist(Vec3 center, float dt) { + if (!groundMist.get()) { + mistList.clear(); + return; + } + + int target = (int) Mth.clamp(22 * density.get() * presetDensity(), 6, MAX_MIST); + + mistList.removeIf(m -> { + m.life += dt; + double dx = m.x - center.x; + double dz = m.z - center.z; + float r = radius.get(); + m.x += m.vx * dt; + m.z += m.vz * dt; + return m.life > m.maxLife || dx * dx + dz * dz > r * r; + }); + + while (mistList.size() < target) { + double angle = random.nextDouble() * Math.PI * 2.0; + double dist = Math.sqrt(random.nextDouble()) * radius.get() * 0.85; + double x = center.x + Math.cos(angle) * dist; + double z = center.z + Math.sin(angle) * dist; + int ground = surfaceY(Mth.floor(x), Mth.floor(z)); + + Mist m = new Mist(); + m.x = x; + m.z = z; + m.y = ground + random(0.05f, 0.9f); + m.size = random(2.2f, 5.6f); + m.maxLife = random(2.4f, 5.5f); + m.life = random(0.0f, 1.0f); + m.vx = random(-0.35f, 0.35f) * wind.get(); + m.vz = random(-0.35f, 0.35f) * wind.get(); + mistList.add(m); + } + } + + private void updateLightning(float dt) { + if (!lightning.get()) { + flash = 0.0f; + return; + } + flash = Math.max(0.0f, flash - dt * 3.4f); + nextFlashIn -= dt; + if (nextFlashIn <= 0.0f) { + flash = random(0.55f, 1.0f); + nextFlashIn = random(6.0f, 16.0f); + } + } + + private float ambientLight(Camera camera) { + try { + int light = mc.level.getBrightness(net.minecraft.world.level.LightLayer.BLOCK, camera.blockPosition()); + return Mth.clamp(light / 15.0f, 0.3f, 1.0f); + } catch (Throwable ignored) { + return 1.0f; + } + } + + // --- Wet-ground screen-space reflection pass (PostProcessPass) --- + + private int reflectionSteps() { + return switch (reflectionQuality.get()) { + case HIGH -> 16; + case BALANCED -> 10; + case PERFORMANCE -> 6; + }; + } + + private float rainAmount() { + return Mth.clamp(0.55f + density.get() * presetDensity() * 0.28f, 0.55f, 1.45f); + } + + @Override + public boolean isActive() { + return isEnabled() + && wetGround.get() + && wetSurfaceDepthSupported + && mc.player != null + && mc.level != null + && (!onlyWhenRaining.get() || mc.level.isRaining()); + } + + @Override + public int getPriority() { + return 2; + } + + @Override + public Phase getPhase() { + return Phase.PRE_HAND; + } + + @Override + public boolean render(GpuTextureView src, GpuTextureView dst, float tickDelta) { + return false; + } + + @Override + public boolean render(PostProcessContext context, GpuTextureView src, GpuTextureView dst) { + if (!isActive() || context == null || src == null || dst == null) return false; + + GpuTextureView mainDepth = context.mainDepth(); + boolean hasDepth = mainDepth != null; + if (!hasDepth) mainDepth = src; + + Matrix4f projection = LoVisualWorldMatrices.renderProjectionMatrix(); + if (projection == null) projection = RenderState.worldProjection; + wetSurfaceProjection.set(projection); + wetSurfaceInvView.set(new Matrix4f().rotation(RenderState.cameraRotation)); + + WetSurfaceUniforms.update( + wetSurfaceProjection, + wetSurfaceInvView, + (float) RenderState.cameraPos.x, (float) RenderState.cameraPos.y, (float) RenderState.cameraPos.z, + context.width() > 0 ? 1.0f / context.width() : 0.0f, + context.height() > 0 ? 1.0f / context.height() : 0.0f, + clock, + hasDepth, + wetness.get(), + puddleCoverage.get(), + reflectionStrength.get(), + puddleRipples.get() ? rippleStrength.get() : 0.0f, + reflectionDistance.get(), + reflectionSteps(), + rainAmount(), + puddleRipples.get() + ); + + try { + FullScreenRenderer.ensureInit(); + FullScreenRenderer.begin("LoVisual WetSurface Pass") + .attachment(dst) + .pipeline(LoVisualRenderPipelines.WET_SURFACE) + .uniform("WetSurface", WetSurfaceUniforms.get()) + .sampler("u_Texture", src, PostProcessManager.getSampler()) + .sampler("u_MainDepth", mainDepth, PostProcessManager.getSampler()) + .end(); + } catch (Throwable t) { + wetSurfaceDepthSupported = false; + return false; + } + + return true; + } + + @Override + public WorldPhase getWorldPhase() { + return WorldPhase.AFTER_POST_PROCESS; + } + + @Override + public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) { + if (!isEnabled() || mc.player == null || mc.level == null || mc.gameRenderer == null) return; + if (mc.gameRenderer.mainCamera() == null) return; + if (onlyWhenRaining.get() && !mc.level.isRaining()) { + clearAll(); + return; + } + + Camera camera = mc.gameRenderer.mainCamera(); + + long now = System.nanoTime(); + if (lastFrameNanos == 0L) lastFrameNanos = now; + float dt = (float) ((now - lastFrameNanos) / 1_000_000_000.0); + lastFrameNanos = now; + dt = Mth.clamp(dt, 0.0f, 0.05f); + clock += dt; + + Vec3 cam = camera.position(); + + updateDrops(cam, dt); + updateSplashes(dt); + updateDroplets(dt); + updateMist(cam, dt); + updateLightning(dt); + + if (drops.isEmpty() && splashList.isEmpty() && dropletList.isEmpty() && mistList.isEmpty()) return; + + int colorArgb = resolveColorArgb(); + float r = ((colorArgb >>> 16) & 0xFF) / 255f; + float g = ((colorArgb >>> 8) & 0xFF) / 255f; + float b = (colorArgb & 0xFF) / 255f; + float[] rgb = {r, g, b}; + + float ambient = ambientLight(camera); + float brightnessMul = Mth.clamp(ambient + flash * 0.9f, 0.0f, 1.65f); + float alphaMul = opacity.get() * brightnessMul; + + Quaternionf camRot = RenderState.cameraRotation; + float camYaw = camera.yRot(); + + MeshBuilder blendMesh = renderer.batch(LoVisualRenderPipelines.WORLD_COLORED, Renderer3D.DepthMode.MAIN); + if (blendMesh != null) { + if (groundMist.get()) drawMist(blendMesh, camRot, rgb, alphaMul); + drawStreaks(blendMesh, camYaw, rgb, alphaMul, false); + if (splashes.get()) drawRipples(blendMesh, rgb, alphaMul); + } + + MeshBuilder additiveMesh = renderer.batch(LoVisualRenderPipelines.WORLD_COLORED_ADDITIVE_DEPTH, Renderer3D.DepthMode.PRE_DEPTH); + if (additiveMesh != null) { + drawStreaks(additiveMesh, camYaw, rgb, alphaMul, true); + if (splashes.get()) { + drawCrowns(additiveMesh, camYaw, rgb, alphaMul); + if (droplets.get()) drawDroplets(additiveMesh, camYaw, rgb, alphaMul); + } + } + } + + private static int packColor(float r, float g, float b, float a) { + int ai = Mth.clamp(Math.round(a * 255.0f), 0, 255); + int ri = Mth.clamp(Math.round(r * 255.0f), 0, 255); + int gi = Mth.clamp(Math.round(g * 255.0f), 0, 255); + int bi = Mth.clamp(Math.round(b * 255.0f), 0, 255); + return (ai << 24) | (ri << 16) | (gi << 8) | bi; + } + + private static Vector3f rotated(Quaternionf rot, float x, float y, float z) { + return new Vector3f(x, y, z).rotate(rot); + } + + private void drawStreaks(MeshBuilder mesh, float camYaw, float[] rgb, float alphaMul, boolean highlight) { + if (drops.isEmpty()) return; + + float sizeMul = dropSize.get(); + float r = radius.get(); + float fadeStart = r * 0.62f; + + for (int i = 0, size = drops.size(); i < size; i++) { + Drop d = drops.get(i); + + double speed = Math.sqrt(d.vx * d.vx + d.vy * d.vy + d.vz * d.vz); + if (speed < 0.0001) continue; + + float length = (float) Mth.clamp((float) (speed * 0.055 * sizeMul), 0.16f, 2.6f); + float width = (float) (0.014 + 0.019 * d.mass) * sizeMul * (highlight ? 0.45f : 1.0f); + + float yawRad = (float) Math.toRadians(camYaw); + double rightX = Math.cos(yawRad); + double rightZ = Math.sin(yawRad); + double horizontal = d.vx * rightX + d.vz * rightZ; + float tilt = (float) Math.toDegrees(Math.atan2(horizontal, Math.abs(d.vy) + 0.0001)); + tilt = Mth.clamp(tilt, -55.0f, 55.0f); + + Quaternionf orient = new Quaternionf() + .rotateY((float) Math.toRadians(-camYaw)) + .rotateZ((float) Math.toRadians(-tilt)); + + float half = width * 0.5f; + float top = length * 0.5f; + float bottom = -length * 0.5f; + + float headAlpha = alphaMul * d.brightness * (highlight ? 0.30f : 0.42f); + if (headAlpha <= 0.006f) continue; + headAlpha = Math.min(headAlpha, 0.95f); + float tailAlpha = 0.0f; + float headWidth = half; + float tailWidth = half * 0.35f; + + Vector3f p1 = rotated(orient, -tailWidth, top, 0.0f); + Vector3f p2 = rotated(orient, tailWidth, top, 0.0f); + Vector3f p3 = rotated(orient, headWidth, bottom, 0.0f); + Vector3f p4 = rotated(orient, -headWidth, bottom, 0.0f); + + mesh.ensureQuadCapacity(); + int i1 = mesh.vec3(d.x + p1.x, d.y + p1.y, d.z + p1.z).color(packColorObj(rgb, tailAlpha)).next(); + int i2 = mesh.vec3(d.x + p2.x, d.y + p2.y, d.z + p2.z).color(packColorObj(rgb, tailAlpha)).next(); + int i3 = mesh.vec3(d.x + p3.x, d.y + p3.y, d.z + p3.z).color(packColorObj(rgb, headAlpha)).next(); + int i4 = mesh.vec3(d.x + p4.x, d.y + p4.y, d.z + p4.z).color(packColorObj(rgb, headAlpha)).next(); + mesh.quad(i1, i2, i3, i4); + } + } + + private void drawRipples(MeshBuilder mesh, float[] rgb, float alphaMul) { + if (splashList.isEmpty()) return; + + for (int i = 0, size = splashList.size(); i < size; i++) { + Splash s = splashList.get(i); + float progress = Mth.clamp(s.life / s.maxLife, 0.0f, 1.0f); + float eased = 1.0f - (1.0f - progress) * (1.0f - progress); + + float outer = s.scale * (0.18f + eased * (s.water ? 1.35f : 0.85f)); + float inner = outer * (0.55f + 0.35f * eased); + float alpha = alphaMul * (1.0f - progress) * (s.water ? 0.5f : 0.38f); + if (alpha <= 0.01f) continue; + + for (int seg = 0; seg < RIPPLE_SEGMENTS; seg++) { + double a0 = (Math.PI * 2.0 / RIPPLE_SEGMENTS) * seg; + double a1 = (Math.PI * 2.0 / RIPPLE_SEGMENTS) * (seg + 1); + + float ix0 = (float) (Math.cos(a0) * inner); + float iz0 = (float) (Math.sin(a0) * inner); + float ix1 = (float) (Math.cos(a1) * inner); + float iz1 = (float) (Math.sin(a1) * inner); + float ox0 = (float) (Math.cos(a0) * outer); + float oz0 = (float) (Math.sin(a0) * outer); + float ox1 = (float) (Math.cos(a1) * outer); + float oz1 = (float) (Math.sin(a1) * outer); + + mesh.ensureQuadCapacity(); + int i1 = mesh.vec3(s.x + ix0, s.y, s.z + iz0).color(packColorObj(rgb, alpha)).next(); + int i2 = mesh.vec3(s.x + ix1, s.y, s.z + iz1).color(packColorObj(rgb, alpha)).next(); + int i3 = mesh.vec3(s.x + ox1, s.y, s.z + oz1).color(packColorObj(rgb, 0.0f)).next(); + int i4 = mesh.vec3(s.x + ox0, s.y, s.z + oz0).color(packColorObj(rgb, 0.0f)).next(); + mesh.quad(i1, i2, i3, i4); + } + } + } + + private void drawCrowns(MeshBuilder mesh, float camYaw, float[] rgb, float alphaMul) { + if (splashList.isEmpty()) return; + + Quaternionf orient = new Quaternionf().rotateY((float) Math.toRadians(-camYaw)); + + for (int i = 0, size = splashList.size(); i < size; i++) { + Splash s = splashList.get(i); + float progress = Mth.clamp(s.life / s.maxLife, 0.0f, 1.0f); + if (progress > 0.55f) continue; + + float local = progress / 0.55f; + float height = s.scale * (0.55f * (float) Math.sin(local * Math.PI)); + float alpha = alphaMul * (1.0f - local) * 0.32f; + if (alpha <= 0.01f || height <= 0.005f) continue; + + float base = s.scale * 0.16f; + Vector3f p1 = rotated(orient, -base * 0.25f, height, 0.0f); + Vector3f p2 = rotated(orient, base * 0.25f, height, 0.0f); + Vector3f p3 = rotated(orient, base, 0.0f, 0.0f); + Vector3f p4 = rotated(orient, -base, 0.0f, 0.0f); + + mesh.ensureQuadCapacity(); + int i1 = mesh.vec3(s.x + p1.x, s.y + p1.y, s.z + p1.z).color(packColorObj(rgb, 0.0f)).next(); + int i2 = mesh.vec3(s.x + p2.x, s.y + p2.y, s.z + p2.z).color(packColorObj(rgb, 0.0f)).next(); + int i3 = mesh.vec3(s.x + p3.x, s.y + p3.y, s.z + p3.z).color(packColorObj(rgb, alpha)).next(); + int i4 = mesh.vec3(s.x + p4.x, s.y + p4.y, s.z + p4.z).color(packColorObj(rgb, alpha)).next(); + mesh.quad(i1, i2, i3, i4); + } + } + + private void drawDroplets(MeshBuilder mesh, float camYaw, float[] rgb, float alphaMul) { + if (dropletList.isEmpty()) return; + + float sizeMul = dropSize.get(); + float yawRad = (float) Math.toRadians(camYaw); + + for (int i = 0, size = dropletList.size(); i < size; i++) { + Droplet d = dropletList.get(i); + float progress = Mth.clamp(d.life / d.maxLife, 0.0f, 1.0f); + float alpha = alphaMul * (1.0f - progress) * 0.5f; + if (alpha <= 0.01f) continue; + + double speed = Math.sqrt(d.vx * d.vx + d.vy * d.vy + d.vz * d.vz); + float length = (float) Mth.clamp((float) (speed * 0.03 * sizeMul), 0.04f, 0.4f); + float width = 0.016f * sizeMul; + + double horizontal = d.vx * Math.cos(yawRad) + d.vz * Math.sin(yawRad); + float tilt = (float) Math.toDegrees(Math.atan2(horizontal, -d.vy + 0.0001)); + + Quaternionf orient = new Quaternionf() + .rotateY((float) Math.toRadians(-camYaw)) + .rotateZ((float) Math.toRadians(-tilt)); + + Vector3f p1 = rotated(orient, -width * 0.4f, length * 0.5f, 0.0f); + Vector3f p2 = rotated(orient, width * 0.4f, length * 0.5f, 0.0f); + Vector3f p3 = rotated(orient, width, -length * 0.5f, 0.0f); + Vector3f p4 = rotated(orient, -width, -length * 0.5f, 0.0f); + + mesh.ensureQuadCapacity(); + int i1 = mesh.vec3(d.x + p1.x, d.y + p1.y, d.z + p1.z).color(packColorObj(rgb, 0.0f)).next(); + int i2 = mesh.vec3(d.x + p2.x, d.y + p2.y, d.z + p2.z).color(packColorObj(rgb, 0.0f)).next(); + int i3 = mesh.vec3(d.x + p3.x, d.y + p3.y, d.z + p3.z).color(packColorObj(rgb, alpha)).next(); + int i4 = mesh.vec3(d.x + p4.x, d.y + p4.y, d.z + p4.z).color(packColorObj(rgb, alpha)).next(); + mesh.quad(i1, i2, i3, i4); + } + } + + private void drawMist(MeshBuilder mesh, Quaternionf camRot, float[] rgb, float alphaMul) { + if (mistList.isEmpty()) return; + + for (int i = 0, size = mistList.size(); i < size; i++) { + Mist mist = mistList.get(i); + float progress = Mth.clamp(mist.life / mist.maxLife, 0.0f, 1.0f); + float fade = (float) Math.sin(progress * Math.PI); + float alpha = alphaMul * fade * 0.05f; + if (alpha <= 0.004f) continue; + + float half = mist.size * 0.5f; + float top = mist.size * 0.42f; + + Vector3f right = new Vector3f(1.0f, 0.0f, 0.0f).rotate(camRot); + Vector3f up = new Vector3f(0.0f, 1.0f, 0.0f).rotate(camRot); + + double bx = mist.x, by = mist.y, bz = mist.z; + + mesh.ensureQuadCapacity(); + int i1 = mesh.vec3(bx - right.x * half, by - right.y * half + up.y * top, bz - right.z * half).color(packColorObj(rgb, 0.0f)).next(); + int i2 = mesh.vec3(bx + right.x * half, by + right.y * half + up.y * top, bz + right.z * half).color(packColorObj(rgb, 0.0f)).next(); + int i3 = mesh.vec3(bx + right.x * half, by + right.y * half, bz + right.z * half).color(packColorObj(rgb, alpha)).next(); + int i4 = mesh.vec3(bx - right.x * half, by - right.y * half, bz - right.z * half).color(packColorObj(rgb, alpha)).next(); + mesh.quad(i1, i2, i3, i4); + } + } + + private dev.loki.lovisual.render.engine.color.RenderColor packColorObj(float[] rgb, float a) { + return new dev.loki.lovisual.render.engine.color.RenderColor(packColor(rgb[0], rgb[1], rgb[2], a)); + } + + public enum Preset implements EnumValue.IdProvider { + DRIZZLE("drizzle"), + RAIN("rain"), + DOWNPOUR("downpour"), + STORM("storm"); + + private final String id; + + Preset(String id) { + this.id = id; + } + + @Override + public String id() { + return id; + } + } + + public enum ColorMode implements EnumValue.IdProvider { + REALISTIC("realistic"), + THEME("theme"), + CUSTOM("custom"); + + private final String id; + + ColorMode(String id) { + this.id = id; + } + + @Override + public String id() { + return id; + } + } + + public enum ReflectionQuality implements EnumValue.IdProvider { + PERFORMANCE("performance"), + BALANCED("balanced"), + HIGH("high"); + + private final String id; + + ReflectionQuality(String id) { + this.id = id; + } + + @Override + public String id() { + return id; + } + } + + private static final class Drop { + double x, y, z; + double vx, vy, vz; + double mass = 1.0; + double ground; + float phase; + float brightness = 1.0f; + boolean dead; + } + + private static final class Droplet { + double x, y, z; + double vx, vy, vz; + float life; + float maxLife = 0.4f; + } + + private static final class Splash { + double x, y, z; + float life; + float maxLife = 0.4f; + float scale = 0.4f; + boolean water; + } + + private static final class Mist { + double x, y, z; + double vx, vz; + float size = 3.0f; + float life; + float maxLife = 4.0f; + } +} 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 index e009dbd..c2e8af1 100644 --- 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 @@ -5,11 +5,17 @@ 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; +import dev.loki.lovisual.features.module.WorldPhase; +import dev.loki.lovisual.render.engine.postprocess.SkyShaderRenderer; +import dev.loki.lovisual.render.engine.renderer.Renderer3D; +import net.minecraft.client.Minecraft; //todo Description @ModuleInfo(id = "shadersky", displayName = "ShaderSky", category = ModuleCategory.VISUALS) public class ShaderSky extends Module { + private final Minecraft mc = Minecraft.getInstance(); + private static final String SETTING_MODE = "mode"; private static final String SETTING_SPEED = "speed"; private static final String SETTING_SCALE = "scale"; @@ -23,7 +29,7 @@ public class ShaderSky extends Module { "shaderSkyMode", SETTING_MODE, "Thunder", - "Thunder", "Pulsar" + "Thunder", "Pulsar", "Sky", "Water", "Caustic" ); private final NumberValue speed = num("shaderSkySpeed", SETTING_SPEED, 1.0f, 0.1f, 5.0f); @@ -75,4 +81,41 @@ public class ShaderSky extends Module { private boolean isThunderMode() { return "Thunder".equals(mode.get()); } -} + + private boolean isSkyShaderMode() { + String m = mode.get(); + return "Sky".equals(m) || "Water".equals(m) || "Caustic".equals(m); + } + + @Override + public WorldPhase getWorldPhase() { + return WorldPhase.AFTER_POST_PROCESS; + } + + @Override + public void onEnable() { + SkyShaderRenderer.reset(); + } + + @Override + public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) { + if (!isEnabled() || mc.level == null || mc.player == null) return; + + if (isSkyShaderMode()) { + SkyShaderRenderer.Mode skyMode = switch (mode.get()) { + case "Water" -> SkyShaderRenderer.Mode.WATER; + case "Caustic" -> SkyShaderRenderer.Mode.CAUSTIC; + default -> SkyShaderRenderer.Mode.SKY; + }; + SkyShaderRenderer.render( + mc, + skyMode, + speed.get(), + scale.get(), + intensity.get(), + alpha.get(), + 0xFF6FA8DC & 0x00FFFFFF + ); + } + } +} \ No newline at end of file diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/KineticLyrics.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/KineticLyrics.java new file mode 100644 index 0000000..eee4d4e --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/KineticLyrics.java @@ -0,0 +1,432 @@ +package dev.loki.lovisual.features.module.modules.visuals.lyrics; + +import dev.loki.lovisual.config.values.BooleanValue; +import dev.loki.lovisual.config.values.EnumValue; +import dev.loki.lovisual.config.values.NumberValue; +import dev.loki.lovisual.config.values.RGBAColorValue; +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.color.RenderColor; +import dev.loki.lovisual.render.engine.renderer.Renderer2D; +import dev.loki.lovisual.render.engine.renderer.Renderer3D; +import dev.loki.lovisual.render.engine.text.FontInfo; +import dev.loki.lovisual.render.engine.text.Fonts; +import dev.loki.lovisual.render.engine.text.TextRenderer; +import dev.loki.lovisual.util.media.MediaSessionService; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.world.phys.Vec3; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ThreadLocalRandom; + +@ModuleInfo( + id = "kineticlyrics", + displayName = "KineticLyrics", + aliases = {"lyrics"}, + category = ModuleCategory.VISUALS, + description = "Рендерит текст текущей песни в 3D-пространстве вокруг игрока" +) +public final class KineticLyrics extends Module { + + private final Minecraft mc = Minecraft.getInstance(); + + private final EnumValue animation = + enumMode("animation", AnimationMode.LYRIC_FLOW, AnimationMode.values()); + private final EnumValue colorMode = + enumMode("color_mode", ColorMode.THEME, ColorMode.values()); + private final RGBAColorValue staticColor = + visibleWhen(color("static_color", "#FFFFFFFF"), () -> colorMode.get() == ColorMode.STATIC); + private final EnumValue fontMode = + enumMode("font", FontMode.ONEST_BOLD, FontMode.values()); + private final EnumValue splitMode = + enumMode("split_mode", SplitMode.SMART_SPLIT, SplitMode.values()); + private final NumberValue maxLines = + num("max_lines", 3, 1, 6); + private final NumberValue minDistance = + num("min_distance", 2.0f, 1.2f, 4.0f); + private final NumberValue maxDistance = + num("max_distance", 4.5f, 2.0f, 7.0f); + private final NumberValue arcSpread = + num("arc_spread", 70.0f, 20.0f, 85.0f); + private final NumberValue floatHeight = + num("float_height", 0.5f, 0.1f, 2.0f); + private final NumberValue timeOffsetMs = + num("time_offset_ms", 0, -5000, 5000); + private final BooleanValue debugMode = + bool("debug", false); + + private final List rawLyrics = new ArrayList<>(); + private final List lyricsQueue = new ArrayList<>(); + private final List activeParticles = new CopyOnWriteArrayList<>(); + private int currentLyricIndex = 0; + + private String lastTrackKey = ""; + private String currentTrackTitle = ""; + private String currentTrackArtist = ""; + private long lastAudioTimeMs = -1L; + private boolean isPlaying = false; + + @Override + public void onEnable() { + resetPlaybackState(); + } + + @Override + public void onDisable() { + resetPlaybackState(); + } + + private void resetPlaybackState() { + activeParticles.clear(); + rawLyrics.clear(); + lyricsQueue.clear(); + lastTrackKey = ""; + currentTrackTitle = ""; + currentTrackArtist = ""; + currentLyricIndex = 0; + lastAudioTimeMs = -1L; + isPlaying = false; + } + + @Override + public void onTick() { + if (!isEnabled() || mc.player == null) return; + syncPlaybackTime(); + } + + private void syncPlaybackTime() { + MediaSessionService.Snapshot snapshot = MediaSessionService.get().snapshot(); + if (!snapshot.hasSession() || snapshot.title().isBlank()) { + if (isPlaying) { + isPlaying = false; + activeParticles.clear(); + } + return; + } + + String title = snapshot.title(); + String artist = snapshot.artist(); + String key = (title + " - " + artist).toLowerCase(); + + if (!key.equalsIgnoreCase(lastTrackKey)) { + loadTrack(title, artist, key); + return; + } + + if (!isPlaying && !lyricsQueue.isEmpty()) { + isPlaying = true; + } + if (lyricsQueue.isEmpty()) return; + + long audioTimeMs = Math.max(0L, snapshot.predictedPositionSeconds() * 1000L + timeOffsetMs.get()); + + if (lastAudioTimeMs >= 0 && Math.abs(audioTimeMs - lastAudioTimeMs) > 1500L) { + resyncQueue(audioTimeMs); + } + lastAudioTimeMs = audioTimeMs; + + advanceQueue(audioTimeMs); + } + + private void loadTrack(String title, String artist, String key) { + this.currentTrackTitle = title; + this.currentTrackArtist = artist; + this.lastTrackKey = key; + + activeParticles.clear(); + rawLyrics.clear(); + lyricsQueue.clear(); + currentLyricIndex = 0; + lastAudioTimeMs = -1L; + isPlaying = false; + + LrcLibClient.fetchLyricsAsync(title, artist, debugMode.get()).thenAccept(lines -> { + if (!this.lastTrackKey.equalsIgnoreCase(key)) return; + + activeParticles.clear(); + rawLyrics.clear(); + lyricsQueue.clear(); + rawLyrics.addAll(lines); + rebuildLyricsQueue(); + currentLyricIndex = 0; + isPlaying = !lyricsQueue.isEmpty(); + }); + } + + private void rebuildLyricsQueue() { + lyricsQueue.clear(); + if (splitMode.get() == SplitMode.SMART_SPLIT) { + lyricsQueue.addAll(LyricLineSplitter.splitLongLines(rawLyrics)); + } else { + lyricsQueue.addAll(rawLyrics); + } + } + + private void resyncQueue(long audioTimeMs) { + activeParticles.clear(); + currentLyricIndex = 0; + long now = System.currentTimeMillis(); + + for (int i = 0; i < lyricsQueue.size(); i++) { + LyricLine line = lyricsQueue.get(i); + long nextStartMs = (i + 1 < lyricsQueue.size()) ? lyricsQueue.get(i + 1).timestampMs() : line.timestampMs() + 3500L; + long durationMs = Math.max(1200L, Math.min(5000L, nextStartMs - line.timestampMs())); + + if (audioTimeMs >= line.timestampMs()) { + long timeSinceLine = audioTimeMs - line.timestampMs(); + if (timeSinceLine < durationMs) { + spawnLyricParticle(line.text(), now - timeSinceLine, durationMs); + } + currentLyricIndex = i + 1; + } else { + break; + } + } + } + + private void advanceQueue(long audioTimeMs) { + if (!isPlaying || lyricsQueue.isEmpty() || mc.player == null) return; + + long now = System.currentTimeMillis(); + + while (currentLyricIndex < lyricsQueue.size()) { + LyricLine line = lyricsQueue.get(currentLyricIndex); + long nextStartMs = (currentLyricIndex + 1 < lyricsQueue.size()) ? lyricsQueue.get(currentLyricIndex + 1).timestampMs() : line.timestampMs() + 3500L; + long durationMs = Math.max(1400L, Math.min(5500L, nextStartMs - line.timestampMs())); + + if (audioTimeMs >= line.timestampMs()) { + long timeSinceLine = audioTimeMs - line.timestampMs(); + if (timeSinceLine < durationMs) { + spawnLyricParticle(line.text(), now - timeSinceLine, durationMs); + } + currentLyricIndex++; + } else { + break; + } + } + + activeParticles.removeIf(particle -> particle.isDead(now)); + } + + private Vec3 findNonOverlappingSpawnPos(Vec3 headPos, float cameraYaw) { + double spread = arcSpread.get(); + double minD = minDistance.get(); + double maxD = Math.max(minD + 0.5, maxDistance.get()); + + Vec3 bestPos = null; + double maxMinDistance = -1.0; + + for (int attempt = 0; attempt < 16; attempt++) { + double randomAngleOffset = ThreadLocalRandom.current().nextDouble(-spread, spread); + double targetYawDeg = cameraYaw + randomAngleOffset; + double distance = ThreadLocalRandom.current().nextDouble(minD, maxD); + double yOffset = ThreadLocalRandom.current().nextDouble(-0.25, 0.55); + + double yawRad = Math.toRadians(targetYawDeg); + double dirX = -Math.sin(yawRad); + double dirZ = Math.cos(yawRad); + + Vec3 candidate = new Vec3( + headPos.x + (dirX * distance), + headPos.y + yOffset, + headPos.z + (dirZ * distance) + ); + + if (activeParticles.isEmpty()) { + return candidate; + } + + double minDistanceToOthers = Double.MAX_VALUE; + for (LyricParticle3D active : activeParticles) { + double d = candidate.distanceTo(active.getBasePosition()); + if (d < minDistanceToOthers) { + minDistanceToOthers = d; + } + } + + if (minDistanceToOthers >= 1.35) { + return candidate; + } + + if (minDistanceToOthers > maxMinDistance) { + maxMinDistance = minDistanceToOthers; + bestPos = candidate; + } + } + + return bestPos; + } + + private void spawnLyricParticle(String text, long spawnTimeMs, long durationMs) { + if (mc.player == null || text == null || text.isBlank()) return; + if (mc.gameRenderer == null || mc.gameRenderer.mainCamera() == null) return; + + Camera camera = mc.gameRenderer.mainCamera(); + Vec3 headPos = mc.player.getEyePosition(); + float cameraYaw = camera.yRot(); + + int maxAllowed = maxLines.get(); + while (activeParticles.size() >= maxAllowed) { + activeParticles.remove(0); + } + + Vec3 spawnPos = findNonOverlappingSpawnPos(headPos, cameraYaw); + if (spawnPos == null) return; + + activeParticles.add(new LyricParticle3D(text, spawnPos, spawnTimeMs, durationMs)); + } + + private TextRenderer getSelectedFont() { + return Fonts.renderer(fontMode.get().family, FontInfo.Type.Regular, TextRenderer.get()); + } + + private int getActiveColorRgb() { + return switch (colorMode.get()) { + case THEME -> Theme.theme().accent(); + case THEME_GRADIENT -> { + float ratio = (float) (Math.sin(System.currentTimeMillis() / 450.0) * 0.5 + 0.5); + int c1 = Theme.theme().accent(); + int c2 = Theme.theme().accentSoft(); + yield mixArgb(c1, c2, ratio); + } + case WHITE -> 0xFFFFFF; + case STATIC -> staticColor.getArgb(); + }; + } + + private static int mixArgb(int from, int to, float t) { + t = Math.max(0.0f, Math.min(1.0f, t)); + int r = mixChannel((from >>> 16) & 0xFF, (to >>> 16) & 0xFF, t); + int g = mixChannel((from >>> 8) & 0xFF, (to >>> 8) & 0xFF, t); + int b = mixChannel(from & 0xFF, to & 0xFF, t); + return (r << 16) | (g << 8) | b; + } + + private static int mixChannel(int from, int to, float t) { + return from + Math.round((to - from) * t); + } + + @Override + public WorldPhase getWorldPhase() { + return WorldPhase.AFTER_POST_PROCESS; + } + + @Override + public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) { + if (!isEnabled() || mc.level == null || mc.player == null) return; + if (activeParticles.isEmpty()) return; + + TextRenderer font = getSelectedFont(); + String animMode = animation.get().name(); + int colorRgb = getActiveColorRgb(); + long now = System.currentTimeMillis(); + + for (LyricParticle3D particle : activeParticles) { + particle.render(renderer, font, colorRgb, animMode, now); + } + } + + @Override + public void onRenderHudEngineForeground(Renderer2D renderer, TextRenderer textRenderer, GuiGraphicsExtractor ctx, float tickDelta) { + if (!isEnabled() || !debugMode.get() || mc.player == null) return; + + TextRenderer tr = Fonts.renderer("OnestMedium", FontInfo.Type.Regular, textRenderer); + String trackLine = "Track: " + (currentTrackTitle.isEmpty() ? "None" : currentTrackTitle + " - " + currentTrackArtist); + long displayTimeMs = Math.max(0L, lastAudioTimeMs); + String timerLine = String.format("Audio time: %02d:%02d.%03d (%d ms)", + displayTimeMs / 60_000, + (displayTimeMs % 60_000) / 1000, + displayTimeMs % 1000, + displayTimeMs + ); + String particlesLine = "Active particles: " + activeParticles.size(); + String queueLine = "Queue: " + currentLyricIndex + " / " + lyricsQueue.size() + " lines"; + + tr.begin(0.85); + tr.render(trackLine, 10.0, 70.0, new RenderColor(0xFFDCDCDC), false); + tr.render(timerLine, 10.0, 82.0, new RenderColor(0xFFFFFFFF), false); + tr.render(particlesLine, 10.0, 94.0, new RenderColor(0xFF78FF78), false); + tr.render(queueLine, 10.0, 106.0, new RenderColor(0xFFB4B4FF), false); + tr.end(); + } + + public enum AnimationMode implements EnumValue.IdProvider { + LYRIC_FLOW("lyric_flow"), + TYPEWRITER("typewriter"), + POP_SCALE("pop_scale"), + KINETIC_SLIDE("kinetic_slide"), + FADE("fade"); + + private final String id; + + AnimationMode(String id) { + this.id = id; + } + + @Override + public String id() { + return id; + } + } + + public enum ColorMode implements EnumValue.IdProvider { + THEME("theme"), + THEME_GRADIENT("theme_gradient"), + WHITE("white"), + STATIC("static"); + + private final String id; + + ColorMode(String id) { + this.id = id; + } + + @Override + public String id() { + return id; + } + } + + public enum FontMode implements EnumValue.IdProvider { + ONEST_BOLD("onest_bold", "OnestBold"), + ONEST_MEDIUM("onest_medium", "OnestMedium"), + ONEST("onest", "Onest"), + INTER_MEDIUM("inter_medium", "InterMedium"); + + private final String id; + private final String family; + + FontMode(String id, String family) { + this.id = id; + this.family = family; + } + + @Override + public String id() { + return id; + } + } + + public enum SplitMode implements EnumValue.IdProvider { + SMART_SPLIT("smart_split"), + FULL_LINE("full_line"); + + private final String id; + + SplitMode(String id) { + this.id = id; + } + + @Override + public String id() { + return id; + } + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LrcLibClient.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LrcLibClient.java new file mode 100644 index 0000000..0ca9d46 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LrcLibClient.java @@ -0,0 +1,184 @@ +package dev.loki.lovisual.features.module.modules.visuals.lyrics; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import dev.loki.lovisual.util.logging.DebugLog; + +import java.net.URI; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Fetches synced lyrics from lrclib.net (no API key required). + */ +public final class LrcLibClient { + private static final String API_GET = "https://lrclib.net/api/get"; + private static final String API_SEARCH = "https://lrclib.net/api/search"; + + private static final String USER_AGENT = "LoVisual/1.0"; + + private static final Map> LYRICS_CACHE = new ConcurrentHashMap<>(); + + private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(12)) + .followRedirects(HttpClient.Redirect.NORMAL) + .build(); + + private LrcLibClient() { + } + + public static CompletableFuture> fetchLyricsAsync(String trackName, String artistName, boolean debugMode) { + if (trackName == null || trackName.isBlank()) { + return CompletableFuture.completedFuture(Collections.emptyList()); + } + + String cacheKey = (trackName.trim() + " - " + (artistName != null ? artistName.trim() : "")).toLowerCase(); + List cached = LYRICS_CACHE.get(cacheKey); + if (cached != null) { + if (debugMode) { + DebugLog.info("[KineticLyrics] Cache hit: %s (%d lines)", cacheKey, cached.size()); + } + return CompletableFuture.completedFuture(cached); + } + + return CompletableFuture.supplyAsync(() -> { + List lines = tryFetchGet(trackName, artistName, debugMode); + if (!lines.isEmpty()) { + LYRICS_CACHE.put(cacheKey, lines); + return lines; + } + + lines = tryFetchSearch(trackName, artistName, debugMode); + if (!lines.isEmpty()) { + LYRICS_CACHE.put(cacheKey, lines); + return lines; + } + + return Collections.emptyList(); + }); + } + + private static List tryFetchGet(String trackName, String artistName, boolean debugMode) { + String cleanTrack = cleanTrackName(trackName); + String cleanArtist = artistName != null ? cleanTrackName(artistName) : ""; + + String query = String.format("?track_name=%s&artist_name=%s", + URLEncoder.encode(cleanTrack, StandardCharsets.UTF_8), + URLEncoder.encode(cleanArtist, StandardCharsets.UTF_8)); + + return executeRequest(API_GET + query, debugMode); + } + + private static List tryFetchSearch(String trackName, String artistName, boolean debugMode) { + String cleanTrack = cleanTrackName(trackName); + String cleanArtist = artistName != null ? cleanTrackName(artistName) : ""; + String searchQuery = (cleanTrack + " " + cleanArtist).trim(); + + String query = "?q=" + URLEncoder.encode(searchQuery, StandardCharsets.UTF_8); + + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(API_SEARCH + query)) + .header("User-Agent", USER_AGENT) + .timeout(Duration.ofSeconds(14)) + .GET() + .build(); + + HttpResponse response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() == 200) { + JsonArray array = JsonParser.parseString(response.body()).getAsJsonArray(); + for (JsonElement elem : array) { + if (elem.isJsonObject()) { + JsonObject obj = elem.getAsJsonObject(); + if (obj.has("syncedLyrics") && !obj.get("syncedLyrics").isJsonNull()) { + String syncedLrc = obj.get("syncedLyrics").getAsString(); + List parsed = LrcParser.parse(syncedLrc); + if (!parsed.isEmpty()) { + if (debugMode) { + DebugLog.info("[KineticLyrics] Found lyrics via search fallback"); + } + return parsed; + } + } + } + } + } else if (response.statusCode() == 404) { + if (debugMode) { + DebugLog.info("[KineticLyrics] Search: track not found (404)"); + } + } + } catch (Exception e) { + if (debugMode) { + DebugLog.error("[KineticLyrics] Search fallback failed", e); + } + } + return Collections.emptyList(); + } + + private static List executeRequest(String url, boolean debugMode) { + for (int attempt = 1; attempt <= 2; attempt++) { + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(url)) + .header("User-Agent", USER_AGENT) + .timeout(Duration.ofSeconds(14)) + .GET() + .build(); + + HttpResponse response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() == 200) { + JsonObject json = JsonParser.parseString(response.body()).getAsJsonObject(); + if (json.has("syncedLyrics") && !json.get("syncedLyrics").isJsonNull()) { + String syncedLrc = json.get("syncedLyrics").getAsString(); + return LrcParser.parse(syncedLrc); + } + if (debugMode) { + DebugLog.info("[KineticLyrics] No syncedLyrics in response"); + } + return Collections.emptyList(); + } else if (response.statusCode() == 404) { + if (debugMode) { + DebugLog.info("[KineticLyrics] Track not found (404)"); + } + return Collections.emptyList(); + } else { + if (debugMode) { + DebugLog.info("[KineticLyrics] Attempt %d returned %d", attempt, response.statusCode()); + } + } + } catch (Exception e) { + if (debugMode) { + DebugLog.error("[KineticLyrics] Attempt " + attempt + " failed", e); + } + if (attempt < 2) { + try { + Thread.sleep(400); + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); + } + } + } + } + return Collections.emptyList(); + } + + static String cleanTrackName(String trackName) { + if (trackName == null) return ""; + return trackName + .replaceAll("\\(.*?\\)", "") + .replaceAll("\\[.*?\\]", "") + .trim(); + } +} \ No newline at end of file diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LrcParser.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LrcParser.java new file mode 100644 index 0000000..65c8185 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LrcParser.java @@ -0,0 +1,58 @@ +package dev.loki.lovisual.features.module.modules.visuals.lyrics; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class LrcParser { + private static final Pattern LRC_PATTERN = Pattern.compile("\\[(\\d+):(\\d{2})(?:\\.(\\d{1,3}))?\\](.*)"); + + private LrcParser() { + } + + public static List parse(String lrcContent) { + if (lrcContent == null || lrcContent.isBlank()) { + return Collections.emptyList(); + } + + List lines = new ArrayList<>(); + String[] rawLines = lrcContent.split("\\r?\\n"); + + for (String line : rawLines) { + String trimmed = line.trim(); + if (trimmed.isEmpty()) continue; + + Matcher matcher = LRC_PATTERN.matcher(trimmed); + if (matcher.matches()) { + try { + long minutes = Long.parseLong(matcher.group(1)); + long seconds = Long.parseLong(matcher.group(2)); + String fractionStr = matcher.group(3); + + long millis = 0; + if (fractionStr != null && !fractionStr.isEmpty()) { + millis = Long.parseLong(fractionStr); + if (fractionStr.length() == 1) { + millis *= 100; + } else if (fractionStr.length() == 2) { + millis *= 10; + } + } + + long totalTimeMs = (minutes * 60_000L) + (seconds * 1_000L) + millis; + String text = matcher.group(4).trim(); + + if (!text.isEmpty()) { + lines.add(new LyricLine(totalTimeMs, text)); + } + } catch (NumberFormatException ignored) { + } + } + } + + Collections.sort(lines); + return lines; + } +} \ No newline at end of file diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LyricLine.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LyricLine.java new file mode 100644 index 0000000..bfcafef --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LyricLine.java @@ -0,0 +1,8 @@ +package dev.loki.lovisual.features.module.modules.visuals.lyrics; + +public record LyricLine(long timestampMs, String text) implements Comparable { + @Override + public int compareTo(LyricLine o) { + return Long.compare(this.timestampMs, o.timestampMs); + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LyricLineSplitter.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LyricLineSplitter.java new file mode 100644 index 0000000..43f3fb9 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LyricLineSplitter.java @@ -0,0 +1,68 @@ +package dev.loki.lovisual.features.module.modules.visuals.lyrics; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public final class LyricLineSplitter { + private LyricLineSplitter() { + } + + /** + * Splits long lyric lines (more than 4 words) into shorter rhythmic chunks with micro-timings. + */ + public static List splitLongLines(List originalLines) { + if (originalLines == null || originalLines.isEmpty()) { + return Collections.emptyList(); + } + + List result = new ArrayList<>(); + + for (int i = 0; i < originalLines.size(); i++) { + LyricLine current = originalLines.get(i); + long nextTimeMs = (i + 1 < originalLines.size()) ? originalLines.get(i + 1).timestampMs() : current.timestampMs() + 4000L; + long lineDuration = Math.max(1500L, nextTimeMs - current.timestampMs()); + + String text = current.text().trim(); + String[] words = text.split("\\s+"); + + if (words.length <= 4) { + result.add(current); + } else { + int wordsPerChunk = words.length > 7 ? 3 : 2; + List chunks = new ArrayList<>(); + StringBuilder sb = new StringBuilder(); + int count = 0; + + for (String word : words) { + if (!sb.isEmpty()) sb.append(" "); + sb.append(word); + count++; + if (count >= wordsPerChunk) { + chunks.add(sb.toString()); + sb.setLength(0); + count = 0; + } + } + if (!sb.isEmpty()) { + chunks.add(sb.toString()); + } + + int numChunks = chunks.size(); + if (numChunks == 0) { + result.add(current); + continue; + } + long chunkDuration = lineDuration / numChunks; + + for (int c = 0; c < numChunks; c++) { + long chunkStartMs = current.timestampMs() + (c * chunkDuration); + result.add(new LyricLine(chunkStartMs, chunks.get(c))); + } + } + } + + Collections.sort(result); + return result; + } +} \ No newline at end of file diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LyricParticle3D.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LyricParticle3D.java new file mode 100644 index 0000000..8aab6ef --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LyricParticle3D.java @@ -0,0 +1,147 @@ +package dev.loki.lovisual.features.module.modules.visuals.lyrics; + +import dev.loki.lovisual.render.engine.color.RenderColor; +import dev.loki.lovisual.render.engine.renderer.Renderer3D; +import dev.loki.lovisual.render.engine.text.TextRenderer; +import dev.loki.lovisual.render.engine.text.WorldTextRenderer; +import net.minecraft.util.Mth; +import net.minecraft.world.phys.Vec3; + +public class LyricParticle3D { + private static final double BASE_WORLD_SCALE = 0.0016; + + private final String text; + private final Vec3 basePosition; + private final long spawnTimeMs; + private final long durationMs; + private boolean forceExpire = false; + + public LyricParticle3D(String text, Vec3 basePosition, long spawnTimeMs, long durationMs) { + this.text = text; + this.basePosition = basePosition; + this.spawnTimeMs = spawnTimeMs; + this.durationMs = durationMs; + } + + public Vec3 getBasePosition() { + return basePosition; + } + + public void dismiss() { + this.forceExpire = true; + } + + public boolean isDead(long nowMs) { + return forceExpire || (nowMs - spawnTimeMs >= durationMs); + } + + /** + * Renders this lyric line as a camera-facing 3D billboard with a selectable entrance/exit animation. + */ + public void render(Renderer3D renderer, TextRenderer textRenderer, int colorRgb, String animMode, long nowMs) { + long elapsed = nowMs - spawnTimeMs; + if (elapsed < 0 || elapsed > durationMs || forceExpire) return; + + float progress = Mth.clamp((float) elapsed / (float) durationMs, 0.0f, 1.0f); + + float alpha = 1.0f; + double offsetY = 0.0; + float animScale = 1.0f; + String displayText = this.text; + + switch (animMode) { + case "TYPEWRITER" -> { + if (progress < 0.30f) { + float typeProgress = progress / 0.30f; + int visibleChars = Math.min(this.text.length(), (int) Math.ceil(this.text.length() * typeProgress)); + displayText = this.text.substring(0, Math.max(1, visibleChars)); + alpha = Math.min(1.0f, progress / 0.08f); + offsetY = progress * 0.1; + } else if (progress < 0.75f) { + offsetY = progress * 0.1; + } else { + float eraseProgress = (progress - 0.75f) / 0.25f; + int remainingChars = Math.max(0, this.text.length() - (int) Math.floor(this.text.length() * eraseProgress)); + displayText = this.text.substring(0, remainingChars); + alpha = 1.0f - (eraseProgress * 0.7f); + offsetY = progress * 0.1; + } + } + case "POP_SCALE" -> { + if (progress < 0.18f) { + float t = progress / 0.18f; + alpha = easeOutCubic(t); + animScale = 0.35f + 0.65f * easeOutBack(t); + } else if (progress >= 0.80f) { + float t = (progress - 0.80f) / 0.20f; + alpha = 1.0f - easeInCubic(t); + animScale = 1.0f - 0.05f * t; + } + } + case "KINETIC_SLIDE" -> { + if (progress < 0.20f) { + float t = progress / 0.20f; + alpha = easeOutCubic(t); + offsetY = -0.15 * (1.0f - easeOutCubic(t)); + animScale = 0.92f + 0.08f * t; + } else if (progress >= 0.80f) { + float t = (progress - 0.80f) / 0.20f; + alpha = 1.0f - easeInCubic(t); + offsetY = 0.05 * easeInCubic(t); + animScale = 1.0f - 0.04f * t; + } + } + case "FADE" -> { + if (progress < 0.15f) { + alpha = progress / 0.15f; + } else if (progress >= 0.80f) { + alpha = 1.0f - ((progress - 0.80f) / 0.20f); + } + } + default -> { // LYRIC_FLOW + if (progress < 0.15f) { + float t = progress / 0.15f; + alpha = easeOutCubic(t); + offsetY = -0.06 * (1.0f - easeOutCubic(t)); + animScale = 0.93f + 0.07f * easeOutBack(t); + } else if (progress >= 0.80f) { + float t = (progress - 0.80f) / 0.20f; + alpha = 1.0f - easeInCubic(t); + offsetY = 0.04 * easeInCubic(t); + animScale = 1.0f - 0.03f * t; + } + } + } + + alpha = Mth.clamp(alpha, 0.0f, 1.0f); + if (alpha <= 0.001f || displayText == null || displayText.isBlank()) return; + + int alphaInt = (int) (alpha * 255.0f); + RenderColor color = new RenderColor((alphaInt << 24) | (colorRgb & 0x00FFFFFF)); + + Vec3 anchor = basePosition.add(0.0, offsetY, 0.0); + + WorldTextRenderer.Options options = WorldTextRenderer.Options.defaults() + .withColor(color) + .withCentered(true) + .withShadow(true) + .withWorldScale(BASE_WORLD_SCALE * animScale) + .withDepthMode(Renderer3D.DepthMode.MAIN); + + WorldTextRenderer.drawBillboard(renderer, textRenderer, displayText, anchor, options); + } + + private static float easeOutCubic(float t) { + return 1.0f - (float) Math.pow(1.0f - t, 3); + } + + private static float easeInCubic(float t) { + return (float) Math.pow(t, 3); + } + + private static float easeOutBack(float t) { + float c1 = 1.70158f; + float c3 = c1 + 1.0f; + return 1.0f + c3 * (float) Math.pow(t - 1.0f, 3) + c1 * (float) Math.pow(t - 1.0f, 2); + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/trollface/TrollfaceMask.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/trollface/TrollfaceMask.java new file mode 100644 index 0000000..47156df --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/module/modules/visuals/trollface/TrollfaceMask.java @@ -0,0 +1,163 @@ +/* + * 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.module.modules.visuals.trollface; + +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; +import dev.loki.lovisual.render.engine.renderer.Renderer3D; +import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines; +import dev.loki.lovisual.render.engine.TextureStorage; +import dev.loki.lovisual.render.engine.uniform.MeshBuilder; +import dev.loki.lovisual.util.target.TargetManager; +import net.minecraft.client.Minecraft; +import net.minecraft.resources.Identifier; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.phys.Vec3; + +@ModuleInfo( + id = "trollface_mask", + displayName = "Trollface Mask", + category = ModuleCategory.VISUALS +) +public class TrollfaceMask extends Module { + + private static TrollfaceMask instance; + private final Minecraft mc = Minecraft.getInstance(); + + public final ModeValue target = mode( + "trollfaceTarget", "Себя", + "Себя", "Друзей", "Всех" + ); + + public final ModeValue maskType = mode( + "trollfaceMaskType", "Style 1", + "Style 1", "Style 2", "Style 3", + "Style 4", "Style 5", "Style 6" + ); + + public final NumberValue size = num( + "trollfaceSize", "size", 1.0f, 0.3f, 3.0f + ); + + public final NumberValue alpha = num( + "trollfaceAlpha", "alpha", 1.0f, 0.1f, 1.0f + ); + + public TrollfaceMask() { + instance = this; + } + + public static TrollfaceMask getInstance() { + return instance; + } + + @Override + public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) { + if (mc.player == null || mc.level == null) return; + + String targetMode = target.get(); + boolean selfMode = "Себя".equals(targetMode); + + LivingEntity currentTarget = TargetManager.getTarget(); + boolean hasCrosshairTarget = currentTarget != null && currentTarget.isAlive(); + + if (!selfMode && !hasCrosshairTarget) return; + + LivingEntity renderTarget; + boolean shouldRender; + + switch (targetMode) { + case "Себя" -> { + renderTarget = mc.player; + shouldRender = true; + } + case "Друзей" -> { + if (!hasCrosshairTarget) { shouldRender = false; renderTarget = null; break; } + renderTarget = currentTarget; + shouldRender = true; + } + case "Всех" -> { + if (!hasCrosshairTarget) { shouldRender = false; renderTarget = null; break; } + renderTarget = currentTarget; + shouldRender = true; + } + default -> { + renderTarget = mc.player; + shouldRender = true; + } + } + if (!shouldRender || renderTarget == null) return; + + Vec3 camPos = mc.gameRenderer.mainCamera().position(); + double headX = renderTarget.getX(); + double headY = renderTarget.getY() + renderTarget.getBbHeight() * 0.72; + double headZ = renderTarget.getZ(); + + Vec3 headPos = new Vec3(headX, headY, headZ); + Vec3 viewDir = camPos.subtract(headPos).normalize(); + + if (viewDir.lengthSqr() < 1.0E-8) return; + + Vec3 upVec = new Vec3(0, 1, 0); + Vec3 right = viewDir.cross(upVec).normalize(); + if (right.lengthSqr() < 1.0E-8) { + right = new Vec3(1, 0, 0); + } + Vec3 actualUp = right.cross(viewDir).normalize(); + + float s = size.get(); + float alpha = this.alpha.get(); + int color = 0xFFFFFFFF; + int r = (color >> 16) & 0xFF; + int g = (color >> 8) & 0xFF; + int b = color & 0xFF; + int a = (int) (alpha * 255); + + Vec3 p1 = headPos.add(right.scale(-s)).add(actualUp.scale(-s * 0.75)); + Vec3 p2 = headPos.add(right.scale(s)).add(actualUp.scale(-s * 0.75)); + Vec3 p3 = headPos.add(right.scale(s)).add(actualUp.scale(s * 0.75)); + Vec3 p4 = headPos.add(right.scale(-s)).add(actualUp.scale(s * 0.75)); + + int textureIndex = maskTypeIndex(maskType.get()); + Identifier texId = switch (textureIndex) { + case 0 -> TextureStorage.TROLLFACE_1; + case 1 -> TextureStorage.TROLLFACE_2; + case 2 -> TextureStorage.TROLLFACE_3; + case 3 -> TextureStorage.TROLLFACE_4; + case 4 -> TextureStorage.TROLLFACE_5; + case 5 -> TextureStorage.TROLLFACE_6; + default -> TextureStorage.TROLLFACE_1; + }; + + MeshBuilder mesh = renderer.batchTextured(LoVisualRenderPipelines.WORLD_TEXTURED, texId, Renderer3D.DepthMode.NONE); + if (mesh == null) return; + + mesh.ensureQuadCapacity(); + int i1 = mesh.vec3(p1.x, p1.y, p1.z).vec2(0, 1).color(r, g, b, a).next(); + int i2 = mesh.vec3(p2.x, p2.y, p2.z).vec2(1, 1).color(r, g, b, a).next(); + int i3 = mesh.vec3(p3.x, p3.y, p3.z).vec2(1, 0).color(r, g, b, a).next(); + int i4 = mesh.vec3(p4.x, p4.y, p4.z).vec2(0, 0).color(r, g, b, a).next(); + mesh.quad(i1, i2, i3, i4); + } + + private static int maskTypeIndex(String maskType) { + if (maskType == null) return 0; + return switch (maskType) { + case "Style 1" -> 0; + case "Style 2" -> 1; + case "Style 3" -> 2; + case "Style 4" -> 3; + case "Style 5" -> 4; + case "Style 6" -> 5; + default -> 0; + }; + } +} \ No newline at end of file diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/ItemInHandRendererMixin.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/ItemInHandRendererMixin.java index e502d33..cc6c0a2 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/ItemInHandRendererMixin.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/ItemInHandRendererMixin.java @@ -26,6 +26,7 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyVariable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import dev.loki.lovisual.features.module.Modules; +import dev.loki.lovisual.features.module.modules.visuals.HoldMyItems; import dev.loki.lovisual.features.module.modules.visuals.ViewModel; @Environment(EnvType.CLIENT) @@ -83,6 +84,20 @@ public abstract class ItemInHandRendererMixin { return player != null && (player.isInWater() || player.isInLava()); } + @ModifyVariable( + method = "submitArmWithItem", + at = @At("HEAD"), + argsOnly = true, + index = 7) + private float lovisual$holdMyItemsEquipCurve(float equipProgress, AbstractClientPlayer player, float tickDelta, + float pitch, InteractionHand hand) { + HoldMyItems hmi = Modules.get(HoldMyItems.class); + if (hmi == null || !hmi.isEnabled()) return equipProgress; + if (hand == InteractionHand.OFF_HAND && !hmi.offhandToo.get()) return equipProgress; + + return hmi.shapeEquipProgress(equipProgress); + } + @Inject( method = "renderItem(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/item/ItemDisplayContext;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;I)V", at = @At( diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/LoVisualMixinPlugin.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/LoVisualMixinPlugin.java index e7a7981..dc6dd14 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/LoVisualMixinPlugin.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/LoVisualMixinPlugin.java @@ -51,7 +51,6 @@ public final class LoVisualMixinPlugin implements IMixinConfigPlugin { "dev.loki.lovisual.mixins.xaero.minimap.AbstractWaypointRenderProviderMixin" ); private static final Set OPTIONAL_XAERO_WORLDMAP_MIXINS = Set.of( - "dev.loki.lovisual.mixins.xaero.worldmap.WorldMapWaypointAccessor", "dev.loki.lovisual.mixins.xaero.worldmap.WaypointRenderProviderMixin" ); private static final Set OPTIONAL_MORECULLING_MIXINS = Set.of( 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 3b10c00..ead663c 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 @@ -189,21 +189,6 @@ public class MinecraftMixin implements MinecraftGameConfigHolder { // FREECAM + INTERACT // =============================== - // Reach (FREECAM) - Reach reachModule = Modules.get(Reach.class); - if (reachModule != null && reachModule.isEnabled()) { - EntityHitResult hit = reachModule.raycastEntities( - mc.player, start, dir - ); - - if (hit != null && hit.getEntity() instanceof LivingEntity target && target.isAlive()) { - mc.hitResult = hit; - mc.crosshairPickEntity = target; - ci.cancel(); - return; - } - } - // Blocks (FREECAM) double reach = mc.player.blockInteractionRange(); Vec3 end = start.add(dir.scale(reach)); @@ -220,16 +205,6 @@ public class MinecraftMixin implements MinecraftGameConfigHolder { ci.cancel(); return; } - - Reach reachModule = Modules.get(Reach.class); - if (reachModule != null && reachModule.isEnabled()) { - EntityHitResult entityHit = reachModule.raycastEntities(mc.player); - if (entityHit != null && entityHit.getEntity() instanceof LivingEntity target && target.isAlive()) { - mc.hitResult = entityHit; - mc.crosshairPickEntity = target; - ci.cancel(); - } - } } @Inject(method = "pick", at = @At("RETURN")) diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/MultiPlayerGameModeMixin.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/MultiPlayerGameModeMixin.java index 60d9207..573b6cb 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/MultiPlayerGameModeMixin.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/MultiPlayerGameModeMixin.java @@ -163,9 +163,5 @@ public class MultiPlayerGameModeMixin { TargetManager.onAttack(target); } - @Inject(method = "attack(Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/world/entity/Entity;)V", - at = @At("RETURN")) - private void lovisual$attributeSwapAfterClientAttack(Player player, Entity target, CallbackInfo ci) { - // AttributeSwap module removed; no post-attack shield break remains. - } + } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/xaero/worldmap/WorldMapWaypointAccessor.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/xaero/worldmap/WorldMapWaypointAccessor.java deleted file mode 100644 index d614e71..0000000 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/mixins/xaero/worldmap/WorldMapWaypointAccessor.java +++ /dev/null @@ -1,30 +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.mixins.xaero.worldmap; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Pseudo; -import org.spongepowered.asm.mixin.gen.Accessor; -import xaero.map.mods.gui.Waypoint; - -@Pseudo -@Mixin(Waypoint.class) -public interface WorldMapWaypointAccessor { - - @Accessor - void setX(int x); - - @Accessor - void setY(int y); - - @Accessor - void setZ(int z); - - @Accessor - void setColor(int color); -} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/TextureStorage.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/TextureStorage.java index 030957e..c66ba7f 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/TextureStorage.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/TextureStorage.java @@ -42,6 +42,20 @@ public enum TextureStorage { Identifier.fromNamespaceAndPath("lovisual", "textures/particles/p11.png"), Identifier.fromNamespaceAndPath("lovisual", "textures/particles/p12.png") }; + public static final Identifier TROLLFACE_1 = + Identifier.fromNamespaceAndPath("lovisual", "textures/trollface/trollface_1.png"); + public static final Identifier TROLLFACE_2 = + Identifier.fromNamespaceAndPath("lovisual", "textures/trollface/trollface_2.png"); + public static final Identifier TROLLFACE_3 = + Identifier.fromNamespaceAndPath("lovisual", "textures/trollface/trollface_3.png"); + public static final Identifier TROLLFACE_4 = + Identifier.fromNamespaceAndPath("lovisual", "textures/trollface/trollface_4.png"); + public static final Identifier TROLLFACE_5 = + Identifier.fromNamespaceAndPath("lovisual", "textures/trollface/trollface_5.png"); + public static final Identifier TROLLFACE_6 = + Identifier.fromNamespaceAndPath("lovisual", "textures/trollface/trollface_6.png"); + public static final Identifier TROLLFACE_CLASSIC = + Identifier.fromNamespaceAndPath("lovisual", "textures/trollface/trollface_classic.png"); public static final Identifier CAPTURE = Identifier.fromNamespaceAndPath("lovisual", "textures/hud/elements/capture.png"); @@ -58,6 +72,13 @@ public enum TextureStorage { tm.getTexture(FUNNEL_DISTORTION); tm.getTexture(PARTICLE_STARS); tm.getTexture(CAPTURE); + tm.getTexture(TROLLFACE_1); + tm.getTexture(TROLLFACE_2); + tm.getTexture(TROLLFACE_3); + tm.getTexture(TROLLFACE_4); + tm.getTexture(TROLLFACE_5); + tm.getTexture(TROLLFACE_6); + tm.getTexture(TROLLFACE_CLASSIC); for (Identifier id : RANDOM_PARTICLES) { tm.getTexture(id); } 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 ffde26a..b9fbf3e 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 @@ -101,6 +101,7 @@ public enum LoVisualRenderPipelines { public static final Identifier SHADER_POST_FX_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/post_fx.frag"); public static final Identifier SHADER_MOTION_BLUR_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/motion_blur.frag"); public static final Identifier SHADER_DEPTH_OF_FIELD_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/depth_of_field.frag"); + public static final Identifier SHADER_WET_SURFACE_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/wet_surface.frag"); public static final Identifier SHADER_HEAT_FX_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/heat_fx.frag"); public static final Identifier SHADER_ESP_GRADIENT_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/shader_esp_gradient.frag"); public static final Identifier SHADER_ESP_SHADOW_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/shader_esp_shadow.frag"); @@ -121,6 +122,9 @@ public enum LoVisualRenderPipelines { 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"); + public static final Identifier SHADER_SKY_SHADER_SKY_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/sky_shader_sky.frag"); + public static final Identifier SHADER_SKY_SHADER_WATER_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/sky_shader_water.frag"); + public static final Identifier SHADER_SKY_SHADER_CAUSTIC_FRAG = Identifier.fromNamespaceAndPath("lovisual", "shaders/sky_shader_caustic.frag"); private static final List PIPELINES = new ArrayList<>(); public static final RenderPipeline GUI_TEXTURE_LOOKUP = add(new ExtendedRenderPipelineBuilder(net.minecraft.client.renderer.RenderPipelines.GUI_TEXTURED_SNIPPET) .withLocation(Identifier.fromNamespaceAndPath("lovisual", "pipeline/gui_texture_lookup")) @@ -735,6 +739,45 @@ public enum LoVisualRenderPipelines { .withCull(false) .build() ); + /** + * Fullscreen SkyShader passes (pos2). Sky/water/caustic share the same uniform layout. + */ + public static final RenderPipeline SKY_SHADER_SKY = add(new ExtendedRenderPipelineBuilder(MESH_UNIFORMS) + .withLocation(Identifier.fromNamespaceAndPath("lovisual", "pipeline/sky_shader_sky")) + .withVertexFormat(LoVisualVertexFormats.POS2, com.mojang.blaze3d.PrimitiveTopology.TRIANGLES) + .withVertexShader(SHADER_DAMAGE_TINT_VERT) + .withFragmentShader(SHADER_SKY_SHADER_SKY_FRAG) + .withUniform("SkyShader", UniformType.UNIFORM_BUFFER) + .withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST) + .withDepthWrite(false) + .withBlend(BlendFunction.TRANSLUCENT) + .withCull(false) + .build() + ); + public static final RenderPipeline SKY_SHADER_WATER = add(new ExtendedRenderPipelineBuilder(MESH_UNIFORMS) + .withLocation(Identifier.fromNamespaceAndPath("lovisual", "pipeline/sky_shader_water")) + .withVertexFormat(LoVisualVertexFormats.POS2, com.mojang.blaze3d.PrimitiveTopology.TRIANGLES) + .withVertexShader(SHADER_DAMAGE_TINT_VERT) + .withFragmentShader(SHADER_SKY_SHADER_WATER_FRAG) + .withUniform("SkyShader", UniformType.UNIFORM_BUFFER) + .withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST) + .withDepthWrite(false) + .withBlend(BlendFunction.TRANSLUCENT) + .withCull(false) + .build() + ); + public static final RenderPipeline SKY_SHADER_CAUSTIC = add(new ExtendedRenderPipelineBuilder(MESH_UNIFORMS) + .withLocation(Identifier.fromNamespaceAndPath("lovisual", "pipeline/sky_shader_caustic")) + .withVertexFormat(LoVisualVertexFormats.POS2, com.mojang.blaze3d.PrimitiveTopology.TRIANGLES) + .withVertexShader(SHADER_DAMAGE_TINT_VERT) + .withFragmentShader(SHADER_SKY_SHADER_CAUSTIC_FRAG) + .withUniform("SkyShader", UniformType.UNIFORM_BUFFER) + .withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST) + .withDepthWrite(false) + .withBlend(BlendFunction.TRANSLUCENT) + .withCull(false) + .build() + ); /** * Fullscreen main menu background (pos2). */ @@ -822,6 +865,23 @@ public enum LoVisualRenderPipelines { .withCull(false) .build() ); + /** + * Fullscreen wet-ground screen-space reflection pass (pos2). + */ + public static final RenderPipeline WET_SURFACE = add(new ExtendedRenderPipelineBuilder(MESH_UNIFORMS) + .withLocation(Identifier.fromNamespaceAndPath("lovisual", "pipeline/wet_surface")) + .withVertexFormat(LoVisualVertexFormats.POS2, com.mojang.blaze3d.PrimitiveTopology.TRIANGLES) + .withVertexShader(SHADER_DAMAGE_TINT_VERT) + .withFragmentShader(SHADER_WET_SURFACE_FRAG) + .withSampler("u_Texture") + .withSampler("u_MainDepth") + .withUniform("WetSurface", UniformType.UNIFORM_BUFFER) + .withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST) + .withDepthWrite(false) + .withBlend(BlendFunction.TRANSLUCENT) + .withCull(false) + .build() + ); /** * Fullscreen heat distortion + vignette (pos2). */ diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/postprocess/SkyShaderRenderer.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/postprocess/SkyShaderRenderer.java new file mode 100644 index 0000000..aebe955 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/postprocess/SkyShaderRenderer.java @@ -0,0 +1,115 @@ +/* + * 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.render.engine.postprocess; + +import com.mojang.blaze3d.ProjectionType; +import com.mojang.blaze3d.buffers.GpuBufferSlice; +import com.mojang.blaze3d.pipeline.RenderPipeline; +import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.systems.RenderSystem; +import dev.loki.lovisual.render.engine.RenderState; +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.impl.SkyShaderUniforms; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.ProjectionMatrixBuffer; +import net.minecraft.util.Util; +import org.joml.Matrix4f; + +/** + * Fullscreen SkyShader pass: sky / water / caustic procedural GLSL effect drawn behind the world, + * with a camera-direction-driven ray so it reads correctly regardless of look direction. + */ +public enum SkyShaderRenderer { + ; + + public enum Mode { + SKY, WATER, CAUSTIC + } + + private static final Matrix4f IDENTITY = new Matrix4f(); + private static ProjectionMatrixBuffer projection; + private static long startMillis = -1L; + + public static void reset() { + startMillis = -1L; + } + + public static void render(Minecraft mc, Mode mode, float speed, float scale, float intensity, + float alpha, int colorRgb) { + if (mc == null || mc.level == null || mc.player == null) return; + + RenderTarget fb = mc.gameRenderer.mainRenderTarget(); + if (fb == null) return; + + int w = mc.getWindow().getWidth(); + int h = mc.getWindow().getHeight(); + if (w <= 0 || h <= 0) return; + + FullScreenRenderer.ensureInit(); + + if (startMillis < 0) startMillis = Util.getMillis(); + float time = (Util.getMillis() - startMillis) / 1000.0f; + + float cr = ((colorRgb >>> 16) & 0xFF) / 255f; + float cg = ((colorRgb >>> 8) & 0xFF) / 255f; + float cb = (colorRgb & 0xFF) / 255f; + + Camera cam = mc.gameRenderer.mainCamera(); + float yawRad = (float) Math.toRadians(-cam.yRot()); + float pitchRad = (float) Math.toRadians(cam.xRot()); + float fov = (float) mc.options.fov().get().intValue(); + + SkyShaderUniforms.update(time, speed, scale, intensity, w, h, alpha, fov, cr, cg, cb, yawRad, pitchRad); + + var mv = RenderSystem.getModelViewStack(); + boolean pushedModelView = false; + + GpuBufferSlice prevProj = RenderSystem.getProjectionMatrixBuffer(); + ProjectionType prevProjType = RenderSystem.getProjectionType(); + Matrix4f prevMeshProjection = MeshRenderer.projection(); + boolean prevRendering3D = RenderState.rendering3D; + + try { + mv.pushMatrix(); + pushedModelView = true; + mv.identity(); + + if (projection == null) { + projection = new ProjectionMatrixBuffer("lovisual-sky-shader-projection"); + } + Matrix4f proj = IDENTITY.identity(); + RenderSystem.setProjectionMatrix(projection.getBuffer(proj), ProjectionType.PERSPECTIVE); + MeshRenderer.setProjection(proj); + RenderState.rendering3D = false; + + RenderPipeline pipeline = switch (mode) { + case WATER -> LoVisualRenderPipelines.SKY_SHADER_WATER; + case CAUSTIC -> LoVisualRenderPipelines.SKY_SHADER_CAUSTIC; + default -> LoVisualRenderPipelines.SKY_SHADER_SKY; + }; + + FullScreenRenderer.begin("LoVisual Fullscreen Pass") + .attachment(fb) + .pipeline(pipeline) + .uniform("SkyShader", SkyShaderUniforms.get()) + .end(); + } finally { + if (pushedModelView) { + mv.popMatrix(); + } + MeshRenderer.setProjection(prevMeshProjection); + RenderState.rendering3D = prevRendering3D; + if (prevProj != null && prevProjType != null) { + RenderSystem.setProjectionMatrix(prevProj, prevProjType); + } + } + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/uniform/impl/SkyShaderUniforms.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/uniform/impl/SkyShaderUniforms.java new file mode 100644 index 0000000..e23f320 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/uniform/impl/SkyShaderUniforms.java @@ -0,0 +1,81 @@ +/* + * 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.render.engine.uniform.impl; + +import dev.loki.lovisual.render.engine.core.LoVisualRenderSystem; + +import com.mojang.blaze3d.buffers.GpuBufferSlice; +import com.mojang.blaze3d.buffers.Std140Builder; +import com.mojang.blaze3d.buffers.Std140SizeCalculator; +import dev.loki.lovisual.render.engine.rhi.uniform.LoVisualUniformAllocator; + +public enum SkyShaderUniforms { + ; + public static final int SIZE = new Std140SizeCalculator() + .putVec4() + .putVec4() + .putVec4() + .putVec4() + .get(); + + private static final Data DATA = new Data(); + private static final String UNIFORM_NAME = "LoVisual - SkyShader UBO"; + private static final int EXPECTED_WRITES_PER_FRAME = 4; + + public static void update(float time, float speed, float scale, float intensity, + float width, float height, float alpha, float fov, + float colorR, float colorG, float colorB, + float cameraDirX, float cameraDirY) { + DATA.params0[0] = time; + DATA.params0[1] = speed; + DATA.params0[2] = scale; + DATA.params0[3] = intensity; + + DATA.params1[0] = width; + DATA.params1[1] = height; + DATA.params1[2] = alpha; + DATA.params1[3] = fov; + + DATA.color[0] = colorR; + DATA.color[1] = colorG; + DATA.color[2] = colorB; + DATA.color[3] = 0.0f; + + DATA.cameraDir[0] = cameraDirX; + DATA.cameraDir[1] = cameraDirY; + DATA.cameraDir[2] = 0.0f; + DATA.cameraDir[3] = 0.0f; + + LoVisualRenderSystem.uniforms().write(UNIFORM_NAME, SIZE, EXPECTED_WRITES_PER_FRAME, DATA); + } + + public static GpuBufferSlice get() { + return LoVisualRenderSystem.uniforms().current(UNIFORM_NAME); + } + + private static final class Data implements LoVisualUniformAllocator.UniformWriter { + private final float[] params0 = new float[4]; + private final float[] params1 = new float[4]; + private final float[] color = new float[4]; + private final float[] cameraDir = new float[4]; + + @Override + public void write(java.nio.ByteBuffer buffer) { + Std140Builder.intoBuffer(buffer) + .putFloat(params0[0]).putFloat(params0[1]).putFloat(params0[2]).putFloat(params0[3]) + .putFloat(params1[0]).putFloat(params1[1]).putFloat(params1[2]).putFloat(params1[3]) + .putFloat(color[0]).putFloat(color[1]).putFloat(color[2]).putFloat(color[3]) + .putFloat(cameraDir[0]).putFloat(cameraDir[1]).putFloat(cameraDir[2]).putFloat(cameraDir[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/WetSurfaceUniforms.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/uniform/impl/WetSurfaceUniforms.java new file mode 100644 index 0000000..b4e5099 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/uniform/impl/WetSurfaceUniforms.java @@ -0,0 +1,86 @@ +/* + * 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.render.engine.uniform.impl; + +import com.mojang.blaze3d.buffers.GpuBufferSlice; +import com.mojang.blaze3d.buffers.Std140Builder; +import com.mojang.blaze3d.buffers.Std140SizeCalculator; +import org.joml.Matrix4f; +import dev.loki.lovisual.render.engine.core.LoVisualRenderSystem; +import dev.loki.lovisual.render.engine.rhi.uniform.LoVisualUniformAllocator; + +public enum WetSurfaceUniforms { + ; + public static final int SIZE = new Std140SizeCalculator() + .putMat4f() + .putMat4f() + .putFloat().putFloat().putFloat().putFloat() + .putFloat().putFloat().putFloat().putFloat() + .putFloat().putFloat().putFloat().putFloat() + .putFloat().putFloat().putFloat().putFloat() + .get(); + + private static final Data DATA = new Data(); + private static final String UNIFORM_NAME = "LoVisual - WetSurface UBO"; + private static final int EXPECTED_WRITES_PER_FRAME = 2; + + public static void update(Matrix4f projection, + Matrix4f invView, + float camX, float camY, float camZ, + float invW, float invH, float time, boolean mainDepthEnabled, + float wetness, float puddleCoverage, float reflectionStrength, float rippleStrength, + float maxDistance, int reflectionSteps, float rainAmount, boolean ripplesEnabled) { + DATA.projection.set(projection); + DATA.invView.set(invView); + DATA.camX = camX; + DATA.camY = camY; + DATA.camZ = camZ; + DATA.invW = invW; + DATA.invH = invH; + DATA.time = time; + DATA.mainDepthEnabled = mainDepthEnabled ? 1.0f : 0.0f; + DATA.wetness = wetness; + DATA.puddleCoverage = puddleCoverage; + DATA.reflectionStrength = reflectionStrength; + DATA.rippleStrength = rippleStrength; + DATA.maxDistance = maxDistance; + DATA.reflectionSteps = reflectionSteps; + DATA.rainAmount = rainAmount; + DATA.ripplesEnabled = ripplesEnabled ? 1.0f : 0.0f; + LoVisualRenderSystem.uniforms().write(UNIFORM_NAME, SIZE, EXPECTED_WRITES_PER_FRAME, DATA); + } + + public static GpuBufferSlice get() { + return LoVisualRenderSystem.uniforms().current(UNIFORM_NAME); + } + + private static final class Data implements LoVisualUniformAllocator.UniformWriter { + private final Matrix4f projection = new Matrix4f(); + private final Matrix4f invView = new Matrix4f(); + private float camX, camY, camZ; + private float invW, invH, time, mainDepthEnabled; + private float wetness, puddleCoverage, reflectionStrength, rippleStrength; + private float maxDistance, reflectionSteps, rainAmount, ripplesEnabled; + + @Override + public void write(java.nio.ByteBuffer buffer) { + Std140Builder b = Std140Builder.intoBuffer(buffer); + b.putMat4f(projection); + b.putMat4f(invView); + b.putFloat(camX).putFloat(camY).putFloat(camZ).putFloat(0.0f); + b.putFloat(invW).putFloat(invH).putFloat(time).putFloat(mainDepthEnabled); + b.putFloat(wetness).putFloat(puddleCoverage).putFloat(reflectionStrength).putFloat(rippleStrength); + b.putFloat(maxDistance).putFloat(reflectionSteps).putFloat(rainAmount).putFloat(ripplesEnabled); + } + + @Override + public boolean equals(Object o) { + return false; + } + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/combat/AttackUtil.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/combat/AttackUtil.java index 7650cc1..f0c8ba4 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/combat/AttackUtil.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/combat/AttackUtil.java @@ -15,7 +15,7 @@ package dev.loki.lovisual.util.combat; import net.minecraft.client.Minecraft; import net.minecraft.client.player.LocalPlayer; -import net.minecraft.network.protocol.game.ServerboundInteractPacket; +import net.minecraft.network.protocol.game.ServerboundAttackPacket; import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; import net.minecraft.network.protocol.game.ServerboundPlayerCommandPacket; import net.minecraft.world.InteractionHand; @@ -155,7 +155,9 @@ public enum AttackUtil { } notifyLocalAttack(target); - mc.getConnection().send(new ServerboundInteractPacket(target.getId(), null, null, player.isShiftKeyDown())); + mc.getConnection().send(new ServerboundAttackPacket(target.getId())); + player.attack(target); + player.resetAttackStrengthTicker(); ResetAttackCooldown.resetAttackCooldown(player); return true; } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/combat/VulcanReachController.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/combat/VulcanReachController.java deleted file mode 100644 index 8c8de16..0000000 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/util/combat/VulcanReachController.java +++ /dev/null @@ -1,721 +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.util.combat; - -import net.minecraft.client.Minecraft; -import net.minecraft.client.player.LocalPlayer; -import net.minecraft.core.Holder; -import net.minecraft.core.Registry; -import net.minecraft.core.registries.Registries; -import net.minecraft.network.protocol.game.*; -import net.minecraft.resources.ResourceKey; -import net.minecraft.util.Mth; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.ai.attributes.Attributes; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.enchantment.Enchantment; -import net.minecraft.world.item.enchantment.EnchantmentHelper; -import net.minecraft.world.item.enchantment.Enchantments; -import net.minecraft.world.phys.AABB; -import net.minecraft.world.phys.Vec3; -import dev.loki.lovisual.events.EventHandler; -import dev.loki.lovisual.events.impl.GameTickEvent; -import dev.loki.lovisual.events.impl.PacketEvent; -import dev.loki.lovisual.util.network.BacktrackController; -import dev.loki.lovisual.util.player.NetworkStatsUtil; - -import java.util.*; - -/** - * Local model for Vulcan 2.9.7 Reach A/B. - * This does not change combat by itself; Reach/Hitbox should query planReach() - * and actual sent attacks are observed through the same attack -> movement split. - */ -public final class VulcanReachController { - - public static final VulcanReachController INSTANCE = new VulcanReachController(); - - private static final double REACH_A_CONFIG_MAX = 3.03; - private static final double REACH_A_BODY_SUBTRACT = 0.52; - private static final double REACH_A_BUFFER_MAX = 3.0; - private static final double REACH_A_LATCH_RELEASE = REACH_A_BUFFER_MAX - 1.0; - private static final double REACH_A_DECAY = 0.035; - private static final double REACH_A_NO_HISTORY_DECAY = 0.025; - private static final double REACH_A_RECENT_SERVER_ALLOWANCE = 1.15; - private static final double REACH_A_LIQUID_ALLOWANCE = 0.1; - private static final double REACH_A_SANITY_CAP = 8.0; - - private static final double REACH_B_CONFIG_MAX = 3.15; - private static final double REACH_B_BODY_SUBTRACT = 0.565; - private static final double REACH_B_BUFFER_MAX = 6.0; - private static final double REACH_B_LATCH_RELEASE = REACH_B_BUFFER_MAX - 1.0; - private static final double REACH_B_DECAY = 0.225; - private static final double REACH_B_BASE_EXTRA = 0.05; - private static final double REACH_B_HORIZONTAL_ALLOWANCE = 0.9; - private static final double REACH_B_VERTICAL_ALLOWANCE = 0.66; - private static final double REACH_B_MOVING_STATE_ALLOWANCE = 0.32; - private static final double REACH_B_FRESH_STATE_ALLOWANCE = 0.35; - private static final double REACH_B_STATE_COUNTER_SCALE = 0.125; - private static final double REACH_B_HIGH_LATENCY_ALLOWANCE = 1.25; - private static final double REACH_B_SANITY_CAP = 6.0; - - private static final long SERVER_POSITION_WINDOW_MS = 1500L; - private static final long JOIN_WORLD_WINDOW_MS = 1500L; - private static final long HISTORY_MAX_AGE_MS = 700L; - private static final long HISTORY_STRICT_WINDOW_MS = 250L; - private static final int HISTORY_MAX_SAMPLES = 20; - private static final double BUFFER_EPSILON = 1.0E-6; - private static final double FAST_POST_HIT_MOVE_THRESHOLD = 0.18; - private static final double FAST_POST_HIT_APPROACH_ALLOWANCE = 0.45; - - private final Minecraft mc = Minecraft.getInstance(); - private final Map> targetHistory = new HashMap<>(); - - private double reachABuffer; - private double reachBBuffer; - private int pendingAttackEntityId = -1; - private long pendingAttackMs; - private Vec3 pendingAttackPosition; - private long lastServerPositionMs = Long.MIN_VALUE; - private long lastJoinOrWorldMs = Long.MIN_VALUE; - private Vec3 lastSentPosition; - private double lastSentHorizontalDelta; - private double lastSentVerticalDelta; - private int movementStateTicks; - private boolean reachAClampLatched; - private boolean reachBClampLatched; - - private VulcanReachController() { - } - - public ReachPlan planReach(Entity target, double requestedReach) { - LocalPlayer player = mc.player; - if (player == null || target == null) { - return ReachPlan.empty(requestedReach); - } - - CheckState state = buildState(player, target, false); - updateClampLatches(); - - boolean forceReachAClamp = reachAClampLatched && !state.reachAExempt; - boolean forceReachBClamp = reachBClampLatched && !state.reachBExempt; - - double strictReachALimit = state.reachAExempt - ? requestedReach - : reachAEdgeLimit(player, target, state.reachAAllowed); - double reachALimit = state.reachAExempt || (state.noReachAHistory && !reachAClampLatched) - ? requestedReach - : strictReachALimit; - if (state.fastPostHitApproach) { - reachALimit = Math.min(requestedReach, reachALimit + FAST_POST_HIT_APPROACH_ALLOWANCE); - } - double reachBLimit = state.reachBExempt - ? requestedReach - : reachBEdgeLimit(player, target, state.reachBAllowed); - - boolean overB = !state.reachBExempt - && requestedReach > reachBLimit; - boolean spendsA = !state.reachAExempt - && !state.noReachAHistory - && requestedReach > strictReachALimit - && state.reachAMeasured < REACH_A_SANITY_CAP; - boolean spendsB = overB && state.reachBMeasured < REACH_B_SANITY_CAP; - - double clamped = requestedReach; - if (forceReachAClamp) { - clamped = Math.min(clamped, strictReachALimit); - } - if (forceReachBClamp) { - clamped = Math.min(clamped, reachBLimit); - } - - return new ReachPlan( - requestedReach, - Math.max(0.0, clamped), - Math.max(0.0, reachALimit), - Math.max(0.0, reachBLimit), - reachABuffer, - reachBBuffer, - state.reachAExempt, - state.reachBExempt, - state.noReachAHistory, - spendsA, - spendsB, - state.reason - ); - } - - public double clampReach(Entity target, double requestedReach) { - return planReach(target, requestedReach).allowedReach(); - } - - public HudSnapshot hudSnapshot(Entity target, double requestedReach) { - if (!(target instanceof Player)) { - return null; - } - - ReachPlan plan = planReach(target, requestedReach); - boolean dirty = reachABuffer > 1.0E-4 - || reachBBuffer > 1.0E-4 - || reachAClampLatched - || reachBClampLatched; - - double displayReach = plan.allowedReach(); - boolean clamped = displayReach + BUFFER_EPSILON < requestedReach; - - int rawRemaining = Math.min( - longHitsRemaining(reachABuffer, REACH_A_BUFFER_MAX, reachAClampLatched), - longHitsRemaining(reachBBuffer, REACH_B_BUFFER_MAX, reachBClampLatched) - ); - int remaining = clamped ? 0 : Math.max(1, rawRemaining); - - return new HudSnapshot(displayReach, Math.max(0, remaining), dirty, plan.reason()); - } - - public HudSnapshot hudSnapshot(double requestedReach) { - boolean dirty = reachABuffer > 1.0E-4 - || reachBBuffer > 1.0E-4 - || reachAClampLatched - || reachBClampLatched; - - int rawRemaining = Math.min( - longHitsRemaining(reachABuffer, REACH_A_BUFFER_MAX, reachAClampLatched), - longHitsRemaining(reachBBuffer, REACH_B_BUFFER_MAX, reachBClampLatched) - ); - int remaining = reachAClampLatched || reachBClampLatched ? 0 : Math.max(1, rawRemaining); - - return new HudSnapshot(requestedReach, Math.max(0, remaining), dirty, "no_target"); - } - - public double getReachABuffer() { - return reachABuffer; - } - - public double getReachBBuffer() { - return reachBBuffer; - } - - public void clear() { - reachABuffer = 0.0; - reachBBuffer = 0.0; - pendingAttackEntityId = -1; - pendingAttackMs = 0L; - pendingAttackPosition = null; - lastSentPosition = null; - lastSentHorizontalDelta = 0.0; - lastSentVerticalDelta = 0.0; - movementStateTicks = 0; - reachAClampLatched = false; - reachBClampLatched = false; - targetHistory.clear(); - } - - @EventHandler - private void onGameTick(GameTickEvent event) { - LocalPlayer player = mc.player; - if (player == null || mc.level == null) { - clear(); - return; - } - - long now = System.currentTimeMillis(); - for (Player other : mc.level.players()) { - if (other == player || !other.isAlive()) { - continue; - } - addHistorySample(other, now); - } - pruneHistory(now); - } - - @EventHandler - private void onPacketSendPost(PacketEvent.SendPost event) { - if (event.getPacket() instanceof ServerboundInteractPacket packet) { - onAttackPacket(packet); - return; - } - - if (event.getPacket() instanceof ServerboundMovePlayerPacket packet) { - updateSentMovement(packet); - evaluatePendingAttack(); - } - } - - @EventHandler - private void onPacketReceive(PacketEvent.Receive event) { - if (event.getPacket() instanceof ClientboundPlayerPositionPacket) { - lastServerPositionMs = System.currentTimeMillis(); - return; - } - - if (event.getPacket() instanceof ClientboundLoginPacket) { - clear(); - lastJoinOrWorldMs = System.currentTimeMillis(); - return; - } - - if (event.getPacket() instanceof ClientboundRespawnPacket) { - pendingAttackEntityId = -1; - } - } - - private void onAttackPacket(ServerboundInteractPacket packet) { - if (!isAttack(packet) || mc.level == null) { - return; - } - - int entityId = packet.entityId(); - Entity target = mc.level.getEntity(entityId); - if (!(target instanceof Player)) { - pendingAttackEntityId = -1; - return; - } - - pendingAttackEntityId = entityId; - pendingAttackMs = System.currentTimeMillis(); - pendingAttackPosition = mc.player != null ? mc.player.position() : null; - } - - private void evaluatePendingAttack() { - if (pendingAttackEntityId < 0 || mc.level == null || mc.player == null) { - return; - } - - Entity target = mc.level.getEntity(pendingAttackEntityId); - pendingAttackEntityId = -1; - - if (!(target instanceof Player)) { - return; - } - - CheckState state = buildState(mc.player, target, true); - applyReachA(state); - applyReachB(state); - pendingAttackPosition = null; - } - - private void applyReachA(CheckState state) { - if (state.reachAExempt) { - decayABuffer(REACH_A_DECAY); - return; - } - - if (state.noReachAHistory) { - decayABuffer(REACH_A_NO_HISTORY_DECAY); - return; - } - - if (state.reachAMeasured > state.reachAAllowed && state.reachAMeasured < REACH_A_SANITY_CAP) { - reachABuffer = increaseLocalBuffer(reachABuffer, REACH_A_BUFFER_MAX); - } else { - decayABuffer(REACH_A_DECAY); - } - } - - private void applyReachB(CheckState state) { - if (state.reachBExempt) { - decayBBuffer(REACH_B_DECAY); - return; - } - - if (state.reachBMeasured > state.reachBAllowed && state.reachBMeasured < REACH_B_SANITY_CAP) { - reachBBuffer = increaseLocalBuffer(reachBBuffer, REACH_B_BUFFER_MAX); - } else { - decayBBuffer(REACH_B_DECAY); - } - } - - private CheckState buildState(LocalPlayer player, Entity target, boolean evaluatingPendingAttack) { - if (!(target instanceof Player targetPlayer)) { - return CheckState.exempt("non_player"); - } - - boolean attackerVehicle = player.isPassenger(); - boolean targetVehicle = targetPlayer.isPassenger(); - boolean attackerBoat = attackerVehicle && isBoatLike(player.getVehicle()); - boolean targetBoat = targetVehicle && isBoatLike(targetPlayer.getVehicle()); - boolean creative = player.isCreative(); - boolean liquid = player.isInWater() || player.isUnderWater() || player.isInLava(); - boolean recentServer = isRecent(lastServerPositionMs, SERVER_POSITION_WINDOW_MS); - boolean recentJoinWorld = isRecent(lastJoinOrWorldMs, JOIN_WORLD_WINDOW_MS); - boolean knockback = hasKnockback(player.getMainHandItem()); - boolean fastPostHitApproach = evaluatingPendingAttack && isFastPostHitApproach(player, targetPlayer); - - boolean reachAExempt = creative || attackerVehicle || attackerBoat || targetVehicle || targetBoat || knockback; - boolean reachBExempt = creative || attackerVehicle || attackerBoat || targetVehicle || targetBoat || recentServer || recentJoinWorld; - - double baseReach = getInteractionRange(player); - double reachAAllowed = baseReach + (REACH_A_CONFIG_MAX - 3.0); - if (recentServer) { - reachAAllowed += REACH_A_RECENT_SERVER_ALLOWANCE; - } - if (liquid) { - reachAAllowed += REACH_A_LIQUID_ALLOWANCE; - } - - double reachBAllowed = baseReach + (REACH_B_CONFIG_MAX - 3.0) + REACH_B_BASE_EXTRA; - reachBAllowed += lastSentHorizontalDelta * REACH_B_HORIZONTAL_ALLOWANCE; - reachBAllowed += lastSentVerticalDelta * REACH_B_VERTICAL_ALLOWANCE; - if (lastSentHorizontalDelta > 1.0E-4 || lastSentVerticalDelta > 1.0E-4) { - reachBAllowed += REACH_B_MOVING_STATE_ALLOWANCE; - } - if (movementStateTicks < 3) { - reachBAllowed += REACH_B_FRESH_STATE_ALLOWANCE; - } - reachBAllowed += Math.min(4, movementStateTicks) * REACH_B_STATE_COUNTER_SCALE; - if (NetworkStatsUtil.getPing(mc) > 100) { - reachBAllowed += REACH_B_HIGH_LATENCY_ALLOWANCE; - } - - double reachAMeasured = reachAMeasureDistance(player, targetPlayer); - double reachBMeasured = Math.max(0.0, player.position().distanceTo(targetPlayer.position()) - REACH_B_BODY_SUBTRACT); - boolean noHistory = reachAMeasured < 0.0; - - String reason = resolveReason(reachAExempt, reachBExempt, noHistory, recentServer, recentJoinWorld, liquid, knockback); - return new CheckState( - reachAExempt, - reachBExempt, - noHistory, - reachAAllowed, - reachBAllowed, - reachAMeasured, - reachBMeasured, - fastPostHitApproach, - reason - ); - } - - private double reachAMeasureDistance(LocalPlayer player, Player target) { - if (shouldUseBacktrackHistory(target)) { - return reachAHistoryDistance(player, target); - } - - Vec3 playerPos = player.position(); - Vec3 targetPos = target.position(); - double dx = playerPos.x - targetPos.x; - double dz = playerPos.z - targetPos.z; - return Math.max(0.0, Math.sqrt(dx * dx + dz * dz) - REACH_A_BODY_SUBTRACT); - } - - private boolean shouldUseBacktrackHistory(Entity target) { - BacktrackController backtrack = BacktrackController.INSTANCE; - Entity backtrackTarget = backtrack.getTarget(); - return backtrack.isEnabled() - && backtrack.isLagging() - && backtrackTarget != null - && target != null - && backtrackTarget.getId() == target.getId(); - } - - private double reachAHistoryDistance(LocalPlayer player, Player target) { - ArrayDeque samples = targetHistory.get(target.getUUID()); - if (samples == null || samples.isEmpty()) { - return -1.0; - } - - long now = pendingAttackMs > 0L ? pendingAttackMs : System.currentTimeMillis(); - Vec3 playerPos = player.position(); - double best = Double.MAX_VALUE; - - for (HistorySample sample : samples) { - long age = Math.abs(now - sample.timeMs()); - if (age > HISTORY_STRICT_WINDOW_MS) { - continue; - } - double dx = playerPos.x - sample.pos().x; - double dz = playerPos.z - sample.pos().z; - best = Math.min(best, Math.sqrt(dx * dx + dz * dz) - REACH_A_BODY_SUBTRACT); - } - - return best == Double.MAX_VALUE ? -1.0 : Math.max(0.0, best); - } - - private double reachAEdgeLimit(LocalPlayer player, Entity target, double allowed) { - Vec3 from = player.getEyePosition(1.0F); - AABB box = target.getBoundingBox(); - Vec3 center = box.getCenter(); - - double centerDistance = Math.sqrt(squaredHorizontalDistance(from.x, from.z, center.x, center.z)); - double edgeDistance = horizontalBoxDistance(box, from); - double centerToEdgeReduction = Math.max(0.0, centerDistance - edgeDistance); - - return Math.max(0.0, allowed + REACH_A_BODY_SUBTRACT - centerToEdgeReduction); - } - - private double reachBEdgeLimit(LocalPlayer player, Entity target, double allowed) { - Vec3 from = player.getEyePosition(1.0F); - AABB box = target.getBoundingBox(); - Vec3 center = box.getCenter(); - - double centerDistance = from.distanceTo(center); - double edgeDistance = Math.sqrt(boxDistanceSq(box, from)); - double centerToEdgeReduction = Math.max(0.0, centerDistance - edgeDistance); - - return Math.max(0.0, allowed + REACH_B_BODY_SUBTRACT - centerToEdgeReduction); - } - - private double horizontalBoxDistance(AABB box, Vec3 point) { - double dx = 0.0; - if (point.x < box.minX) dx = box.minX - point.x; - else if (point.x > box.maxX) dx = point.x - box.maxX; - - double dz = 0.0; - if (point.z < box.minZ) dz = box.minZ - point.z; - else if (point.z > box.maxZ) dz = point.z - box.maxZ; - - return Math.sqrt(dx * dx + dz * dz); - } - - private double boxDistanceSq(AABB box, Vec3 point) { - double dx = 0.0; - if (point.x < box.minX) dx = box.minX - point.x; - else if (point.x > box.maxX) dx = point.x - box.maxX; - - double dy = 0.0; - if (point.y < box.minY) dy = box.minY - point.y; - else if (point.y > box.maxY) dy = point.y - box.maxY; - - double dz = 0.0; - if (point.z < box.minZ) dz = box.minZ - point.z; - else if (point.z > box.maxZ) dz = point.z - box.maxZ; - - return dx * dx + dy * dy + dz * dz; - } - - private double squaredHorizontalDistance(double x1, double z1, double x2, double z2) { - double dx = x1 - x2; - double dz = z1 - z2; - return dx * dx + dz * dz; - } - - private void updateSentMovement(ServerboundMovePlayerPacket packet) { - LocalPlayer player = mc.player; - if (player == null) { - return; - } - - Vec3 current = new Vec3( - packet.getX(player.getX()), - packet.getY(player.getY()), - packet.getZ(player.getZ()) - ); - - if (lastSentPosition == null) { - lastSentPosition = current; - lastSentHorizontalDelta = 0.0; - lastSentVerticalDelta = 0.0; - movementStateTicks = 0; - return; - } - - double dx = current.x - lastSentPosition.x; - double dy = current.y - lastSentPosition.y; - double dz = current.z - lastSentPosition.z; - lastSentHorizontalDelta = Math.sqrt(dx * dx + dz * dz); - lastSentVerticalDelta = Math.abs(dy); - lastSentPosition = current; - - if (lastSentHorizontalDelta > 1.0E-4 || lastSentVerticalDelta > 1.0E-4) { - movementStateTicks = Mth.clamp(movementStateTicks + 1, 0, 20); - } else { - movementStateTicks = 0; - } - } - - private boolean isFastPostHitApproach(LocalPlayer player, Player target) { - if (pendingAttackPosition == null) { - return false; - } - - Vec3 current = player.position(); - double moved = Math.sqrt(squaredHorizontalDistance( - pendingAttackPosition.x, - pendingAttackPosition.z, - current.x, - current.z - )); - if (moved < FAST_POST_HIT_MOVE_THRESHOLD) { - return false; - } - - Vec3 targetCenter = target.getBoundingBox().getCenter(); - double then = Math.sqrt(squaredHorizontalDistance( - pendingAttackPosition.x, - pendingAttackPosition.z, - targetCenter.x, - targetCenter.z - )); - double now = Math.sqrt(squaredHorizontalDistance( - current.x, - current.z, - targetCenter.x, - targetCenter.z - )); - - return now + 0.03 < then; - } - - private void addHistorySample(Player player, long now) { - ArrayDeque samples = targetHistory.computeIfAbsent(player.getUUID(), ignored -> new ArrayDeque<>()); - samples.addLast(new HistorySample(player.position(), now)); - while (samples.size() > HISTORY_MAX_SAMPLES) { - samples.removeFirst(); - } - } - - private void pruneHistory(long now) { - Iterator>> iterator = targetHistory.entrySet().iterator(); - while (iterator.hasNext()) { - ArrayDeque samples = iterator.next().getValue(); - while (!samples.isEmpty() && now - samples.peekFirst().timeMs() > HISTORY_MAX_AGE_MS) { - samples.removeFirst(); - } - if (samples.isEmpty()) { - iterator.remove(); - } - } - } - - private double getInteractionRange(LocalPlayer player) { - try { - return player.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE); - } catch (Throwable ignored) { - return 3.0; - } - } - - private boolean hasKnockback(ItemStack stack) { - if (stack == null || stack.isEmpty()) { - return false; - } - Holder entry = getEnchantmentEntry(Enchantments.KNOCKBACK); - return entry != null && EnchantmentHelper.getItemEnchantmentLevel(entry, stack) > 0; - } - - private Holder getEnchantmentEntry(ResourceKey key) { - if (mc.level == null) { - return null; - } - Registry registry = mc.level.registryAccess().lookupOrThrow(Registries.ENCHANTMENT); - Enchantment enchantment = registry.getValue(key); - return enchantment != null ? registry.wrapAsHolder(enchantment) : null; - } - - private boolean isBoatLike(Entity entity) { - return entity != null && entity.getType().toString().toLowerCase().contains("boat"); - } - - private boolean isAttack(ServerboundInteractPacket packet) { - return packet.hand() == null && packet.location() == null; - } - - private boolean isAtPreAlert(double buffer, double max) { - return buffer + 1.0 > max + BUFFER_EPSILON; - } - - private void updateClampLatches() { - if (isAtPreAlert(reachABuffer, REACH_A_BUFFER_MAX)) { - reachAClampLatched = true; - } else if (reachABuffer <= REACH_A_LATCH_RELEASE) { - reachAClampLatched = false; - } - - if (isAtPreAlert(reachBBuffer, REACH_B_BUFFER_MAX)) { - reachBClampLatched = true; - } else if (reachBBuffer <= REACH_B_LATCH_RELEASE) { - reachBClampLatched = false; - } - } - - private boolean isRecent(long timestamp, long windowMs) { - return timestamp != Long.MIN_VALUE && System.currentTimeMillis() - timestamp <= windowMs; - } - - private void decayABuffer(double amount) { - reachABuffer = Math.max(0.0, reachABuffer - amount); - } - - private void decayBBuffer(double amount) { - reachBBuffer = Math.max(0.0, reachBBuffer - amount); - } - - private double increaseLocalBuffer(double buffer, double max) { - return Math.min(max, buffer + 1.0); - } - - private int longHitsRemaining(double buffer, double max, boolean latched) { - if (latched || isAtPreAlert(buffer, max)) { - return 0; - } - return Math.max(0, (int) Math.floor(max - buffer + BUFFER_EPSILON)); - } - - private String resolveReason(boolean reachAExempt, - boolean reachBExempt, - boolean noHistory, - boolean recentServer, - boolean recentJoinWorld, - boolean liquid, - boolean knockback) { - if (recentServer) return "server_position"; - if (recentJoinWorld) return "join_world"; - if (noHistory) return "no_history"; - if (liquid) return "liquid"; - if (knockback) return "knockback"; - if (reachAExempt || reachBExempt) return "exempt"; - return "normal"; - } - - public record ReachPlan( - double requestedReach, - double allowedReach, - double reachALimit, - double reachBLimit, - double reachABuffer, - double reachBBuffer, - boolean reachAExempt, - boolean reachBExempt, - boolean noReachAHistory, - boolean usesReachABuffer, - boolean usesReachBBuffer, - String reason - ) { - private static ReachPlan empty(double requestedReach) { - return new ReachPlan(requestedReach, requestedReach, requestedReach, requestedReach, - 0.0, 0.0, true, true, true, false, false, "empty"); - } - } - - public record HudSnapshot( - double displayReach, - int longHitsRemaining, - boolean dirty, - String reason - ) { - } - - private record HistorySample(Vec3 pos, long timeMs) { - } - - private record CheckState( - boolean reachAExempt, - boolean reachBExempt, - boolean noReachAHistory, - double reachAAllowed, - double reachBAllowed, - double reachAMeasured, - double reachBMeasured, - boolean fastPostHitApproach, - String reason - ) { - private static CheckState exempt(String reason) { - return new CheckState(true, true, true, 0.0, 0.0, -1.0, -1.0, false, reason); - } - } -} diff --git a/combatant-client-26.2/src/main/resources/assets/lovisual/lang/en_us.json b/combatant-client-26.2/src/main/resources/assets/lovisual/lang/en_us.json index 5ae948e..c64ab3d 100644 --- a/combatant-client-26.2/src/main/resources/assets/lovisual/lang/en_us.json +++ b/combatant-client-26.2/src/main/resources/assets/lovisual/lang/en_us.json @@ -588,6 +588,9 @@ "setting.targetesp.mode.option.ring": "Ring", "setting.targetesp.mode.option.capture_mark": "Capture mark", "setting.targetesp.mode.option.crystals": "Crystals", + "setting.targetesp.mode.option.blackhole": "Blackhole", + "setting.targetesp.mode.option.vortex": "Vortex", + "setting.targetesp.vortex_speed": "Vortex speed", "setting.targetesp.color": "Color", "setting.targetesp.color_mode": "Color mode", "setting.targetesp.color_mode.option.Static": "Static", @@ -2820,6 +2823,8 @@ "setting.common.shulker_bg_radius": "Background radius", "setting.common.shulker_bg_softness": "Background softness", "setting.common.shulker_padding": "Padding", + "setting.common.tooltip_font": "Tooltip font", + "setting.common.tooltip_text_size": "Tooltip text size", "setting.common.shulker_preview": "Shulker Preview", "setting.common.shulker_preview_hold": "Preview hold", "setting.common.shulker_slot_bg_color": "Slot background color", diff --git a/combatant-client-26.2/src/main/resources/assets/lovisual/lang/ru_ru.json b/combatant-client-26.2/src/main/resources/assets/lovisual/lang/ru_ru.json index 8234346..485bc83 100644 --- a/combatant-client-26.2/src/main/resources/assets/lovisual/lang/ru_ru.json +++ b/combatant-client-26.2/src/main/resources/assets/lovisual/lang/ru_ru.json @@ -523,6 +523,9 @@ "setting.targetesp.mode.option.ring": "Кольцо", "setting.targetesp.mode.option.capture_mark": "Метка захвата", "setting.targetesp.mode.option.crystals": "Кристаллы", + "setting.targetesp.mode.option.blackhole": "Чёрная дыра", + "setting.targetesp.mode.option.vortex": "Вихрь", + "setting.targetesp.vortex_speed": "Скорость вихря", "setting.targetesp.color": "Цвет", "setting.targetesp.color_mode": "Режим цвета", "setting.targetesp.color_mode.option.Static": "Статичный", @@ -2619,6 +2622,8 @@ "setting.common.shulker_bg_radius": "Радиус фона", "setting.common.shulker_bg_softness": "Мягкость фона", "setting.common.shulker_padding": "Отступ", + "setting.common.tooltip_font": "Шрифт тултипа", + "setting.common.tooltip_text_size": "Размер текста тултипа", "setting.common.shulker_preview": "Превью шалкера", "setting.common.shulker_preview_hold": "Удержание превью", "setting.common.shulker_slot_bg_color": "Цвет фона слота", diff --git a/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/sky_shader_caustic.frag b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/sky_shader_caustic.frag new file mode 100644 index 0000000..f78270c --- /dev/null +++ b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/sky_shader_caustic.frag @@ -0,0 +1,91 @@ +#version 330 core + +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 pivosos2007. + * + * Portions of this file are based on, adapted from, or implemented + * with reference to a "SkyShader" module distributed for another + * Minecraft client. The original attribution was not preserved. + * This implementation has since been substantially modified for LoVisual. + * + * Licensed under the GNU General Public License v3.0. + */ + +out vec4 fragColor; + +layout (std140) uniform SkyShader { + vec4 u_Params0; // x = time, y = speed, z = scale, w = intensity + vec4 u_Params1; // x = resWidth, y = resHeight, z = alpha, w = fov + vec4 u_Color; // rgb = base color + vec4 u_CameraDir; // x = -yawRad, y = pitchRad +}; + +in vec2 v_TexCoord; + +#define MAX_ITER 4 + +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); +} + +void main() { + float time = u_Params0.x; + float speed = u_Params0.y; + float scale = u_Params0.z; + float intensity = u_Params0.w; + + vec2 res = u_Params1.xy; + float alpha = u_Params1.z; + float fov = u_Params1.w; + + vec3 baseColor = u_Color.rgb; + + vec2 sp = v_TexCoord * 2.0 - 1.0; + float aspect = res.x / max(res.y, 1.0); + + float tanV = tan(radians(fov) * 0.5); + vec3 rayV = normalize(vec3(sp.x * tanV * aspect, sp.y * tanV, 1.0)); + vec3 rayW = rotY(u_CameraDir.x) * rotX(u_CameraDir.y) * rayV; + + vec3 p = rayW * scale; + vec3 i = p; + float c = 1.0; + float inten = intensity; + + for (int n = 0; n < MAX_ITER; n++) { + float t = time * speed * (1.0 - (3.0 / float(n + 1))); + i = p + vec3( + cos(t - i.x) + sin(t + i.y), + sin(t - i.y) + cos(t + i.z), + cos(t - i.z) + sin(t + i.x) + ); + c += 1.0 / length(vec3( + p.x / (sin(i.x + t) / inten), + p.y / (cos(i.y + t) / inten), + p.z / (sin(i.z + t) / inten) + )); + } + + c /= float(MAX_ITER); + c = 1.5 - sqrt(c); + float brightness = c * c * c * c; + + // Sharper, more crystalline highlights than the original for a proper caustic look. + float sharp = pow(brightness, 1.4); + vec3 color = baseColor * sharp * 1.8 + baseColor * 0.15; + + float vignette = smoothstep(1.4, 0.15, length(sp)); + color *= mix(0.7, 1.0, vignette); + + fragColor = vec4(clamp(color, 0.0, 1.0), alpha); +} diff --git a/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/sky_shader_sky.frag b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/sky_shader_sky.frag new file mode 100644 index 0000000..166fbdb --- /dev/null +++ b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/sky_shader_sky.frag @@ -0,0 +1,94 @@ +#version 330 core + +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 pivosos2007. + * + * Portions of this file are based on, adapted from, or implemented + * with reference to a "SkyShader" module distributed for another + * Minecraft client. The original attribution was not preserved. + * This implementation has since been substantially modified for LoVisual. + * + * Licensed under the GNU General Public License v3.0. + */ + +out vec4 fragColor; + +layout (std140) uniform SkyShader { + vec4 u_Params0; // x = time, y = speed, z = scale, w = intensity + vec4 u_Params1; // x = resWidth, y = resHeight, z = alpha, w = fov + vec4 u_Color; // rgb = base color + vec4 u_CameraDir; // x = -yawRad, y = pitchRad +}; + +in vec2 v_TexCoord; + +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); +} + +float wave(vec3 dir, float phase, float scale, float speed, float time) { + return sin(dir.x * scale + time * speed + phase) + * sin(dir.z * scale * 0.7 - time * speed * 0.8 + phase * 1.7) + * sin(dir.y * scale * 1.3 + time * speed * 0.5 + phase * 2.3); +} + +void main() { + float time = u_Params0.x; + float speed = u_Params0.y; + float scale = u_Params0.z; + float intensity = u_Params0.w; + + vec2 res = u_Params1.xy; + float alpha = u_Params1.z; + float fov = u_Params1.w; + + vec3 baseColor = u_Color.rgb; + + vec2 sp = v_TexCoord * 2.0 - 1.0; + float aspect = res.x / max(res.y, 1.0); + + float tanV = tan(radians(fov) * 0.5); + vec3 rayV = normalize(vec3(sp.x * tanV * aspect, sp.y * tanV, 1.0)); + vec3 rayW = rotY(u_CameraDir.x) * rotX(u_CameraDir.y) * rayV; + + float h = clamp(rayW.y, -1.0, 1.0); + + // Vertical atmosphere gradient with a warmer, higher-contrast horizon glow. + vec3 zenith = baseColor * 0.12; + vec3 horizon = baseColor * 0.95 + vec3(0.05, 0.03, 0.01); + vec3 base = mix(horizon, zenith, pow(clamp(h, 0.0, 1.0), 0.55)); + base += baseColor * 0.4 * exp(-abs(h) * 4.5); + + // Flowing aurora bands, seamless on the full sphere (world-space direction). + float bands = 0.0; + bands += wave(rayW, 0.0, scale, speed, time); + bands += wave(rayW * 1.9 + 13.7, 2.1, scale, speed, time) * 0.55; + bands += wave(rayW * 3.7 + 41.3, 4.7, scale, speed, time) * 0.3; + bands *= intensity * 40.0; + + // Fade aurora towards the horizon so it hugs the upper sky, softened edge. + float auroraMask = smoothstep(-0.1, 0.5, h); + vec3 aurora = baseColor * bands * auroraMask; + + // Subtle star shimmer near the zenith, gently pulsing. + float star = pow(max(sin(rayW.x * 220.0) * sin(rayW.y * 260.0) * sin(rayW.z * 240.0), 0.0), 26.0); + star *= smoothstep(0.2, 0.85, h) * (0.55 + 0.45 * sin(time * speed * 2.4)); + + vec3 color = base + aurora + vec3(star) * (0.6 + 0.4 * baseColor); + + // Gentle vignette so the effect reads as sky rather than a flat overlay. + float vignette = smoothstep(1.35, 0.2, length(sp)); + color *= mix(0.82, 1.0, vignette); + + fragColor = vec4(clamp(color, 0.0, 1.0), alpha); +} diff --git a/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/sky_shader_water.frag b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/sky_shader_water.frag new file mode 100644 index 0000000..22199ee --- /dev/null +++ b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/sky_shader_water.frag @@ -0,0 +1,91 @@ +#version 330 core + +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 pivosos2007. + * + * Portions of this file are based on, adapted from, or implemented + * with reference to a "SkyShader" module distributed for another + * Minecraft client. The original attribution was not preserved. + * This implementation has since been substantially modified for LoVisual. + * + * Licensed under the GNU General Public License v3.0. + */ + +out vec4 fragColor; + +layout (std140) uniform SkyShader { + vec4 u_Params0; // x = time, y = speed, z = scale, w = intensity + vec4 u_Params1; // x = resWidth, y = resHeight, z = alpha, w = fov + vec4 u_Color; // rgb = base color + vec4 u_CameraDir; // x = -yawRad, y = pitchRad +}; + +in vec2 v_TexCoord; + +#define MAX_ITER 4 + +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); +} + +void main() { + float time = u_Params0.x; + float speed = u_Params0.y; + float scale = u_Params0.z; + float intensity = u_Params0.w; + + vec2 res = u_Params1.xy; + float alpha = u_Params1.z; + float fov = u_Params1.w; + + vec3 baseColor = u_Color.rgb; + + vec2 sp = v_TexCoord * 2.0 - 1.0; + float aspect = res.x / max(res.y, 1.0); + + float tanV = tan(radians(fov) * 0.5); + vec3 rayV = normalize(vec3(sp.x * tanV * aspect, sp.y * tanV, 1.0)); + vec3 rayW = rotY(u_CameraDir.x) * rotX(u_CameraDir.y) * rayV; + + // 3D noise from the world ray direction - seamless on the full sphere. + vec3 p = rayW * scale; + vec3 i = p; + float c = 1.0; + float inten = intensity; + + for (int n = 0; n < MAX_ITER; n++) { + float t = time * speed * (11.0 - (3.0 / float(n + 1))); + i = p + vec3( + cos(t - i.x) + sin(t + i.y), + sin(t - i.y) + cos(t + i.z), + cos(t - i.z) + sin(t + i.x) + ); + c += 1.0 / length(vec3( + p.x / (sin(i.x + t) / inten), + p.y / (cos(i.y + t) / inten), + p.z / (sin(i.z + t) / inten) + )); + } + + c /= float(MAX_ITER); + c = 1.5 - sqrt(c); + float brightness = c * c * c * c; + + // Softer highlight rolloff and a deeper base tone for a more "liquid" feel. + vec3 color = baseColor * pow(brightness, 0.85) * 1.15 + baseColor * 0.18; + + float vignette = smoothstep(1.4, 0.15, length(sp)); + color *= mix(0.75, 1.0, vignette); + + fragColor = vec4(clamp(color, 0.0, 1.0), alpha); +} diff --git a/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/wet_surface.frag b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/wet_surface.frag new file mode 100644 index 0000000..41c4f86 --- /dev/null +++ b/combatant-client-26.2/src/main/resources/assets/lovisual/shaders/wet_surface.frag @@ -0,0 +1,174 @@ +#version 330 core + +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 pivosos2007. + * + * Licensed under the GNU General Public License v3.0. + */ + +out vec4 color; + +uniform sampler2D u_Texture; +uniform sampler2D u_MainDepth; + +layout (std140) uniform WetSurface { + mat4 u_Projection; + mat4 u_InvView; // camera-space -> world-space rotation (camera-to-world) + vec4 u_CameraPos; // world-space camera position, w unused + vec4 u_Screen; // invW, invH, time, mainDepthEnabled + vec4 u_Params; // wetness, puddleCoverage, reflectionStrength, rippleStrength + vec4 u_Params2; // maxDistance, reflectionSteps, rainAmount, ripplesEnabled +}; + +in vec2 v_TexCoord; + +const float DEPTH_NEAR_EPS = 0.000001; +const float DEPTH_FAR_EPS = 0.999999; + +bool validRawDepth(float d) { + return d == d && d > DEPTH_NEAR_EPS && d < DEPTH_FAR_EPS; +} + +vec3 viewPositionFromDepth(vec2 uv, float rawDepth) { + vec4 clip = vec4(uv * 2.0 - 1.0, rawDepth, 1.0); + vec4 view = inverse(u_Projection) * clip; + return view.xyz / view.w; +} + +bool projectToScreen(vec3 viewPos, out vec2 uv, out float rawDepth) { + vec4 clip = u_Projection * vec4(viewPos, 1.0); + if (clip.w <= 0.0001) return false; + vec3 ndc = clip.xyz / clip.w; + uv = ndc.xy * 0.5 + 0.5; + rawDepth = ndc.z; + return uv.x >= 0.0 && uv.x <= 1.0 && uv.y >= 0.0 && uv.y <= 1.0; +} + +float hash(vec2 p) { + return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); +} + +float valueNoise(vec2 p) { + vec2 i = floor(p); + vec2 f = fract(p); + float a = hash(i); + float b = hash(i + vec2(1.0, 0.0)); + float c = hash(i + vec2(0.0, 1.0)); + float d = hash(i + vec2(1.0, 1.0)); + vec2 u = f * f * (3.0 - 2.0 * f); + return mix(mix(a, b, u.x), mix(c, d, u.x), u.y); +} + +float puddleNoise(vec2 worldXZ) { + float n = valueNoise(worldXZ * 0.18) * 0.65 + + valueNoise(worldXZ * 0.45 + 17.0) * 0.35; + return n; +} + +void main() { + vec2 uv = v_TexCoord; + vec4 base = texture(u_Texture, uv); + + if (u_Screen.w < 0.5) { + color = base; + return; + } + + float rawDepth = texture(u_MainDepth, uv).r; + if (!validRawDepth(rawDepth)) { + color = base; + return; + } + + vec3 viewPos = viewPositionFromDepth(uv, rawDepth); + + vec3 ddxPos = dFdx(viewPos); + vec3 ddyPos = dFdy(viewPos); + vec3 viewNormal = normalize(cross(ddxPos, ddyPos)); + vec3 viewDir = normalize(viewPos); + if (dot(viewNormal, -viewDir) < 0.0) { + viewNormal = -viewNormal; + } + + vec3 worldNormal = normalize(mat3(u_InvView) * viewNormal); + float groundFactor = clamp(dot(worldNormal, vec3(0.0, 1.0, 0.0)), 0.0, 1.0); + groundFactor = smoothstep(0.82, 0.98, groundFactor); + if (groundFactor <= 0.001) { + color = base; + return; + } + + vec3 worldPos = u_CameraPos.xyz + (mat3(u_InvView) * viewPos); + + float wetness = clamp(u_Params.x, 0.0, 2.0); + float puddleCoverage = clamp(u_Params.y, 0.0, 1.0); + float reflectionStrength = clamp(u_Params.z, 0.0, 2.0); + float rippleStrength = clamp(u_Params.w, 0.0, 2.0); + float maxDistance = max(u_Params2.x, 1.0); + int steps = int(clamp(floor(u_Params2.y + 0.5), 1.0, 32.0)); + float rainAmount = clamp(u_Params2.z, 0.0, 2.0); + bool ripplesEnabled = u_Params2.w > 0.5; + + float noise = puddleNoise(worldPos.xz); + float wetMask = smoothstep(0.5 - puddleCoverage * 0.55, 0.5 + puddleCoverage * 0.35, noise); + wetMask *= groundFactor; + if (wetMask <= 0.003) { + color = base; + return; + } + + vec3 reflNormal = viewNormal; + if (ripplesEnabled && rippleStrength > 0.001) { + float t = u_Screen.z; + float ripple = sin((worldPos.x + worldPos.z) * 3.1 + t * 2.4) * 0.5 + + sin((worldPos.x - worldPos.z * 1.7) * 4.3 - t * 3.1) * 0.5; + vec2 perturb = vec2(dFdx(ripple), dFdy(ripple)) * 0.0; + reflNormal = normalize(viewNormal + vec3(ripple, 0.0, ripple * 0.6) * rippleStrength * 0.05); + } + + vec3 reflDir = reflect(viewDir, reflNormal); + + vec3 reflectionColor = mix(vec3(0.45, 0.52, 0.62), vec3(0.75, 0.82, 0.92), clamp(reflDir.y * 0.5 + 0.5, 0.0, 1.0)); + bool hit = false; + + float stepLen = maxDistance / float(steps); + vec3 marchPos = viewPos + reflNormal * 0.02; + for (int i = 0; i < 32; i++) { + if (i >= steps) break; + + marchPos += reflDir * stepLen; + + vec2 sampleUv; + float sampleClipDepth; + if (!projectToScreen(marchPos, sampleUv, sampleClipDepth)) break; + + float sceneRawDepth = texture(u_MainDepth, sampleUv).r; + if (!validRawDepth(sceneRawDepth)) continue; + + vec3 scenePos = viewPositionFromDepth(sampleUv, sceneRawDepth); + + float marchDist = length(marchPos); + float sceneDist = length(scenePos); + float thickness = max(0.15, marchDist * 0.03); + + if (marchDist > sceneDist && marchDist - sceneDist < thickness) { + reflectionColor = texture(u_Texture, sampleUv).rgb; + hit = true; + break; + } + } + + float fresnel = pow(1.0 - clamp(dot(-viewDir, viewNormal), 0.0, 1.0), 3.0); + fresnel = mix(0.25, 1.0, fresnel); + + float hitBoost = hit ? 1.0 : 0.55; + float reflectionAmount = wetMask * reflectionStrength * fresnel * hitBoost + * mix(0.6, 1.0, clamp(rainAmount, 0.0, 1.0)) * wetness; + reflectionAmount = clamp(reflectionAmount, 0.0, 1.0); + + vec3 darkened = base.rgb * mix(1.0, 0.82, wetMask * clamp(wetness, 0.0, 1.0)); + vec3 result = mix(darkened, reflectionColor, reflectionAmount); + + color = vec4(clamp(result, 0.0, 1.0), 1.0); +} diff --git a/combatant-client-26.2/src/main/resources/assets/lovisual/textures/trollface/trollface_1.png b/combatant-client-26.2/src/main/resources/assets/lovisual/textures/trollface/trollface_1.png new file mode 100644 index 0000000000000000000000000000000000000000..434e26cd0a0ced5a239df329087f778638ee3ca7 GIT binary patch literal 56539 zcmcG#1#ld}k{~)li^*bUmMmswW=4yd$s)^QW@eT~%uE(DGg{1&#Vq;8@9n;Qd$Io_ zeq3Dkbahr$c4c;TbyikZ&YzV(+W-_9326xc7#J8p3UmPe?1HUGi;5a5sVGWF%ZY<3 z006KN00h05H(;4=^%uar`ItKc*n6hoyhg&NKXv6#wT! zSW`0>6A;N2=p=UrF$c+n4#Lqb{)ton#f|@o^Zms=Tpe9OG)jMQXEha35N-;>sV)8u zH~u%=#L@XLeH4g>&(7BEFR#Dw_vsMK?A294R~XQV4{!mf03-n-f1e+e2Z2KY0Kjz* z06@h3SDA4-0MHT)0AR2ER~dO80Dv3>05nhkSJ{8{iKCIT(Lb7l1f}5S<^aG=DFA?| z4FG(e0RZ51|4|1@{y$_R236sM-2d3JNkJG7{=%Y;;sKEHor!415eM92`76JQQ>S zA_80@Y+O9tzm0%FLP0@8K_h(ngn)~RjEejJ8~*eGP+`Dlz-J)9NCDueU=XNae+EDg z02)qEf5+Ru00s;k0{RmaB&Ze>G(!Kq3Pb}2{$~w<009O7M}a^AWn;Jhb>sgv0)NS@ z$uI=19MV?))dJfl$=I|kqz#(KEI&HQ7?i}Blvt< zxxvv1{cqgCJmr=asyGz3Bz~2fzM68}9}G(MoJd{&^d(&5`U6;B7-o6lVL@xXjd#};RPw-N5t8QGJU5@4 z8)l~rO@ll4yzMXfa(j+38T0M&7y14G0>8E{GGE-W_t=~QfhwtRv^288O~8VJl}XCC z8;qCFtTga%Cxc-D9Fy6^kueoh$N>|D5EP(6|DOQ0O6TNLw8ZPFd!lkc3t(UzF1r@3 zbeZY+>@hp?5mO@?`t62Tk|DF!hEhK^ z`b;dg(&S~7bo(QJ6+f4IL2n#&AuIEM7cb{NBPPUWLO$h18zuyVu7jNfyv_iP|oWz?_4P|;>UilAc z7{7G?k~+KjJa!+klnezGqh_|$5`8?JlZqs})ac@N(1XdsZAkLci^MUi>PggGUCU17 zF^}!6$IyR=3?XusM7%7s>UqtGxdCbq``ELy!s=nlp{>#}*`SDU(XK?@GqSOaEkBmXd153mLz};hd}nX4kj2 zG>dK;%gD`Onk&fT*I}_jo39F+UUvFPr{>e`D6-b=F~RDX`<|uoh`LPDvO5o38c$!YiJ?gjoPkbRvR|;Y9ed^rL?jI{<)FcgS$cH)oI; zqN4_y7=X7Qoy|E8g;(!`jHMY~Udz~f!zKqG6e`Kea4DrTK6k744I3vLVseuOF!!X2 zn!S%nphiAXouRHgnHHHOLhDww0{X8BGyA8!Dm?*AzrFzg+;aNqN*gUmJ`{f$f>$2$ zo_Nj`3-YZ_fA9XZ;$=gt*Srm$3;VcUbii@2R_ChyRHew%AP5V8;`;-j={?IUOww7S zvzO?+sw)Dsm*zCpR7QpQJAB80ZVmGaGzm4(v{_;C#C~ba(s9*(Mw&0N+?#zO^grr8 z#d*9m3rbxdKuM(1)O7>^j6>(K)+dhp^T z9+7(wz#13*)t?I6`>lJvD0*6cb5tMs5S^R-y+@&qO2Qw@2~$@G>RB;67gau07mW@m zq6o}JkJ7h1_M`MZr94*oAQqnsV04?4(@0i=f~$KvYKcs9>A&0s4Vn<0DgYhv-lnvP ztdo_8&;<^u#ga_ttoICg)ojBQ1u2fZNujuQ<}OOfWP>`BDhCPx z!wl}-Vys4y@0t1d9ctzG^0eg94&Ql7v#6~!2R|3pNuqn5MHvA^(Bs=U9DkX&mA-=& zs;ERZ7aFQVeI(7W2Y+8^@|~{^P4n~YO{w-=N6Vb(B+s`qNH+HzF)sqnNVTNd$fJB| zsIh~#$4wa^lEWNxT-lo_! zt=hUj;B6`hD}8@?6ef5`B{mYwkxxugkSYk4!tDS?^mX0$PByzLkT{t=r#0y!zmTJj z?-$V2KGgHRe`Z}Ip28U{Mdj%-Ovx!V*>=_LIPLvjFO7Trnx8DJPOXWYOdwwqxGqf0 zhNEWDNeyfnegMYz#3^iYg>GU2?*k_v7+Qf!m>cxBUCh|eYz^VXH_dz#0jadgN>D-o z@VR;gwhvQ1W?Ece{awZ&Hj=^;U(z*Mdg=}RU~IvR)6EqXRa>M-W7W?Ny_Y(Q3UenLmk)`7_TRE6{FcwYP%=OM4ttR0VvIG_%4XL9jx`B>2Kq`6xVb;kQxtR8 zh_LS5lEE%MO_i#Z@VAu3eNvP8`2~Cc$$uSy7R^v-K3hUoN_W+R?6q6VbQ2A!^q5AG@q0v*Zxq?`jVYY% zak}bfBms;2A|hx(3MKq(U{}^l$%2Hbk!zc$Wp<=o{6P zwzw>TDCVT0rM#*ZL_DG2=u#IK?$;HsJz9|)&2D0WBN-?%hMD!r%0>aK-mlz?H`<=p z!Y77kGDed@<4UQzzc4N&!iz3(J=e)x{rAPbC)AamNwW`oUdUdVA5-y4w{Dna&S=;W zc9%87?2TCvC5RXn);rCYx&uADrV--F>PpppdSn*z6>|?4V=YE&Hojf7%yRuAO*6(n zRWWBpe|>GSCN)?ml}PP)+JfzDBKYx|vH%2tnLT6?1t@$|^ZXwAlR{A{K>zMCxj(6j zmbwfIbH#yfN5$kai^`1>YDus8N}&m1a!lt8&r@c3j|&;)!N+-=PUNkXC7VU9SG&OS z1KL(nWV+II4n}*iO+@qj*^I#Wyq(=0N=Rn^j@vlZa;!d%wnHU!)WK$gzlE@XA5 z@*YP{>^V4X@9%bx50jo=-OZ27eKi|Y<+R!DaoVK2eTP;UAN1HWibUJnCzH#&;SXq0 zDfaA@SynAA^29W1n3H;#G>K3Xs6HWZ_8Lpp-C_qMjN2TyV<)atkcBB53M|bw9-}A@wM@ zOim?Z3HXRsy`cqlu+yrcH@^^abaf$PtyUL#)p2YpbuTKx!fqIE;)(^jKk2tPxHL1V?eba>0_2=sdLx26z5cZaR2In zBzYYj-n-TxiOf!nKBCfJ6Sz@UR{XMvADbcLMUQuTq!A$w=e8x0vn;Wv-YtFRqvsHnof-IMTGLZ= zF`T_C6IQ8&=Qqugfm*}b6x#oNZk*kSRA_>TLEx3uP_0B z0O&E%C&VAsh%8cmJ5vUHk}XyHNkFf;3=&_+i%QRn;aY&sTw%r_Hx_>4$?L<3im1IAS5&&3G^XZD~)Cn}4cH4RexBlUp7`W@oa6PI4_(ujdt;qmjBZw~=d&myT- zc8?tXu`KmQW&u84)Nfo`kM8@)b}3q2o!DHJ%})5qqp~=U3h7TI9=OUOu{TH6^(~9 zpnA~Gl~P6C2ymefCSrp$Cc#m`PiIXwa)KJe%=KNM=(B$zeAk72Nt+91Si5yFr73M%XzyWFq4Uf7zs27X3 zU}^2M;094%9f2$XtnWKCxBfCwnS>3Z4YsobGVo*s3eS^`V_0XuP>k5jZp13fO4z8TavnA6s=F;mB(oU(B0C1CO+FY8KAd$EZ$V}qRnn3#d zAFoznx&n45H_6So9AtIUjJ`|;q4kp&hWl&=X3JAR`TP>M(NP1Bm>Gnj3?h;kt{FGx zJ4x)wK~?Ee72*ph#y~H&{M*n!f!e!4he{S>RM(y>%OyU3reC(|9~JO7Z_m*&#=S99 zQZMTu>klk8!R=LwB0znJbuCG0dWMSKQ&UBDp}b57|AF$Mn$|U3ef%Y@Sb~=|S~Sxm z0%cq~d-j_?kHm~;nq<$s z4zgJPLe-~vkLjd@O?MdS#L7C5+5|wcBlh*-*s#X%Qqi3I*e_)D_|V6V&v$!2Ck#7$ zfxeT;RapN40MEhdit=B06+ln_sL8Fvzg4(QCPV6Aq81_Ia35k#+H`Q{a=X?2P26Sv zJ4Uo*u{?S0CDwt=K9a>KeCK>^p@PqHm2A(SoEMq*_!Ihe=H!0f-FcVL9%{4H(C+$| z+J6WKFdbrfCt}~vI%AR$A*i$ac^zy1OYk>eO~>vGQsvP)^Z8tpfaQBr3RxUJ}Ft;(sIjV`?t*TY4@ zDCF7lQh%xhV!tvE8*u)SEKXuo9#j78_hDbXL*#fO|3bO{;GR!x3{>mQf4Y`ThgkQY zZ?fBke{RLda7nUx#uIoe;yjP8ADwie9CGm(WEictZ7MX*JltL^bMV9`rYLOs1Sel~ zCg;lLUwhG)ZyG`EUv}f$i={Y44|+HtpWTcY3RC_8)EvCnD1lAwYWi(c9x)X}Requ8 zld@Ri+SO+=I$z@NW)KkLti)C1oH!fQLZ~P7 z&)x&DnBZJA&|HpMrYB{*r1Jj`DJ_1eGJWZ)Qq9V?P+jf1O)~7Xkt}O;J}*^kQC^KL zJbEe5>PZ>HHzmF;)yS(uC1dZ8m@hgTt$Fc79@BOE^!2XQuJqgZ&toaR*{zaIpqfLr z7pqSFr@lRQR%2zxUw8Py%w9RYz|A@L;GJjF%Nq%W$$5~<@-1irfE9@0jKj!rxfuP2 zbO5NO*!(~m{Y1TdIvzHplc3V;?}%B^hHBBXu^tA6^6nc#pcU3l{%9d2{{Cz=102LS$-_Gwm+JGQX$`t-Bv zRzR0tLPm!<*&jgHisy5O;dmA2yXa1t?iXa2egd-D{dJ=jAftfjnN9ok%!c16Z?Q@4 zPwDo-+^l3^@gtfqfp1}}X5vezsI@@&rT)=-@iv$p8<|Z^_ZN3&S2LNw@z>{JDaQ8o zP{`jtSy9*xM*r?yHSGbs(8Szw3Lj~rW#$X}2Bi{G7B0ynZ=WV{ij*vr)6 zzQO8DMrpa%%C!YwESWtnLFUs;_vTMefvN>MU2A2fs*Kr&Cq9!Af$ynZuK_P=U}HRu zZd^I^E3!iW&K`g<8s>|fOOjjW9M9hM#IPcbcH^s(2c!lT6k-jYAR7KW-i!6*-wJYn zCNGAWmu4bkk(-Hn-v1G^T132^>tK&B`ZKnKkkm_2tA**ISj8o5sZ+NwVeI8>&U-Rdj0{3nX@D6i>&`!;eVjja-!^x8>Sru zgT8+N;GufIO!%d1E`DC*rE5yshqzh{KR|)jdVc{PzT3`#BU6&1xY$W!<{w4>J;BIp zZ$@bON+iy-VP2d{Wd3`a*5l%V6n62xS00!qwOUWmHQBXi^X(5n`mq}iS$xmOd{qCl z4$pi1un_q&_WnJ~+x?JX#E<{p>c4W}Em|zgFfTMAB|3XpG%Dw5A>PbzGjK}XtrslX zc5wfbME|-MWUdMQTju{c3UVZaJPQD5FbI$n91H>y92^3~6XazE`NBV;prR2oeMV=- zz$6h;!eSRDBWGbFr4aeb`XdhHXodl~pTVHO{{S97`~Tc_O*co4|3O~pm9ofiH+iN!_Iu*>6_2FU$p?9)*P3)nMP)DPFeQ!6UQlxo5}&=mI>|;z zQ{17M$_n3Mt?zH-*e-1%uX=&UuY_*sp}(%e{FEyCn$W zCeY|jUepATVp{vIt&@Xy8c*SVo+e?G-XCI>I`uUk_cdNB8RV{#)4?Yez$ye3)-kzqkQ4aR#F<+C?PxG? z1WCQrx(V&_<~Mykc=`e;%lH%>Jw`@60k6E>rZ{1aq{kc4DzY-Ngm?CSqhZ)aaz`%c zIW?lvKyvwB8&lXP`r|$^P2-9u@YRx}+q7=3h!j4`0%={d89M24_=#kRF% z(d~f^*k7f_P7A|v{b5Nah0=E3$K&N;O&HSf2EP~}7(ol4(Kw$GEXixW9eqQ)7hqBW@(p$x){E6YQ~E365nYV z$zLFO&Gbn>jlv0mkjqcPe85twP&G?j*{BvEapDu7A5tMRYNCg(AgL7#Ao;o1WJl7M z9IYXX*7(2@m{xTMb{~~e%MN7KWwU06w8m$Jovx7kc&#AvU4WRT^*_4L*9nCTxW`FA zvgQf>b-M*Saw9F&eVJq-o!E(t(&5M*utv=vu#PI7S0Co)33+N^WnZ{4jJ7?TvABiM zELv4n%E9uU6dNC*Oi;N%9+&fsrW3I_WO`S;U}Qh4r)Ar3YmVhomLrk363-3fpha`e z)E3(a#*bJSR#W6jkU>`OizDc_)<-(I@XTmDsrDYmQo2VRnlaYq4L8(dW%1I!l>d(JKX>&Z-*za*G=* zv5C~{Qy1Vx%4WsfvlUAUw?!1SOonK{9^L5&#sg6@kxp5E-i}Lt+FYJOZgn=pTCR5u z9IKf@=4c~%tZLOTv(FFOl&Lq2jhXi?iuJ(vE&*ep+DpbMliSzwZW>z|k6mJ@rHyHU zZ=Jr5GlPOrGgg@NEKwSR!B*=Zt`4*Kl_8Xb+mAlK53?d)xD(B*o#X@8Ry)M3@zcJW zPM9H|EIMi226gUrm=N@8zv+ZqMPx@RGs=frfihEg0W=$S_A2#9eHFJh)!<=_Q zmPs8?Qpm=fXK`)q#htcK?G8$Y(jo(OX&+v>#>K`g4wnJ&Me zoyaEqj51EX)K20X1#~_sUA%j^A+7WAm3=bXnvP`Jke%W2l##F9w_GDM+!3b(RrxSI z@oQ7;TTaxa(37L(F}W4r=U0iyAAW)bh7jOo_eD<)+Br9;Y&c8>b8-}VB{7YGavY=I zqZdeXbu}9F{lbRizgEx(H{NmXjX5o>*9FKKHvV2P@40>Y^g_{KS2j4ioNVUtkd}W9-rVYxq zR#qmmwogU+3r1E!@Fzl<(tQEZ&o=MD^@=&~q7Lt2n#V;w5U}OkDPo4P@bQ^$!bIu1 zH}t=a)Jw*d2ptdBC$j#|{}c1JdJ%6J9uE{>VA(%DIk2=>_CMlKsM&>xw>7c zCYIYa$gKjmLSEk@svq_bVBE?DCtz~R@oZ;UDua6>6 zJ4vF=WGhw7>YFVN*=rRH@<)_M$L&l*%}4y|`ICSJ&~8T9s{1n+i90%8k)##s`f<-U zabJc;8H-Iu{gw6a(;x+!3_c6c_EJZ+U4Az~mENc?PK->{m~_OJFTuZ`)m z#RbOUG=+>eojzaAulxaAj^DOL>bYaoJ2;iGRUlT$*PxHR&wRnukHyG@JD?hQbmnL5 z^QRGWG>x?&=9*huZNZ$o#!tGyyZSZD?ndZqbDx0V|17}E6KgM7UWdD8Dy*F&^YzT0 zbggA-Oq;kKVN+jm+)HJMHl|}$v4Grr+=Gz{L4SbVOOefLta>$2&-b1)p=_!{Am%vS z0@GQNS2|`R<;Dv`RR&f$U(5HVitMRD&wI|LC0^Ev&{nC5W7RapPBFeo??6-0tk12q zbSS2$ypKpFg8%KSN;>lyu~!*^Dfhmvszi(NVfKqBKEzcV)yI7fYW5b*ZVqn?40@kPAlt8j?-0-L1eLPb9}0OxDG$ ztDzw*NkJZs-9uwkXs>411(VPJx-G8sqDFFedPDTG6zEI}&cc5vV(!h+`njrHTx_L$ z7}u>L-%f6D95c=J2~N)0f=lpF;o<8a+x~LuCWQx<2iY+o_Ym)ul30%AJ z)`1*XS|~%<9giW6awexA3rCD6o|UK0bL_n2RBgb`HIRfS05M$W>lZc^QhwB_Fux zt&Ahl6Rpc%9_UP-D1XL+`Vd20R=5!_a@eW$KK|*QjDU=Bh@VWjtVt}=E-qOC8I8%V zh%E-%L-~RtaFe1*?Ay?hq!4qsYLaI2_>E#|V=-=&`rLT4bC2SRtz>C1N_Y`ef6Q@i z$kHSoAFip=nXHkwWYV1^oy+nn?OQ=}FO4#F%f6P5c$E#wxIqJdU6h;q4XzynS?h?N zi4()DazGv8K(mx0A^m7|J8E>j;-=FfyCa4EazC<#x_q!TItvsqzKuaNz^L-3?B|lG zI(5Acy0{CL|2gd^UNa`&dnW!rfS-MJcL|hOXw$p-H8Lpe4dQCBDp)kvQG0YP?(`DI z?Df;d*bnljI?h~}=(XpwP64A4_*S-GjFrP$RCh9Q4hp|EAxU{Cm0mv2799hwwKf_D6=!Sn55P`G zmc@W3p=2~WN?WCc{Jjh znyD+Aa1&FISINZ#({;wp{Fm~So8B5G^Hs@23bTEUH#j8*xdH*%s*G*AYI?NU&Wz+M zU9m~XCUtMxsq&$J04RnK3OjaQdh)jI8;WnX2PNpAayYlJ-IO&KA1u&(;e^I3j5cg& z#WUddJa9$JvcohKxOrS_51}mmAH-X2AeyvdOKoT1t8 z>FlH)J3fBb`5>?e=1Z`EmT(3%ejQ(;J`4*rX!2jr*ib3>GeWBG1b33~;yTPvf5OP{Mqizge~MC^R<;#nyGi?{ZY{{Z|}UEW1%k8s8| z+LG&1$%;Y&Bc=#RK2=25pRVnzJ4Dz20K$gHBaGtx#;D%wpB6NGHlhV1PgfqaHU=ui z@9Q?S+*S5(PNoXUf^B2`B!=er^(;M@EJ#$+r4kb~#Aq^tacVEUjT=I973{{It5(FX zwKbS62S2`0W_z1FyW9t$FHte+H**!c#rKp-AHBGcrMx=Cw0(6zIsUn>8jcZxWRS!4c_OT!`I4Z(XQ&#Uw&SI8PPu5|wftg! zR08;&y*c%B!cIwC!3ndx2W9$;?#MFzQtIsIX@%`q{di*cvFeHvKs`>AEp^gv+| zAZ02QcBdY)sS8&=RXl*P%HW%hdb>Fdrh+HT?X5SXp|6t2HwDBr(W~=yeNRF+ewc!7 z-3<1zOADlw9vWaMf_d?zFpea&4i*sqF3UUwUux-M_~U^$nbr1J(2zXY#wOOb)%oUv z52kubly$ZT2OAR?WuHEg%2YP>eL3$2;Vld5sL$)A?;W>Pg(Du%&%&4~JIWRgX|yY8*& zrJ=eZ)_mA1>SG}xI^D479BWKXIIHJGdC||A;kH822BPIn!dKZakO6JGyGQ+6>US}< zxvNYdJq2ZxaqFq|9;!ob-<@rcw!LV)xclOS!W7gXFUNHyNe8$h$Wn;{re{|w!z-m} z1;=SlLly{}mt%Dm3;qP{&oZ=)g)+}u(~WZyz=|a44#%B))ScYe5__0=;;1t<;Vv+5 zaQ#gWpu>F0j;p`uR<)psgF`q*P~j3o`S;O-x!cxDsr5Ql`TneF zeAOCbKhC|OD!D-KeMWN78WT}PI{Nf=8;mw$^tV8utW;o!)h0^OgkRV( zEG`=&(lqDWvX$Xn@ghLV{<*Nh=s@0=qxiCS4pkJn@JT!ZG` zV?vDy0~7A;j;W>5z{8yf$Np%Bz%7D>C)z;Z7h;idI?5of)Xz2=la0DjUiv)OmgT9g zu#g5&6?ujG^LYw^xqYtLlh#U^w%5a>!x}e3%u&^|hZ&;OSL<8iCDx~x;`4t11g&I; zn={SBBpYqoqR^#g-{I*SUy}1QhpiQ@&&>MdeK_d%e7o}|Wv{WE5?4CN@Y+)}f;ZFV zd?mnA3F0bEm)lfxDMZL0sEBncN*X^Lc}0ws-N&jTnM%JpnxbzI4l#YlyN>#@-Fr zzdKZ*ubb&cgr~XErAZn}5$(Jj$mKCtz%D~FV^xc0NIqSeFNo9l8?uj`sLv~#AU<74ix0ts zimjbsTt2XzuBtP-7opQp&(CfGV_o~Ofj23iKRwpHr#6 z6vM~uW{O1&E5rPi$%o(b*srvAH@4DkrMax$tVp&t{|xhZdV<|lx=7mzFU`QM{$-Bp zhADH}ipbr?9Q%2nUL3e)7BA3;I%cWamyNZ)nnjJCi{A11R?HioZ1q z;gdH+6jglXzb-sXw0+--aP%W&VgAV+tm93`a`Dy(fLy>HcJJW6FVr zFBjgszcLt_KIeW#9!p#;&a_uvNiMbs<<5VD%apV(S=M4T!wwOCPg9kA)JQ>(J6~(- zGwWw>wiGxt<>||EQe+TJR`Zfy{)S*C@8$8tPSXBpudjYj+f_GkH|M@aRo?oc1DZE9 zeJ%S`+R47$)T6JWmz$Yti)zMs3`RyUG-16u3UZDj3fA9kU3JbGI8Ybq^XGC0-O8Hd*SWuDq4f^wB5$57CxgmoE^ne$F@#{)%SeL$B7753tAze3rP$Un`)ZgA8 z+1bZr3bGSPS!D;-p~DMQ!7sq=qPd}H9bj@0PfVaxjoH${3FH?RHN4Dt-hb7|`$=3S z5yOI?9XZn1!&V&$dHZrzB^rXPtP8hb7V3s(6zbA}g;vN8L|YJFr~$*GQizG@YQp;f z&5LP0%+2OR;T3Gdr?_Hk7blUNJEYS(1@-Lmwq`Gey)azsk=g<|Z6Kw)4){>bBj2>@!b|r-iX( zKX|$6*2Z7TJtruFxq^Z*5~%j7Hg6K5w1Z<`Rpe>0Kfd073uRBA@L1tJ7FE8dpLOTE=W5Dp30eU>l4{LAlAz z4yN47c0HdlWYvX3C^lzPD92tJ zo8Pao>^I17IdMH?%;rr;R7Z(#HWYjJA*yz$R)|;j(v%2Nzt*_2s|bWtEycf-{aR2o zr;jlQsuKt5jk9Jw8G*(dtPawY`P<33ibNC3D3U1?CC#1#^_!jZwYdZhZpZZKmO1cY z#Vn2E!3ch@bTcMWG+K6gaUamZ7mx;5h6C?5Mx*ux+>85u77G`-*>TVBEgY@MpT&g1lUxO;?Eo1h~<5 zSlKh*n2*U~ILbRxl@2(}&<4<6LYyqlPbJ9Uw>Ghc{l&-3vE=H+-}9VSETyCF_p@sU zt}Sm08dPE9FRt21SeV`Y6Nmq}# z&`w&>KnS?^h(+WJe!2oa$;}^X6*AI#%b7a+E{>Zm9Ju<~eDYpB(AU~s*sYT(XlOWj zLz$v~FaFwp@=DXG8-O+~cEbg2h2fmwqg+sh_cqEBjgYEb`#DwkoxaCRHXrGBa%I?} z<4)Wc#6W>gEYy>>S8^ZZ?90UX^6BM;4k+iU0(I!ozp&kMi+Onpr@mRD!kCp!TZI(%+p3}OLIqgk1>H7{6M~|?2$k}Udwv4&l-0}=K1=YAUuIW4F)4BZmk z&94HZoa6iiTV?R$FXu0r+B+}hob=;_8x-cl7x{ng_3G1#bYPH_^e-o$e{L(572Xv2 z5!WS7d{{8Ts*$J2{CJS}I=9nLUr4MaJ|uT+b>23!VOwWZJ`pdB;elUL?I=byCl%8e zz{u-~r7b5@BuF|rBA_c|DlUSS-{9;H)KiubVkz24? zM=Y@RE)!8Pz)C14RkOK4qd2JRUzHHX|9EUAdAtD9e~icWoJ-z=576;7DQTEFZp+6A z^UE8jx-5PIM9hTiV#0YF`l7Cnv0!YB^wR7PdpEEMP=ogb?xS_k{-#{0&Si__ODNE` zHycS5HKdmQX_Ldv<4FScZE(|iZN#BM%vVH3OmD4R$b1XohXRFAj{p>Tl7f1_D|gf) zJt2{b$@!T7gPkz+wO2{MdfW=6n0O08GsmnAPD68U(r7`yb`8{ePS?p?;0IRxv>Aso z2k?~ToBQZd%f2J}941jwO#=DXdxg-(3rIgJ15pI_i&Wb7Z!a8kr*> zJ>_^9i`HcUr!i+j%`mKE3+RbGas|#1AGkY0`ieP9OjYQkUVfD!)h7B~CYCVHqOR30AQ->zvN{csc*t-N z#EdMEGZLCB{B3u=KTXW%W4&$cWB$xgo&@q$4IZk5Lm4G$F2nom z>eZe_xC!;<3QoHrqBgqmd8RxdHd0$EXT7PJnrCr`6sMrk)-8$)VUP0}811GBLM zs*H#kU)pLPbd_cjflQ#!1x_`b=qFXipC{ zUJoDEh$wdVz;>U$LEP79yq_&fE_JE-+Wgx^7dhIi=cQsXcGK|3_tQ7b*NUsm%bH$? z_G+r;=%6s>^^w&#OL(mq2VEkGoS=ix;@X0GGQeS?%v!YD=RwnL`N7 zLv5{_otPmg*m18aO5RI1p#=JBV9i0XvpTl-Ifes5cDodz0aEsBO#&w~*NV`z)sx0H) zno8C_-@q+3L|)XW#-13I5?HVHKPp0r@#jQ-u1{BgdhVdnPU$W9aXCXR-S_1H&Hl7<{o4}z#wW6zf)v0FDv0PHb zdH1D1F()J$g<93!cW{se;4Xa{Zy&eX<+6DKAN*1y?}luJ{jtetB2= zL%T*s^agi^T#&)KpQcJyQV{p>e0ahptub~dypp)l=no*GC-ouX$bt2{^%0lsyPZKh z-e9!lMCwts`;uX@Qx(D>KX9vc1gWPvvmX1hKpTBtzW3WmrksTxdb~{SsbJ1jnrEj$ zHk3ZV6_NZB70Zf!e*><_q)@Orco>n_YCe7WIxpoIRpLD5!g7qvZ4TRQsnL-rrHL%a zhwf9Gnf`b|g7ovG9rq$aN>~{lBN^LwQK?TuZD*G2pie#hMczeaB8NdIHA+C|e*QtD z_;lyJ=MP}*e&CfZyPF8I+Wk=C3Zt@K%J;C^eo}zF3=TP^(el8mlF@Fq`t(ZsX$1X4lQ^~j`^@1j@pKPIW$f_OK(u#U7QrlwR3TLC#~ss zxT!#^`OvpHLdLGp5fYHO4}PUva=gNDPOVRu`WfX%Rw7P>`}XbQT8C1xy-ABOy0BH~ zcnyZlycSy5chly%xjWmHG3*7IVtZ$Jiz6D>#b%t#b9J@x+H^)Li4-Y6sh1-Xz9B1T zPa|U3B`S?H!Mh@(G41hCi9QnhF{AVF*r#clE%Dn@ae}Hl@FwX%u0X7676wXy(!J^nZ{d#xa9vznu$xH&fVzj==Ol!EkX-a?cG2?K}(+8Hl z3?k-{OOxB3ml`^L?s--ow#hCc7;f>TH!I)_f$HuNnd3eU3g%wmZ!;XWp~rg9$k z)2{!LAk*Vbth<1AQ#~b}(^CUzo4T5Il~vO4yHpYy{HWgI#`x`WW$L?^rBelZE&|<@ z?Z;9{$M*J&jl~bIXkpL)1KK3-F18Y0ah4>7#f}(i;gSd z`Do#fOFL9n@$5zV@`|3i%|U{WthR8D_N^LERVbWhxzn4FEvzpmfTfp(^x>pLzw<#5 z1yQgM$Xf7(Lu&C36fx7o}7IJux%Bm)oZ33Zz zdWJ3I(LATp*n@eaeZwF-NZG<8{{S@&q}Z%V3u@MVt>FY5MsTRsO)RT{aUo^$LaK*vdG z3%5-l3EKKDb8ZxF-_TmN=RcFjCmzeqTj}`pXwHC?^l|FRA>5;r=TiXPk)+X14v0p* zw%bJ9T*%$n^c$s0}!{Jj-c1E2xk&i*2*m3zx z(WKN-{{V>E&(g?tYh^OrxQz!{ri+6)r0w2a~0T*0tu0eAVkwebuQvXX9E3 zJtfp@rL!>=r}BJsgPDU*iTr_*W1RlN4Xs(%4s!>hokFz{o4{jOyllQBptsz(vB`za zymEN07PT#I-P%QGM55+Da`=46O_&4p`SgZgyPjth)k zwhV?@h%f9W^G!X=5>COop|@SG)UiPd3N2v z^((s(kUVTvhgh#ByFPOm+u9uwR@Ie#2BQVq6=D1SS7{Z@T*$u~0{ukkylt%eSu?k< zSjtOi15Gp>6QiihS@#mOgzq^mJDVDkd2}KSO+3$`J%!vdS~R2u&g-DT*8|^D#I727 zR|Np{4djiXy&z~U^d=M9T_UrsCRqn@1+gNq)8I53Ow(40j8y?6+E)G29JLVfv5c0$Rk<8rQU9H?3gsNCQLQdXC_%k zxy#Z(u72t@_|Xt&&fUU@w2fdL`fJXvUP0L>M=y0?ml6T`tDSa#9!;ZvOj6BfY||-C zBpUS>`ik{pJXlwti{jPzu&>N(R(Q|OYg9Y_hpu)@h>v+Rmb@g`P3*br$k5%Ua-Z6a zZZo4kgYW6H&N16|_xMnX(mc-mn#vEtwA(^{L0+p5*eliG`vrgcs(xDLrHAEVTDeSD zNF+nPnfNWZ#q2c^ace64m1f2#!;BqSIJw5K^jocHm{ZCk-nE-7PKWf@F$AyRm5kt9 zC%r)2OC-j*yF~f5t5(_({{W0v;K%k>m?A$Qu5!{N^5I&s7*D#?e2VA5IIeQtop+|S zV8a8H8(W7?>~RzY?YhV}u7g>w{7xfdijygFTwKgg z>%z6QzC8~$8$an<)U%ro^uoUcAn$tTCy51f*JtGw&dDqMYt{3-Sl2gc5cy~-3>0h~ z0ZW*cKzE93t`5bZ8&|DqEVxm-l0*LhIUWmvjyn$OXeRg_{SwINLNUM`oPE$D)j!mC zP)LAO8vx6lcP<;3eHGy35U**pnkH+h4m*u$noqjDCi>QgZ>@6|5~s~WkIlGV{k5Au zHm&b$tmvFN?-@0KHK7;j$kQ61g=(UYha{^%8#iHV&GR5l)eJGp9i6bitqlJFORp*N zh!;DwD8B~D#;;+v(MyQTZaSNi5l1o(yHZ04!4P0MZ8feC-G0qRJGhGX@-z%t8*HP4wPZyM(96XRG%5-))1`0Gfx^-oPY=n2*B+rLc_4AIQ3qYP;ASn)Dz zB`voIv1mP(>sk(n6>T8V%=LuQhyE#%#%n3V4uDH?H9D7lNf`D9ps1~r+B)6U91ti} zL4d<6TiM|y&spk9X(w{-+w$sy)%Y^~m9HBQ5y-~QW{FuRwTFkO|9)y-4 z0ZnVcN4cf{0IDqPSe-g@9uT-(G0_-2tjoAoLDk(xP7FM9b`IJpBC@dRBO=Kt;;pxy zzY17wlQjqL(3q~H5+UDSRciRdjv0q;MMcs{q<28~EHN&6zbep3%RBgJvj=Vb=Jl*)T6pjHrDM!0uZprheLJP|jFQOu8sed$H*Pn1SJ zv{Ga-w&uzn+D47LjRoNZx}<9`2l$irX{#eKpVBM5lIkn!y?6JNTb?3{CI6+WBmx5trkxMJ!vmzVzls9h!` z{ulk!!`?X~@6(b=-)I_xd8e9Voa}MPCbL>@|q#f&8ZIJwHnSvzo(_f4u{UWpOBxxJJDob#t zD{cL%NUhK!F2Fx6c%Ra2mcBg(y9Mgg$t9R59cGMt9z3F6*Rx#p!BN>=6|8ab<=flh z#R=9J^;WqNqSq0@h}W}wo{0F?ed~<#>?XmHl=X_0H#a62I*HcsrH(xcqXANPvXg3R7e zF6OLLNZu)?x_s3iy)uGgw(3FGiD0aFG?qyel3nh<)i^Yjuz76{*F) z&0m6_eRG^#e>Hi{(C|?}n7H@e=Cfl}McfDGuH)S$K`U%m1kigqM4jDZmnz0FWI6eH zna*crw2Bg3YO3r(vniV6A0~+XXe4S8M^NNuOB%?#h^M*Wr8&DyU-ep0!5>>Vu5v#p zleF~BWe|qpc44sBl1y+(TQsk|$h`jP9E<(K~JIS6Gt2>uGv=ITngQE17u9|$@#KQ#~kgLwY{IY0qPsPjDjrk)z z(_p>bbQ<2_4~fFHr9tyoJ9e1v8f(npc|TT)=8`yt4=h!hKD;zkcNXUqVbPc_lodUD zt35HA-?j$N9GmH3xz3i`_gYzPTsj@xqVmO=A@-L=d9~Res8_8=UUPEK+@ieF6%X4x zX`_`xqYMZ%*A_!Q+pKmnTTV1!`e|5uT6rxbniWPSgxg~ekHBS?MmPmrY1?_rhD$%~ zV-V`(yz+3V@?J1C?_&Ee3!{q?FytQtony}36q9fbE7VEMG_Xjz8Ls)T5o7XX&jOC+$h8%Cze$2tQnq*T$68P1=tmyRcEl8fAkUnFnWO z6<6XT+?9)x$G?$mf2zOlOW~05@Bsa_&}pB8-;YkmU1nTH(YEHu8W&`4>Ir(jQ6KL^ ziI^+)A3^9fokzxx8Fvu*tJakGR*4w=wStMygml*Y6K!xzni+~=qO@7K@uP8d7)Zx{ zP4&z`@8B!&IKS6kb8?aRE;T0=BS>FvJu_d}uAlJ}{&K9=u48dRJAxy+9?xr|yK(5} z;4)lV6YsAfthhA(xhQA;3}-q+K~P`-(Uu!DK7h_{RmHj5kB&%}yCg@tt*!v{PH8zf zo-w$ram>d?To*~K=3gg^d)J&7oxYVKF2!nA9?8b7rc;h`4n}3%DV>(G%!A1iwoLomA#=UiZhP5vf<6O+B{s*IeJMi;#)Z)`j zIp;GdWNg+Qbhg|q*f&(W{A)P~BfFrUc0P*eqReqErzNq+1#_5k-OJgG&_&YBbW%nK zI>Q+cepiwG6&Td>w%2Y_#&j&hHzD^%Mvt;;;v6w-+(i{TMvSWsx)+hQ3R$jzn&T-{D6!*nn{$=}Gv)^<_5s)BHoSbOPN_Oh&lxb#q-p)N#| z{5I8bLD1r?F$W{r@Nvuwq2v{vBNuJwE-*~-p5~ypcw8s%SlhS{)`w{5TZ8Qz`>a3P zlp?t2t^4+h6IwWB{{U`Wb`c#uljT<%RO3+0yB%@J;~X>C3by{>^NlTU9yriTtrbP}{}-9tK2IQPW?djX5yQ;m7O!sEE@tr~e(Dx+C- zhE}zd>%-{&nw8gTt~Bh&7AGc!IC2&3*_J`GHluskOj3XL<(Mto8>jyOO%`pZCm+H& z$r1Eab=Tk*{YJe}Yea1y={1d=(Fb#i2SmZR)oo`@o zH6+s6_R_D*T5{uD#mi>)TS%ZU?eX`LwJsGH`!AxgQW$p}rCUffAOo3`Sq@8VJr+5p znGV7*sV(4zj%#f@C_f3ypYA5g;;rFW({L4xmT`l9AAK@3 z_b-})jE-=LeFfdgy0_dx87&jt#;v1_dwEQ8oFlxev*uzW_7it2GaV8}awFYmXDGu# zuQiKbv~--ZW}IV>Q93Nl-1OT^J+02E+Az{9@$va+OB048*~4u-L5;Gh!!c?YWR;|j zLxySPnW7x@I!AvejI@=bY6-%Lf&OC9dudid-IDB|vl#YYE|a&v@i{wZo+Wc?!x|?=a3?fvrQKT4Cel6*-a(HhDUC*k}GNP&65$Ms-6jk&4+m8S|583 zOzaL4ILunp(D&ryZQEDdQ?aN#Et@B_hiJ*=7KBG}*N(=oD!w4Pj1@;O16*_?&F0hK zaw_A7Cuy#JA_r4@CodUkD@5My1mQUI{q?JfEaq7YjNCFS#&BggiUjDLG)TP$Y{;@M z;wY7ii9Y6vlI9DX_|vJmINQk4A9UeZJe+v<>kG?Lz3EymLN?~EZd7ju<%0a&>*&26 zDFw>!Q&=^mp!ilENiWib+$x-0U5)e-F`9}n1E41AbdTdqoEs5y+ijJdcQY{RTyzda zpX;WP1~bE@d^r~Hxy1rH`3Y!;QW1~Gr4rk@jmKD}hlKg5f0@*1GLXRT=w!E(uhBb= zNd&_OLy+)w`mBaOLp!rgCkD3={gbk@F5)ZibsV^3qbg5o#4zgLSO(0v9+Do0UwDC$ zV})~_wgFuAw5#$~o=-I^$Kch?-Ldi0TD~q~_#@vwcF-9F?gnTG7{{W~z=Ckc(Sr^BbZ5L_sM#p4ewP{dmp0$mS zg<}|!Biz(YrN}<(Eq(_X`VKYE!twZyNM!VekatzI?WLJDylBaC;xyiCHq~0-))w(c zAC8*!lD*0+TJl=;xqh|la{X)8<@(pHOZBZc5+9niosY+o(MKSUG-IZNT;p>6`ztoy zB#d`ZtH16Vi}bhqWVM^Nq#i|~{*vXneVDFq*YX&OjojlZYQI8Nw1Y>B9&aX;{{XC> zLpj1JPAlBmOm|U<6TDO0@O$RQnpqo_pKl{Z{_O?5K{{{ip`RxZ`<}6h)yHw$oDBv;g9-ve=MiXs`^$O#yq-0iGBP)KxVRulG0jmxv_@98ZsRfzon1~04mAb9^oZ8+=)d(FRsQH7 z?~*y;Isw*Q;*xuI#as5tdHuYWvF*j{HtIZ2Qw^ewmR@rf?`T#Hx0js^jEA_ET z-Z?p=pAp*`sA5RUI<_o17Js8HxA%V~EtH2t>nB9fxD1V;ifq+BPbQe#{xV`Qdw2BH z{%K~naOi6D`$r&rHDg?IlRQ94+g-F;*UlpMcGhf)Rkt34I?i^FxNI~NgrUdZN*+&g znD$PJ+see96(srMU`L{x=`AjEEu>L`Vd$E~9UPJkK49jg)?Ai<+8n8exM+it%)H1O zGXslOo0z`(xy`wVw-#_2-36ES##?Xheo7hB*lLbc(yqAlQd`Z}=$*!%-sE7*`{d5I zY*X5d>@{>1gi~=xn{1a;p0)}(I9$NF-Jzh-ZVj6|+^EX(9N~L2A*;n;#sO@V0q$Ob)GRj#FBe^46`JAB}2RkBP2s-W8&`v8X$R z5_bq(Su zm$wSr*F_eyxhvf3jY$(6N;U`hOk;Zri909;TW{C-IA%VEI5&kWDZ~-cwZ*D|a-;92 zR|FBiA|^Oz94aJFMOUG33c6v}D}TO03>pT~+o*r;f>`T<5H^-`;TGwE|!I1CQ@ zEQ)R9J!wbDO588d*PPqDedK8V2MPXI&g5GE0CVzMaz${RUYB^JniHIkF&!bfvQU>j zW2vad>zd2AAZS|{{iHh$XTbPBw1;Tr%rU{G)XBy;=HS-&)&r5QfxJa?mdYEnv|EFx zBi(xQm^FJ#v-7-bo+jV4cAA7n#nMIV$#Zahk(M|8+`@gNG)7B5(OmQqzlzp8NsRlZyxQ!q>aJzum*a(NS~Gr)YUeFx zUy`-dua6_qKTc3K_UALS+M>0kiA@M5kz{-^)yR-8h1((ADi|Ues2l(?OK^QD9}k90 zNWJ{(NXFJUR4}f@Tsaos-Tbn=Oi|Zi6fLfZ7fLjH-6oo7t`IskH1-(J2JW@3$0mKN z=<7qU(#JU#^X@whXxT;-I7vQ_hDb5=W7a(psetNplF$2mtsoj$lmVEXSv;?V5WaEL zTITJe9}4^=`c}QPXW?4ban#Z9dU0__tC24_NilP!BRy4s7!B0YNN_|LlT+M3v~+l6 z4cZ|?qGWQmHpodi&}y&-N!!hODj zGGJD@w+E8bTHyuG{4^Rd-NsYDsO1Wh@sV~Ok=Z!4MZgS~7i>1_uS9Bd@0^yO+vVN9 zIE?%Ug({7#aI1KiVxZ(tH?@WfMV#Wzq|ymTGaj?|<>Dhc^XT|wStZj9I>*Ma6$#xg zMv5d7#>X5Qj7SB#9l3`gxN(G&AN2BVzq|Q>=tv@#(}Wl#yAn>-@IrqLU*=+m9j(cdS_Q+X|01RqLG3Jw11pqXY zQAdY3+Ru@XW<@{m$O_sak6FzsAi-Ba@~Uit2RNC40D%pt+}nZ@-VrG6ThySOAk1Yv+Yqy zkfRQxlR?6~-sP~|bkG;J;z7Mm6az?MKK*kuBm8++XZlT+*ZX6YFb7PUxh*_#{kaod z$?S~-d8d|Bi@C()v|o1|hQiqM(taTwLm-ktj4(~IJ9Ro z9GE!9vy)je;U>D)qiNm9!tMoQ)ZKJngJsSghoZMoJY;urF^!6z>Kt9uvitgL&LNIL z-J-C#hDiJN)~(uuxYsv4ZQ#{=hw_^st8eYED%#uTIU+rfPwmJSP#$hAFZ#0DkLijq z*|3)A{{W_*Nm#S7(oRN-)^>#(ZfU}^rE=}O@-f4SgSONnv`ja*S>>a4Wc=qDX!vg( zMuIUBTy>%U0E)uu1dvudNgpw+-U-GW-6cWvs{=ENh_S`G>rG<1ty<$1-ZEl`3c`Io z^@<#M8M7kDyNIIDgKpmK7Pc!o(HAhhU#WRoj?3U_qw<*wHi8RdlNvjcQ~_Y z%9ha|Cq-N}Hk!_DB=U1nOE-3!8PRbGz3V>b&Sm$uR%#5gAih9OQHM9=39d(^yA+@+cs>@tQ4qIn?>(^P-a0D_dvuTC(cDMcjEZF3aQyatvz*?vI}HSs z`8@rTmx;TNJ%8G~qk~6PFzI@t~}-sduX6-q#6BacI7RZ#Le4E<829%N3|Vsd?Ozo2eq-N zbNowglSN|;isQPqmrnX7J$0`9nvP9#sfT#3X@h?O#k30l0Mqyf7Lm1DUDUBD&u?vx z(sJX;5`Qr<{sJB*C`VYK9|(>sdp0b>;J`a8GG=(;f|*5!rgWgM`p9wwWep~l?}&F*c2B6tn<#?k)`nD@O_f$%;z7HAF%%b z!Er_^d#Eg}5Y|TB@%|1Rgr7`fWDBn!FsK?=2!!%GbYn|1E z*Ad5uRyBpPJm0l>yeSZ@@jF2Y_KvM%f5H<;dz!VRmA*B$U%Ij956xeM-+8ZB{{T_1 zQ(vjATR6Uzsci`P1!~5F;nHIxst)r+aolU}O*O=DTx}Y+U}DC&-WGC$vyQQ$9jr?l z@?^M};D{;iW!h1jlXLa{2d9(fvk*HcVWp1!c!@h+%?62ssp}bi5nBZo3yvK^id*n} zk)GU?c>9L~vNTq)CnuF%;A4-`UFNlRbXZ2&jlPeP-P_6B(c%99HKr7C?yYmFoBE=y zCO@*j!TT#pLVgwaaQ!Rr;rdpNC4O4bC?BOO2;SmP3iFmRufEh8`e&WD4HcVf3diBf zX(eEYy=J(mJz76RZ||uuE)$DFnX8W`=lE7OvYgJe>NFDT&F1gfSgFK!ax~XHX%ij! zv`j4UcxkUTxkn!LXWPcu_M&>iQ7ez?rZffZuf+{(NYMVJu4d{Z^H!?^;CdVwVk@1t ziM&#@*+ur(s{!$^SN-eNzk1cPkK!?|YT6|5O3~06E~N{&b(->fG;Sju3I70evtAKB z#_Ti>R%dPh0ES>6jfb$*6(5VA`EAf^qPK{ycG4sA;aZ5%{+6t5ZrwjsA0)oc`RlG# z_!`3RgR{T!wPAH+%iwEWUO$F4>lk>gYTiZhY%5ag7Vvu2bw3`Q`9;bCJ&+ zID$x3wO?&OgI(T?e{^cm8n>{;@&rB@OQNj+0meTJWLy zYhKPjrG67HgI6~!sa(bL50<&B;A@(>2mb&iXqCPXTW2+-Pmk%XXy4+MrXT+RoqDpL z{B`PU_8Ql)1o&6~0F5=Lf}iVL*95oLwJo@>lCW;|Le89VUTP<7Mr&XyulD z01Rs4exlkxhlyZw9EGggcf5fxx?tJ__* z=JT$1WavM^WSV1}8VpT5=R#~jxPKJIP6mKUv(R-unjTeZ4|=r@H;jPyiso)(Z;fcv z`TSdDHMOH*YP)^^0OL6I*E4k>8;el#l}my2GzH2$9rca9%y04h+dQlhM_Hz|uq7?F z6WjhhiZN5&LyEa#y_VBJUfU~orC`$W4e#iz=pG5NQ5hUT?vYKl$#CbYz$4zI*#oKxR z0NEJ9D@1+Mg$&!4aqnrae%&Pa*FS3_{+ia5#rl@DFC(oNF@5#;@qOaI6+ZD_j~Cu6 zTIMglwXWj&*RCsg16;&1%4qpC2^WrC0RaI4000315g{=_QDJd`5FnAEvBA;sFhKF)P+)+N|Jncu0RaF3 zKOz4B^ZiPd%2O&@T3^6c^2TOUsGVf$ol~lHOEas)sZy^=d2SM=N|pZruxChIg&~JB zhy92(SUSRuCLVp|a`u%g!mN~=OK^@g$Ax(+<+jhPr-Z871JZO#;W+h$r-YySY9WCGf|M6?Z^DpQk$6M*(yuex|Cr*pm}Yz|{r)T#IM zW_b|bxr0l@qwWJ87!kA4{{V;qqTN4V+Q1?z4i2zx90V~S%fo@Qj&v1 z&dQYlQ@GUw#df7)Nu|g9O_PO}?kJVb1ODKk_CMVHU*->~SMPc5$WOT1qgB-MQz>U$F? zW5{!Val_h&?hku~))VIk)b@-osJ1I{v+7Jn(!0cbQ-`#x{{SZDlb14+{{Rekm0RVC zxnYp60%tp*Wl%m~s#1^Qx{fP-M1yjDX!V;D@xLsmx!2%KL7ZPm9~TBMjTC-Ze5B_; z5RXL!{{T3jOP=WiPiX)#UBav8KS%s>786XrSp{S?>X@rbN>Lq6C}+<&_In< z*OX{%)M)7f^}{L>rAqQGMuzA*z{tkCHx??o2iC#uGP^Ri_0WZYd-k|p5)@LP29l

83su30;cq5aBLsQ>bCN*Ig4rnW~WVbMGn4 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 Cny(1} literal 0 HcmV?d00001 diff --git a/combatant-client-26.2/src/main/resources/assets/lovisual/textures/trollface/trollface_2.png b/combatant-client-26.2/src/main/resources/assets/lovisual/textures/trollface/trollface_2.png new file mode 100644 index 0000000000000000000000000000000000000000..3ce925959fac5cdc424a87b0791bdadf34d25194 GIT binary patch literal 71484 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 @s^2j7f})5 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� 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 literal 0 HcmV?d00001 diff --git a/combatant-client-26.2/src/main/resources/assets/lovisual/textures/trollface/trollface_3.png b/combatant-client-26.2/src/main/resources/assets/lovisual/textures/trollface/trollface_3.png new file mode 100644 index 0000000000000000000000000000000000000000..1d82eb599b6280a2c91ca9680f7c4f210a0a3a1a GIT binary patch literal 46267 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 literal 0 HcmV?d00001 diff --git a/combatant-client-26.2/src/main/resources/assets/lovisual/textures/trollface/trollface_4.png b/combatant-client-26.2/src/main/resources/assets/lovisual/textures/trollface/trollface_4.png new file mode 100644 index 0000000000000000000000000000000000000000..6524597442e7467dd4a53cc46b15a60212cc4dd2 GIT binary patch literal 43753 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 literal 0 HcmV?d00001 diff --git a/combatant-client-26.2/src/main/resources/assets/lovisual/textures/trollface/trollface_6.png b/combatant-client-26.2/src/main/resources/assets/lovisual/textures/trollface/trollface_6.png new file mode 100644 index 0000000000000000000000000000000000000000..ee63cdc94cd210246760eeaeac301246378e79bf GIT binary patch literal 46112 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 literal 0 HcmV?d00001 diff --git a/combatant-client-26.2/src/main/resources/assets/lovisual/textures/trollface/trollface_classic.png b/combatant-client-26.2/src/main/resources/assets/lovisual/textures/trollface/trollface_classic.png new file mode 100644 index 0000000000000000000000000000000000000000..5e5812bab0d51922745203bf162148890a628ce7 GIT binary patch literal 46256 zcmb5V1ym%>voF}V4esvl?l8E!ySux?V1v86ySu~S4DRka_~6jk@_qlkd+)yU_MNkl zT~)u#h>FPU&dQ3&%>G>c+yx*>i%W?EKtMnMl3xk%xeu}`B_d*=sH`9^B`fyD002P7 z0AB>o1OV9CyE-dN2oq^)X%j*10zkgfzhq?M;`kr*zde6RJud%8c7gtXEAf9WgfTU9 zG5Het`*n~xe;NO(6WSL>v-l6D`Ue~T2lM@dJzO1Kzho5u!Op76B460_3sYJA4{ZED zu!*DdKl0IEGJJNnZvWW&hyH2ZH#2)Rm9H!G*MSFc0Vo3`0K)&Y|11AR4ut>!*8>0m z7XRO6#u)%WTPOg4wf^5_WcdI9Vh8}xI`iLU|NWgf8aW&Ns}B6@3~Fu;0Nj=V0PtD> z0O~9N0ITya@9XscQ8%J57T#CA9KMnTz!qQzAOc7M>;WbKhA+edU;;1$I6gN3A^>nO zuzx<_VBiqo;1H0oP>^2)4+9GgiwKW|gb0s_fQ*WThKz!Vf`EvQhmMJjjf;zmgoaOu zk3)!sgNyTz5)g0*2uKLXZ%|O*aF7v^asGeP=Kug18sr?584QF30E!F(h79sK^wj_m z04OLJ$bYr<>k0)80tpHR0sh6s`0Anmll8>~0sUMDeEYfq6bTIJD_ghw&#hp2P7ApK zLU&H<{PTQ6U1#y`wuqY=cv{o9we;%J6;b*$K`W8Dt$47dExyu~EhJTc2A*V9NpFhlzJ%_W zojXEtl6RtPc6bP z|F3C;ZN914qoF&H>;uv`M!Y*W`Ytmr_Z83CPN!ztYg9h;{ndlfwN^enZc|eL$nGLs zm(B$K#MHg)4*-|NL7n;elP9Vbh8?fEeC}jiBwn_odd6UmDg8B7rxL*FkQg}QM*~xM zb9wf!F&AF`I)s5Fjd^UaqCQBcJg%zF>a<@gt-l%l`QQVC>D+Q^Y(_a31ODI!@4d=4 zBvjdvDsH@8ivj@5hd#&+b#ln2rOp6=$@oL4Qj7NWki8)odg0`EQJ96T`F{+6_0=`M z)@BnMPW}K8C{*+K>t79~YFo97JL+dkIeh{~QytA*qF+yfTV6A%*(Mf?P8ls|P6X<< zzdEwEZ3`dW^c?nY3l5&?<+{2=Va9tHkRzbEzhFH86g#7trO?g8EywW7jEg&-A7Stc zgF=-uaKa_CtPS|`6&Z1zgbjKmQnqZA`WhJEGeRn!Gq z#~)qfF+v(5s-SV+#=Z9$03y#Ikf^HFi}Z)=&+9>dDtRNBCL>=+iDg`Z*9Y*x1VQ#& zJ?qMtDm=yCrCXl8onPkw0JlZP*rC&U@#$*mHu>o}RUEJ1LCW~D4MBg;DDTOv;uXg+ zF+Y4-sTozATU-pP`d@M_vZ|gZl@+S%2_L@sc!r

RPDhF%zt0N(z!)&9BzWQw+lz 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

kfFC{E60Ind&X?XM^~7Vc|2{hBh#2TRHUR- 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 JasKbi{{fSKPv8Im literal 0 HcmV?d00001 diff --git a/combatant-client-26.2/src/main/resources/lovisual.mixins.json b/combatant-client-26.2/src/main/resources/lovisual.mixins.json index 6b6aaf0..3d22f0f 100644 --- a/combatant-client-26.2/src/main/resources/lovisual.mixins.json +++ b/combatant-client-26.2/src/main/resources/lovisual.mixins.json @@ -177,8 +177,7 @@ "sodium.SodiumVertexConsumerTrackerMixin", "sodium.SodiumWorldRendererFreecamMixin", "xaero.minimap.AbstractWaypointRenderProviderMixin", - "xaero.worldmap.WaypointRenderProviderMixin", - "xaero.worldmap.WorldMapWaypointAccessor" + "xaero.worldmap.WaypointRenderProviderMixin" ], "injectors": { "defaultRequire": 1 diff --git a/combatant-client-26.2/src/test/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LrcLibClientTest.java b/combatant-client-26.2/src/test/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LrcLibClientTest.java new file mode 100644 index 0000000..8d28d8b --- /dev/null +++ b/combatant-client-26.2/src/test/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LrcLibClientTest.java @@ -0,0 +1,57 @@ +package dev.loki.lovisual.features.module.modules.visuals.lyrics; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +final class LrcLibClientTest { + + @Test + void cleanTrackNameRemovesParenthetical() { + assertEquals("Song Title", LrcLibClient.cleanTrackName("Song Title (feat. Guest)")); + assertEquals("Song Title", LrcLibClient.cleanTrackName("Song Title (Remix)")); + assertEquals("", LrcLibClient.cleanTrackName("(Song)")); + } + + @Test + void cleanTrackNameRemovesBrackets() { + assertEquals("Song Title", LrcLibClient.cleanTrackName("Song Title [Deluxe]")); + assertEquals("", LrcLibClient.cleanTrackName("[Song]")); + } + + @Test + void cleanTrackNameHandlesNull() { + assertEquals("", LrcLibClient.cleanTrackName(null)); + } + + @Test + void cleanTrackNameHandlesEmpty() { + assertEquals("", LrcLibClient.cleanTrackName("")); + } + + @Test + void cleanTrackNameHandlesWhitespaceOnly() { + assertEquals("", LrcLibClient.cleanTrackName(" ")); + } + + @Test + void cleanTrackNameTrims() { + assertEquals("Song Title", LrcLibClient.cleanTrackName(" Song Title ")); + } + + @Test + void fetchLyricsAsyncReturnsEmptyForBlankTrack() { + var future = LrcLibClient.fetchLyricsAsync("", "Artist", false); + List result = future.join(); + assertTrue(result.isEmpty()); + } + + @Test + void fetchLyricsAsyncReturnsEmptyForNullTrack() { + var future = LrcLibClient.fetchLyricsAsync(null, "Artist", false); + List result = future.join(); + assertTrue(result.isEmpty()); + } +} \ No newline at end of file diff --git a/combatant-client-26.2/src/test/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LrcParserTest.java b/combatant-client-26.2/src/test/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LrcParserTest.java new file mode 100644 index 0000000..784d1e4 --- /dev/null +++ b/combatant-client-26.2/src/test/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LrcParserTest.java @@ -0,0 +1,136 @@ +package dev.loki.lovisual.features.module.modules.visuals.lyrics; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +final class LrcParserTest { + + @Test + void parsesStandardLrcFormat() { + String lrc = "[00:12.00]Hello world\n[00:15.00]Second line"; + List lines = LrcParser.parse(lrc); + + assertEquals(2, lines.size()); + assertEquals(12000L, lines.get(0).timestampMs()); + assertEquals("Hello world", lines.get(0).text()); + assertEquals(15000L, lines.get(1).timestampMs()); + assertEquals("Second line", lines.get(1).text()); + } + + @Test + void parsesSingleDigitMinutes() { + String lrc = "[1:05.20]Test"; + List lines = LrcParser.parse(lrc); + + assertEquals(1, lines.size()); + assertEquals(65200L, lines.get(0).timestampMs()); + } + + @Test + void parsesWithoutFractionalSeconds() { + String lrc = "[01:30]No fraction"; + List lines = LrcParser.parse(lrc); + + assertEquals(1, lines.size()); + assertEquals(90000L, lines.get(0).timestampMs()); + assertEquals("No fraction", lines.get(0).text()); + } + + @Test + void parsesOneDigitCentisecond() { + String lrc = "[00:05.1]Test"; + List lines = LrcParser.parse(lrc); + + assertEquals(1, lines.size()); + assertEquals(5100L, lines.get(0).timestampMs()); + } + + @Test + void parsesTwoDigitCentisecond() { + String lrc = "[00:05.12]Test"; + List lines = LrcParser.parse(lrc); + + assertEquals(1, lines.size()); + assertEquals(5120L, lines.get(0).timestampMs()); + } + + @Test + void parsesThreeDigitCentisecond() { + String lrc = "[00:05.123]Test"; + List lines = LrcParser.parse(lrc); + + assertEquals(1, lines.size()); + assertEquals(5123L, lines.get(0).timestampMs()); + } + + @Test + void ignoresLinesWithoutTimestamp() { + String lrc = "Just a plain line\n[00:01.00]Has timestamp"; + List lines = LrcParser.parse(lrc); + + assertEquals(1, lines.size()); + assertEquals("Has timestamp", lines.get(0).text()); + } + + @Test + void ignoresEmptyLines() { + String lrc = "\n\n[00:01.00]Line\n\n"; + List lines = LrcParser.parse(lrc); + + assertEquals(1, lines.size()); + } + + @Test + void returnsEmptyForNullInput() { + assertTrue(LrcParser.parse(null).isEmpty()); + } + + @Test + void returnsEmptyForBlankInput() { + assertTrue(LrcParser.parse("").isEmpty()); + assertTrue(LrcParser.parse(" ").isEmpty()); + } + + @Test + void returnsEmptyWhenNoSyncedLyrics() { + String lrc = "Some text without timestamp format"; + List lines = LrcParser.parse(lrc); + + assertTrue(lines.isEmpty()); + } + + @Test + void sortsLinesByTimestamp() { + String lrc = "[00:30.00]Third\n[00:05.00]First\n[00:15.00]Second"; + List lines = LrcParser.parse(lrc); + + assertEquals(3, lines.size()); + assertEquals("First", lines.get(0).text()); + assertEquals("Second", lines.get(1).text()); + assertEquals("Third", lines.get(2).text()); + } + + @Test + void stripsWhitespaceFromText() { + String lrc = "[00:01.00] Trim me "; + List lines = LrcParser.parse(lrc); + + assertEquals(1, lines.size()); + assertEquals("Trim me", lines.get(0).text()); + } + + @Test + void handlesMultipleLinesWithMixedFormats() { + String lrc = "[0:15.5]Short\n[01:30]No fraction\n[00:05.12]Two digits\n[00:05.123]Three digits"; + List lines = LrcParser.parse(lrc); + + assertEquals(4, lines.size()); + assertEquals(5120L, lines.get(0).timestampMs()); + assertEquals(5123L, lines.get(1).timestampMs()); + assertEquals(15500L, lines.get(2).timestampMs()); + assertEquals(90000L, lines.get(3).timestampMs()); + } +} \ No newline at end of file diff --git a/combatant-client-26.2/src/test/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LyricLineSplitterTest.java b/combatant-client-26.2/src/test/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LyricLineSplitterTest.java new file mode 100644 index 0000000..773190b --- /dev/null +++ b/combatant-client-26.2/src/test/java/dev/loki/lovisual/features/module/modules/visuals/lyrics/LyricLineSplitterTest.java @@ -0,0 +1,99 @@ +package dev.loki.lovisual.features.module.modules.visuals.lyrics; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +final class LyricLineSplitterTest { + + @Test + void returnsEmptyForNullInput() { + assertTrue(LyricLineSplitter.splitLongLines(null).isEmpty()); + } + + @Test + void returnsEmptyForEmptyList() { + assertTrue(LyricLineSplitter.splitLongLines(Collections.emptyList()).isEmpty()); + } + + @Test + void keepsShortLinesUnsplit() { + LyricLine line = new LyricLine(1000L, "Short text"); + List input = Collections.singletonList(line); + List result = LyricLineSplitter.splitLongLines(input); + + assertEquals(1, result.size()); + assertEquals(1000L, result.get(0).timestampMs()); + assertEquals("Short text", result.get(0).text()); + } + + @Test + void splitsLongLineIntoChunks() { + LyricLine line = new LyricLine(2000L, "This is a long line with many words in it"); + List input = Collections.singletonList(line); + List result = LyricLineSplitter.splitLongLines(input); + + assertTrue(result.size() > 1, "Long line should be split into multiple chunks"); + assertEquals("This is a", result.get(0).text()); + } + + @Test + void maintainsTimestampOrder() { + LyricLine line = new LyricLine(3000L, "One two three four five six seven eight nine ten"); + List input = Collections.singletonList(line); + List result = LyricLineSplitter.splitLongLines(input); + + for (int i = 1; i < result.size(); i++) { + assertTrue(result.get(i).timestampMs() >= result.get(i - 1).timestampMs(), + "Timestamps should be in order"); + } + } + + @Test + void sortsResultByTimestamp() { + LyricLine line1 = new LyricLine(3000L, "Word word word word word word"); + LyricLine line2 = new LyricLine(1000L, "Another line with several words here"); + List input = Arrays.asList(line1, line2); + List result = LyricLineSplitter.splitLongLines(input); + + for (int i = 1; i < result.size(); i++) { + assertTrue(result.get(i).timestampMs() >= result.get(i - 1).timestampMs(), + "Result should be sorted by timestamp"); + } + } + + @Test + void splitsLineIntoMultipleChunksWithDecreasingDurations() { + LyricLine line = new LyricLine(5000L, "This line has too many words in it to be short"); + List input = Collections.singletonList(line); + List result = LyricLineSplitter.splitLongLines(input); + + assertTrue(result.size() > 1, "Long line should be split"); + for (int i = 1; i < result.size(); i++) { + assertTrue(result.get(i).timestampMs() > result.get(i - 1).timestampMs(), + "Timestamps should be strictly increasing"); + } + } + + @Test + void handlesExactFourWordLine() { + LyricLine line = new LyricLine(1000L, "One two three four"); + List input = Collections.singletonList(line); + List result = LyricLineSplitter.splitLongLines(input); + + assertEquals(1, result.size(), "Exactly 4 words should not be split"); + } + + @Test + void splitsFiveWordLine() { + LyricLine line = new LyricLine(1000L, "One two three four five"); + List input = Collections.singletonList(line); + List result = LyricLineSplitter.splitLongLines(input); + + assertTrue(result.size() > 1, "5 words should be split"); + } +} \ No newline at end of file