diff --git a/src-theme/public/components/array-list.json b/src-theme/public/components/array-list.json new file mode 100644 index 00000000000..c828585891a --- /dev/null +++ b/src-theme/public/components/array-list.json @@ -0,0 +1,10 @@ +{ + "name": "ArrayList", + "default": true, + "alignment": { + "horizontal": "Right", + "horizontalOffset": 0, + "vertical": "Top", + "verticalOffset": 0 + } +} diff --git a/src-theme/public/components/block-counter.json b/src-theme/public/components/block-counter.json new file mode 100644 index 00000000000..9e1df078519 --- /dev/null +++ b/src-theme/public/components/block-counter.json @@ -0,0 +1,10 @@ +{ + "name": "BlockCounter", + "default": true, + "alignment": { + "horizontal": "Center", + "horizontalOffset": -20, + "vertical": "CenterTranslated", + "verticalOffset": 0 + } +} diff --git a/src-theme/public/components/effects.json b/src-theme/public/components/effects.json new file mode 100644 index 00000000000..dc266dc6121 --- /dev/null +++ b/src-theme/public/components/effects.json @@ -0,0 +1,11 @@ +{ + "name": "Effects", + "default": true, + "alignment": { + "horizontal": "Right", + "horizontalOffset": 15, + "vertical": "Bottom", + "verticalOffset": 15 + }, + "tweaks": ["DISABLE_STATUS_EFFECT_OVERLAY"] +} diff --git a/src-theme/public/components/hotbar.json b/src-theme/public/components/hotbar.json new file mode 100644 index 00000000000..324d25674df --- /dev/null +++ b/src-theme/public/components/hotbar.json @@ -0,0 +1,17 @@ +{ + "name": "Hotbar", + "default": true, + "alignment": { + "horizontal": "CenterTranslated", + "horizontalOffset": 0, + "vertical": "Bottom", + "verticalOffset": 15 + }, + "tweaks": [ + "TWEAK_HOTBAR", + "DISABLE_STATUS_BAR", + "DISABLE_EXP_BAR", + "DISABLE_HELD_ITEM_TOOL_TIP", + "DISABLE_OVERLAY_MESSAGE" + ] +} diff --git a/src-theme/public/components/keystrokes.json b/src-theme/public/components/keystrokes.json new file mode 100644 index 00000000000..8c1083ec68d --- /dev/null +++ b/src-theme/public/components/keystrokes.json @@ -0,0 +1,10 @@ +{ + "name": "Keystrokes", + "default": true, + "alignment": { + "horizontal": "Left", + "horizontalOffset": 15, + "vertical": "Bottom", + "verticalOffset": 15 + } +} diff --git a/src-theme/public/components/notifications.json b/src-theme/public/components/notifications.json new file mode 100644 index 00000000000..1071b231f57 --- /dev/null +++ b/src-theme/public/components/notifications.json @@ -0,0 +1,10 @@ +{ + "name": "Notifications", + "default": true, + "alignment": { + "horizontal": "Right", + "horizontalOffset": 15, + "vertical": "Bottom", + "verticalOffset": 5 + } +} diff --git a/src-theme/public/components/scoreboard.json b/src-theme/public/components/scoreboard.json new file mode 100644 index 00000000000..0e7ba61f909 --- /dev/null +++ b/src-theme/public/components/scoreboard.json @@ -0,0 +1,11 @@ +{ + "name": "Scoreboard", + "default": true, + "alignment": { + "horizontal": "Left", + "horizontalOffset": 15, + "vertical": "Top", + "verticalOffset": 550 + }, + "tweaks": ["DISABLE_SCOREBOARD"] +} diff --git a/src-theme/public/components/tab-gui.json b/src-theme/public/components/tab-gui.json new file mode 100644 index 00000000000..533eae33c6b --- /dev/null +++ b/src-theme/public/components/tab-gui.json @@ -0,0 +1,10 @@ +{ + "name": "TabGui", + "default": false, + "alignment": { + "horizontal": "Left", + "horizontalOffset": 15, + "vertical": "Top", + "verticalOffset": 90 + } +} diff --git a/src-theme/public/components/taco.json b/src-theme/public/components/taco.json new file mode 100644 index 00000000000..d84ea92ed9d --- /dev/null +++ b/src-theme/public/components/taco.json @@ -0,0 +1,10 @@ +{ + "name": "Taco", + "default": false, + "alignment": { + "horizontal": "Left", + "horizontalOffset": 0, + "vertical": "Bottom", + "verticalOffset": 65 + } +} diff --git a/src-theme/public/components/target-hud.json b/src-theme/public/components/target-hud.json new file mode 100644 index 00000000000..6e2674f6546 --- /dev/null +++ b/src-theme/public/components/target-hud.json @@ -0,0 +1,10 @@ +{ + "name": "TargetHud", + "default": true, + "alignment": { + "horizontal": "Center", + "horizontalOffset": 20, + "vertical": "CenterTranslated", + "verticalOffset": 0 + } +} diff --git a/src-theme/public/components/text.json b/src-theme/public/components/text.json new file mode 100644 index 00000000000..a4fe9071da7 --- /dev/null +++ b/src-theme/public/components/text.json @@ -0,0 +1,127 @@ +{ + "name": "Text", + "default": false, + "alignment": { + "horizontal": "Center", + "horizontalOffset": 0, + "vertical": "Center", + "verticalOffset": 0 + }, + "settings": [ + { + "type": "TEXT", + "name": "Text", + "default": "Text" + }, + { + "type": "COLOR", + "name": "Color", + "default": 4294967295 + }, + { + "type": "TEXT", + "name": "Font", + "default": "Inter" + }, + { + "type": "INT", + "name": "Size", + "default": 14, + "range": { + "min": 1, + "max": 100 + }, + "suffix": "px" + }, + { + "type": "CONFIGURABLE", + "name": "Decorations", + "settings": [ + { + "type": "BOOLEAN", + "name": "Bold", + "default": false + }, + { + "type": "BOOLEAN", + "name": "Italic", + "default": false + }, + { + "type": "BOOLEAN", + "name": "Underline", + "default": false + }, + { + "type": "BOOLEAN", + "name": "Strikethrough", + "default": false + } + ] + }, + { + "type": "TOGGLEABLE", + "name": "Shadow", + "default": false, + "settings": [ + { + "type": "INT", + "name": "OffsetX", + "default": 0, + "range": { + "min": -10, + "max": 10 + }, + "suffix": "px" + }, + { + "type": "INT", + "name": "OffsetY", + "default": 0, + "range": { + "min": -10, + "max": 10 + }, + "suffix": "px" + }, + { + "type": "INT", + "name": "BlurRadius", + "default": 0, + "range": { + "min": 0, + "max": 10 + }, + "suffix": "px" + }, + { + "type": "COLOR", + "name": "Color", + "default": 4278190080 + } + ] + }, + { + "type": "TOGGLEABLE", + "name": "Glow", + "default": false, + "settings": [ + { + "type": "INT", + "name": "Radius", + "default": 0, + "range": { + "min": 0, + "max": 10 + }, + "suffix": "px" + }, + { + "type": "COLOR", + "name": "Color", + "default": 4294967295 + } + ] + } + ] +} diff --git a/src-theme/public/components/watermark.json b/src-theme/public/components/watermark.json new file mode 100644 index 00000000000..4bc994c0e0a --- /dev/null +++ b/src-theme/public/components/watermark.json @@ -0,0 +1,10 @@ +{ + "name": "Watermark", + "default": true, + "alignment": { + "horizontal": "Left", + "horizontalOffset": 15, + "vertical": "Top", + "verticalOffset": 15 + } +} diff --git a/src-theme/public/metadata.json b/src-theme/public/metadata.json index a6db49331e2..6e1f758bf69 100644 --- a/src-theme/public/metadata.json +++ b/src-theme/public/metadata.json @@ -1,7 +1,7 @@ { "name": "LiquidBounce", "version": "0.1.0", - "author": "CCBlueX", + "authors": ["CCBlueX"], "supports": [ "clickgui", "title", @@ -9,7 +9,8 @@ "multiplayer", "altmanager", "customize", - "singleplayer" + "singleplayer", + "editor" ], "overlays": [ "hud", @@ -19,370 +20,6 @@ "disconnected", "browser" ], - "components": [ - { - "name": "Watermark", - "value": [ - { - "name": "Enabled", - "value": true - }, - { - "name": "Alignment", - "value": [ - { - "name": "Horizontal", - "value": "Left" - }, - { - "name": "HorizontalOffset", - "value": 15 - }, - { - "name": "Vertical", - "value": "Top" - }, - { - "name": "VerticalOffset", - "value": 15 - } - ] - } - ] - }, - { - "name": "TabGui", - "value": [ - { - "name": "Enabled", - "value": true - }, - { - "name": "Alignment", - "value": [ - { - "name": "Horizontal", - "value": "Left" - }, - { - "name": "HorizontalOffset", - "value": 15 - }, - { - "name": "Vertical", - "value": "Top" - }, - { - "name": "VerticalOffset", - "value": 90 - } - ] - } - ] - }, - { - "name": "ArrayList", - "value": [ - { - "name": "Enabled", - "value": true - }, - { - "name": "Alignment", - "value": [ - { - "name": "Horizontal", - "value": "Right" - }, - { - "name": "HorizontalOffset", - "value": 0 - }, - { - "name": "Vertical", - "value": "Top" - }, - { - "name": "VerticalOffset", - "value": 0 - } - ] - } - ] - }, - { - "name": "Notifications", - "value": [ - { - "name": "Enabled", - "value": true - }, - { - "name": "Alignment", - "value": [ - { - "name": "Horizontal", - "value": "Right" - }, - { - "name": "HorizontalOffset", - "value": 15 - }, - { - "name": "Vertical", - "value": "Bottom" - }, - { - "name": "VerticalOffset", - "value": 5 - } - ] - } - ] - }, - { - "name": "Hotbar", - "value": [ - { - "name": "Enabled", - "value": true - }, - { - "name": "Alignment", - "value": [ - { - "name": "Horizontal", - "value": "CenterTranslated" - }, - { - "name": "HorizontalOffset", - "value": 0 - }, - { - "name": "Vertical", - "value": "Bottom" - }, - { - "name": "VerticalOffset", - "value": 15 - } - ] - } - ] - }, - { - "name": "Scoreboard", - "value": [ - { - "name": "Enabled", - "value": true - }, - { - "name": "Alignment", - "value": [ - { - "name": "Horizontal", - "value": "Left" - }, - { - "name": "HorizontalOffset", - "value": 15 - }, - { - "name": "Vertical", - "value": "Top" - }, - { - "name": "VerticalOffset", - "value": 550 - } - ] - } - ] - }, - { - "name": "Minimap", - "value": [ - { - "name": "Enabled", - "value": true - }, - { - "name": "Alignment", - "value": [ - { - "name": "Horizontal", - "value": "Left" - }, - { - "name": "HorizontalOffset", - "value": 7 - }, - { - "name": "Vertical", - "value": "Top" - }, - { - "name": "VerticalOffset", - "value": 180 - } - ] - }, - { - "name": "Size", - "value": 82 - } - ] - }, - { - "name": "TargetHud", - "value": [ - { - "name": "Enabled", - "value": true - }, - { - "name": "Alignment", - "value": [ - { - "name": "Horizontal", - "value": "Center" - }, - { - "name": "HorizontalOffset", - "value": 20 - }, - { - "name": "Vertical", - "value": "CenterTranslated" - }, - { - "name": "VerticalOffset", - "value": 0 - } - ] - } - ] - }, - { - "name": "BlockCounter", - "value": [ - { - "name": "Enabled", - "value": true - }, - { - "name": "Alignment", - "value": [ - { - "name": "Horizontal", - "value": "Center" - }, - { - "name": "HorizontalOffset", - "value": -20 - }, - { - "name": "Vertical", - "value": "CenterTranslated" - }, - { - "name": "VerticalOffset", - "value": 0 - } - ] - } - ] - }, - { - "name": "Effects", - "value": [ - { - "name": "Enabled", - "value": true - }, - { - "name": "Alignment", - "value": [ - { - "name": "Horizontal", - "value": "Right" - }, - { - "name": "HorizontalOffset", - "value": 15 - }, - { - "name": "Vertical", - "value": "Bottom" - }, - { - "name": "VerticalOffset", - "value": 15 - } - ] - } - ] - }, - { - "name": "Keystrokes", - "value": [ - { - "name": "Enabled", - "value": false - }, - { - "name": "Alignment", - "value": [ - { - "name": "Horizontal", - "value": "Left" - }, - { - "name": "HorizontalOffset", - "value": 15 - }, - { - "name": "Vertical", - "value": "Bottom" - }, - { - "name": "VerticalOffset", - "value": 15 - } - ] - } - ] - }, - { - "name": "Taco", - "value": [ - { - "name": "Enabled", - "value": false - }, - { - "name": "Alignment", - "value": [ - { - "name": "Horizontal", - "value": "Left" - }, - { - "name": "HorizontalOffset", - "value": 0 - }, - { - "name": "Vertical", - "value": "Bottom" - }, - { - "name": "VerticalOffset", - "value": 65 - } - ] - } - ] - } - ] + "font": "Inter Regular", + "wallpaper": "hills.png" } diff --git a/src-theme/public/textures/button.png b/src-theme/public/textures/button.png new file mode 100644 index 00000000000..fee3c8459ba Binary files /dev/null and b/src-theme/public/textures/button.png differ diff --git a/src-theme/public/textures/button_disabled.png b/src-theme/public/textures/button_disabled.png new file mode 100644 index 00000000000..fee3c8459ba Binary files /dev/null and b/src-theme/public/textures/button_disabled.png differ diff --git a/src-theme/public/textures/button_hover.png b/src-theme/public/textures/button_hover.png new file mode 100644 index 00000000000..18f37ab2ae3 Binary files /dev/null and b/src-theme/public/textures/button_hover.png differ diff --git a/src-theme/public/textures/button_slider_drag.png b/src-theme/public/textures/button_slider_drag.png new file mode 100644 index 00000000000..b43e3055068 Binary files /dev/null and b/src-theme/public/textures/button_slider_drag.png differ diff --git a/src-theme/public/background.frag b/src-theme/public/wallpapers/hills.frag similarity index 100% rename from src-theme/public/background.frag rename to src-theme/public/wallpapers/hills.frag diff --git a/src-theme/public/background.png b/src-theme/public/wallpapers/hills.png similarity index 100% rename from src-theme/public/background.png rename to src-theme/public/wallpapers/hills.png diff --git a/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/client/MixinMinecraftClient.java b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/client/MixinMinecraftClient.java index d9613ccb127..b3f609f1dff 100644 --- a/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/client/MixinMinecraftClient.java +++ b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/client/MixinMinecraftClient.java @@ -31,8 +31,8 @@ import net.ccbluex.liquidbounce.features.module.modules.misc.ModuleMiddleClickAction; import net.ccbluex.liquidbounce.features.module.modules.render.ModuleClickGui; import net.ccbluex.liquidbounce.features.module.modules.render.ModuleXRay; -import net.ccbluex.liquidbounce.integration.BrowserScreen; -import net.ccbluex.liquidbounce.integration.VrScreen; +import net.ccbluex.liquidbounce.integration.VirtualDisplayScreen; +import net.ccbluex.liquidbounce.integration.browser.BrowserScreen; import net.ccbluex.liquidbounce.render.engine.RenderingFlags; import net.ccbluex.liquidbounce.utils.client.vfp.VfpCompatibility; import net.ccbluex.liquidbounce.utils.combat.CombatManager; @@ -357,7 +357,7 @@ private boolean injectMultiActionsAttackingWhileUsingAndEnforcedBlockingState(bo private boolean injectFixAttackCooldownOnVirtualBrowserScreen(MinecraftClient instance, int value) { // Do not reset attack cooldown when we are in the vr/browser screen, as this poses an // unintended modification to the attack cooldown, which is not intended. - return !(this.currentScreen instanceof BrowserScreen || this.currentScreen instanceof VrScreen || + return !(this.currentScreen instanceof BrowserScreen || this.currentScreen instanceof VirtualDisplayScreen || this.currentScreen instanceof ModuleClickGui.ClickScreen); } diff --git a/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/entity/MixinClientPlayerEntity.java b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/entity/MixinClientPlayerEntity.java index 405b2b19ff7..f189af66f7a 100644 --- a/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/entity/MixinClientPlayerEntity.java +++ b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/entity/MixinClientPlayerEntity.java @@ -32,8 +32,8 @@ import net.ccbluex.liquidbounce.features.module.modules.render.ModuleClickGui; import net.ccbluex.liquidbounce.features.module.modules.render.ModuleFreeCam; import net.ccbluex.liquidbounce.features.module.modules.render.ModuleNoSwing; -import net.ccbluex.liquidbounce.integration.BrowserScreen; -import net.ccbluex.liquidbounce.integration.VrScreen; +import net.ccbluex.liquidbounce.integration.VirtualDisplayScreen; +import net.ccbluex.liquidbounce.integration.browser.BrowserScreen; import net.ccbluex.liquidbounce.integration.interop.protocol.rest.v1.game.PlayerData; import net.ccbluex.liquidbounce.interfaces.ClientPlayerEntityAddition; import net.ccbluex.liquidbounce.utils.aiming.Rotation; @@ -375,7 +375,7 @@ private boolean hookNetworkSprint(boolean original) { @WrapWithCondition(method = "closeScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MinecraftClient;setScreen(Lnet/minecraft/client/gui/screen/Screen;)V")) private boolean preventCloseScreen(MinecraftClient instance, Screen screen) { // Prevent closing screen if the current screen is a client screen - return !(instance.currentScreen instanceof BrowserScreen || instance.currentScreen instanceof VrScreen || + return !(instance.currentScreen instanceof BrowserScreen || instance.currentScreen instanceof VirtualDisplayScreen || instance.currentScreen instanceof ModuleClickGui.ClickScreen); } diff --git a/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinInGameHud.java b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinInGameHud.java index fd83b1f95c4..9ae3c4f51e8 100644 --- a/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinInGameHud.java +++ b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinInGameHud.java @@ -25,9 +25,9 @@ import net.ccbluex.liquidbounce.features.module.modules.combat.ModuleSwordBlock; import net.ccbluex.liquidbounce.features.module.modules.render.ModuleAntiBlind; import net.ccbluex.liquidbounce.features.module.modules.render.ModuleFreeCam; -import net.ccbluex.liquidbounce.integration.theme.component.ComponentOverlay; -import net.ccbluex.liquidbounce.integration.theme.component.FeatureTweak; -import net.ccbluex.liquidbounce.integration.theme.component.types.IntegratedComponent; +import net.ccbluex.liquidbounce.integration.theme.layout.component.Component; +import net.ccbluex.liquidbounce.integration.theme.layout.component.ComponentManager; +import net.ccbluex.liquidbounce.integration.theme.layout.component.ComponentTweak; import net.ccbluex.liquidbounce.render.engine.UiRenderer; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; @@ -79,10 +79,10 @@ private void hookRenderEventStart(DrawContext context, RenderTickCounter tickCou UiRenderer.INSTANCE.startUIOverlayDrawing(context, tickCounter.getTickDelta(false)); // Draw after overlay event - var component = ComponentOverlay.getComponentWithTweak(FeatureTweak.TWEAK_HOTBAR); - if (component != null && component.getRunning() && - client.interactionManager.getCurrentGameMode() != GameMode.SPECTATOR) { - drawHotbar(context, tickCounter, component); + if (client.interactionManager.getCurrentGameMode() != GameMode.SPECTATOR) { + for (var component : ComponentManager.getComponentsWithTweak(ComponentTweak.TWEAK_HOTBAR)) { + drawCustomHotbar(context, tickCounter, component); + } } } @@ -106,7 +106,7 @@ private void injectPumpkinBlur(DrawContext context, Identifier texture, float op @Inject(method = "renderCrosshair", at = @At("HEAD"), cancellable = true) private void hookFreeCamRenderCrosshairInThirdPerson(DrawContext context, RenderTickCounter tickCounter, CallbackInfo ci) { if ((ModuleFreeCam.INSTANCE.getRunning() && ModuleFreeCam.INSTANCE.shouldDisableCameraInteract()) - || ComponentOverlay.isTweakEnabled(FeatureTweak.DISABLE_CROSSHAIR)) { + || ComponentManager.isTweakEnabled(ComponentTweak.DISABLE_CROSSHAIR)) { ci.cancel(); } } @@ -122,42 +122,42 @@ private void hookRenderPortalOverlay(CallbackInfo ci) { @Inject(method = "renderScoreboardSidebar*", at = @At("HEAD"), cancellable = true) private void renderScoreboardSidebar(CallbackInfo ci) { - if (ComponentOverlay.isTweakEnabled(FeatureTweak.DISABLE_SCOREBOARD)) { + if (ComponentManager.isTweakEnabled(ComponentTweak.DISABLE_SCOREBOARD)) { ci.cancel(); } } @Inject(method = "renderHotbar", at = @At("HEAD"), cancellable = true) private void hookRenderHotbar(CallbackInfo ci) { - if (ComponentOverlay.isTweakEnabled(FeatureTweak.TWEAK_HOTBAR)) { + if (ComponentManager.isTweakEnabled(ComponentTweak.TWEAK_HOTBAR)) { ci.cancel(); } } @Inject(method = "renderStatusBars", at = @At("HEAD"), cancellable = true) private void hookRenderStatusBars(CallbackInfo ci) { - if (ComponentOverlay.isTweakEnabled(FeatureTweak.DISABLE_STATUS_BAR)) { + if (ComponentManager.isTweakEnabled(ComponentTweak.DISABLE_STATUS_BAR)) { ci.cancel(); } } @Inject(method = "renderExperienceBar", at = @At("HEAD"), cancellable = true) private void hookRenderExperienceBar(CallbackInfo ci) { - if (ComponentOverlay.isTweakEnabled(FeatureTweak.DISABLE_EXP_BAR)) { + if (ComponentManager.isTweakEnabled(ComponentTweak.DISABLE_EXP_BAR)) { ci.cancel(); } } @Inject(method = "renderExperienceLevel", at = @At("HEAD"), cancellable = true) private void hookRenderExperienceLevel(CallbackInfo ci) { - if (ComponentOverlay.isTweakEnabled(FeatureTweak.DISABLE_EXP_BAR)) { + if (ComponentManager.isTweakEnabled(ComponentTweak.DISABLE_EXP_BAR)) { ci.cancel(); } } @Inject(method = "renderHeldItemTooltip", at = @At("HEAD"), cancellable = true) private void hookRenderHeldItemTooltip(CallbackInfo ci) { - if (ComponentOverlay.isTweakEnabled(FeatureTweak.DISABLE_HELD_ITEM_TOOL_TIP)) { + if (ComponentManager.isTweakEnabled(ComponentTweak.DISABLE_HELD_ITEM_TOOL_TIP)) { ci.cancel(); } } @@ -166,14 +166,14 @@ private void hookRenderHeldItemTooltip(CallbackInfo ci) { private void hookSetOverlayMessage(Text message, boolean tinted, CallbackInfo ci) { EventManager.INSTANCE.callEvent(new OverlayMessageEvent(message, tinted)); - if (ComponentOverlay.isTweakEnabled(FeatureTweak.DISABLE_OVERLAY_MESSAGE)) { + if (ComponentManager.isTweakEnabled(ComponentTweak.DISABLE_OVERLAY_MESSAGE)) { ci.cancel(); } } @Inject(method = "renderStatusEffectOverlay", at = @At("HEAD"), cancellable = true) private void hookRenderStatusEffectOverlay(CallbackInfo ci) { - if (ComponentOverlay.isTweakEnabled(FeatureTweak.DISABLE_STATUS_EFFECT_OVERLAY)) { + if (ComponentManager.isTweakEnabled(ComponentTweak.DISABLE_STATUS_EFFECT_OVERLAY)) { ci.cancel(); } } @@ -184,7 +184,7 @@ private boolean hookOffhandItem(boolean original) { } @Unique - private void drawHotbar(DrawContext context, RenderTickCounter tickCounter, IntegratedComponent component) { + private void drawCustomHotbar(DrawContext context, RenderTickCounter tickCounter, Component component) { var playerEntity = this.getCameraPlayer(); if (playerEntity == null) { return; @@ -192,10 +192,11 @@ private void drawHotbar(DrawContext context, RenderTickCounter tickCounter, Inte var itemWidth = 22.5; var offset = 98; - var bounds = component.getAlignment().getBounds(0, 0); + var factor = (float) client.getWindow().getScaledWidth() / (float) client.getWindow().getWidth(); + var bounds = component.getAlignment().getBounds(0, 0, factor); int center = (int) bounds.getXMin(); - var y = bounds.getYMin() - 12; + var y = bounds.getYMin() - 20; int l = 1; for (int m = 0; m < 9; ++m) { diff --git a/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinScreen.java b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinScreen.java index cd5095d61c2..57bd574fb58 100644 --- a/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinScreen.java +++ b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinScreen.java @@ -61,18 +61,20 @@ public abstract class MixinScreen { @Inject(method = "init(Lnet/minecraft/client/MinecraftClient;II)V", at = @At("TAIL")) private void objInit(CallbackInfo ci) { - ThemeManager.INSTANCE.initialiseBackground(); + ThemeManager.INSTANCE.getActiveWallpaper().load(); } @Inject(method = "init()V", at = @At("TAIL")) protected void init(CallbackInfo ci) { - ThemeManager.INSTANCE.initialiseBackground(); + ThemeManager.INSTANCE.getActiveWallpaper().load(); } @Inject(method = "renderBackground", at = @At("HEAD"), cancellable = true) private void renderBackgroundTexture(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) { if (this.client != null && this.client.world == null && !HideAppearance.INSTANCE.isHidingNow()) { - if (ThemeManager.INSTANCE.drawBackground(context, width, height, new Vec2i(mouseX, mouseY), delta)) { + var wallpaper = ThemeManager.INSTANCE.getActiveWallpaper(); + + if (wallpaper.draw(context, width, height, new Vec2i(mouseX, mouseY), delta)) { ci.cancel(); } } diff --git a/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinSplashOverlay.java b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinSplashOverlay.java index 47bbbc91982..25186c375a6 100644 --- a/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinSplashOverlay.java +++ b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/MixinSplashOverlay.java @@ -25,6 +25,7 @@ import net.ccbluex.liquidbounce.event.events.SplashOverlayEvent; import net.ccbluex.liquidbounce.event.events.SplashProgressEvent; import net.ccbluex.liquidbounce.features.misc.HideAppearance; +import net.ccbluex.liquidbounce.integration.theme.ThemeManager; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.SplashOverlay; @@ -115,7 +116,7 @@ private void render(DrawContext context, int mouseX, int mouseY, float delta, Ca @WrapWithCondition(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawTexture(Ljava/util/function/Function;Lnet/minecraft/util/Identifier;IIFFIIIIIII)V")) private boolean drawTexture(DrawContext instance, Function renderLayers, Identifier sprite, int x, int y, float u, float v, int width, int height, int regionWidth, int regionHeight, int textureWidth, int textureHeight, int color) { // do not draw texture - only when hiding - return HideAppearance.INSTANCE.isHidingNow(); + return HideAppearance.INSTANCE.isHidingNow() || !ThemeManager.INSTANCE.getActiveTheme().canSplash(); } } diff --git a/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/widget/MixinClickableWidget.java b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/widget/MixinClickableWidget.java new file mode 100644 index 00000000000..ef2e65af1f1 --- /dev/null +++ b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/widget/MixinClickableWidget.java @@ -0,0 +1,54 @@ +package net.ccbluex.liquidbounce.injection.mixins.minecraft.gui.widget; + +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +/* + * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce) + * + * Copyright (c) 2015 - 2025 CCBlueX + * + * LiquidBounce is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiquidBounce is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LiquidBounce. If not, see . + * + */ +@Mixin(ClickableWidget.class) +public abstract class MixinClickableWidget { + @Shadow + public abstract int getRight(); + + @Shadow + public abstract int getHeight(); + + @Shadow + public abstract int getWidth(); + + @Shadow + public abstract boolean isHovered(); + + @Shadow + public boolean active; + @Shadow + protected float alpha; + + @Shadow + public abstract int getY(); + + @Shadow + public abstract int getX(); + + @Shadow + public abstract Text getMessage(); +} diff --git a/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/widget/MixinPressableWidget.java b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/widget/MixinPressableWidget.java new file mode 100644 index 00000000000..990dcece2dc --- /dev/null +++ b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/gui/widget/MixinPressableWidget.java @@ -0,0 +1,111 @@ +/* + * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce) + * + * Copyright (c) 2024 CCBlueX + * + * LiquidBounce is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiquidBounce is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LiquidBounce. If not, see . + * + */ + +package net.ccbluex.liquidbounce.injection.mixins.minecraft.gui.widget; + +import com.mojang.blaze3d.systems.RenderSystem; +import net.ccbluex.liquidbounce.features.misc.HideAppearance; +import net.ccbluex.liquidbounce.integration.theme.ThemeManager; +import net.ccbluex.liquidbounce.render.engine.Color4b; +import net.ccbluex.liquidbounce.utils.math.Easing; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.widget.PressableWidget; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.MathHelper; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(PressableWidget.class) +public abstract class MixinPressableWidget extends MixinClickableWidget { + + @Shadow + public abstract void drawMessage(DrawContext context, TextRenderer textRenderer, int color); + + /** + * An animation factor that is increased when hovering over the button. It serves + * for the easing (0.0 - 1.0) value and will be increased and decreased by a [ANIMATION_SPEED] * [delta] + */ + @Unique + private float factor; + @Unique + private final float ANIMATION_SPEED = 0.5f; + + @Inject(method = "renderWidget", at = @At("HEAD"), cancellable = true) + private void renderWidget(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo callbackInfo) { + if (HideAppearance.INSTANCE.isHidingNow() || HideAppearance.INSTANCE.isDestructed()) { + return; + } + + // Update animation factor + if (!isHovered()) { + factor = (float) Math.max(0.0, factor - ANIMATION_SPEED * delta); + } else { + factor = (float) Math.min(1.0, factor + ANIMATION_SPEED * delta); + } + + RenderSystem.enableBlend(); + RenderSystem.enableDepthTest(); + + var theme = ThemeManager.INSTANCE.getActiveTheme(); + var GUI_BUTTON_TEXTURE = theme.getTextures().get("button"); + var GUI_BUTTON_DISABLED_TEXTURE = theme.getTextures().get("button_disabled"); + var GUI_BUTTON_HOVER_TEXTURE = theme.getTextures().get("button_hover"); + + // If any of the textures are null, we will use the default ones + if (GUI_BUTTON_TEXTURE == null || GUI_BUTTON_DISABLED_TEXTURE == null || GUI_BUTTON_HOVER_TEXTURE == null) { + return; + } + + if (factor < 1) { + Identifier texture; + if (active) { + texture = GUI_BUTTON_TEXTURE.getValue(); + } else { + texture = GUI_BUTTON_DISABLED_TEXTURE.getValue(); + } + + context.drawTexture(RenderLayer::getGuiTextured, texture, this.getX(), this.getY(), 0, 0, this.getWidth(), this.getHeight(), + this.getWidth(), this.getHeight(), new Color4b(1f, 1f, 1f, this.alpha).toARGB()); + } + + if (factor > 0) { + var a = Easing.QUAD_IN_OUT.transform(factor); + context.drawTexture(RenderLayer::getGuiTextured, GUI_BUTTON_HOVER_TEXTURE.getValue(), this.getX(), this.getY(), 0, 0, this.getWidth(), this.getHeight(), + this.getWidth(), this.getHeight(), new Color4b(1f, 1f, 1f, a * this.alpha).toARGB()); + } + + // Draw the message + var i = this.active ? 16777215 : 10526880; + var color = i | MathHelper.ceil(this.alpha * 255.0F) << 24; + + this.drawMessage(context, MinecraftClient.getInstance().textRenderer, color); + + // Cancel the method + callbackInfo.cancel(); + } + +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/LiquidBounce.kt b/src/main/kotlin/net/ccbluex/liquidbounce/LiquidBounce.kt index b6a5d8ba120..4637795824a 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/LiquidBounce.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/LiquidBounce.kt @@ -52,7 +52,6 @@ import net.ccbluex.liquidbounce.integration.browser.BrowserManager import net.ccbluex.liquidbounce.integration.interop.ClientInteropServer import net.ccbluex.liquidbounce.integration.interop.protocol.rest.v1.game.ActiveServerList import net.ccbluex.liquidbounce.integration.theme.ThemeManager -import net.ccbluex.liquidbounce.integration.theme.component.ComponentOverlay import net.ccbluex.liquidbounce.lang.LanguageManager import net.ccbluex.liquidbounce.render.FontManager import net.ccbluex.liquidbounce.render.HAS_AMD_VEGA_APU @@ -171,9 +170,8 @@ object LiquidBounce : EventListener { // Load user scripts ScriptManager.loadAll() - // Load theme and component overlay - ThemeManager - ComponentOverlay.insertComponents() + // Load theme + ThemeManager.loadThemes() // Load config system from disk ConfigSystem.loadAll() diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/config/gson/GsonInstance.kt b/src/main/kotlin/net/ccbluex/liquidbounce/config/gson/GsonInstance.kt index 061c3d60c00..0fcbb6a9637 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/config/gson/GsonInstance.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/config/gson/GsonInstance.kt @@ -25,6 +25,7 @@ import com.google.gson.Gson import com.google.gson.GsonBuilder import net.ccbluex.liquidbounce.authlib.account.MinecraftAccount import net.ccbluex.liquidbounce.config.gson.adapter.* +import net.ccbluex.liquidbounce.config.gson.adapter.lookup.WallpaperLookupAdapter import net.ccbluex.liquidbounce.config.gson.serializer.* import net.ccbluex.liquidbounce.config.gson.serializer.minecraft.* import net.ccbluex.liquidbounce.config.gson.stategies.ExcludeStrategy @@ -32,9 +33,11 @@ import net.ccbluex.liquidbounce.config.gson.stategies.ProtocolExclusionStrategy import net.ccbluex.liquidbounce.config.types.ChoiceConfigurable import net.ccbluex.liquidbounce.config.types.Configurable import net.ccbluex.liquidbounce.config.types.NamedChoice -import net.ccbluex.liquidbounce.integration.theme.component.Component +import net.ccbluex.liquidbounce.integration.theme.Wallpaper +import net.ccbluex.liquidbounce.integration.theme.layout.component.Component import net.ccbluex.liquidbounce.render.engine.Color4b import net.ccbluex.liquidbounce.utils.input.InputBind +import net.ccbluex.liquidbounce.utils.render.Alignment import net.minecraft.block.Block import net.minecraft.client.gui.screen.Screen import net.minecraft.client.network.ServerInfo @@ -93,6 +96,7 @@ val fileGson: Gson = GsonBuilder() .addSerializationExclusionStrategy(ExcludeStrategy()) .registerCommonTypeAdapters() .registerTypeHierarchyAdapter(Configurable::class.javaObjectType, ConfigurableSerializer.FILE_SERIALIZER) + .registerTypeHierarchyAdapter(Component::class.javaObjectType, ComponentSerializer) .create() /** @@ -147,6 +151,8 @@ internal fun GsonBuilder.registerCommonTypeAdapters() = .registerTypeAdapter(ChoiceConfigurable::class.javaObjectType, ChoiceConfigurableSerializer) .registerTypeHierarchyAdapter(NamedChoice::class.javaObjectType, EnumChoiceSerializer) .registerTypeHierarchyAdapter(MinecraftAccount::class.javaObjectType, MinecraftAccountAdapter) + .registerTypeHierarchyAdapter(Alignment::class.javaObjectType, AlignmentAdapter) + .registerTypeHierarchyAdapter(Wallpaper::class.javaObjectType, WallpaperLookupAdapter) .registerTypeHierarchyAdapter(Text::class.javaObjectType, TextSerializer) .registerTypeHierarchyAdapter(Screen::class.javaObjectType, ScreenSerializer) .registerTypeAdapter(Session::class.javaObjectType, SessionSerializer) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/config/gson/adapter/AlignmentAdapter.kt b/src/main/kotlin/net/ccbluex/liquidbounce/config/gson/adapter/AlignmentAdapter.kt new file mode 100644 index 00000000000..f908fb73b67 --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/config/gson/adapter/AlignmentAdapter.kt @@ -0,0 +1,61 @@ +/* + * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce) + * + * Copyright (c) 2024 CCBlueX + * + * LiquidBounce is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiquidBounce is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LiquidBounce. If not, see . + * + * + */ + +package net.ccbluex.liquidbounce.config.gson.adapter + +import com.google.gson.* +import net.ccbluex.liquidbounce.utils.render.Alignment +import net.ccbluex.liquidbounce.utils.render.Alignment.ScreenAxisX +import java.lang.reflect.Type + +object AlignmentAdapter : JsonDeserializer, JsonSerializer { + + override fun deserialize( + json: JsonElement, + typeOfT: Type, + context: JsonDeserializationContext + ): Alignment { + val obj = json.asJsonObject + val horizontal = obj["horizontal"].asString + val horizontalEnum = ScreenAxisX.entries.find { it.choiceName == horizontal } + ?: throw IllegalArgumentException("Invalid horizontal alignment: $horizontal") + val horizontalOffset = obj["horizontalOffset"].asInt + val vertical = obj["vertical"].asString + val verticalEnum = Alignment.ScreenAxisY.entries.find { it.choiceName == vertical } + ?: throw IllegalArgumentException("Invalid vertical alignment: $vertical") + val verticalOffset = obj["verticalOffset"].asInt + + return Alignment(horizontalEnum, horizontalOffset, verticalEnum, verticalOffset) + + } + + override fun serialize( + src: Alignment, + typeOfSrc: Type, + context: JsonSerializationContext + ) = JsonObject().apply { + addProperty("horizontal", src.horizontalAlignment.choiceName) + addProperty("horizontalOffset", src.horizontalOffset) + addProperty("vertical", src.verticalAlignment.choiceName) + addProperty("verticalOffset", src.verticalOffset) + } + +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/config/gson/adapter/lookup/WallpaperLookupAdapter.kt b/src/main/kotlin/net/ccbluex/liquidbounce/config/gson/adapter/lookup/WallpaperLookupAdapter.kt new file mode 100644 index 00000000000..097d88c1867 --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/config/gson/adapter/lookup/WallpaperLookupAdapter.kt @@ -0,0 +1,59 @@ +/* + * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce) + * + * Copyright (c) 2024 CCBlueX + * + * LiquidBounce is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiquidBounce is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LiquidBounce. If not, see . + * + * + */ + +package net.ccbluex.liquidbounce.config.gson.adapter.lookup + +import com.google.gson.* +import net.ccbluex.liquidbounce.integration.theme.ThemeManager +import net.ccbluex.liquidbounce.integration.theme.Wallpaper +import java.lang.reflect.Type + +/** + * Unlike a deserializer, this adapter is used to look up a wallpaper by its name. + * This is useful when the wallpaper cannot be deserialized directly from the JSON and + * requires an instance to be present in the [ThemeManager.availableWallpapers] map. + */ +object WallpaperLookupAdapter : JsonDeserializer, JsonSerializer { + + override fun deserialize( + json: JsonElement, + typeOfT: Type, + context: JsonDeserializationContext + ): Wallpaper { + val jsonObject = json.asJsonObject + val name = jsonObject["name"].asString + val theme = jsonObject["theme"].asString + + return ThemeManager.availableWallpapers.find { wallpaper -> + wallpaper.name == name && wallpaper.theme.name == theme + } ?: Wallpaper.MinecraftWallpaper + } + + override fun serialize( + src: Wallpaper, + typeOfSrc: Type, + context: JsonSerializationContext + ) = JsonObject().apply { + addProperty("theme", src.theme.name) + addProperty("name", src.name) + } + +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/config/gson/serializer/ReadOnlyComponentSerializer.kt b/src/main/kotlin/net/ccbluex/liquidbounce/config/gson/serializer/ComponentSerializer.kt similarity index 63% rename from src/main/kotlin/net/ccbluex/liquidbounce/config/gson/serializer/ReadOnlyComponentSerializer.kt rename to src/main/kotlin/net/ccbluex/liquidbounce/config/gson/serializer/ComponentSerializer.kt index 35699095c2d..d9f1529e410 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/config/gson/serializer/ReadOnlyComponentSerializer.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/config/gson/serializer/ComponentSerializer.kt @@ -15,43 +15,50 @@ * * You should have received a copy of the GNU General Public License * along with LiquidBounce. If not, see . - * - * */ package net.ccbluex.liquidbounce.config.gson.serializer import com.google.gson.JsonObject -import com.google.gson.JsonPrimitive import com.google.gson.JsonSerializationContext import com.google.gson.JsonSerializer import net.ccbluex.liquidbounce.config.types.Configurable -import net.ccbluex.liquidbounce.integration.theme.component.Component +import net.ccbluex.liquidbounce.integration.theme.layout.component.Component import net.ccbluex.liquidbounce.utils.client.toLowerCamelCase -import net.ccbluex.liquidbounce.utils.render.Alignment import java.lang.reflect.Type +object ComponentSerializer : JsonSerializer { + + override fun serialize( + src: Component, typeOfSrc: Type, context: JsonSerializationContext + ) = JsonObject().apply { + addProperty("name", src.name) + addProperty("theme", src.theme.name) + add("value", context.serialize(src.inner)) + } + +} + object ReadOnlyComponentSerializer : JsonSerializer { override fun serialize( - src: Component, - typeOfSrc: Type, - context: JsonSerializationContext + src: Component, typeOfSrc: Type, context: JsonSerializationContext ) = JsonObject().apply { addProperty("name", src.name) + addProperty("id", src.id.toString()) add("settings", serializeReadOnly(src, context)) } private fun serializeReadOnly( - configurable: Configurable, - context: JsonSerializationContext + configurable: Configurable, context: JsonSerializationContext ): JsonObject = JsonObject().apply { for (v in configurable.inner) { - add(v.name.toLowerCamelCase(), when (v) { - is Alignment -> JsonPrimitive(v.toStyle()) - is Configurable -> serializeReadOnly(v, context) - else -> context.serialize(v.inner) - }) + add( + v.name.toLowerCamelCase(), when (v) { + is Configurable -> serializeReadOnly(v, context) + else -> context.serialize(v.inner) + } + ) } } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/config/types/ChoiceConfigurable.kt b/src/main/kotlin/net/ccbluex/liquidbounce/config/types/ChoiceConfigurable.kt index 01639913841..bf3b5109520 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/config/types/ChoiceConfigurable.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/config/types/ChoiceConfigurable.kt @@ -108,9 +108,12 @@ class ChoiceConfigurable( } /** - * A mode is sub-module to separate different bypasses into extra classes + * A mode is submodule to separate different bypasses into extra classes */ -abstract class Choice(name: String) : Configurable(name), EventListener, NamedChoice, MinecraftShortcuts { +abstract class Choice( + name: String, + value: MutableList> = mutableListOf() +) : Configurable(name, value), EventListener, NamedChoice, MinecraftShortcuts { override val choiceName: String get() = this.name diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/event/events/ClientEvents.kt b/src/main/kotlin/net/ccbluex/liquidbounce/event/events/ClientEvents.kt index 3785296a59a..675e201077c 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/event/events/ClientEvents.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/event/events/ClientEvents.kt @@ -31,7 +31,7 @@ import net.ccbluex.liquidbounce.features.misc.proxy.Proxy import net.ccbluex.liquidbounce.integration.browser.supports.IBrowser import net.ccbluex.liquidbounce.integration.interop.protocol.event.WebSocketEvent import net.ccbluex.liquidbounce.integration.interop.protocol.rest.v1.game.PlayerData -import net.ccbluex.liquidbounce.integration.theme.component.Component +import net.ccbluex.liquidbounce.integration.theme.layout.component.Component import net.ccbluex.liquidbounce.utils.client.Nameable import net.ccbluex.liquidbounce.utils.inventory.InventoryAction import net.ccbluex.liquidbounce.utils.inventory.InventoryActionChain diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/command/Command.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/command/Command.kt index d9853fbc3dc..d310bf4542c 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/command/Command.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/command/Command.kt @@ -73,7 +73,7 @@ class Command( } fun result(key: String, vararg args: Any): MutableText { - return translation("$translationBaseKey.result.$key", args = args) + return translation("$translationBaseKey.result.$key", *args) } fun resultWithTree(key: String, vararg args: Any): MutableText { @@ -84,10 +84,10 @@ class Command( parentCommand = parentCommand.parentCommand } - return parentCommand!!.result(key, args = args) + return parentCommand!!.result(key, *args) } - return translation("$translationBaseKey.result.$key", args = args) + return translation("$translationBaseKey.result.$key", *args) } /** diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/command/commands/client/client/CommandClient.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/command/commands/client/client/CommandClient.kt index 1fc3560e8f0..68ddb17b438 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/command/commands/client/client/CommandClient.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/command/commands/client/client/CommandClient.kt @@ -42,7 +42,6 @@ object CommandClient : CommandFactory { .subcommand(CommandClientIntegrationSubcommand.integrationCommand()) .subcommand(CommandClientLanguageSubcommand.languageCommand()) .subcommand(CommandClientThemeSubcommand.themeCommand()) - .subcommand(CommandClientComponentSubcommand.componentCommand()) .subcommand(CommandClientAppearanceSubcommand.appereanceCommand()) .subcommand(CommandClientPrefixSubcommand.prefixCommand()) .subcommand(CommandClientDestructSubcommand.destructCommand()) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/command/commands/client/client/CommandClientBrowserSubcommand.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/command/commands/client/client/CommandClientBrowserSubcommand.kt index 197533c72ad..9cfd6cce5fe 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/command/commands/client/client/CommandClientBrowserSubcommand.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/command/commands/client/client/CommandClientBrowserSubcommand.kt @@ -3,7 +3,7 @@ package net.ccbluex.liquidbounce.features.command.commands.client.client import com.mojang.blaze3d.systems.RenderSystem import net.ccbluex.liquidbounce.features.command.builder.CommandBuilder import net.ccbluex.liquidbounce.features.command.builder.ParameterBuilder -import net.ccbluex.liquidbounce.integration.BrowserScreen +import net.ccbluex.liquidbounce.integration.browser.BrowserScreen import net.ccbluex.liquidbounce.utils.client.chat import net.ccbluex.liquidbounce.utils.client.mc import net.ccbluex.liquidbounce.utils.client.regular diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/command/commands/client/client/CommandClientComponentSubcommand.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/command/commands/client/client/CommandClientComponentSubcommand.kt deleted file mode 100644 index 0a700455a18..00000000000 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/command/commands/client/client/CommandClientComponentSubcommand.kt +++ /dev/null @@ -1,103 +0,0 @@ -package net.ccbluex.liquidbounce.features.command.commands.client.client - -import net.ccbluex.liquidbounce.features.command.builder.CommandBuilder -import net.ccbluex.liquidbounce.features.command.builder.ParameterBuilder -import net.ccbluex.liquidbounce.integration.theme.component.ComponentOverlay -import net.ccbluex.liquidbounce.integration.theme.component.components -import net.ccbluex.liquidbounce.integration.theme.component.customComponents -import net.ccbluex.liquidbounce.integration.theme.component.types.ImageComponent -import net.ccbluex.liquidbounce.integration.theme.component.types.TextComponent -import net.ccbluex.liquidbounce.utils.client.chat -import net.ccbluex.liquidbounce.utils.client.regular - -object CommandClientComponentSubcommand { - fun componentCommand() = CommandBuilder.begin("component") - .hub() - .subcommand(listSubcommand()) - .subcommand(addSubcommand()) - .subcommand(removeSubcommand()) - .subcommand(clearSubcommand()) - .subcommand(updateSubcommand()) - .build() - - private fun updateSubcommand() = CommandBuilder.begin("update") - .handler { command, args -> - ComponentOverlay.fireComponentsUpdate() - - chat("Successfully updated components.") - }.build() - - private fun clearSubcommand() = CommandBuilder.begin("clear") - .handler { command, args -> - customComponents.clear() - ComponentOverlay.fireComponentsUpdate() - - chat("Successfully cleared components.") - }.build() - - private fun removeSubcommand() = CommandBuilder.begin("remove") - .parameter( - ParameterBuilder.begin("id") - .verifiedBy(ParameterBuilder.INTEGER_VALIDATOR).required() - .build() - ).handler { command, args -> - val index = args[-1] as Int - val component = customComponents.getOrNull(index) - - if (component == null) { - chat(regular("Component ID is out of range.")) - return@handler - } - - customComponents -= component - ComponentOverlay.fireComponentsUpdate() - chat("Successfully removed component.") - }.build() - - private fun addSubcommand() = CommandBuilder.begin("add") - .hub() - .subcommand( - CommandBuilder.begin("text") - .parameter( - ParameterBuilder.begin("text") - .vararg() - .verifiedBy(ParameterBuilder.STRING_VALIDATOR).required() - .build() - ).handler { command, args -> - val arg = (args[-1] as Array<*>).joinToString(" ") { it as String } - customComponents += TextComponent(arg) - ComponentOverlay.fireComponentsUpdate() - - chat("Successfully added text component.") - }.build() - ) - .subcommand( - CommandBuilder.begin("image") - .parameter( - ParameterBuilder.begin("url") - .vararg() - .verifiedBy(ParameterBuilder.STRING_VALIDATOR).required() - .build() - ).handler { command, args -> - val arg = (args[-1] as Array<*>).joinToString(" ") { it as String } - customComponents += ImageComponent(arg) - ComponentOverlay.fireComponentsUpdate() - - chat("Successfully added image component.") - }.build() - ) - .build() - - private fun listSubcommand() = CommandBuilder.begin("list") - .handler { command, args -> - chat(regular("In-built:")) - for (component in components) { - chat(regular("-> ${component.name}")) - } - - chat(regular("Custom:")) - for ((index, component) in customComponents.withIndex()) { - chat(regular("-> ${component.name} (#$index}")) - } - }.build() -} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/command/commands/client/client/CommandClientIntegrationSubcommand.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/command/commands/client/client/CommandClientIntegrationSubcommand.kt index 41a3182d1f8..a3349d7bff9 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/command/commands/client/client/CommandClientIntegrationSubcommand.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/command/commands/client/client/CommandClientIntegrationSubcommand.kt @@ -3,13 +3,10 @@ package net.ccbluex.liquidbounce.features.command.commands.client.client import net.ccbluex.liquidbounce.features.command.builder.CommandBuilder import net.ccbluex.liquidbounce.features.command.builder.ParameterBuilder import net.ccbluex.liquidbounce.integration.IntegrationListener -import net.ccbluex.liquidbounce.integration.IntegrationListener.clientJcef import net.ccbluex.liquidbounce.integration.VirtualScreenType import net.ccbluex.liquidbounce.integration.theme.ThemeManager -import net.ccbluex.liquidbounce.utils.client.MessageMetadata -import net.ccbluex.liquidbounce.utils.client.chat -import net.ccbluex.liquidbounce.utils.client.regular -import net.ccbluex.liquidbounce.utils.client.variable +import net.ccbluex.liquidbounce.integration.theme.type.RouteType +import net.ccbluex.liquidbounce.utils.client.* import net.minecraft.text.ClickEvent import net.minecraft.text.HoverEvent @@ -24,7 +21,7 @@ object CommandClientIntegrationSubcommand { private fun resetSubcommand() = CommandBuilder.begin("reset") .handler { _, _ -> chat(regular("Resetting client JCEF browser...")) - IntegrationListener.updateIntegrationBrowser() + IntegrationListener.sync() }.build() private fun overrideSubcommand() = CommandBuilder.begin("override") @@ -33,15 +30,20 @@ object CommandClientIntegrationSubcommand { .verifiedBy(ParameterBuilder.STRING_VALIDATOR).required() .build() ).handler { _, args -> - chat(regular("Overrides client JCEF browser...")) - clientJcef.loadUrl(args[0] as String) +// chat(regular("Overrides client JCEF browser...")) + // TODO: FIX +// clientJcef.loadUrl(args[0] as String) }.build() private fun menuSubcommand() = CommandBuilder.begin("menu") .alias("url") .handler { _, _ -> chat(variable("Client Integration")) - val baseUrl = ThemeManager.route().url + val baseUrl = (ThemeManager.route() as? RouteType.Web)?.url + ?: run { + chat(markAsError("Your current theme does not support web menu.")) + return@handler + } chat( regular("Base URL: ") @@ -63,9 +65,15 @@ object CommandClientIntegrationSubcommand { chat(metadata = MessageMetadata(prefix = false)) chat(regular("Integration Menu:")) for (screenType in VirtualScreenType.entries) { - val url = runCatching { - ThemeManager.route(screenType, true) + var url = runCatching { + ThemeManager.route(screenType) as? RouteType.Web }.getOrNull()?.url ?: continue + + // If the screen type is marked as static, it already contains ?static at the end + if (!screenType.isStatic) { + url = "$url?static" + } + val upperFirstName = screenType.routeName.replaceFirstChar { it.uppercase() } chat( @@ -98,8 +106,7 @@ object CommandClientIntegrationSubcommand { ) } - chat( - variable("Hint: You can also access the integration from another device.") - .styled { it.withItalic(true) }) + chat(variable("Hint: You can also access the integration from another device.") + .styled { it.withItalic(true) }) }.build() } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/command/commands/client/client/CommandClientThemeSubcommand.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/command/commands/client/client/CommandClientThemeSubcommand.kt index d103b3c1781..608db502932 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/command/commands/client/client/CommandClientThemeSubcommand.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/command/commands/client/client/CommandClientThemeSubcommand.kt @@ -27,19 +27,15 @@ object CommandClientThemeSubcommand { ParameterBuilder.begin("theme") .verifiedBy(ParameterBuilder.STRING_VALIDATOR).required() .autocompletedWith { s, _ -> - ThemeManager.themes().filter { it.startsWith(s, true) } + ThemeManager.themes + .map { theme -> theme.name } + .filter { name -> name.startsWith(name, true) } } .build() ) .handler { command, args -> val name = args[0] as String - if (name.equals("default", true)) { - ThemeManager.activeTheme = ThemeManager.defaultTheme - chat(regular("Switching theme to default...")) - return@handler - } - runCatching { ThemeManager.chooseTheme(name) }.onFailure { @@ -52,14 +48,14 @@ object CommandClientThemeSubcommand { private fun listSubcommand() = CommandBuilder.begin("list") .handler { command, args -> @Suppress("SpreadOperator") - (chat( + chat( regular("Available themes: "), - *ThemeManager.themes().flatMapIndexed { index, name -> + *ThemeManager.themes.flatMapIndexed { index, theme -> listOf( regular(if (index == 0) "" else ", "), - variable(name) + variable(theme.name) ) }.toTypedArray() - )) + ) }.build() } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/misc/HideAppearance.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/misc/HideAppearance.kt index 93ec2745219..20c31aba8ac 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/misc/HideAppearance.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/misc/HideAppearance.kt @@ -56,9 +56,9 @@ object HideAppearance : EventListener { private fun updateClient() { if (isHidingNow) { - IntegrationListener.restoreOriginalScreen() + IntegrationListener.restoreOriginal() } else { - IntegrationListener.updateIntegrationBrowser() + IntegrationListener.sync() } mc.updateWindowTitle() diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/ModuleClickGui.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/ModuleClickGui.kt index 67cbbb98d7f..0b470a779bd 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/ModuleClickGui.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/ModuleClickGui.kt @@ -26,11 +26,11 @@ import net.ccbluex.liquidbounce.event.handler import net.ccbluex.liquidbounce.event.sequenceHandler import net.ccbluex.liquidbounce.features.module.Category import net.ccbluex.liquidbounce.features.module.ClientModule +import net.ccbluex.liquidbounce.integration.VirtualDisplayScreen import net.ccbluex.liquidbounce.integration.VirtualScreenType -import net.ccbluex.liquidbounce.integration.VrScreen import net.ccbluex.liquidbounce.integration.browser.supports.tab.ITab import net.ccbluex.liquidbounce.integration.interop.protocol.rest.v1.game.isTyping -import net.ccbluex.liquidbounce.integration.theme.ThemeManager +import net.ccbluex.liquidbounce.integration.theme.ThemeManager.route import net.ccbluex.liquidbounce.utils.client.asText import net.ccbluex.liquidbounce.utils.client.inGame import net.ccbluex.liquidbounce.utils.kotlin.EventPriorityConvention @@ -63,7 +63,7 @@ object ModuleClickGui : closeView() } - if (mc.currentScreen is VrScreen || mc.currentScreen is ClickScreen) { + if (mc.currentScreen is VirtualDisplayScreen || mc.currentScreen is ClickScreen) { enable() } } @@ -75,7 +75,7 @@ object ModuleClickGui : } val isInSearchBar: Boolean - get() = (mc.currentScreen is VrScreen || mc.currentScreen is ClickScreen) && isTyping + get() = (mc.currentScreen is VirtualDisplayScreen || mc.currentScreen is ClickScreen) && isTyping object Snapping : ToggleableConfigurable(this, "Snapping", true) { @@ -106,7 +106,7 @@ object ModuleClickGui : mc.setScreen( if (clickGuiTab == null) { - VrScreen(VirtualScreenType.CLICK_GUI) + VirtualDisplayScreen(route(VirtualScreenType.CLICK_GUI)) } else { ClickScreen() } @@ -122,9 +122,10 @@ object ModuleClickGui : return } - clickGuiTab = ThemeManager.openInputAwareImmediate(VirtualScreenType.CLICK_GUI, true) { - mc.currentScreen is ClickScreen - }.preferOnTop() + // todo: fix after merge +// clickGuiTab = ThemeManager.openInputAwareImmediate(VirtualScreenType.CLICK_GUI, true) { +// mc.currentScreen is ClickScreen +// }.preferOnTop() } /** diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/ModuleHud.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/ModuleHud.kt index 6308728e75e..0da640cac4b 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/ModuleHud.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/ModuleHud.kt @@ -18,8 +18,7 @@ */ package net.ccbluex.liquidbounce.features.module.modules.render -import net.ccbluex.liquidbounce.config.types.Configurable -import net.ccbluex.liquidbounce.config.types.Value +import com.mojang.blaze3d.systems.RenderSystem import net.ccbluex.liquidbounce.event.EventManager import net.ccbluex.liquidbounce.event.events.ScreenEvent import net.ccbluex.liquidbounce.event.events.SpaceSeperatedNamesChangeEvent @@ -28,18 +27,15 @@ import net.ccbluex.liquidbounce.features.misc.HideAppearance.isDestructed import net.ccbluex.liquidbounce.features.misc.HideAppearance.isHidingNow import net.ccbluex.liquidbounce.features.module.Category import net.ccbluex.liquidbounce.features.module.ClientModule -import net.ccbluex.liquidbounce.integration.VirtualScreenType -import net.ccbluex.liquidbounce.integration.browser.supports.tab.ITab import net.ccbluex.liquidbounce.integration.theme.ThemeManager -import net.ccbluex.liquidbounce.integration.theme.component.components -import net.ccbluex.liquidbounce.integration.theme.component.customComponents -import net.ccbluex.liquidbounce.integration.theme.component.types.minimap.ChunkRenderer +import net.ccbluex.liquidbounce.integration.theme.layout.Layout +import net.ccbluex.liquidbounce.integration.theme.layout.component.ComponentManager +import net.ccbluex.liquidbounce.integration.theme.themes.liquidbounce.components.minimap.ChunkRenderer import net.ccbluex.liquidbounce.utils.block.ChunkScanner import net.ccbluex.liquidbounce.utils.client.chat import net.ccbluex.liquidbounce.utils.client.inGame import net.ccbluex.liquidbounce.utils.client.markAsError import net.ccbluex.liquidbounce.utils.entity.RenderedEntities -import net.minecraft.client.MinecraftClient import net.minecraft.client.gui.screen.DisconnectedScreen /** @@ -53,8 +49,6 @@ object ModuleHud : ClientModule("HUD", Category.RENDER, state = true, hide = tru override val running get() = this.enabled && !isDestructed - private var browserTab: ITab? = null - override val baseKey: String get() = "liquidbounce.module.hud" @@ -67,32 +61,40 @@ object ModuleHud : ClientModule("HUD", Category.RENDER, state = true, hide = tru } val isBlurable - get() = blur && !(mc.options.hudHidden && mc.currentScreen == null) && - // Only blur on Windows and Linux - Mac seems to have issues with it - // TODO: fix blur on macOS - !MinecraftClient.IS_SYSTEM_MAC + get() = blur && !(mc.options.hudHidden && mc.currentScreen == null) + + val layouts = choices("Layouts", 0) { + ThemeManager.themes.map { theme -> Layout(theme) }.toTypedArray() + }.apply { + onChanged { + if (enabled) { + RenderSystem.recordRenderCall { + ComponentManager.update() + } + } + } + } init { - tree(Configurable("In-built", components as MutableList>)) - tree(Configurable("Custom", customComponents as MutableList>)) + RenderSystem.recordRenderCall { + ChunkRenderer + } } - @Suppress("unused") - private val screenHandler = handler { - if (!running || !inGame || it.screen is DisconnectedScreen || isHidingNow) { - browserTab?.closeTab() - browserTab = null - } else if (browserTab == null) { - browserTab = ThemeManager.openImmediate(VirtualScreenType.HUD, true) + val screenHandler = handler { event -> + if (!enabled || !inGame || event.screen is DisconnectedScreen || isHidingNow) { + ComponentManager.clear() + } else { + ComponentManager.update() } } fun refresh() { // Should not happen, but in-case there is already a tab open, close it - browserTab?.closeTab() + ComponentManager.clear() // Create a new tab and open it - browserTab = ThemeManager.openImmediate(VirtualScreenType.HUD, true) + ComponentManager.update() } override fun enable() { @@ -109,8 +111,7 @@ object ModuleHud : ClientModule("HUD", Category.RENDER, state = true, hide = tru override fun disable() { // Closes tab entirely - browserTab?.closeTab() - browserTab = null + ComponentManager.clear() // Minimap RenderedEntities.unsubscribe(this) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/ModuleItemTags.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/ModuleItemTags.kt index c18dff257e7..8bf3ae768c0 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/ModuleItemTags.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/ModuleItemTags.kt @@ -38,7 +38,9 @@ import net.ccbluex.liquidbounce.utils.kotlin.EventPriorityConvention.FIRST_PRIOR import net.ccbluex.liquidbounce.utils.kotlin.forEachWithSelf import net.ccbluex.liquidbounce.utils.kotlin.proportionOfValue import net.ccbluex.liquidbounce.utils.kotlin.valueAtProportion -import net.ccbluex.liquidbounce.utils.math.* +import net.ccbluex.liquidbounce.utils.math.Easing +import net.ccbluex.liquidbounce.utils.math.average +import net.ccbluex.liquidbounce.utils.math.sq import net.ccbluex.liquidbounce.utils.render.WorldToScreen import net.minecraft.client.gui.DrawContext import net.minecraft.entity.ItemEntity diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/DrawerReference.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/DrawerReference.kt new file mode 100644 index 00000000000..90e034d1815 --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/DrawerReference.kt @@ -0,0 +1,132 @@ +package net.ccbluex.liquidbounce.integration + +import net.ccbluex.liquidbounce.event.EventManager +import net.ccbluex.liquidbounce.event.events.VirtualScreenEvent +import net.ccbluex.liquidbounce.integration.browser.BrowserManager +import net.ccbluex.liquidbounce.integration.browser.supports.tab.ITab +import net.ccbluex.liquidbounce.integration.theme.type.RouteType +import net.ccbluex.liquidbounce.integration.theme.type.native.NativeDrawer +import net.ccbluex.liquidbounce.utils.client.mc +import net.ccbluex.liquidbounce.utils.render.refreshRate +import net.minecraft.client.gui.screen.ChatScreen + +sealed class DrawerReference : AutoCloseable { + data class Native( + val drawer: NativeDrawer, + var route: RouteType.Native, + ) : DrawerReference() + + data class Web( + val browser: ITab, var route: RouteType.Web + ) : DrawerReference() + + companion object { + + private val takesInputHandler: () -> Boolean + get() = { mc.currentScreen != null && mc.currentScreen !is ChatScreen } + + // TODO: Implement Custom HUD editor + private val takesInputOnEditor: () -> Boolean + get() = { false } + + fun newComponentRef(route: RouteType, stage: DrawingStage = DrawingStage.OVERLAY) = when (route) { + is RouteType.Web -> Web( + BrowserManager.browser?.createTab( + route.url, frameRate = 60, takesInput = takesInputOnEditor + )?.stage(stage) ?: error("Browser is not initialized"), route + ) + + is RouteType.Native -> Native(NativeDrawer(route.drawableRoute, stage, takesInputOnEditor), route) + } + + fun newInputRef(route: RouteType, stage: DrawingStage = DrawingStage.SCREEN) = when (route) { + is RouteType.Web -> Web( + BrowserManager.browser?.createTab( + route.url, frameRate = refreshRate, takesInput = takesInputHandler + )?.stage(stage) ?: error("Browser is not initialized"), route + ) + + is RouteType.Native -> Native(NativeDrawer(route.drawableRoute, stage, takesInputHandler), route) + } + + } + + fun update(route: RouteType) { + when (route) { + is RouteType.Web -> when (this) { + is Web -> { + // If the reference is already on the same route, we don't need to update it + if (this.route.theme == route.theme && this.route.type == route.type) { + return + } + + if (this.route.theme != route.theme) { + browser.loadUrl(route.url) + this.route = route + return + } + + when (val type = route.type) { + // If the new route type is null, we should trigger a close event, + // but if the current route type is null as well, we simply ignore it + null -> EventManager.callEvent( + VirtualScreenEvent( + // Use the current route name + this.route.type?.routeName ?: return, VirtualScreenEvent.Action.CLOSE + ) + ) + // If the new route type is not null, we should trigger an open event + else -> EventManager.callEvent( + VirtualScreenEvent( + type.routeName, VirtualScreenEvent.Action.OPEN + ) + ) + } + this.route = route + } + + is Native -> error("Unable to update tab, drawer reference is not a web tab") + } + + is RouteType.Native -> when (this) { + is Native -> { + drawer.select(route.drawableRoute) + this.route = route + } + + is Web -> error("Unable to update tab, drawer reference is not a native tab") + } + } + } + + fun sync() { + when (this) { + is Web -> browser.loadUrl(route.url) + is Native -> drawer.select(route.drawableRoute) + } + } + + fun isCompatible(route: RouteType): Boolean { + return when (this) { + is Web -> route is RouteType.Web + is Native -> route is RouteType.Native + } + } + + fun matches(route: RouteType): Boolean { + return when (this) { + is Web -> route is RouteType.Web && route.url == this.route.url + is Native -> route is RouteType.Native && route.drawableRoute == this.route.drawableRoute + } + } + + override fun close() = when (this) { + is Web -> browser.closeTab() + is Native -> drawer.close() + } + +} + +enum class DrawingStage { + OVERLAY, SCREEN +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/IntegrationListener.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/IntegrationListener.kt index 718dedd2ef5..1318b60f6c5 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/IntegrationListener.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/IntegrationListener.kt @@ -22,15 +22,15 @@ package net.ccbluex.liquidbounce.integration import com.mojang.blaze3d.systems.RenderSystem import net.ccbluex.liquidbounce.LiquidBounce import net.ccbluex.liquidbounce.event.EventListener -import net.ccbluex.liquidbounce.event.EventManager -import net.ccbluex.liquidbounce.event.events.* +import net.ccbluex.liquidbounce.event.events.BrowserReadyEvent +import net.ccbluex.liquidbounce.event.events.GameTickEvent +import net.ccbluex.liquidbounce.event.events.ScreenEvent +import net.ccbluex.liquidbounce.event.events.WorldChangeEvent import net.ccbluex.liquidbounce.event.handler import net.ccbluex.liquidbounce.features.misc.HideAppearance -import net.ccbluex.liquidbounce.integration.browser.BrowserManager -import net.ccbluex.liquidbounce.integration.theme.Theme -import net.ccbluex.liquidbounce.integration.theme.ThemeManager +import net.ccbluex.liquidbounce.integration.theme.ThemeManager.route +import net.ccbluex.liquidbounce.integration.theme.type.RouteType import net.ccbluex.liquidbounce.mcef.progress.MCEFProgressMenu -import net.ccbluex.liquidbounce.utils.client.Chronometer import net.ccbluex.liquidbounce.utils.client.logger import net.ccbluex.liquidbounce.utils.client.mc import net.minecraft.client.gui.screen.Screen @@ -39,125 +39,80 @@ import org.lwjgl.glfw.GLFW object IntegrationListener : EventListener { - /** - * This tab is always open and initialized. We keep this tab open to make it possible to draw on the screen, - * even when no specific tab is open. - * It also reduces the time required to open a new tab and allows for smooth transitions between tabs. - * - * The client tab will be initialized when the browser is ready. - */ - val clientJcef by lazy { - ThemeManager.openInputAwareImmediate().preferOnTop() - } - - var momentaryVirtualScreen: VirtualScreen? = null - private set - - var runningTheme = ThemeManager.activeTheme - private set - - /** - * Acknowledgement is used to detect desyncs between the integration browser and the client. - * It is reset when the client opens a new screen and confirmed when the integration browser - * opens the same screen. - * - * If the acknowledgement is not confirmed after 500ms, the integration browser will be reloaded. - */ - val acknowledgement = Acknowledgement() - - private val standardCursor = GLFW.glfwCreateStandardCursor(GLFW.GLFW_ARROW_CURSOR) - - data class VirtualScreen(val type: VirtualScreenType, val openSince: Chronometer = Chronometer()) - - class Acknowledgement( - val since: Chronometer = Chronometer(), - var confirmed: Boolean = false - ) { - - @Suppress("unused") - val isDesynced - get() = !confirmed && since.hasElapsed(1000) - - fun confirm() { - confirmed = true - } + private lateinit var ref: DrawerReference - fun reset() { - since.reset() - confirmed = false + val route: RouteType + get() = when (val ref = ref) { + is DrawerReference.Native -> ref.route + is DrawerReference.Web -> ref.route } - } + private var browserIsReady = false internal val parent: Screen get() = mc.currentScreen ?: TitleScreen() - private var browserIsReady = false + /** + * GLFW cursor for the standard cursor + */ + private val standardCursor = GLFW.glfwCreateStandardCursor(GLFW.GLFW_ARROW_CURSOR) @Suppress("unused") val handleBrowserReady = handler { logger.info("Browser is ready.") - // Fires up the client tab - clientJcef + // Reference will be either a NativeDrawer or a WebDrawer depending on what the user has selected + ref = DrawerReference.newInputRef(route(null)) browserIsReady = true } @Suppress("unused") fun virtualOpen(name: String) { val type = VirtualScreenType.byName(name) ?: return - virtualOpen(type = type) + virtualOpen(route(type)) } - fun virtualOpen(theme: Theme = ThemeManager.activeTheme, type: VirtualScreenType) { - // Check if the virtual screen is already open - if (momentaryVirtualScreen?.type == type) { - return - } - - if (runningTheme != theme) { - runningTheme = theme - ThemeManager.updateImmediate(clientJcef, type) - } - - val virtualScreen = VirtualScreen(type).apply { momentaryVirtualScreen = this } - acknowledgement.reset() - EventManager.callEvent( - VirtualScreenEvent( - virtualScreen.type.routeName, - VirtualScreenEvent.Action.OPEN - ) - ) + fun virtualOpen(route: RouteType) { + apply(route) } fun virtualClose() { - val virtualScreen = momentaryVirtualScreen ?: return - - momentaryVirtualScreen = null - acknowledgement.reset() - EventManager.callEvent( - VirtualScreenEvent( - virtualScreen.type.routeName, - VirtualScreenEvent.Action.CLOSE - ) - ) + apply(route(null)) } - fun updateIntegrationBrowser() { - if (!browserIsReady || BrowserManager.browser?.isInitialized() != true) { + /** + * Apply a new route to the drawer reference + */ + private fun apply(route: RouteType) { + // If the reference is already on the same route, we don't need to update it + if (ref.matches(route)) { return } - logger.info( - "Reloading integration browser ${clientJcef.javaClass.simpleName} " + - "to ${ThemeManager.route()}" - ) - ThemeManager.updateImmediate(clientJcef, momentaryVirtualScreen?.type) + if (ref.isCompatible(route)) { + // If the reference is compatible with the route, we can update it + ref.update(route) + } else { + // Otherwise, we close the reference and create a new one + ref.close() + ref = DrawerReference.newInputRef(route) + } + } + + /** + * Sync the drawer reference. This might fix desyncs. + */ + fun sync() { + logger.info("Reloading integration browser ${ref.javaClass.simpleName}") + ref.sync() } - fun restoreOriginalScreen() { - if (mc.currentScreen is VrScreen) { - mc.setScreen((mc.currentScreen as VrScreen).originalScreen) + /** + * Restore the original screen if the current screen is a virtual display screen + */ + fun restoreOriginal() { + if (mc.currentScreen is VirtualDisplayScreen) { + mc.setScreen((mc.currentScreen as VirtualDisplayScreen).originalScreen) } } @@ -165,7 +120,7 @@ object IntegrationListener : EventListener { * Handle opening new screens */ @Suppress("unused") - val screenHandler = handler { event -> + private val screenHandler = handler { event -> // Set to default GLFW cursor GLFW.glfwSetCursor(mc.window.handle, standardCursor) @@ -174,8 +129,10 @@ object IntegrationListener : EventListener { } } + // TODO: Can we remove this? It should usually be handled by ScreenEvent already @Suppress("unused") - val screenRefresher = handler { + private val screenRefresher = handler { + if (browserIsReady && mc.currentScreen !is MCEFProgressMenu) { handleCurrentScreen(mc.currentScreen) } @@ -186,57 +143,46 @@ object IntegrationListener : EventListener { * and go back to the main menu. */ @Suppress("unused") - val worldChangeEvent = handler { - updateIntegrationBrowser() + private val worldChangeEvent = handler { + sync() } private fun handleCurrentScreen(screen: Screen?): Boolean { - return when { - screen !is VrScreen && HideAppearance.isHidingNow -> { - virtualClose() + if (screen !is VirtualDisplayScreen && HideAppearance.isHidingNow) { + virtualClose() + return false + } - false - } - !browserIsReady -> { - return if (screen !is MCEFProgressMenu) { - RenderSystem.recordRenderCall { - mc.setScreen(MCEFProgressMenu(LiquidBounce.CLIENT_NAME)) - } - - true - } else { - false + if (!browserIsReady) { + if (screen !is MCEFProgressMenu) { + RenderSystem.recordRenderCall { + mc.setScreen(MCEFProgressMenu(LiquidBounce.CLIENT_NAME)) } + return true } - screen is VrScreen -> false - else -> { - // Are we currently playing the game? - if (mc.world != null && screen == null) { - virtualClose() - return false - } + return false + } - handleCurrentMinecraftScreen(screen ?: TitleScreen()) - } + if (screen is VirtualDisplayScreen) { + return false } - } - /** - * @return should cancel the minecraft screen - */ - private fun handleCurrentMinecraftScreen(virtScreen: Screen): Boolean { - val virtualScreenType = VirtualScreenType.recognize(virtScreen) + val screen = screen ?: if (mc.world != null) { + virtualClose() + return false + } else { + TitleScreen() + } + val virtualScreenType = VirtualScreenType.recognize(screen) if (virtualScreenType == null) { virtualClose() - return false } - val name = virtualScreenType.routeName val route = runCatching { - ThemeManager.route(virtualScreenType, false) + route(virtualScreenType) }.getOrNull() if (route == null) { @@ -246,23 +192,17 @@ object IntegrationListener : EventListener { val theme = route.theme - return when { - theme.doesSupport(name) -> { - mc.setScreen(VrScreen(virtualScreenType, theme, originalScreen = virtScreen)) - - true - } - theme.doesOverlay(name) -> { - virtualOpen(theme, virtualScreenType) - - false - } - else -> { - virtualClose() - - false - } + if (theme.doesSupport(virtualScreenType)) { + val virtualDisplayScreen = VirtualDisplayScreen(route, originalScreen = screen) + mc.setScreen(virtualDisplayScreen) + return true + } else if (theme.doesOverlay(virtualScreenType)) { + virtualOpen(route) + } else { + virtualClose() } + + return false } } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/VrScreen.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/VirtualDisplayScreen.kt similarity index 78% rename from src/main/kotlin/net/ccbluex/liquidbounce/integration/VrScreen.kt rename to src/main/kotlin/net/ccbluex/liquidbounce/integration/VirtualDisplayScreen.kt index 0e0c737341a..6f0b7cc6929 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/VrScreen.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/VirtualDisplayScreen.kt @@ -19,21 +19,19 @@ */ package net.ccbluex.liquidbounce.integration -import net.ccbluex.liquidbounce.integration.theme.Theme -import net.ccbluex.liquidbounce.integration.theme.ThemeManager +import net.ccbluex.liquidbounce.integration.theme.type.RouteType import net.ccbluex.liquidbounce.utils.client.asText import net.ccbluex.liquidbounce.utils.client.mc import net.minecraft.client.gui.screen.Screen -class VrScreen( - private val screenType: VirtualScreenType, - private val theme: Theme = ThemeManager.route(screenType).theme, +class VirtualDisplayScreen( + val route: RouteType, val originalScreen: Screen? = null, val parentScreen: Screen? = mc.currentScreen -) : Screen("VS-${screenType.routeName.uppercase()}".asText()) { +) : Screen("VS-${route.type?.routeName?.uppercase()}".asText()) { override fun init() { - IntegrationListener.virtualOpen(theme, screenType) + IntegrationListener.virtualOpen(route) } override fun close() { diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/VirtualScreenType.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/VirtualScreenType.kt index 1b7324f1f36..6e153f91637 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/VirtualScreenType.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/VirtualScreenType.kt @@ -22,6 +22,8 @@ package net.ccbluex.liquidbounce.integration import com.mojang.blaze3d.systems.RenderSystem +import net.ccbluex.liquidbounce.integration.browser.BrowserScreen +import net.ccbluex.liquidbounce.integration.theme.ThemeManager.route import net.ccbluex.liquidbounce.utils.client.mc import net.ccbluex.liquidbounce.utils.client.openVfpProtocolSelection import net.minecraft.client.gui.screen.DisconnectedScreen @@ -49,11 +51,11 @@ private val Screen.isLunar enum class VirtualScreenType( val routeName: String, val recognizer: (Screen) -> Boolean = { false }, - val isInGame: Boolean = false, - private val open: () -> Unit = { mc.setScreen(VrScreen(byName(routeName)!!)) } + val isStatic: Boolean = false, + private val open: () -> Unit = { mc.setScreen(VirtualDisplayScreen(route(byName(routeName)))) } ) { - HUD("hud", isInGame = true), + HUD("hud", isStatic = true), CLICK_GUI("clickgui"), ALT_MANAGER("altmanager"), PROXY_MANAGER("proxymanager"), diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/browser/BrowserDrawer.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/browser/BrowserDrawer.kt index a18b9b9fed4..e250b374f68 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/browser/BrowserDrawer.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/browser/BrowserDrawer.kt @@ -23,6 +23,7 @@ import com.mojang.blaze3d.systems.RenderSystem import net.ccbluex.liquidbounce.event.EventListener import net.ccbluex.liquidbounce.event.events.* import net.ccbluex.liquidbounce.event.handler +import net.ccbluex.liquidbounce.integration.DrawingStage import net.ccbluex.liquidbounce.integration.browser.supports.IBrowser import net.ccbluex.liquidbounce.render.engine.UiRenderer import net.ccbluex.liquidbounce.utils.client.mc @@ -123,7 +124,7 @@ class BrowserDrawer(val browser: () -> IBrowser?) : EventListener { continue } - if (tab.preferOnTop && mc.currentScreen != null) { + if (tab.drawingStage == DrawingStage.SCREEN && mc.currentScreen != null) { continue } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/BrowserScreen.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/browser/BrowserScreen.kt similarity index 92% rename from src/main/kotlin/net/ccbluex/liquidbounce/integration/BrowserScreen.kt rename to src/main/kotlin/net/ccbluex/liquidbounce/integration/browser/BrowserScreen.kt index 0e7d2cfdd76..50fb1e146af 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/BrowserScreen.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/browser/BrowserScreen.kt @@ -16,16 +16,16 @@ * You should have received a copy of the GNU General Public License * along with LiquidBounce. If not, see . */ -package net.ccbluex.liquidbounce.integration +package net.ccbluex.liquidbounce.integration.browser import net.ccbluex.liquidbounce.event.EventManager import net.ccbluex.liquidbounce.event.events.BrowserUrlChangeEvent +import net.ccbluex.liquidbounce.integration.DrawingStage +import net.ccbluex.liquidbounce.integration.browser.supports.tab.ITab +import net.ccbluex.liquidbounce.integration.browser.supports.tab.TabPosition import net.ccbluex.liquidbounce.utils.client.asText import net.ccbluex.liquidbounce.utils.client.mc import net.ccbluex.liquidbounce.utils.render.refreshRate -import net.ccbluex.liquidbounce.integration.browser.BrowserManager -import net.ccbluex.liquidbounce.integration.browser.supports.tab.ITab -import net.ccbluex.liquidbounce.integration.browser.supports.tab.TabPosition import net.minecraft.client.gui.DrawContext import net.minecraft.client.gui.screen.Screen import net.minecraft.text.Text @@ -52,8 +52,8 @@ class BrowserScreen(val url: String, title: Text = "".asText()) : Screen(title) if (browserTabs.isEmpty()) { val browser = BrowserManager.browser ?: return - browser.createInputAwareTab(url, position, refreshRate) { mc.currentScreen == this } - .preferOnTop() + browser.createTab(url, position, refreshRate) { mc.currentScreen == this } + .stage(DrawingStage.SCREEN) .also { browserTabs.add(it) } return } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/browser/supports/IBrowser.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/browser/supports/IBrowser.kt index ec226eae2dc..839b7c533ca 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/browser/supports/IBrowser.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/browser/supports/IBrowser.kt @@ -36,10 +36,8 @@ interface IBrowser { fun isInitialized(): Boolean - fun createTab(url: String, position: TabPosition = TabPosition.FULLSCREEN, frameRate: Int): ITab - - fun createInputAwareTab(url: String, position: TabPosition = TabPosition.FULLSCREEN, frameRate: Int, - takesInput: () -> Boolean): ITab + fun createTab(url: String, position: TabPosition = TabPosition.FULLSCREEN, frameRate: Int, + takesInput: () -> Boolean): ITab fun getTabs(): List diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/browser/supports/JcefBrowser.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/browser/supports/JcefBrowser.kt index 5f9166f2b83..9322f5a4e1d 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/browser/supports/JcefBrowser.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/browser/supports/JcefBrowser.kt @@ -156,23 +156,19 @@ class JcefBrowser : IBrowser, EventListener { override fun isInitialized() = MCEF.INSTANCE.isInitialized - override fun createTab(url: String, position: TabPosition, frameRate: Int) = - JcefTab(this, url, position, frameRate) { false }.apply(::addTab) - - override fun createInputAwareTab(url: String, position: TabPosition, frameRate: Int, takesInput: () -> Boolean) = - JcefTab(this, url, position, frameRate, takesInput = takesInput).apply(::addTab) + override fun createTab(url: String, position: TabPosition, frameRate: Int, takesInput: () -> Boolean) = + JcefTab(this, url, position, frameRate, takesInput = takesInput).apply { + tabs.sortedInsert(this, JcefTab::drawingStage) + } - override fun getTabs(): List = tabs - - private fun addTab(tab: JcefTab) { - tabs.sortedInsert(tab, JcefTab::preferOnTop) - } + override fun getTabs() = tabs internal fun removeTab(tab: JcefTab) { tabs.remove(tab) } override fun getBrowserType() = BrowserType.JCEF + override fun drawGlobally() { if (MCEF.INSTANCE.isInitialized) { try { diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/browser/supports/tab/ITab.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/browser/supports/tab/ITab.kt index 3eff3bf1cdd..9b8048239ed 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/browser/supports/tab/ITab.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/browser/supports/tab/ITab.kt @@ -18,6 +18,7 @@ */ package net.ccbluex.liquidbounce.integration.browser.supports.tab +import net.ccbluex.liquidbounce.integration.DrawingStage import net.minecraft.util.Identifier interface ITab { @@ -25,7 +26,7 @@ interface ITab { var position: TabPosition var drawn: Boolean - var preferOnTop: Boolean + var drawingStage: DrawingStage fun forceReload() fun reload() @@ -37,10 +38,9 @@ interface ITab { fun getTexture(): Identifier fun resize(width: Int, height: Int) - fun preferOnTop(): ITab { - preferOnTop = true + fun stage(stage: DrawingStage): ITab { + drawingStage = stage return this } - } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/browser/supports/tab/JcefTab.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/browser/supports/tab/JcefTab.kt index f335d1c7b13..41432e9f2a9 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/browser/supports/tab/JcefTab.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/browser/supports/tab/JcefTab.kt @@ -19,6 +19,7 @@ package net.ccbluex.liquidbounce.integration.browser.supports.tab import net.ccbluex.liquidbounce.features.module.MinecraftShortcuts +import net.ccbluex.liquidbounce.integration.DrawingStage import net.ccbluex.liquidbounce.integration.browser.supports.JcefBrowser import net.ccbluex.liquidbounce.mcef.MCEF import net.ccbluex.liquidbounce.mcef.MCEFBrowser @@ -59,7 +60,7 @@ class JcefTab( private val texture = Identifier.of("liquidbounce", "browser/tab/${mcefBrowser.hashCode()}") override var drawn = false - override var preferOnTop = false + override var drawingStage = DrawingStage.SCREEN init { mc.textureManager.registerTexture(texture, object : AbstractTexture() { @@ -87,7 +88,7 @@ class JcefTab( mcefBrowser.loadURL(url) } - override fun getUrl() = mcefBrowser.getURL() + override fun getUrl(): String = mcefBrowser.url override fun closeTab() { mcefBrowser.close() diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/interop/protocol/rest/v1/InteropFunctionRegistry.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/interop/protocol/rest/v1/InteropFunctionRegistry.kt index 69f64de888d..9537029e754 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/interop/protocol/rest/v1/InteropFunctionRegistry.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/interop/protocol/rest/v1/InteropFunctionRegistry.kt @@ -44,7 +44,12 @@ internal fun registerInteropFunctions(node: Node) = node.withPath("/api/v1/clien // Theme Functions get("/theme", ::getThemeInfo) - post("/shader", ::postToggleShader) + get("/fonts", ::getFonts) + get("/fonts/:name", ::getFont) + + // Wallpaper Functions + get("/wallpaper", ::getWallpaper) + put("/wallpaper/:theme/:name", ::putWallpaper) // VirtualScreen Functions get("/virtualScreen", ::getVirtualScreenInfo) @@ -64,9 +69,13 @@ internal fun registerInteropFunctions(node: Node) = node.withPath("/api/v1/clien } get("/module/:name", ::getModule) + // Component Array Functions + get("/components", ::getAllComponents) + get("/components/:name", ::getComponents) // Component Functions - get("/components", ::getComponents) + put("/component/:id", ::updateComponentSettings) + get("/component/:id", ::getComponentSettings) // Session Functions get("/session", ::getSessionInfo) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/interop/protocol/rest/v1/client/ComponentFunctions.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/interop/protocol/rest/v1/client/ComponentFunctions.kt index 98c7b9c75cc..9dd8cebfb65 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/interop/protocol/rest/v1/client/ComponentFunctions.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/interop/protocol/rest/v1/client/ComponentFunctions.kt @@ -20,13 +20,63 @@ package net.ccbluex.liquidbounce.integration.interop.protocol.rest.v1.client +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import io.netty.handler.codec.http.FullHttpResponse +import net.ccbluex.liquidbounce.config.ConfigSystem import net.ccbluex.liquidbounce.config.gson.accessibleInteropGson -import net.ccbluex.liquidbounce.integration.theme.component.components -import net.ccbluex.liquidbounce.integration.theme.component.customComponents +import net.ccbluex.liquidbounce.config.gson.interopGson +import net.ccbluex.liquidbounce.integration.theme.layout.component.ComponentManager import net.ccbluex.netty.http.model.RequestObject +import net.ccbluex.netty.http.util.httpBadRequest import net.ccbluex.netty.http.util.httpOk +import java.io.StringReader +import java.util.* // GET /api/v1/client/components @Suppress("UNUSED_PARAMETER") -fun getComponents(requestObject: RequestObject) = - httpOk(accessibleInteropGson.toJsonTree(components + customComponents).asJsonArray) +fun getAllComponents(requestObject: RequestObject) = httpOk(JsonArray().apply { + for (component in ComponentManager.activeComponents) { + add(accessibleInteropGson.toJsonTree(component)) + } +}) + +// GET /api/v1/client/components/:name +fun getComponents(requestObject: RequestObject): FullHttpResponse { + val name = requestObject.params["name"] ?: return httpBadRequest("No name provided") + val components = ComponentManager.activeComponents.filter { theme -> theme.theme.name.equals(name, true) } + + return httpOk(JsonArray().apply { + for (component in components) { + add(accessibleInteropGson.toJsonTree(component)) + } + }) +} + +// GET /api/v1/client/component/:id +fun getComponentSettings(requestObject: RequestObject): FullHttpResponse { + val id = requestObject.params["id"]?.let { UUID.fromString(it) } + ?: return httpBadRequest("No ID provided") + + val component = ComponentManager.activeComponents + .find { it.id == id } ?: return httpBadRequest("No component found") + val json = ConfigSystem.serializeConfigurable(component, gson = interopGson) + + ComponentManager.fireComponentsUpdate() + + return httpOk(json) +} + +// PUT /api/v1/client/component/:id +fun updateComponentSettings(requestObject: RequestObject): FullHttpResponse { + val id = requestObject.params["id"]?.let { UUID.fromString(it) } + ?: return httpBadRequest("No ID provided") + + val component = ComponentManager.activeComponents + .find { it.id == id } ?: return httpBadRequest("No component found") + ConfigSystem.deserializeConfigurable(component, StringReader(requestObject.body), gson = interopGson) + + ComponentManager.fireComponentsUpdate() + + return httpOk(JsonObject()) +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/interop/protocol/rest/v1/client/ScreenFunctions.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/interop/protocol/rest/v1/client/ScreenFunctions.kt index f1238542332..1fe1bfd5356 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/interop/protocol/rest/v1/client/ScreenFunctions.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/interop/protocol/rest/v1/client/ScreenFunctions.kt @@ -25,8 +25,8 @@ import com.google.gson.JsonObject import com.mojang.blaze3d.systems.RenderSystem import io.netty.handler.codec.http.FullHttpResponse import net.ccbluex.liquidbounce.integration.IntegrationListener +import net.ccbluex.liquidbounce.integration.VirtualDisplayScreen import net.ccbluex.liquidbounce.integration.VirtualScreenType -import net.ccbluex.liquidbounce.integration.VrScreen import net.ccbluex.liquidbounce.utils.client.inGame import net.ccbluex.liquidbounce.utils.client.mc import net.ccbluex.netty.http.model.RequestObject @@ -40,25 +40,11 @@ import net.minecraft.client.gui.screen.TitleScreen @Suppress("UNUSED_PARAMETER") fun getVirtualScreenInfo(requestObject: RequestObject): FullHttpResponse { return httpOk(JsonObject().apply { - addProperty("name", IntegrationListener.momentaryVirtualScreen?.type?.routeName) + addProperty("name", IntegrationListener.route.type?.routeName ?: "none") addProperty("showingSplash", mc.overlay is SplashOverlay) }) } -// POST /api/v1/client/virtualScreen -fun postVirtualScreen(requestObject: RequestObject): FullHttpResponse { - val body = requestObject.asJson() - val name = body["name"]?.asString ?: return httpForbidden("No name") - - val virtualScreen = IntegrationListener.momentaryVirtualScreen - if ((virtualScreen?.type?.routeName ?: "none") != name) { - return httpForbidden("Wrong virtual screen") - } - - IntegrationListener.acknowledgement.confirm() - return httpOk(JsonObject()) -} - // GET /api/v1/client/screen @Suppress("UNUSED_PARAMETER") fun getScreenInfo(requestObject: RequestObject): FullHttpResponse { @@ -95,7 +81,7 @@ fun putScreen(requestObject: RequestObject): FullHttpResponse { fun deleteScreen(requestObject: RequestObject): FullHttpResponse { val screen = mc.currentScreen ?: return httpForbidden("No screen") - if (screen is VrScreen && screen.parentScreen != null) { + if (screen is VirtualDisplayScreen && screen.parentScreen != null) { RenderSystem.recordRenderCall { mc.setScreen(screen.parentScreen) } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/interop/protocol/rest/v1/client/ThemeFunctions.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/interop/protocol/rest/v1/client/ThemeFunctions.kt index b826f5a9370..c47ee4df3bc 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/interop/protocol/rest/v1/client/ThemeFunctions.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/interop/protocol/rest/v1/client/ThemeFunctions.kt @@ -22,7 +22,6 @@ package net.ccbluex.liquidbounce.integration.interop.protocol.rest.v1.client import com.google.gson.JsonArray import com.google.gson.JsonObject import io.netty.handler.codec.http.FullHttpResponse -import net.ccbluex.liquidbounce.config.ConfigSystem import net.ccbluex.liquidbounce.integration.theme.ThemeManager import net.ccbluex.liquidbounce.render.FontManager import net.ccbluex.netty.http.model.RequestObject @@ -31,19 +30,10 @@ import net.ccbluex.netty.http.util.* // GET /api/v1/client/theme @Suppress("UNUSED_PARAMETER") fun getThemeInfo(requestObject: RequestObject): FullHttpResponse = httpOk(JsonObject().apply { - addProperty("activeTheme", ThemeManager.activeTheme.name) - addProperty("shaderEnabled", ThemeManager.shaderEnabled) + addProperty("theme", ThemeManager.activeTheme.name) + addProperty("wallpaper", ThemeManager.activeWallpaper.name) }) -// POST /api/v1/client/shader -@Suppress("UNUSED_PARAMETER") -fun postToggleShader(requestObject: RequestObject): FullHttpResponse { - ThemeManager.shaderEnabled = !ThemeManager.shaderEnabled - ConfigSystem.storeConfigurable(ThemeManager) - return httpOk(JsonObject()) -} - - // GET /api/v1/client/fonts @Suppress("UNUSED_PARAMETER") fun getFonts(requestObject: RequestObject): FullHttpResponse = httpOk(JsonArray().apply { diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/interop/protocol/rest/v1/client/WallpaperFunctions.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/interop/protocol/rest/v1/client/WallpaperFunctions.kt new file mode 100644 index 00000000000..865caffa67f --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/interop/protocol/rest/v1/client/WallpaperFunctions.kt @@ -0,0 +1,72 @@ +/* + * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce) + * + * Copyright (c) 2015 - 2024 CCBlueX + * + * LiquidBounce is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiquidBounce is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LiquidBounce. If not, see . + * + */ +package net.ccbluex.liquidbounce.integration.interop.protocol.rest.v1.client + +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import com.mojang.blaze3d.systems.RenderSystem +import io.netty.handler.codec.http.FullHttpResponse +import net.ccbluex.liquidbounce.config.ConfigSystem +import net.ccbluex.liquidbounce.integration.theme.ThemeManager +import net.ccbluex.netty.http.model.RequestObject +import net.ccbluex.netty.http.util.httpBadRequest +import net.ccbluex.netty.http.util.httpOk + +// GET /api/v1/client/wallpaper +@Suppress("UNUSED_PARAMETER") +fun getWallpaper(requestObject: RequestObject): FullHttpResponse = + httpOk(JsonObject().apply { + add("active", JsonObject().apply { + val activeWallpaper = ThemeManager.activeWallpaper + addProperty("name", activeWallpaper.name) + addProperty("theme", activeWallpaper.theme.name) + }) + + add("available", JsonArray().apply { + ThemeManager.availableWallpapers.groupBy { wallpaper -> + wallpaper.theme.name + }.forEach { (name, wallpapers) -> + // TODO: Might be sub-optimal to specify the theme name for each wallpaper + // but it is easier to deserialize on the browser side + wallpapers.forEach { wallpaper -> + add(JsonObject().apply { + addProperty("theme", name) + addProperty("name", wallpaper.name) + }) + } + } + }) + }) + +// PUT /api/v1/client/wallpaper/:theme/:name +fun putWallpaper(requestObject: RequestObject): FullHttpResponse { + val theme = requestObject.params["theme"] ?: return httpBadRequest("Missing theme") + val name = requestObject.params["name"] ?: return httpBadRequest("Missing name") + + val wallpaper = + ThemeManager.availableWallpapers.find { wallpaper -> wallpaper.name == name && wallpaper.theme.name == theme } + ?: return httpBadRequest("Invalid theme or wallpaper") + + ThemeManager.activeWallpaper = wallpaper + RenderSystem.recordRenderCall(wallpaper::load) + ConfigSystem.storeConfigurable(ThemeManager) + + return httpOk(JsonObject()) +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/interop/protocol/rest/v1/features/BrowserFunctions.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/interop/protocol/rest/v1/features/BrowserFunctions.kt index e477c000fdf..56b99f3fcda 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/interop/protocol/rest/v1/features/BrowserFunctions.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/interop/protocol/rest/v1/features/BrowserFunctions.kt @@ -20,8 +20,8 @@ package net.ccbluex.liquidbounce.integration.interop.protocol.rest.v1.features import com.google.gson.JsonObject -import net.ccbluex.liquidbounce.integration.BrowserScreen -import net.ccbluex.liquidbounce.integration.browserTabs +import net.ccbluex.liquidbounce.integration.browser.BrowserScreen +import net.ccbluex.liquidbounce.integration.browser.browserTabs import net.ccbluex.liquidbounce.utils.client.mc import net.ccbluex.netty.http.model.RequestObject import net.ccbluex.netty.http.util.httpBadRequest diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/ThemeManager.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/ThemeManager.kt index db6b9360db3..846c5306e5c 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/ThemeManager.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/ThemeManager.kt @@ -19,318 +19,130 @@ */ package net.ccbluex.liquidbounce.integration.theme -import com.google.gson.JsonArray -import com.google.gson.annotations.SerializedName -import com.mojang.blaze3d.systems.RenderSystem import net.ccbluex.liquidbounce.config.ConfigSystem -import net.ccbluex.liquidbounce.config.gson.util.decode import net.ccbluex.liquidbounce.config.types.Configurable -import net.ccbluex.liquidbounce.features.module.modules.render.ModuleClickGui -import net.ccbluex.liquidbounce.features.module.modules.render.ModuleHud +import net.ccbluex.liquidbounce.config.types.ValueType import net.ccbluex.liquidbounce.integration.IntegrationListener import net.ccbluex.liquidbounce.integration.VirtualScreenType -import net.ccbluex.liquidbounce.integration.browser.BrowserManager -import net.ccbluex.liquidbounce.integration.browser.supports.tab.ITab -import net.ccbluex.liquidbounce.integration.interop.ClientInteropServer -import net.ccbluex.liquidbounce.integration.theme.component.Component -import net.ccbluex.liquidbounce.integration.theme.component.ComponentOverlay -import net.ccbluex.liquidbounce.integration.theme.component.ComponentType -import net.ccbluex.liquidbounce.render.shader.CanvasShader -import net.ccbluex.liquidbounce.utils.client.logger -import net.ccbluex.liquidbounce.utils.client.mc -import net.ccbluex.liquidbounce.utils.io.extractZip -import net.ccbluex.liquidbounce.utils.io.resource -import net.ccbluex.liquidbounce.utils.io.resourceToString -import net.ccbluex.liquidbounce.utils.math.Vec2i -import net.ccbluex.liquidbounce.utils.render.refreshRate -import net.minecraft.client.gui.DrawContext -import net.minecraft.client.gui.screen.ChatScreen -import net.minecraft.client.render.RenderLayer -import net.minecraft.client.texture.NativeImage -import net.minecraft.client.texture.NativeImageBackedTexture -import net.minecraft.util.Identifier -import java.io.Closeable +import net.ccbluex.liquidbounce.integration.theme.themes.liquidbounce.LiquidBounceTheme +import net.ccbluex.liquidbounce.integration.theme.type.RouteType +import net.ccbluex.liquidbounce.integration.theme.type.Theme +import net.ccbluex.liquidbounce.integration.theme.type.web.LegacyWebTheme +import net.ccbluex.liquidbounce.render.FontManager +import net.ccbluex.liquidbounce.render.engine.font.FontRenderer import java.io.File object ThemeManager : Configurable("theme") { - internal val themesFolder = File(ConfigSystem.rootFolder, "themes") - internal val defaultTheme = Theme.defaults() + val themesFolder = File(ConfigSystem.rootFolder, "themes") + const val DEFAULT_THEME = "LiquidBounce" - var shaderEnabled by boolean("Shader", false) - .onChange { enabled -> - if (enabled) { - RenderSystem.recordRenderCall { - activeTheme.compileShader() - defaultTheme.compileShader() - } - } - - return@onChange enabled - } + val inbuiltThemes = arrayOf(LiquidBounceTheme) + var themes = mutableListOf(*inbuiltThemes) - var activeTheme = defaultTheme + /** + * The preferred active theme which is used as UI of the client. + */ + var activeTheme: Theme = themes.firstOrNull { it.name == DEFAULT_THEME } ?: LiquidBounceTheme set(value) { - if (!value.exists) { - logger.warn("Unable to set theme to ${value.name}, theme does not exist") - return - } - - if (field != defaultTheme) { - activeTheme.close() - } - field = value - // Update components - ComponentOverlay.insertComponents() + // Update active wallpaper + value.defaultWallpaper?.let { wallpaper -> activeWallpaper = wallpaper } // Update integration browser - IntegrationListener.updateIntegrationBrowser() - ModuleHud.refresh() - ModuleClickGui.restartView() + IntegrationListener.sync() } - private val takesInputHandler: () -> Boolean = { mc.currentScreen != null && mc.currentScreen !is ChatScreen } - - init { - ConfigSystem.root(this) - } - /** - * Open [ITab] with the given [VirtualScreenType] and mark as static if [markAsStatic] is true. - * This tab will be locked to 60 FPS since it is not input aware. + * The fallback theme which is used when the active theme does not support a virtual screen type. */ - fun openImmediate(virtualScreenType: VirtualScreenType? = null, markAsStatic: Boolean = false): ITab = - BrowserManager.browser?.createTab(route(virtualScreenType, markAsStatic).url, frameRate = 60) - ?: error("Browser is not initialized") + private var fallbackTheme = themes.firstOrNull { it.name == DEFAULT_THEME } ?: LiquidBounceTheme /** - * Open [ITab] with the given [VirtualScreenType] and mark as static if [markAsStatic] is true. - * This tab will be locked to the highest refresh rate since it is input aware. + * List of all available wallpapers that can be displayed in the background of the client UI. */ - fun openInputAwareImmediate( - virtualScreenType: VirtualScreenType? = null, - markAsStatic: Boolean = false, - takesInput: () -> Boolean = takesInputHandler - ): ITab = BrowserManager.browser?.createInputAwareTab( - route(virtualScreenType, markAsStatic).url, - frameRate = refreshRate, - takesInput = takesInput - ) ?: error("Browser is not initialized") - - fun updateImmediate(tab: ITab?, virtualScreenType: VirtualScreenType? = null, markAsStatic: Boolean = false) = - tab?.loadUrl(route(virtualScreenType, markAsStatic).url) - - fun route(virtualScreenType: VirtualScreenType? = null, markAsStatic: Boolean = false): Route { - val theme = if (virtualScreenType == null || activeTheme.doesAccept(virtualScreenType.routeName)) { - activeTheme - } else if (defaultTheme.doesAccept(virtualScreenType.routeName)) { - defaultTheme - } else { - error("No theme supports the route ${virtualScreenType.routeName}") - } - - return Route( - theme, - theme.getUrl(virtualScreenType?.routeName, markAsStatic) - ) - } - - fun initialiseBackground() { - // Load background image of active theme and fallback to default theme if not available - if (!activeTheme.loadBackgroundImage()) { - defaultTheme.loadBackgroundImage() - } + val availableWallpapers + get() = themes.flatMap { wallpaper -> wallpaper.wallpapers } - // Compile shader of active theme and fallback to default theme if not available - if (shaderEnabled && !activeTheme.compileShader()) { - defaultTheme.compileShader() - } - } - - fun drawBackground(context: DrawContext, width: Int, height: Int, mousePos: Vec2i, delta: Float): Boolean { - if (shaderEnabled) { - val shader = activeTheme.compiledShaderBackground ?: defaultTheme.compiledShaderBackground - - if (shader != null) { - shader.draw(mousePos.x, mousePos.y, delta) - return true - } - } - - val image = activeTheme.loadedBackgroundImage ?: defaultTheme.loadedBackgroundImage - if (image != null) { - context.drawTexture( - RenderLayer::getGuiTextured, - image, - 0, - 0, - 0f, - 0f, - width, - height, - width, - height - ) - return true - } - - return false - } - - fun chooseTheme(name: String) { - activeTheme = Theme(name) - } - - fun themes() = themesFolder.listFiles()?.filter { it.isDirectory }?.mapNotNull { it.name } ?: emptyList() - - data class Route(val theme: Theme, val url: String) - -} - -class Theme(val name: String) : Closeable { + /** + * The active wallpaper that is displayed as replacement of the standard Minecraft wallpaper. + * If set to [Wallpaper.MinecraftWallpaper], the standard Minecraft wallpaper will be displayed. + * The wallpaper does not have to match the active theme and can be set independently. + */ + var activeWallpaper: Wallpaper by value( + "wallpaper", + activeTheme.defaultWallpaper ?: Wallpaper.MinecraftWallpaper, + ValueType.WALLPAPER + ) - private val folder = File(ThemeManager.themesFolder, name) + /** + * Later on, we might want to add a way to change the font renderer as option. This acts as default font renderer, + * if no other font is specified. Useful for GUI buttons and so on, where no configuration is needed. + * + * @return The default font renderer of the active theme or the system font renderer. + */ + val fontRenderer: FontRenderer + get() = activeTheme.fontRenderer ?: FontManager.FONT_RENDERER init { - if (!exists) { - error("Theme $name does not exist") - } - } - - private val metadata: ThemeMetadata = run { - val metadataFile = File(folder, "metadata.json") - if (!metadataFile.exists()) { - error("Theme $name does not contain a metadata file") - } - - decode(metadataFile.inputStream()) + ConfigSystem.root(this) } - val exists: Boolean - get() = folder.exists() - - private val url: String - get() = "${ClientInteropServer.url}/$name/#/" - - private val backgroundShader: File - get() = File(folder, "background.frag") - private val backgroundImage: File - get() = File(folder, "background.png") - var compiledShaderBackground: CanvasShader? = null - private set - var loadedBackgroundImage: Identifier? = null - private set - - fun compileShader(): Boolean { - if (compiledShaderBackground != null) { - return true - } + /** + * Load all themes from the themes folder. + */ + fun loadThemes() { + var themes = mutableListOf() - readShaderBackground()?.let { shaderBackground -> - compiledShaderBackground = CanvasShader(resourceToString("/resources/liquidbounce/shaders/vertex.vert"), - shaderBackground) - logger.info("Compiled background shader for theme $name") - return true - } - return false - } + for (folder in themesFolder.listFiles()) { + // A theme cannot be a file + if (!folder.isDirectory) { + continue + } - private fun readShaderBackground() = backgroundShader.takeIf { it.exists() }?.readText() - private fun readBackgroundImage() = backgroundImage.takeIf { it.exists() } - ?.inputStream()?.use { NativeImage.read(it) } + // Check if folder is not a pre-installed theme + if (inbuiltThemes.any { theme -> theme.folder == folder }) { + continue + } - fun loadBackgroundImage(): Boolean { - if (loadedBackgroundImage != null) { - return true + // Create a new theme + themes += LegacyWebTheme(folder) } - val image = NativeImageBackedTexture(readBackgroundImage() ?: return false) - loadedBackgroundImage = Identifier.of("liquidbounce-theme-bg-${name.lowercase()}") - mc.textureManager.registerTexture(loadedBackgroundImage, image) - logger.info("Loaded background image for theme $name") - return true + this.themes = themes } /** - * Get the URL to the given page name in the theme. + * Get the route for the given virtual screen type. */ - fun getUrl(name: String? = null, markAsStatic: Boolean = false) = "$url${name.orEmpty()}".let { - if (markAsStatic) { - "$it?static" - } else { - it - } - } - - fun doesAccept(name: String?) = doesSupport(name) || doesOverlay(name) - - fun doesSupport(name: String?) = name != null && metadata.supports.contains(name) - - fun doesOverlay(name: String?) = name != null && metadata.overlays.contains(name) - - fun parseComponents(): MutableList { - val themeComponent = metadata.rawComponents - .map { it.asJsonObject } - .associateBy { it["name"].asString!! } - - val componentList = mutableListOf() - - for ((name, obj) in themeComponent) { - runCatching { - val componentType = ComponentType.byName(name) ?: error("Unknown component type: $name") - val component = componentType.createComponent() - - runCatching { - ConfigSystem.deserializeConfigurable(component, obj) - }.onFailure { - logger.error("Failed to deserialize component $name", it) - } - - componentList.add(component) - }.onFailure { - logger.error("Failed to create component $name", it) - } + fun route(virtualScreenType: VirtualScreenType? = null): RouteType { + val theme = when { + virtualScreenType == null || activeTheme.doesAccept(virtualScreenType) -> activeTheme + fallbackTheme.doesAccept(virtualScreenType) -> fallbackTheme + else -> themes.firstOrNull { theme -> theme.doesAccept(virtualScreenType) } + ?: error("No theme supports the route ${virtualScreenType.routeName}") } - return componentList + return theme.route(virtualScreenType) } - override fun close() { - mc.textureManager.destroyTexture(loadedBackgroundImage) + /** + * Choose a theme by name. + */ + fun chooseTheme(name: String) { + activeTheme = themes.firstOrNull { it.name == name } ?: error("Theme $name does not exist") } - companion object { - - fun defaults() = runCatching { - val folder = ThemeManager.themesFolder.resolve("default") - val stream = resource("/resources/liquidbounce/default_theme.zip") - - if (folder.exists()) { - folder.deleteRecursively() - } - - extractZip(stream, folder) - folder.deleteOnExit() - - Theme("default") - }.onFailure { - logger.error("Unable to extract default theme", it) - }.onSuccess { - logger.info("Successfully extracted default theme") - }.getOrThrow() + /** + * Get theme by [name] + */ + fun getTheme(name: String): Theme? = themes.firstOrNull { it.name == name } - } + /** + * Get font. If name is blank, the default font renderer is returned. + */ + fun getFontRenderer(name: String): FontRenderer = + if (name.isBlank()) fontRenderer else FontManager.fontFace(name)?.renderer ?: fontRenderer } - -data class ThemeMetadata( - val name: String, - val author: String, - val version: String, - val supports: List, - val overlays: List, - @SerializedName("components") - val rawComponents: JsonArray -) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/Wallpaper.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/Wallpaper.kt new file mode 100644 index 00000000000..5b5808eb1d8 --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/Wallpaper.kt @@ -0,0 +1,109 @@ +package net.ccbluex.liquidbounce.integration.theme + +import net.ccbluex.liquidbounce.integration.theme.themes.liquidbounce.LiquidBounceTheme +import net.ccbluex.liquidbounce.integration.theme.type.Theme +import net.ccbluex.liquidbounce.render.shader.CanvasShader +import net.ccbluex.liquidbounce.utils.client.logger +import net.ccbluex.liquidbounce.utils.client.mc +import net.ccbluex.liquidbounce.utils.io.resourceToString +import net.ccbluex.liquidbounce.utils.math.Vec2i +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.render.RenderLayer +import net.minecraft.client.texture.NativeImage +import net.minecraft.client.texture.NativeImageBackedTexture +import net.minecraft.util.Identifier +import java.io.File + +/** + * A client wallpaper that is displayed in the background of the client UI no matter what theme implementation is + * being used. + * + * There are two types of wallpapers: + * - [ImageWallpaper] which is a simple image that is displayed in the background. + * - [ShaderWallpaper] which is a shader that is rendered in the background. + */ +abstract class Wallpaper(val theme: Theme, val name: String) { + + companion object { + fun fromFile(theme: Theme, file: File): Wallpaper { + return when (file.extension) { + "png" -> ImageWallpaper(theme, file.name, file) + "frag" -> ShaderWallpaper(theme, file.name, file) + else -> throw IllegalArgumentException("Unknown wallpaper type for file $file") + } + } + } + + object MinecraftWallpaper : Wallpaper(LiquidBounceTheme, "Minecraft") { + override fun load() = true + override fun draw(context: DrawContext, width: Int, height: Int, mouse: Vec2i, delta: Float) = + // When returning false, the default Minecraft wallpaper will be displayed + false + } + + class ImageWallpaper(theme: Theme, name: String, val file: File) : Wallpaper(theme, name) { + + private var imageId: Identifier? = null + + override fun load(): Boolean { + if (imageId != null || !file.exists()) { + return true + } + + val image = NativeImageBackedTexture(NativeImage.read(file.inputStream())) + imageId = Identifier.of("liquidbounce", "bg-${name.lowercase()}") + mc.textureManager.registerTexture(imageId, image) + logger.info("Loaded background image for theme $name") + return true + } + + override fun draw( + context: DrawContext, width: Int, height: Int, mouse: Vec2i, delta: Float + ): Boolean { + val imageId = imageId ?: return false + context.drawTexture( + RenderLayer::getGuiTextured, + imageId, + 0, + 0, + 0f, + 0f, + width, + height, + width, + height + ) + return true + } + + } + + class ShaderWallpaper(theme: Theme, name: String, val file: File) : Wallpaper(theme, name) { + + private var shader: CanvasShader? = null + + override fun load(): Boolean { + if (shader != null || !file.exists()) { + return true + } + + val shaderSource = file.readText() + shader = CanvasShader(resourceToString("/resources/liquidbounce/shaders/vertex.vert"), shaderSource) + logger.info("Compiled background shader for theme $name") + return true + } + + override fun draw( + context: DrawContext, width: Int, height: Int, mouse: Vec2i, delta: Float + ): Boolean { + val shader = shader ?: return false + shader.draw(mouse.x, mouse.y, delta) + return true + } + + } + + abstract fun load(): Boolean + abstract fun draw(context: DrawContext, width: Int, height: Int, mouse: Vec2i, delta: Float): Boolean + +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/ComponentOverlay.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/ComponentOverlay.kt deleted file mode 100644 index 8ad7169924b..00000000000 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/ComponentOverlay.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce) - * - * Copyright (c) 2015 - 2025 CCBlueX - * - * LiquidBounce is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * LiquidBounce is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LiquidBounce. If not, see . - * - * - */ - -package net.ccbluex.liquidbounce.integration.theme.component - -import net.ccbluex.liquidbounce.event.EventListener -import net.ccbluex.liquidbounce.event.EventManager -import net.ccbluex.liquidbounce.event.events.ComponentsUpdate -import net.ccbluex.liquidbounce.features.misc.HideAppearance -import net.ccbluex.liquidbounce.features.module.modules.render.ModuleHud -import net.ccbluex.liquidbounce.integration.theme.ThemeManager -import net.ccbluex.liquidbounce.integration.theme.component.types.IntegratedComponent -import net.ccbluex.liquidbounce.integration.theme.component.types.TextComponent -import net.ccbluex.liquidbounce.utils.client.logger - -val components: MutableList = mutableListOf() -val customComponents: MutableList = mutableListOf( - TextComponent("hello! :)", enabled = false) -) - -object ComponentOverlay : EventListener { - - @JvmStatic - fun isTweakEnabled(tweak: FeatureTweak) = this.running && !HideAppearance.isHidingNow && - components.filterIsInstance().any { it.enabled && it.tweaks.contains(tweak) } - - @JvmStatic - fun getComponentWithTweak(tweak: FeatureTweak): IntegratedComponent? { - if (!running || HideAppearance.isHidingNow) { - return null - } - - return components.filterIsInstance() - .find { it.enabled && it.tweaks.contains(tweak) } - } - - fun insertComponents() { - val componentList = ThemeManager.activeTheme.parseComponents() - - // todo: fix custom components being removed - components.clear() - components += componentList - - logger.info("Inserted ${components.size} components") - } - - fun fireComponentsUpdate() = EventManager.callEvent(ComponentsUpdate(components + customComponents)) - - override fun parent() = ModuleHud - -} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/ComponentType.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/ComponentType.kt deleted file mode 100644 index abed226af89..00000000000 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/ComponentType.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce) - * - * Copyright (c) 2015 - 2025 CCBlueX - * - * LiquidBounce is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * LiquidBounce is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LiquidBounce. If not, see . - * - * - */ - -package net.ccbluex.liquidbounce.integration.theme.component - -import net.ccbluex.liquidbounce.config.types.NamedChoice -import net.ccbluex.liquidbounce.integration.theme.component.types.IntegratedComponent -import net.ccbluex.liquidbounce.integration.theme.component.types.minimap.MinimapComponent - -enum class ComponentType( - override val choiceName: String, - val tweaks: Array = emptyArray(), - val createComponent: () -> Component = { IntegratedComponent(choiceName, tweaks) } -) : NamedChoice { - - WATERMARK("Watermark"), - TAB_GUI("TabGui"), - ARRAY_LIST("ArrayList"), - NOTIFICATIONS("Notifications"), - HOTBAR("Hotbar", tweaks = arrayOf( - FeatureTweak.TWEAK_HOTBAR, - FeatureTweak.DISABLE_STATUS_BAR, - FeatureTweak.DISABLE_EXP_BAR, - FeatureTweak.DISABLE_HELD_ITEM_TOOL_TIP, - FeatureTweak.DISABLE_OVERLAY_MESSAGE - )), - EFFECTS("Effects", tweaks = arrayOf( - FeatureTweak.DISABLE_STATUS_EFFECT_OVERLAY - )), - SCOREBOARD("Scoreboard", tweaks = arrayOf( - FeatureTweak.DISABLE_SCOREBOARD - )), - MINIMAP("Minimap", createComponent = { MinimapComponent }), - TARGET_HUD("TargetHud"), - BLOCK_COUNTER("BlockCounter"), - KEYSTROKES("Keystrokes"), - TACO("Taco"); - - companion object { - - fun byName(name: String) = entries.find { it.choiceName == name } - - } - -} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/types/TextComponent.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/types/TextComponent.kt deleted file mode 100644 index d38b33a2efc..00000000000 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/types/TextComponent.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce) - * - * Copyright (c) 2015 - 2025 CCBlueX - * - * LiquidBounce is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * LiquidBounce is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LiquidBounce. If not, see . - * - * - */ - -package net.ccbluex.liquidbounce.integration.theme.component.types - -import net.ccbluex.liquidbounce.config.types.Configurable -import net.ccbluex.liquidbounce.config.types.NamedChoice -import net.ccbluex.liquidbounce.config.types.ToggleableConfigurable -import net.ccbluex.liquidbounce.integration.theme.component.Component -import net.ccbluex.liquidbounce.render.engine.Color4b - -/** - * A text component - */ -@Suppress("unused") -class TextComponent(text: String, enabled: Boolean = true) : Component("Text", enabled) { - - private val text by text("Text", text) - private val color by color("Color", Color4b.WHITE) - private val font by text("Font", "Inter") - private val fontSize by int("Size", 14, 1.. 100, "px") - - private val decorations = tree(object : Configurable("Decorations") { - val bold by boolean("Bold", false) - val italic by boolean("Italic", false) - val underline by boolean("Underline", false) - val strikethrough by boolean("Strikethrough", false) - }) - - private val shadow = tree(object : ToggleableConfigurable(this, "Shadow", - false) { - val offsetX by int("OffsetX", 0, -10..10, "px") - val offsetY by int("OffsetY", 0, -10..10, "px") - val blurRadius by int("BlurRadius", 0, 0..10, "px") - val color by color("Color", Color4b.BLACK) - }) - - private val glow = tree(object : ToggleableConfigurable(this, "Glow", - false) { - val radius by int("Radius", 0, 0..10, "px") - val color by color("Color", Color4b.WHITE) - }) - - init { - registerComponentListen() - } - - enum class TextAlignment(override val choiceName: String) : NamedChoice { - LEFT("Left"), - CENTER("Center"), - RIGHT("Right") - } - -} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/layout/Layout.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/layout/Layout.kt new file mode 100644 index 00000000000..7d8ed523274 --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/layout/Layout.kt @@ -0,0 +1,18 @@ +package net.ccbluex.liquidbounce.integration.theme.layout + +import net.ccbluex.liquidbounce.config.types.Choice +import net.ccbluex.liquidbounce.config.types.ChoiceConfigurable +import net.ccbluex.liquidbounce.features.module.modules.render.ModuleHud +import net.ccbluex.liquidbounce.integration.theme.type.Theme + +class Layout(theme: Theme) : Choice( + theme.name, + value = mutableListOf( + *theme.components + .filter { factory -> factory.default } + .map { factory -> factory.createComponent(theme) }.toTypedArray() + ) +) { + override val parent: ChoiceConfigurable<*> + get() = ModuleHud.layouts +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/Component.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/layout/component/Component.kt similarity index 60% rename from src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/Component.kt rename to src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/layout/component/Component.kt index 8dda4c68ae6..92d079f1c9d 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/Component.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/layout/component/Component.kt @@ -19,24 +19,33 @@ * */ -package net.ccbluex.liquidbounce.integration.theme.component +package net.ccbluex.liquidbounce.integration.theme.layout.component import net.ccbluex.liquidbounce.config.types.Configurable import net.ccbluex.liquidbounce.config.types.ToggleableConfigurable +import net.ccbluex.liquidbounce.config.types.ValueType +import net.ccbluex.liquidbounce.integration.theme.type.Theme import net.ccbluex.liquidbounce.utils.render.Alignment +import java.util.* /** * Represents a HUD component */ -abstract class Component(name: String, enabled: Boolean) - : ToggleableConfigurable(parent = ComponentOverlay, name = name, enabled = enabled) { +open class Component( + val theme: Theme, + name: String, + enabled: Boolean, + alignment: Alignment, + val tweaks: Array = emptyArray() +) : ToggleableConfigurable(parent = ComponentManager, name = name, enabled = enabled) { - val alignment = tree(Alignment( - Alignment.ScreenAxisX.CENTER, - 0, - Alignment.ScreenAxisY.CENTER, - 0 - )) + /** + * Identifier of the component. Instead of using indexes, we use UUIDs to make sure we work with + * the correct component + */ + val id: UUID = UUID.randomUUID() + + var alignment by value("Alignment", alignment, valueType = ValueType.ALIGNMENT) protected fun registerComponentListen(cfg: Configurable = this) { for (v in cfg.inner) { @@ -44,12 +53,12 @@ abstract class Component(name: String, enabled: Boolean) registerComponentListen(v) } else { v.onChanged { - ComponentOverlay.fireComponentsUpdate() + ComponentManager.fireComponentsUpdate() } } } } - override fun parent() = ComponentOverlay + override fun parent() = ComponentManager } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/layout/component/ComponentFactory.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/layout/component/ComponentFactory.kt new file mode 100644 index 00000000000..d423eb082d4 --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/layout/component/ComponentFactory.kt @@ -0,0 +1,71 @@ +/* + * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce) + * + * Copyright (c) 2015 - 2025 CCBlueX + * + * LiquidBounce is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiquidBounce is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LiquidBounce. If not, see . + * + * + */ + +package net.ccbluex.liquidbounce.integration.theme.layout.component + +import com.google.gson.JsonObject +import net.ccbluex.liquidbounce.integration.theme.type.Theme +import net.ccbluex.liquidbounce.integration.theme.type.web.WebComponent +import net.ccbluex.liquidbounce.utils.render.Alignment + +abstract class ComponentFactory { + + abstract val name: String + abstract val default: Boolean + + class JsonComponentFactory( + override val name: String, + override val default: Boolean, + private val alignment: Alignment, + private val tweaks: Array?, + private val settings: Array?, + ) : ComponentFactory() { + + /** + * [theme] has to be passed on to the component, + * as we do not know which theme the component belongs to + * when we deserialized it from JSON. + */ + override fun createComponent(theme: Theme) = + WebComponent( + theme, + name, + true, + alignment, + tweaks ?: emptyArray(), + settings ?: emptyArray() + ) + } + + class NativeComponentFactory( + override val name: String, + override val default: Boolean = false, + private val function: () -> Component, + ) : ComponentFactory() { + /** + * Consistent with the [JsonComponentFactory], we have to pass the [theme] to the component. + */ + override fun createComponent(theme: Theme) = function() + } + + abstract fun createComponent(theme: Theme): Component + +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/layout/component/ComponentManager.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/layout/component/ComponentManager.kt new file mode 100644 index 00000000000..37f279a7639 --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/layout/component/ComponentManager.kt @@ -0,0 +1,102 @@ +/* + * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce) + * + * Copyright (c) 2015 - 2025 CCBlueX + * + * LiquidBounce is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiquidBounce is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LiquidBounce. If not, see . + * + * + */ + +package net.ccbluex.liquidbounce.integration.theme.layout.component + +import net.ccbluex.liquidbounce.event.EventListener +import net.ccbluex.liquidbounce.event.EventManager +import net.ccbluex.liquidbounce.event.events.ComponentsUpdate +import net.ccbluex.liquidbounce.features.misc.HideAppearance +import net.ccbluex.liquidbounce.features.module.modules.render.ModuleHud +import net.ccbluex.liquidbounce.integration.DrawerReference +import net.ccbluex.liquidbounce.integration.VirtualScreenType +import net.ccbluex.liquidbounce.integration.theme.type.Theme +import net.ccbluex.liquidbounce.utils.client.logger + +object ComponentManager : EventListener { + + val activeComponents + @Suppress("UNCHECKED_CAST") + get() = ModuleHud.layouts.activeChoice.inner as List + private val drawerReferenceMap = mutableMapOf() + + /** + * Update drawer references for the active components + * and clean-up the unused references + */ + fun update() { + activeComponents.forEach { component -> + val theme = component.theme + + if (!theme.doesAccept(VirtualScreenType.HUD)) { + logger.warn("${component.name} is not compatible with the ${theme.name} theme") + return@forEach + } + + // Check if the web overlay is already open + if (drawerReferenceMap.containsKey(theme)) { + return@forEach + } + + val route = theme.route(VirtualScreenType.HUD) + drawerReferenceMap[theme] = DrawerReference.newComponentRef(route) + } + + // Clean-up the drawer references if it's not used by any component + drawerReferenceMap.entries.removeIf { (theme, ref) -> + val isUsed = activeComponents.any { it.theme == theme } + if (!isUsed) { + ref.close() + } + !isUsed + } + } + + /** + * Clear all the drawer references + */ + fun clear() { + if (drawerReferenceMap.isEmpty()) { + return + } + + drawerReferenceMap.forEach { (_, ref) -> ref.close() } + drawerReferenceMap.clear() + } + + @JvmStatic + fun isTweakEnabled(tweak: ComponentTweak) = this.running && !HideAppearance.isHidingNow && + activeComponents.any { it.enabled && it.tweaks.contains(tweak) } + + @JvmStatic + fun getComponentsWithTweak(tweak: ComponentTweak): List { + if (!this.running || HideAppearance.isHidingNow) { + return emptyList() + } + + return activeComponents.filter { component -> component.enabled && component.tweaks.contains(tweak) } + } + + fun fireComponentsUpdate() = EventManager.callEvent(ComponentsUpdate(activeComponents)) + + override fun parent() = ModuleHud + +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/FeatureTweak.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/layout/component/ComponentTweak.kt similarity index 91% rename from src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/FeatureTweak.kt rename to src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/layout/component/ComponentTweak.kt index 0e492334bf4..ce29d18afe7 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/FeatureTweak.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/layout/component/ComponentTweak.kt @@ -19,14 +19,14 @@ * */ -package net.ccbluex.liquidbounce.integration.theme.component +package net.ccbluex.liquidbounce.integration.theme.layout.component import net.ccbluex.liquidbounce.config.types.NamedChoice /** * A set of tweaks that can be applied to the Original HUD by the component */ -enum class FeatureTweak(override val choiceName: String) : NamedChoice { +enum class ComponentTweak(override val choiceName: String) : NamedChoice { /** * Disables the Item Hotbar and draws only the items instead diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/themes/liquidbounce/LiquidBounceTheme.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/themes/liquidbounce/LiquidBounceTheme.kt new file mode 100644 index 00000000000..9ecc1ff21d2 --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/themes/liquidbounce/LiquidBounceTheme.kt @@ -0,0 +1,81 @@ +/* + * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce) + * + * Copyright (c) 2015 - 2025 CCBlueX + * + * LiquidBounce is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiquidBounce is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LiquidBounce. If not, see . + * + * + */ + +package net.ccbluex.liquidbounce.integration.theme.themes.liquidbounce + +import net.ccbluex.liquidbounce.integration.theme.ThemeManager.DEFAULT_THEME +import net.ccbluex.liquidbounce.integration.theme.ThemeManager.themesFolder +import net.ccbluex.liquidbounce.integration.theme.Wallpaper +import net.ccbluex.liquidbounce.integration.theme.layout.component.ComponentFactory +import net.ccbluex.liquidbounce.integration.theme.themes.liquidbounce.components.minimap.MinimapComponent +import net.ccbluex.liquidbounce.integration.theme.type.web.WebTheme +import net.ccbluex.liquidbounce.utils.client.logger +import net.ccbluex.liquidbounce.utils.io.extractZip +import net.ccbluex.liquidbounce.utils.io.resource + +/** + * The default theme for LiquidBounce. + */ +object LiquidBounceTheme : WebTheme(themesFolder.resolve(DEFAULT_THEME)) { + + override fun init() { + extract() + super.init() + } + + private fun extract() { + runCatching { + // Delete old generated default theme + runCatching { + folder.takeIf { file -> file.exists() } + ?.deleteRecursively() + themesFolder.resolve("default").takeIf { file -> file.exists() } + ?.deleteRecursively() + }.onFailure { exception -> + logger.error("Unable to delete old default theme", exception) + } + + // Extract default theme + resource("/resources/liquidbounce/default_theme.zip").use { stream -> + extractZip(stream, folder) + } + folder.deleteOnExit() + + logger.info("Extracted default theme") + }.onFailure { + logger.error("Unable to extract default theme", it) + }.onSuccess { + logger.info("Successfully extracted default theme") + }.getOrThrow() + } + + override val name = "LiquidBounce" + override val components: List + get() = super.components + listOf( + // Additional Components + ComponentFactory.NativeComponentFactory("Minimap", true) { MinimapComponent(this) } + ) + override val wallpapers: List + get() = listOf( + Wallpaper.MinecraftWallpaper + ) + super.wallpapers + +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/types/minimap/ChunkRenderer.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/themes/liquidbounce/components/minimap/ChunkRenderer.kt similarity index 98% rename from src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/types/minimap/ChunkRenderer.kt rename to src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/themes/liquidbounce/components/minimap/ChunkRenderer.kt index e2da3cde690..069054628f4 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/types/minimap/ChunkRenderer.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/themes/liquidbounce/components/minimap/ChunkRenderer.kt @@ -18,7 +18,7 @@ * * */ -package net.ccbluex.liquidbounce.integration.theme.component.types.minimap +package net.ccbluex.liquidbounce.integration.theme.themes.liquidbounce.components.minimap import net.ccbluex.liquidbounce.utils.block.ChunkScanner import net.ccbluex.liquidbounce.utils.client.logger diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/types/minimap/MinimapComponent.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/themes/liquidbounce/components/minimap/MinimapComponent.kt similarity index 87% rename from src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/types/minimap/MinimapComponent.kt rename to src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/themes/liquidbounce/components/minimap/MinimapComponent.kt index 98e8448a1c4..295c0a69db6 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/types/minimap/MinimapComponent.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/themes/liquidbounce/components/minimap/MinimapComponent.kt @@ -19,14 +19,12 @@ * */ -package net.ccbluex.liquidbounce.integration.theme.component.types.minimap +package net.ccbluex.liquidbounce.integration.theme.themes.liquidbounce.components.minimap import com.mojang.blaze3d.systems.RenderSystem -import net.ccbluex.liquidbounce.event.events.OverlayRenderEvent -import net.ccbluex.liquidbounce.event.handler -import net.ccbluex.liquidbounce.features.misc.HideAppearance import net.ccbluex.liquidbounce.features.module.modules.render.esp.ModuleESP -import net.ccbluex.liquidbounce.integration.theme.component.Component +import net.ccbluex.liquidbounce.integration.theme.type.Theme +import net.ccbluex.liquidbounce.integration.theme.type.native.NativeComponent import net.ccbluex.liquidbounce.render.* import net.ccbluex.liquidbounce.render.engine.Color4b import net.ccbluex.liquidbounce.render.engine.Vec3 @@ -35,9 +33,11 @@ import net.ccbluex.liquidbounce.utils.client.toRadians import net.ccbluex.liquidbounce.utils.entity.RenderedEntities import net.ccbluex.liquidbounce.utils.entity.interpolateCurrentPosition import net.ccbluex.liquidbounce.utils.entity.interpolateCurrentRotation -import net.ccbluex.liquidbounce.utils.kotlin.EventPriorityConvention import net.ccbluex.liquidbounce.utils.math.Vec2i +import net.ccbluex.liquidbounce.utils.render.Alignment +import net.ccbluex.liquidbounce.utils.render.Alignment.ScreenAxisX import net.minecraft.client.gl.ShaderProgramKeys +import net.minecraft.client.gui.DrawContext import net.minecraft.client.render.BufferBuilder import net.minecraft.client.render.VertexFormat import net.minecraft.client.render.VertexFormats @@ -53,7 +53,12 @@ import org.lwjgl.opengl.GL11 import kotlin.math.ceil import kotlin.math.sqrt -object MinimapComponent : Component("Minimap", true) { +class MinimapComponent(theme: Theme) : NativeComponent(theme, "Minimap", true, Alignment( + horizontalAlignment = ScreenAxisX.LEFT, + horizontalOffset = 7, + verticalAlignment = Alignment.ScreenAxisY.TOP, + verticalOffset = 180, +)) { private val size by int("Size", 96, 1..256) private val viewDistance by float("ViewDistance", 3.0F, 1.0F..8.0F) @@ -63,25 +68,21 @@ object MinimapComponent : Component("Minimap", true) { registerComponentListen() } - val renderHandler = handler(priority = EventPriorityConvention.MODEL_STATE) { event -> - if (HideAppearance.isHidingNow) { - return@handler - } - - val matStack = MatrixStack() + override fun render(context: DrawContext, delta: Float) { + val matStack = context.matrices - val playerPos = player.interpolateCurrentPosition(event.tickDelta) - val playerRotation = player.interpolateCurrentRotation(event.tickDelta) + val playerPos = player.interpolateCurrentPosition(delta) + val playerRotation = player.interpolateCurrentRotation(delta) val minimapSize = size - val boundingBox = alignment.getBounds(minimapSize.toFloat(), minimapSize.toFloat()) + val scissorBox = alignment.getBounds(minimapSize.toFloat(), minimapSize.toFloat()) val scaleFactor = mc.window.scaleFactor GL11.glEnable(GL11.GL_SCISSOR_TEST) GL11.glScissor( - (boundingBox.xMin * scaleFactor).toInt(), - mc.framebuffer.viewportHeight - ((boundingBox.yMin + minimapSize) * scaleFactor).toInt(), + (scissorBox.xMin * scaleFactor).toInt(), + mc.framebuffer.viewportHeight - ((scissorBox.yMin + minimapSize) * scaleFactor).toInt(), (minimapSize * scaleFactor).toInt(), (minimapSize * scaleFactor).toInt(), ) @@ -98,7 +99,7 @@ object MinimapComponent : Component("Minimap", true) { matStack.push() - matStack.translate(boundingBox.xMin + minimapSize * 0.5, boundingBox.yMin + minimapSize * 0.5, 0.0) + matStack.translate(minimapSize * 0.5, minimapSize * 0.5, 0.0) matStack.scale(scale, scale, scale) matStack.multiply(Quaternionf(AxisAngle4f(-(playerRotation.yaw + 180.0F).toRadians(), 0.0F, 0.0F, 1.0F))) @@ -126,7 +127,7 @@ object MinimapComponent : Component("Minimap", true) { ) { matrix -> for (renderedEntity in RenderedEntities) { drawEntityOnMinimap( - this, matStack, renderedEntity, event.tickDelta, Vec2f(baseX.toFloat(), baseZ.toFloat()) + this, matStack, renderedEntity, delta, Vec2f(baseX.toFloat(), baseZ.toFloat()) ) } } @@ -134,6 +135,7 @@ object MinimapComponent : Component("Minimap", true) { matStack.pop() + val boundingBox = BoundingBox2f(0f, 0f, minimapSize.toFloat(), minimapSize.toFloat()) val centerBB = Vec2f( boundingBox.xMin + (boundingBox.xMax - boundingBox.xMin) * 0.5F, boundingBox.yMin + (boundingBox.yMax - boundingBox.yMin) * 0.5F @@ -167,6 +169,8 @@ object MinimapComponent : Component("Minimap", true) { } + override fun size() = size + 5 to size + 5 + private fun RenderEnvironment.drawShadowForBB( boundingBox: BoundingBox2f, from: Color4b, to: Color4b, offset: Float = 3.0F, width: Float = 3.0F ) { diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/types/minimap/MinimapHeightmapManager.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/themes/liquidbounce/components/minimap/MinimapHeightmapManager.kt similarity index 97% rename from src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/types/minimap/MinimapHeightmapManager.kt rename to src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/themes/liquidbounce/components/minimap/MinimapHeightmapManager.kt index 05032c0959f..d6ccad8b7b6 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/types/minimap/MinimapHeightmapManager.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/themes/liquidbounce/components/minimap/MinimapHeightmapManager.kt @@ -18,7 +18,7 @@ * * */ -package net.ccbluex.liquidbounce.integration.theme.component.types.minimap +package net.ccbluex.liquidbounce.integration.theme.themes.liquidbounce.components.minimap import net.ccbluex.liquidbounce.utils.client.logger import net.ccbluex.liquidbounce.utils.client.mc @@ -26,7 +26,6 @@ import net.minecraft.block.BlockState import net.minecraft.block.MapColor import net.minecraft.util.math.BlockPos import net.minecraft.util.math.ChunkPos -import net.minecraft.world.Heightmap import net.minecraft.world.chunk.Chunk import java.util.concurrent.ConcurrentHashMap diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/types/minimap/MinimapTextureAtlasManager.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/themes/liquidbounce/components/minimap/MinimapTextureAtlasManager.kt similarity index 98% rename from src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/types/minimap/MinimapTextureAtlasManager.kt rename to src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/themes/liquidbounce/components/minimap/MinimapTextureAtlasManager.kt index a0067387e42..fd32527682b 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/types/minimap/MinimapTextureAtlasManager.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/themes/liquidbounce/components/minimap/MinimapTextureAtlasManager.kt @@ -18,7 +18,7 @@ * * */ -package net.ccbluex.liquidbounce.integration.theme.component.types.minimap +package net.ccbluex.liquidbounce.integration.theme.themes.liquidbounce.components.minimap import net.ccbluex.liquidbounce.render.engine.Color4b import net.ccbluex.liquidbounce.render.engine.font.BoundingBox2f diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/type/Theme.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/type/Theme.kt new file mode 100644 index 00000000000..01fe686a21a --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/type/Theme.kt @@ -0,0 +1,77 @@ +/* + * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce) + * + * Copyright (c) 2024 CCBlueX + * + * LiquidBounce is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiquidBounce is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LiquidBounce. If not, see . + * + * + */ + +package net.ccbluex.liquidbounce.integration.theme.type + +import net.ccbluex.liquidbounce.integration.VirtualScreenType +import net.ccbluex.liquidbounce.integration.theme.Wallpaper +import net.ccbluex.liquidbounce.integration.theme.layout.component.ComponentFactory +import net.ccbluex.liquidbounce.integration.theme.type.native.NativeDrawableRoute +import net.ccbluex.liquidbounce.render.engine.font.FontRenderer +import net.ccbluex.liquidbounce.utils.client.logger +import net.minecraft.util.Identifier + +interface Theme { + + val name: String + val version: ThemeVersion + val components: List + val wallpapers: List + val defaultWallpaper: Wallpaper? get() = wallpapers.firstOrNull() + val fontRenderer: FontRenderer? + get() = null + val textures: Map> + get() = hashMapOf() + + fun init() { + logger.info("[ThemeManager] Loading theme $name") + } + + fun route(screenType: VirtualScreenType? = null): RouteType + fun doesAccept(type: VirtualScreenType?): Boolean = doesOverlay(type) || doesSupport(type) + fun doesSupport(type: VirtualScreenType?): Boolean + fun doesOverlay(type: VirtualScreenType?): Boolean + fun canSplash(): Boolean + +} + +/** + * Allows for backwards compatibility + */ +enum class ThemeVersion { + V1, + + /** + * Adds support for Custom HUDs + */ + V2 +} + +sealed class RouteType(open val type: VirtualScreenType?, open val theme: Theme) { + data class Native( + override val type: VirtualScreenType?, + override val theme: Theme, + val drawableRoute: NativeDrawableRoute + ) : RouteType(type, theme) + + data class Web(override val type: VirtualScreenType?, override val theme: Theme, val url: String) : + RouteType(type, theme) +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/types/IntegratedComponent.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/type/native/NativeComponent.kt similarity index 51% rename from src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/types/IntegratedComponent.kt rename to src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/type/native/NativeComponent.kt index 27a3d317262..cf2755b5a8e 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/types/IntegratedComponent.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/type/native/NativeComponent.kt @@ -1,7 +1,7 @@ /* * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce) * - * Copyright (c) 2015 - 2025 CCBlueX + * Copyright (c) 2024 CCBlueX * * LiquidBounce is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,24 +19,21 @@ * */ -package net.ccbluex.liquidbounce.integration.theme.component.types +package net.ccbluex.liquidbounce.integration.theme.type.native -import net.ccbluex.liquidbounce.integration.theme.component.Component -import net.ccbluex.liquidbounce.integration.theme.component.FeatureTweak +import net.ccbluex.liquidbounce.integration.theme.layout.component.Component +import net.ccbluex.liquidbounce.integration.theme.layout.component.ComponentTweak +import net.ccbluex.liquidbounce.integration.theme.type.Theme +import net.ccbluex.liquidbounce.utils.render.Alignment +import net.minecraft.client.gui.DrawContext -/** - * Unlike other components integrated are built into the theme and are being configured - * by the metadata of the theme - * - * TODO: These should be serializable from the Metadata JSON - */ -class IntegratedComponent( +abstract class NativeComponent( + theme: Theme, name: String, - val tweaks: Array = emptyArray() -) : Component(name, true) { - - init { - registerComponentListen() - } - + enabled: Boolean, + alignment: Alignment, + tweaks: Array = emptyArray() +) : Component(theme, name, enabled, alignment, tweaks) { + abstract fun render(context: DrawContext, delta: Float) + abstract fun size(): Pair } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/type/native/NativeDrawableRoute.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/type/native/NativeDrawableRoute.kt new file mode 100644 index 00000000000..550e7f03a0d --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/type/native/NativeDrawableRoute.kt @@ -0,0 +1,38 @@ +/* + * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce) + * + * Copyright (c) 2024 CCBlueX + * + * LiquidBounce is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiquidBounce is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LiquidBounce. If not, see . + * + * + */ + +package net.ccbluex.liquidbounce.integration.theme.type.native + +import net.minecraft.client.gui.DrawContext + +abstract class NativeDrawableRoute { + + abstract fun render(context: DrawContext, delta: Float) + + open fun mouseClicked(mouseX: Double, mouseY: Double, mouseButton: Int) = false + open fun mouseReleased(mouseX: Double, mouseY: Double, mouseButton: Int) = false + open fun mouseMoved(mouseX: Double, mouseY: Double) {} + open fun mouseScrolled(mouseX: Double, mouseY: Double) {} + open fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int) = false + open fun keyReleased(keyCode: Int, scanCode: Int, modifiers: Int) = false + open fun charTyped(char: Char, modifiers: Int) = false + +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/type/native/NativeDrawer.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/type/native/NativeDrawer.kt new file mode 100644 index 00000000000..98b772f2879 --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/type/native/NativeDrawer.kt @@ -0,0 +1,136 @@ +/* + * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce) + * + * Copyright (c) 2024 CCBlueX + * + * LiquidBounce is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiquidBounce is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LiquidBounce. If not, see . + * + * + */ + +package net.ccbluex.liquidbounce.integration.theme.type.native + +import net.ccbluex.liquidbounce.event.EventListener +import net.ccbluex.liquidbounce.event.EventManager +import net.ccbluex.liquidbounce.event.events.* +import net.ccbluex.liquidbounce.event.handler +import net.ccbluex.liquidbounce.integration.DrawingStage +import net.ccbluex.liquidbounce.utils.client.mc +import net.ccbluex.liquidbounce.utils.kotlin.EventPriorityConvention +import org.lwjgl.glfw.GLFW + +class NativeDrawer( + var route: NativeDrawableRoute?, + val stage: DrawingStage = DrawingStage.OVERLAY, + val takesInput: () -> Boolean = { false } +) : EventListener, AutoCloseable { + + private var drawn = false + + @Suppress("unused") + private val gameRenderHandler = handler { + drawn = false + } + + @Suppress("unused") + private val onScreenRender = handler { + if (drawn) { + return@handler + } + + route?.render(it.context, it.partialTicks) + drawn = true + } + + @Suppress("unused") + private val onOverlayRender = handler( + priority = EventPriorityConvention.READ_FINAL_STATE + ) { + if (drawn) { + return@handler + } + + if (stage == DrawingStage.SCREEN && mc.currentScreen != null) { + // We will draw this layer later on the screen render event + return@handler + } + + route?.render(it.context, it.tickDelta) + drawn = true + } + + private var mouseX: Double = 0.0 + private var mouseY: Double = 0.0 + + @Suppress("unused") + private val mouseButtonHandler = handler { event -> + if (!takesInput()) return@handler + + if (event.action == GLFW.GLFW_PRESS) { + route?.mouseClicked(mouseX, mouseY, event.button) + } else if (event.action == GLFW.GLFW_RELEASE) { + route?.mouseReleased(mouseX, mouseY, event.button) + } + } + + @Suppress("unused") + private val mouseScrollHandler = handler { + route?.mouseScrolled(it.horizontal, it.vertical) + } + + @Suppress("unused") + private val mouseCursorHandler = handler { event -> + val factorW = mc.window.scaledWidth.toDouble() / mc.window.width.toDouble() + val factorV = mc.window.scaledHeight.toDouble() / mc.window.height.toDouble() + val mouseX = event.x * factorW + val mouseY = event.y * factorV + + this.mouseX = mouseX + this.mouseY = mouseY + + route?.mouseMoved(mouseX, mouseY) + } + + @Suppress("unused") + private val keyboardKeyHandler = handler { event -> + if (!takesInput()) return@handler + + val action = event.action + val key = event.keyCode + val scancode = event.scanCode + val modifiers = event.mods + + if (action == GLFW.GLFW_PRESS || action == GLFW.GLFW_REPEAT) { + route?.keyPressed(key, scancode, modifiers) + } else if (action == GLFW.GLFW_RELEASE) { + route?.keyReleased(key, scancode, modifiers) + } + } + + @Suppress("unused") + private val keyboardCharHandler = handler { event -> + if (!takesInput()) return@handler + + route?.charTyped(event.codePoint.toChar(), event.modifiers) + } + + fun select(route: NativeDrawableRoute?) { + this.route = route + } + + override fun close() { + EventManager.unregisterEventHandler(this) + } + +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/type/web/LegacySupport.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/type/web/LegacySupport.kt new file mode 100644 index 00000000000..817d3848dfb --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/type/web/LegacySupport.kt @@ -0,0 +1,214 @@ +/* + * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce) + * + * Copyright (c) 2024 CCBlueX + * + * LiquidBounce is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiquidBounce is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LiquidBounce. If not, see . + * + * + */ + +package net.ccbluex.liquidbounce.integration.theme.type.web + +import com.google.gson.JsonArray +import com.google.gson.annotations.SerializedName +import net.ccbluex.liquidbounce.config.ConfigSystem +import net.ccbluex.liquidbounce.config.gson.interopGson +import net.ccbluex.liquidbounce.config.types.Configurable +import net.ccbluex.liquidbounce.config.types.NamedChoice +import net.ccbluex.liquidbounce.config.types.ToggleableConfigurable +import net.ccbluex.liquidbounce.integration.VirtualScreenType +import net.ccbluex.liquidbounce.integration.interop.ClientInteropServer +import net.ccbluex.liquidbounce.integration.theme.Wallpaper +import net.ccbluex.liquidbounce.integration.theme.layout.component.Component +import net.ccbluex.liquidbounce.integration.theme.layout.component.ComponentFactory +import net.ccbluex.liquidbounce.integration.theme.layout.component.ComponentManager +import net.ccbluex.liquidbounce.integration.theme.layout.component.ComponentTweak +import net.ccbluex.liquidbounce.integration.theme.type.RouteType +import net.ccbluex.liquidbounce.integration.theme.type.Theme +import net.ccbluex.liquidbounce.integration.theme.type.ThemeVersion +import net.ccbluex.liquidbounce.render.FontManager +import net.ccbluex.liquidbounce.render.engine.font.FontRenderer +import net.ccbluex.liquidbounce.utils.client.logger +import net.ccbluex.liquidbounce.utils.render.Alignment +import net.minecraft.util.Identifier +import java.io.File + +open class LegacyWebTheme(val folder: File) : Theme { + + init { + init() + } + + private val metadata: LegacyThemeMetadata = run { + val metadataFile = File(folder, "metadata.json") + if (!metadataFile.exists()) { + error("Theme $name does not contain a metadata file") + } + + interopGson.fromJson(metadataFile.readText(), LegacyThemeMetadata::class.java) + } + + override val name: String + get() = metadata.name + + override val version = ThemeVersion.V1 + + override val components: List = run { + val themeComponent = metadata.rawComponents + .map { jsonValue -> jsonValue.asJsonObject } + .associateBy { jsonObject -> jsonObject["name"].asString!! } + + val componentList = mutableListOf() + + for ((name, obj) in themeComponent) { + runCatching { + val componentType = ComponentType.byName(name) ?: error("Unknown component type: $name") + val component = LegacyComponent(name, componentType, true) + + runCatching { + ConfigSystem.deserializeConfigurable(component, obj) + }.onFailure { + logger.error("Failed to deserialize component $name", it) + } + + componentList.add(component.factory) + }.onFailure { + logger.error("Failed to create component $name", it) + } + } + + return@run componentList + } + + private val url: String + get() = "${ClientInteropServer.url}/${folder.name}/#/" + + override val wallpapers: List = emptyList() + override val defaultWallpaper: Wallpaper? = null + override val fontRenderer: FontRenderer? = null + override val textures: Map> = emptyMap() + + init { + // Load fonts from the assets folder + FontManager.queueFolder(folder.resolve("assets")) + } + + override fun init() { } + + override fun route(screenType: VirtualScreenType?) = "$url${screenType?.routeName ?: ""}".let { url -> + RouteType.Web( + type = screenType, theme = this, url = if (screenType?.isStatic == true) { + "$url?static" + } else { + url + } + ) + } + + override fun doesSupport(type: VirtualScreenType?) = type != null && metadata.supports.contains(type.routeName) + + override fun doesOverlay(type: VirtualScreenType?) = type != null && metadata.overlays.contains(type.routeName) + + override fun canSplash() = metadata.overlays.contains("splash") + +} + + +@Deprecated("Use ThemeMetadataV2 instead") +data class LegacyThemeMetadata( + val name: String, + val author: String, + val version: String, + val supports: List, + val overlays: List, + @SerializedName("components") + val rawComponents: JsonArray +) + +@Deprecated("Use Alignment instead", ReplaceWith("Alignment")) +class LegacyAlignment() : Configurable("Alignment") { + val horizontalAlignment by enumChoice("Horizontal", Alignment.ScreenAxisX.LEFT) + val horizontalOffset by int("HorizontalOffset", 0, -1000..1000) + val verticalAlignment by enumChoice("Vertical", Alignment.ScreenAxisY.TOP) + val verticalOffset by int("VerticalOffset", 0, -1000..1000) +} + +@Deprecated("ComponentType is not required anymore") +enum class ComponentType( + override val choiceName: String, + val tweaks: Array = emptyArray() +) : NamedChoice { + + WATERMARK("Watermark"), + TAB_GUI("TabGui"), + ARRAY_LIST("ArrayList"), + NOTIFICATIONS("Notifications"), + HOTBAR("Hotbar", tweaks = arrayOf( + ComponentTweak.TWEAK_HOTBAR, + ComponentTweak.DISABLE_STATUS_BAR, + ComponentTweak.DISABLE_EXP_BAR, + ComponentTweak.DISABLE_HELD_ITEM_TOOL_TIP, + ComponentTweak.DISABLE_OVERLAY_MESSAGE + )), + EFFECTS("Effects", tweaks = arrayOf( + ComponentTweak.DISABLE_STATUS_EFFECT_OVERLAY + )), + SCOREBOARD("Scoreboard", tweaks = arrayOf( + ComponentTweak.DISABLE_SCOREBOARD + )), + TARGET_HUD("TargetHud"), + BLOCK_COUNTER("BlockCounter"), + KEYSTROKES("Keystrokes"), + TACO("Taco"), + MINIMAP("Minimap"); + + companion object { + fun byName(name: String) = entries.find { it.choiceName == name } + } + +} + +@Deprecated("Use JsonComponentFactory instead") +class LegacyComponent(name: String, type: ComponentType, enabled: Boolean) + : ToggleableConfigurable(parent = ComponentManager, name = name, enabled = enabled) { + val alignment = tree(LegacyAlignment()) + val factory = LegacyComponentFactory(name, enabled, alignment, type) +} + +@Deprecated("Use JsonComponentFactory instead") +class LegacyComponentFactory( + override val name: String, + override val default: Boolean, + private val alignment: LegacyAlignment, + private val type: ComponentType +) : ComponentFactory() { + + override fun createComponent(theme: Theme): Component { + val tweaks = type.tweaks + + return WebComponent( + theme, + name, + default, + Alignment( + alignment.horizontalAlignment, + alignment.horizontalOffset, + alignment.verticalAlignment, + alignment.verticalOffset + ), + tweaks = tweaks + ) + } +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/types/ImageComponent.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/type/web/ThemeMetadata.kt similarity index 64% rename from src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/types/ImageComponent.kt rename to src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/type/web/ThemeMetadata.kt index 5f4c95fc12f..88165a8eb10 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/component/types/ImageComponent.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/type/web/ThemeMetadata.kt @@ -1,7 +1,7 @@ /* * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce) * - * Copyright (c) 2015 - 2025 CCBlueX + * Copyright (c) 2024 CCBlueX * * LiquidBounce is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,17 +19,15 @@ * */ -package net.ccbluex.liquidbounce.integration.theme.component.types +package net.ccbluex.liquidbounce.integration.theme.type.web -import net.ccbluex.liquidbounce.integration.theme.component.Component - -class ImageComponent(src: String, enabled: Boolean = true) : Component("Image", enabled) { - - val src by text("Src", src) - val scale by float("Scale", 1.0f, 0.0f..10.0f) - - init { - registerComponentListen() - } - -} +data class ThemeMetadata( + val name: String, + val authors: List, + val version: String, + val supports: List, + val overlays: List, + val font: String? = null, + // If null, there is no default wallpaper for this theme assigned + val wallpaper: String? = null +) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/type/web/WebComponent.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/type/web/WebComponent.kt new file mode 100644 index 00000000000..0a6ccc2a8de --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/type/web/WebComponent.kt @@ -0,0 +1,151 @@ +/* + * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce) + * + * Copyright (c) 2024 CCBlueX + * + * LiquidBounce is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiquidBounce is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LiquidBounce. If not, see . + * + * + */ + +package net.ccbluex.liquidbounce.integration.theme.type.web + +import com.google.gson.JsonObject +import net.ccbluex.liquidbounce.config.types.Configurable +import net.ccbluex.liquidbounce.config.types.ToggleableConfigurable +import net.ccbluex.liquidbounce.integration.theme.layout.component.Component +import net.ccbluex.liquidbounce.integration.theme.layout.component.ComponentTweak +import net.ccbluex.liquidbounce.integration.theme.type.Theme +import net.ccbluex.liquidbounce.render.engine.Color4b +import net.ccbluex.liquidbounce.utils.render.Alignment + +class WebComponent( + theme: Theme, + name: String, + enabled: Boolean, + alignment: Alignment, + tweaks: Array = emptyArray(), + settings: Array = emptyArray() +) : Component(theme, name, enabled, alignment, tweaks) { + + init { + for (setting in settings) { + configureConfigurable(this, setting) + } + registerComponentListen() + } + +} + +/** + * Assigns the value of the settings to the component + * + * A component can have dynamic settings which can be assigned through the JSON file + * These have to be interpreted and assigned to the configurable + * + * An example: + * { + * "type": "INT", + * "name": "Size", + * "default": 14, + * "range": { + * "min": 1, + * "max": 100 + * }, + * "suffix": "px" + * } + * + * TOOD: Check if I can integrate this using Dynamic Configurable + * + * @param jsonObject JsonObject + */ +private fun configureConfigurable(configurable: Configurable, jsonObject: JsonObject) { + val type = jsonObject["type"].asString + val name = jsonObject["name"].asString + + // todo: might replace this with serious deserialization + when (type) { + "BOOLEAN" -> { + val default = jsonObject["default"].asBoolean + configurable.boolean(name, default) + } + + "INT" -> { + val default = jsonObject["default"].asInt + val min = jsonObject["range"].asJsonObject["min"].asInt + val max = jsonObject["range"].asJsonObject["max"].asInt + val suffix = jsonObject["suffix"]?.asString ?: "" + configurable.int(name, default, min..max, suffix) + } + + "INT_RANGE" -> { + val defaultMin = jsonObject["default"].asJsonObject["min"].asInt + val defaultMax = jsonObject["default"].asJsonObject["max"].asInt + val min = jsonObject["range"].asJsonObject["min"].asInt + val max = jsonObject["range"].asJsonObject["max"].asInt + val suffix = jsonObject["suffix"]?.asString ?: "" + configurable.intRange(name, defaultMin..defaultMax, min..max, suffix) + } + + "FLOAT" -> { + val default = jsonObject["default"].asFloat + val min = jsonObject["range"].asJsonObject["min"].asFloat + val max = jsonObject["range"].asJsonObject["max"].asFloat + val suffix = jsonObject["suffix"]?.asString ?: "" + configurable.float(name, default, min..max, suffix) + } + + "FLOAT_RANGE" -> { + val defaultMin = jsonObject["default"].asJsonObject["min"].asFloat + val defaultMax = jsonObject["default"].asJsonObject["max"].asFloat + val min = jsonObject["range"].asJsonObject["min"].asFloat + val max = jsonObject["range"].asJsonObject["max"].asFloat + val suffix = jsonObject["suffix"]?.asString ?: "" + configurable.floatRange(name, defaultMin..defaultMax, min..max, suffix) + } + + "TEXT" -> { + val default = jsonObject["default"].asString + configurable.text(name, default) + } + + "COLOR" -> { + val default = jsonObject["default"].asInt + configurable.color(name, Color4b(default, hasAlpha = true)) + } + + "CONFIGURABLE" -> { + val configurableObject = Configurable(name) + val settings = jsonObject["settings"].asJsonArray + for (setting in settings) { + configureConfigurable(configurableObject, setting.asJsonObject) + } + configurable.tree(configurableObject) + } + // same as configurable but it is [ToggleableConfigurable] + "TOGGLEABLE" -> { + val default = jsonObject["default"].asBoolean + // Parent is NULL in that case because we are not dealing with Listenable anyway and only use it + // as toggleable Configurable + val configurableObject = object : ToggleableConfigurable(null, name, default) {} + val settings = jsonObject["settings"].asJsonArray + for (setting in settings) { + configureConfigurable(configurableObject, setting.asJsonObject) + } + configurable.tree(configurableObject) + } + + else -> error("Unsupported type: $type") + } +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/type/web/WebTheme.kt b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/type/web/WebTheme.kt new file mode 100644 index 00000000000..921b9387594 --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/integration/theme/type/web/WebTheme.kt @@ -0,0 +1,124 @@ +/* + * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce) + * + * Copyright (c) 2024 CCBlueX + * + * LiquidBounce is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiquidBounce is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LiquidBounce. If not, see . + * + * + */ + +package net.ccbluex.liquidbounce.integration.theme.type.web + +import net.ccbluex.liquidbounce.config.gson.interopGson +import net.ccbluex.liquidbounce.integration.VirtualScreenType +import net.ccbluex.liquidbounce.integration.interop.ClientInteropServer +import net.ccbluex.liquidbounce.integration.theme.Wallpaper +import net.ccbluex.liquidbounce.integration.theme.layout.component.ComponentFactory +import net.ccbluex.liquidbounce.integration.theme.type.RouteType +import net.ccbluex.liquidbounce.integration.theme.type.Theme +import net.ccbluex.liquidbounce.integration.theme.type.ThemeVersion +import net.ccbluex.liquidbounce.render.FontManager +import net.ccbluex.liquidbounce.render.engine.font.FontRenderer +import net.ccbluex.liquidbounce.utils.client.logger +import net.ccbluex.liquidbounce.utils.client.mc +import net.minecraft.client.texture.NativeImage +import net.minecraft.client.texture.NativeImageBackedTexture +import net.minecraft.util.Identifier +import java.io.File + +open class WebTheme(val folder: File) : Theme { + + init { + init() + } + + private val metadata: ThemeMetadata = run { + val metadataFile = File(folder, "metadata.json") + if (!metadataFile.exists()) { + error("Theme $name does not contain a metadata file") + } + + interopGson.fromJson(metadataFile.readText(), ThemeMetadata::class.java) + } + + override val name: String + get() = metadata.name + + override val version = ThemeVersion.V1 + + override val components: List = + folder.resolve("components").listFiles()?.mapNotNull { file -> + runCatching { + interopGson.fromJson(file.readText(), ComponentFactory.JsonComponentFactory::class.java) + }.onFailure { error -> + logger.error("Failed to load $name component factory from file ${file.name}", error) + }.getOrNull() + } ?: emptyList() + + private val url: String + get() = "${ClientInteropServer.url}/${folder.name}/#/" + + override val wallpapers: List = folder.resolve("wallpapers").listFiles()?.mapNotNull { file -> + runCatching { + Wallpaper.fromFile(this, file) + }.onFailure { error -> + logger.error("Failed to load wallpaper from file ${file.name} ${error.message}") + }.getOrNull() + } ?: emptyList() + + override val defaultWallpaper: Wallpaper? + get() = wallpapers.firstOrNull { wallpaper -> wallpaper.name == metadata.wallpaper } + + override val fontRenderer: FontRenderer? + get() = metadata.font?.let { FontManager.fontFace(it)?.renderer } + + override val textures: Map> = folder.resolve("textures").listFiles { _, name -> + name.endsWith(".png") + }?.associateBy { it.nameWithoutExtension }?.mapValues { (name, file) -> + lazy { + val identifier = Identifier.of("liquidbounce", "theme-${this.name.lowercase()}-texture-${name.lowercase()}") + + mc.textureManager.registerTexture( + identifier, NativeImageBackedTexture(NativeImage.read(file.inputStream())) + ) + + identifier + } + } ?: hashMapOf() + + init { + // Load fonts from the assets folder + FontManager.queueFolder(folder.resolve("assets")) + } + + override fun init() { } + + override fun route(screenType: VirtualScreenType?) = "$url${screenType?.routeName ?: ""}".let { url -> + RouteType.Web( + type = screenType, theme = this, url = if (screenType?.isStatic == true) { + "$url?static" + } else { + url + } + ) + } + + override fun doesSupport(type: VirtualScreenType?) = type != null && metadata.supports.contains(type.routeName) + + override fun doesOverlay(type: VirtualScreenType?) = type != null && metadata.overlays.contains(type.routeName) + + override fun canSplash() = metadata.overlays.contains("splash") + +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/render/engine/RenderTasks.kt b/src/main/kotlin/net/ccbluex/liquidbounce/render/engine/RenderTasks.kt index 8af8027bd94..9e7d7ec0f16 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/render/engine/RenderTasks.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/render/engine/RenderTasks.kt @@ -22,7 +22,6 @@ import net.minecraft.util.math.Vec3d import net.minecraft.util.math.Vec3i import org.lwjgl.opengl.GL20 import java.awt.Color -import kotlin.Throws import kotlin.math.cos import kotlin.math.sin @@ -120,6 +119,9 @@ data class Color4b(val r: Int, val g: Int, val b: Int, val a: Int = 255) { constructor(color: Color) : this(color.red, color.green, color.blue, color.alpha) constructor(hex: Int, hasAlpha: Boolean = false) : this(Color(hex, hasAlpha)) + constructor(r: Int, g: Int, b: Int) : this(r, g, b, 255) + constructor(r: Float, g: Float, b: Float, a: Float) : + this((r * 255).toInt(), (g * 255).toInt(), (b * 255).toInt(), (a * 255).toInt()) fun with( r: Int = this.r, diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/render/Alignment.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/render/Alignment.kt index d728b915733..b276a43fb2a 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/render/Alignment.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/render/Alignment.kt @@ -18,44 +18,43 @@ */ package net.ccbluex.liquidbounce.utils.render -import net.ccbluex.liquidbounce.config.types.Configurable import net.ccbluex.liquidbounce.config.types.NamedChoice import net.ccbluex.liquidbounce.render.engine.font.BoundingBox2f import net.ccbluex.liquidbounce.utils.client.mc -class Alignment( - horizontalAlignment: ScreenAxisX, - horizontalOffset: Int, - verticalAlignment: ScreenAxisY, - verticalOffset: Int, -) : Configurable("Alignment") { +data class Alignment( + val horizontalAlignment: ScreenAxisX, + val horizontalOffset: Int, + val verticalAlignment: ScreenAxisY, + val verticalOffset: Int, +) { - val horizontalAlignment by enumChoice("Horizontal", horizontalAlignment) - val horizontalOffset by int("HorizontalOffset", horizontalOffset, -1000..1000) - val verticalAlignment by enumChoice("Vertical", verticalAlignment) - val verticalOffset by int("VerticalOffset", verticalOffset, -1000..1000) + constructor() : this(ScreenAxisX.LEFT, 0, ScreenAxisY.TOP, 0) fun getBounds( width: Float, height: Float, + factor: Float = 1f ): BoundingBox2f { val screenWidth = mc.window.scaledWidth.toFloat() val screenHeight = mc.window.scaledHeight.toFloat() + val horizontalOffset = (horizontalOffset * factor).toFloat() val x = when (horizontalAlignment) { - ScreenAxisX.LEFT -> horizontalOffset.toFloat() - ScreenAxisX.CENTER_TRANSLATED -> screenWidth / 2f - width / 2f + horizontalOffset.toFloat() - ScreenAxisX.RIGHT -> screenWidth - width - horizontalOffset.toFloat() - ScreenAxisX.CENTER -> screenWidth / 2f - width / 2f + horizontalOffset.toFloat() + ScreenAxisX.LEFT -> horizontalOffset + ScreenAxisX.CENTER_TRANSLATED -> screenWidth / 2f - width / 2f + horizontalOffset + ScreenAxisX.RIGHT -> screenWidth - width - horizontalOffset + ScreenAxisX.CENTER -> screenWidth / 2f - width / 2f + horizontalOffset } + val verticalOffset = (verticalOffset * factor).toFloat() val y = when (verticalAlignment) { - ScreenAxisY.TOP -> verticalOffset.toFloat() - ScreenAxisY.CENTER_TRANSLATED -> screenHeight / 2f - height / 2f + verticalOffset.toFloat() - ScreenAxisY.BOTTOM -> screenHeight - height - verticalOffset.toFloat() - ScreenAxisY.CENTER -> screenWidth / 2f - height / 2f + verticalOffset.toFloat() + ScreenAxisY.TOP -> verticalOffset + ScreenAxisY.CENTER_TRANSLATED -> screenHeight / 2f - height / 2f + verticalOffset + ScreenAxisY.BOTTOM -> screenHeight - height - verticalOffset + ScreenAxisY.CENTER -> screenWidth / 2f - height / 2f + verticalOffset } return BoundingBox2f(x, y, x + width, y + height) @@ -75,9 +74,18 @@ class Alignment( BOTTOM("Bottom"), } + /** + * Checks if the given point is inside the bounds of the alignment + */ + fun contains(x: Float, y: Float, width: Float, height: Float): Boolean { + val bounds = getBounds(width, height) + return x >= bounds.xMin && x <= bounds.xMax && y >= bounds.yMin && y <= bounds.yMax + } + /** * Converts the alignement configurable to style (CSS) */ + @Deprecated("Style Transformation should be handled by the theme") fun toStyle() = """ position: fixed; ${when (horizontalAlignment) { diff --git a/src/main/resources/liquidbounce.mixins.json b/src/main/resources/liquidbounce.mixins.json index 6f4ff32d18b..465c0f7530f 100644 --- a/src/main/resources/liquidbounce.mixins.json +++ b/src/main/resources/liquidbounce.mixins.json @@ -37,6 +37,7 @@ "minecraft.entity.MixinClientPlayerEntity", "minecraft.entity.MixinEntity", "minecraft.entity.MixinLivingEntity", + "minecraft.entity.MixinLivingEntityCompatibility", "minecraft.entity.MixinPlayerEntity", "minecraft.entity.MixinPlayerEntityAccessor", "minecraft.entity.projectile.MixinFishingBobberEntity", @@ -57,7 +58,9 @@ "minecraft.gui.custom.MixinConnectScreen", "minecraft.gui.custom.MixinDisconnectedScreen", "minecraft.gui.custom.MixinDownloadingTerrainScreen", + "minecraft.gui.widget.MixinClickableWidget", "minecraft.gui.widget.MixinEntryListWidget", + "minecraft.gui.widget.MixinPressableWidget", "minecraft.item.MixinArmorItem", "minecraft.item.MixinHeldItemRenderer", "minecraft.item.MixinItem", @@ -109,8 +112,7 @@ "truffle.MixinHostClassDesc", "truffle.MixinHostClassLoader", "truffle.MixinHostContext", - "truffle.MixinTruffleLanguage", - "minecraft.entity.MixinLivingEntityCompatibility" + "truffle.MixinTruffleLanguage" ], "server": [], "injectors": {