From cc8e670d9b818069cce0ff46e41b4446fc1f5f17 Mon Sep 17 00:00:00 2001 From: FullRisk Date: Mon, 13 Jul 2026 15:53:23 +0200 Subject: [PATCH] added Coords, Hitbox and more Settings --- .../java/de/fullrisk/citrusDev/Config.java | 10 + .../fullrisk/citrusDev/UI/Module_Coords.java | 12 - .../fullrisk/citrusDev/UI/Module_Effects.java | 222 ++++++++++++++++++ .../fullrisk/citrusDev/UI/Module_Hitbox.java | 4 +- .../de/fullrisk/citrusDev/UI/MyScreen.java | 173 +++++++++++--- .../de/fullrisk/citrusDev/UI/QuestsPanel.java | 14 +- .../citrusDev/UIHelper/DraggableHud.java | 7 + .../citrusDev/UIHelper/ModuleRow.java | 2 +- .../citrusDev/UIHelper/SettingsControls.java | 41 +++- .../citrusDev/client/CitrusDevClient.java | 2 + .../citrus-dev/textures/icons/effect.png | Bin 0 -> 444 bytes .../assets/citrus-dev/textures/icons/fps.png | Bin 0 -> 288 bytes .../citrus-dev/textures/icons/fullbright.png | Bin 0 -> 476 bytes .../citrus-dev/textures/icons/keystrokes.png | Bin 0 -> 16798 bytes 14 files changed, 428 insertions(+), 59 deletions(-) create mode 100644 src/main/java/de/fullrisk/citrusDev/UI/Module_Effects.java create mode 100644 src/main/resources/assets/citrus-dev/textures/icons/effect.png create mode 100644 src/main/resources/assets/citrus-dev/textures/icons/fps.png create mode 100644 src/main/resources/assets/citrus-dev/textures/icons/fullbright.png create mode 100644 src/main/resources/assets/citrus-dev/textures/icons/keystrokes.png diff --git a/src/main/java/de/fullrisk/citrusDev/Config.java b/src/main/java/de/fullrisk/citrusDev/Config.java index 91d9dd6..6231e1b 100644 --- a/src/main/java/de/fullrisk/citrusDev/Config.java +++ b/src/main/java/de/fullrisk/citrusDev/Config.java @@ -35,6 +35,16 @@ public class Config { public boolean hitboxEnabled = false; public int hitboxHue = 0; + public int effectsX = 1; + public int effectsY = 1; + public int effectsP = 4; + public boolean effectsEnabled = true; + public boolean effectsBgEnabled = true; + public boolean effectsTimerEnabled = true; + public boolean effectsNameEnabled = true; + + public float panelBlur = 5.0f; + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); private static final Path PATH = FabricLoader.getInstance().getConfigDir().resolve("citrus-dev.json"); diff --git a/src/main/java/de/fullrisk/citrusDev/UI/Module_Coords.java b/src/main/java/de/fullrisk/citrusDev/UI/Module_Coords.java index 47ccece..f75c88e 100644 --- a/src/main/java/de/fullrisk/citrusDev/UI/Module_Coords.java +++ b/src/main/java/de/fullrisk/citrusDev/UI/Module_Coords.java @@ -35,8 +35,6 @@ public class Module_Coords { private static final Surface GLASS_PANEL = Surface.blur(5f, 15f) .and(Surface.flat(0x40FFFFFF)); - // ---- coords toggle ---- - public static boolean isEnabled() { return toggle; } @@ -47,8 +45,6 @@ public class Module_Coords { CONFIG.save(); } - // ---- biome toggle ---- - public static boolean isBiomeEnabled() { return toggleBiome; } @@ -59,8 +55,6 @@ public class Module_Coords { CONFIG.save(); } - // ---- positioning ---- - public static void setPosition(int x, int y) { if (coordinates != null) { coordinates.positioning(Positioning.relative(x, y)); @@ -82,8 +76,6 @@ public class Module_Coords { return coordinates; } - // ---- HUD registration ---- - public static void register() { Hud.add(Identifier.fromNamespaceAndPath("citrus-dev", "coords_display"), () -> { if (coordinates == null) { @@ -104,8 +96,6 @@ public class Module_Coords { }); } - // ---- data getters ---- - public static Vec3 getPlayerPosition() { LocalPlayer player = Minecraft.getInstance().player; return player != null ? player.position() : null; @@ -145,8 +135,6 @@ public class Module_Coords { .orElse("Unknown"); } - // ---- tick ---- - public static void tick() { if (coordinates == null) return; diff --git a/src/main/java/de/fullrisk/citrusDev/UI/Module_Effects.java b/src/main/java/de/fullrisk/citrusDev/UI/Module_Effects.java new file mode 100644 index 0000000..2479ed1 --- /dev/null +++ b/src/main/java/de/fullrisk/citrusDev/UI/Module_Effects.java @@ -0,0 +1,222 @@ +package de.fullrisk.citrusDev.UI; + +import io.wispforest.owo.ui.component.LabelComponent; +import io.wispforest.owo.ui.component.UIComponents; +import io.wispforest.owo.ui.container.FlowLayout; +import io.wispforest.owo.ui.container.UIContainers; +import io.wispforest.owo.ui.core.*; +import io.wispforest.owo.ui.hud.Hud; +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import net.minecraft.world.effect.MobEffectInstance; +import de.fullrisk.citrusDev.Config; + +import java.util.ArrayList; +import java.util.List; + +public class Module_Effects { + + public static FlowLayout effectsContainer; + private static final List rows = new ArrayList<>(); + private static List lastEffects = List.of(); + + private static final Config CONFIG = Config.get(); + + static boolean toggle = CONFIG.effectsEnabled; + static boolean bgToggle = CONFIG.effectsBgEnabled; + static boolean timerToggle = CONFIG.effectsTimerEnabled; + static boolean nameToggle = CONFIG.effectsNameEnabled; + private static boolean shown = false; + + private static final Surface GLASS_PANEL = Surface.blur(5f, 15f) + .and(Surface.flat(0x40FFFFFF)); + + public static boolean isEnabled() { + return toggle; + } + + public static void setEnabled(boolean value) { + toggle = value; + CONFIG.effectsEnabled = value; + CONFIG.save(); + } + + public static boolean isBackgroundEnabled() { + return bgToggle; + } + + public static void setBackgroundEnabled(boolean value) { + bgToggle = value; + CONFIG.effectsBgEnabled = value; + CONFIG.save(); + updateSurface(); + } + + public static boolean isTimerEnabled() { + return timerToggle; + } + + public static void setTimerEnabled(boolean value) { + timerToggle = value; + CONFIG.effectsTimerEnabled = value; + CONFIG.save(); + rebuildRows(); + } + + public static boolean isNameEnabled() { + return nameToggle; + } + + public static void setNameEnabled(boolean value) { + nameToggle = value; + CONFIG.effectsNameEnabled = value; + CONFIG.save(); + rebuildRows(); + } + + private static void rebuildRows() { + if (effectsContainer == null || !shown) return; + List current = lastEffects; + if (current.isEmpty()) return; + effectsContainer.clearChildren(); + rows.clear(); + for (MobEffectInstance instance : current) { + FlowLayout row = makeEffectRow(instance); + rows.add(row); + effectsContainer.child(row); + } + } + + + + private static void updateSurface() { + if (effectsContainer == null || !shown) return; + effectsContainer.surface(bgToggle ? GLASS_PANEL : Surface.flat(0)); + } + + public static void setPosition(int x, int y) { + if (effectsContainer != null) { + effectsContainer.positioning(Positioning.relative(x, y)); + } + CONFIG.effectsX = x; + CONFIG.effectsY = y; + CONFIG.save(); + } + + public static void setEffectsPadding(int padding) { + CONFIG.effectsP = padding; + CONFIG.save(); + + if (effectsContainer == null) return; + effectsContainer.padding(Insets.of(padding)); + } + + public static FlowLayout getEffectsContainer() { + return effectsContainer; + } + + public static void register() { + Hud.add(Identifier.fromNamespaceAndPath("citrus-dev", "effects_display"), () -> { + if (effectsContainer == null) { + effectsContainer = UIContainers.verticalFlow(Sizing.content(), Sizing.content()); + effectsContainer.gap(2); + effectsContainer.verticalAlignment(VerticalAlignment.CENTER); + effectsContainer.horizontalAlignment(HorizontalAlignment.LEFT); + effectsContainer.padding(Insets.of(0)); + + effectsContainer.positioning(Positioning.relative(CONFIG.effectsX, CONFIG.effectsY)); + } + return effectsContainer; + }); + } + + private static FlowLayout makeEffectRow(MobEffectInstance instance) { + FlowLayout row = UIContainers.horizontalFlow(Sizing.content(), Sizing.content()); + row.gap(4); + row.verticalAlignment(VerticalAlignment.CENTER); + row.horizontalAlignment(HorizontalAlignment.CENTER); + + Identifier iconId = Identifier.fromNamespaceAndPath( + "minecraft", + "textures/mob_effect/" + BuiltInRegistries.MOB_EFFECT.getKey(instance.getEffect().value()).getPath() + ".png" + ); + row.child(UIComponents.texture(iconId, 0, 0, 18, 18, 18, 18)); + + if (nameToggle) { + var effectLabel = UIComponents.label(instance.getEffect().value().getDisplayName()); + row.child(effectLabel); + } + + if (timerToggle) { + var durationLabel = UIComponents.label(Component.literal(formatDuration(instance.getDuration()))); + durationLabel.color(Color.ofRgb(0xAAAAAA)); + row.child(durationLabel); + } + + return row; + } + + private static String formatDuration(int ticks) { + int totalSeconds = ticks / 20; + int minutes = totalSeconds / 60; + int seconds = totalSeconds % 60; + return String.format("%d:%02d", minutes, seconds); + } + + public static void tick() { + if (effectsContainer == null) return; + + LocalPlayer player = Minecraft.getInstance().player; + + if (toggle && !shown) { + effectsContainer.surface(bgToggle ? GLASS_PANEL : Surface.flat(0)); + effectsContainer.padding(Insets.of(CONFIG.effectsP)); + shown = true; + } else if (!toggle && shown) { + effectsContainer.clearChildren(); + rows.clear(); + lastEffects = List.of(); + effectsContainer.surface(Surface.flat(0)); + effectsContainer.padding(Insets.of(0)); + shown = false; + return; + } + + if (!toggle || player == null) return; + + List current = new ArrayList<>(player.getActiveEffects()); + + boolean changed = current.size() != lastEffects.size(); + if (!changed) { + for (int i = 0; i < current.size(); i++) { + if (!current.get(i).getEffect().equals(lastEffects.get(i).getEffect())) { + changed = true; + break; + } + } + } + + if (changed) { + effectsContainer.clearChildren(); + rows.clear(); + for (MobEffectInstance instance : current) { + FlowLayout row = makeEffectRow(instance); + rows.add(row); + effectsContainer.child(row); + } + } else { + int durationIdx = nameToggle ? 2 : 1; + for (int i = 0; i < current.size(); i++) { + if (timerToggle && rows.get(i).children().size() > durationIdx) { + var label = (LabelComponent) rows.get(i).children().get(durationIdx); + label.text(Component.literal(formatDuration(current.get(i).getDuration()))); + } + } + } + + lastEffects = current; + } +} \ No newline at end of file diff --git a/src/main/java/de/fullrisk/citrusDev/UI/Module_Hitbox.java b/src/main/java/de/fullrisk/citrusDev/UI/Module_Hitbox.java index 5730628..b6ca78b 100644 --- a/src/main/java/de/fullrisk/citrusDev/UI/Module_Hitbox.java +++ b/src/main/java/de/fullrisk/citrusDev/UI/Module_Hitbox.java @@ -4,7 +4,7 @@ import de.fullrisk.citrusDev.Config; public class Module_Hitbox { private static boolean enabled = Config.get().hitboxEnabled; - private static int hue = Config.get().hitboxHue; // 0-360 + private static int hue = Config.get().hitboxHue; public static volatile int color = hueToArgb(hue); public static boolean isEnabled() { @@ -31,6 +31,6 @@ public class Module_Hitbox { private static int hueToArgb(int hueDegrees) { float h = (hueDegrees % 360) / 360.0F; int rgb = java.awt.Color.HSBtoRGB(h, 1.0F, 1.0F); - return 0xFF000000 | (rgb & 0x00FFFFFF); // force opaque + return 0xFF000000 | (rgb & 0x00FFFFFF); } } \ No newline at end of file diff --git a/src/main/java/de/fullrisk/citrusDev/UI/MyScreen.java b/src/main/java/de/fullrisk/citrusDev/UI/MyScreen.java index 66b7e5f..ef64dca 100644 --- a/src/main/java/de/fullrisk/citrusDev/UI/MyScreen.java +++ b/src/main/java/de/fullrisk/citrusDev/UI/MyScreen.java @@ -26,16 +26,36 @@ public class MyScreen extends BaseOwoScreen { static final Identifier MC_FIVE = Identifier.fromNamespaceAndPath("citrus-dev", "minecraft_five"); - static final Surface GLASS_PANEL = Surface.blur(5f, 15f) + static Surface PANEL_SURFACE = buildPanelSurface(); + + static Surface GLASS_PANEL = Surface.blur(5f, 15f) .and(Surface.flat(0x40FFFFFF)) .and(Surface.outline(0x64de4b)); - private static final Surface DISABLED = Surface.blur(5f, 15f) + private static Surface DISABLED = Surface.blur(5f, 15f) .and(Surface.flat(0x401c1c1c)); - private static final Surface FLAT = Surface.blur(5f, 15f) + private static Surface FLAT = Surface.blur(5f, 15f) .and(Surface.flat(0x40FFFFFF)); + private static Surface buildPanelSurface() { + float blur = Config.get().panelBlur; + if (blur <= 0f) { + return Surface.flat(0x40FFFFFF) + .and(Surface.outline(0x64de4b)); + } + return Surface.blur(blur, 15f) + .and(Surface.flat(0x40FFFFFF)) + .and(Surface.outline(0x64de4b)); + } + + public static void rebuildSurfaces() { + PANEL_SURFACE = buildPanelSurface(); + if (panelRef != null) { + panelRef.surface(PANEL_SURFACE); + } + } + private static final int PANEL_WIDTH = 250; private static final int PANEL_HEIGHT = 150; private static final int INNER_WIDTH = PANEL_WIDTH - 4; @@ -56,6 +76,7 @@ public class MyScreen extends BaseOwoScreen { private FlowLayout content; private final String initialModule; + private static FlowLayout panelRef; public MyScreen() { @@ -83,6 +104,7 @@ public class MyScreen extends BaseOwoScreen { .verticalAlignment(VerticalAlignment.CENTER); FlowLayout panel = UIContainers.verticalFlow(Sizing.content(), Sizing.content()); + panelRef = panel; panel.gap(0); panel.padding(Insets.of(2)); panel.horizontalAlignment(HorizontalAlignment.CENTER); @@ -91,7 +113,7 @@ public class MyScreen extends BaseOwoScreen { panel.verticalSizing(Sizing.fixed(0)); panel.horizontalSizing().animate(300, Easing.CUBIC, Sizing.fixed(PANEL_WIDTH)).forwards(); panel.verticalSizing().animate(300, Easing.CUBIC, Sizing.fixed(PANEL_HEIGHT)).forwards(); - panel.surface(GLASS_PANEL); + panel.surface(PANEL_SURFACE); FlowLayout navbar = UIContainers.horizontalFlow(Sizing.fixed(INNER_WIDTH), Sizing.fixed(NAVBAR_HEIGHT)); buildNavbar(navbar); @@ -281,8 +303,8 @@ public class MyScreen extends BaseOwoScreen { FlowLayout fpsRow = ModuleRow.build( GLASS_PANEL, DISABLED, FLAT, - Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/block_culling_file.png"), - 18, 12, + Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/fps.png"), + 18, 18, MC_FIVE, "FPS", "Displays your FPS", @@ -302,8 +324,8 @@ public class MyScreen extends BaseOwoScreen { FlowLayout keyStrokesRow = ModuleRow.build( GLASS_PANEL, DISABLED, FLAT, - Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/block_culling_file.png"), - 18, 12, + Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/keystrokes.png"), + 24, 24, MC_FIVE, "Keystrokes", "Current Key Presses", @@ -323,8 +345,8 @@ public class MyScreen extends BaseOwoScreen { FlowLayout coordinatesRow = ModuleRow.build( GLASS_PANEL, DISABLED, FLAT, - Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/block_culling_file.png"), - 18, 12, + Identifier.fromNamespaceAndPath("minecraft", "textures/item/map.png"), + 18, 18, MC_FIVE, "Coordinates", "Shows your Position", @@ -348,8 +370,8 @@ public class MyScreen extends BaseOwoScreen { FlowLayout fullbrightRow = ModuleRow.build( GLASS_PANEL, DISABLED, FLAT, - Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/block_culling_file.png"), - 18, 12, + Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/fullbright.png"), + 18, 18, MC_FIVE, "Fullbright", "Max brightness", @@ -387,11 +409,33 @@ public class MyScreen extends BaseOwoScreen { "Module Disabled", "Hitbox color is now inactive" ); + FlowLayout effectsRow = ModuleRow.build( + GLASS_PANEL, DISABLED, FLAT, + Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/effect.png"), + 18, 18, + MC_FIVE, + "Effects", + "Shows active effects", + new ModuleRow.ToggleState() { + public boolean get() { return Module_Effects.isEnabled(); } + public void set(boolean value) { Module_Effects.setEnabled(value); } + }, + Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/settings.png"), + () -> { + Config.get().lastModule = "Effects"; + Config.get().save(); + openModuleSettings("Effects", effectsSettings()); + }, + "Module Enabled", "Effects display is now active", + "Module Disabled", "Effects display is now inactive" + ); + FlowLayout rowsContainer = UIContainers.verticalFlow(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.content()); rowsContainer.gap(4); rowsContainer.verticalAlignment(VerticalAlignment.TOP); rowsContainer.horizontalAlignment(HorizontalAlignment.CENTER); + rowsContainer.child(effectsRow); rowsContainer.child(hitboxRow); rowsContainer.child(coordinatesRow); rowsContainer.child(keyStrokesRow); @@ -423,10 +467,35 @@ public class MyScreen extends BaseOwoScreen { editButton.horizontalSizing(Sizing.fixed(CONTENT_WIDTH - 8)); rowsContainer.child(editButton); + FlowLayout blurSlider = SettingsControls.makeSlider( + FLAT, + 80, + 0.0, 15.0, + Config.get().panelBlur, + value -> (int) value + "px", + value -> { + Config.get().panelBlur = (float) (double) value; + Config.get().save(); + rebuildSurfaces(); + } + ); + + FlowLayout blurRow = UIContainers.horizontalFlow(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.fixed(16)); + blurRow.verticalAlignment(VerticalAlignment.CENTER); + blurRow.horizontalAlignment(HorizontalAlignment.LEFT); + blurRow.padding(Insets.of(0)); + blurRow.gap(4); + blurRow.child(UIComponents.label(Component.literal("Panel Blur") + .withStyle(style -> style.withFont(new FontDescription.Resource(MC_FIVE)))) + .color(Color.ofRgb(0xFF5c5c66))); + blurRow.child(UIComponents.spacer()); + blurRow.child(blurSlider); + rowsContainer.child(blurRow); + FlowLayout hintRow = UIContainers.verticalFlow(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.content()); hintRow.padding(Insets.of(4, 0, 0, 0)); hintRow.child(UIComponents.label(Component.literal("Drag HUD modules to reposition. Press ESC to stop.")) - .color(Color.ofRgb(0x797b86))); + .color(Color.ofRgb(0xFF5c5c66))); rowsContainer.child(hintRow); content.child(rowsContainer); @@ -452,7 +521,7 @@ public class MyScreen extends BaseOwoScreen { header.child(UIComponents.label(Component.literal(title) .withStyle(style -> style.withFont(new FontDescription.Resource(MC_FIVE)))) - .color(Color.ofRgb(0xCCCCCC))); + .color(Color.ofRgb(0x80FFFFFF))); content.child(header); @@ -464,7 +533,7 @@ public class MyScreen extends BaseOwoScreen { for (SettingRow row : rows) { FlowLayout settingRow = makeSettingRow(row); - settingRow.verticalSizing(Sizing.fixed(16)); // pin each row to a compact fixed height + settingRow.verticalSizing(Sizing.fixed(16)); rowsContainer.child(settingRow); } @@ -482,6 +551,7 @@ public class MyScreen extends BaseOwoScreen { case "Key" -> openModuleSettings("Keystrokes", keyStrokesSettings()); case "Coords" -> openModuleSettings("Coordinates", CoordsSettings()); case "Hitbox" -> openModuleSettings("Hitbox", hitboxSettings()); + case "Effects" -> openModuleSettings("Effects", effectsSettings()); default -> buildGeneralPanel(content); } } @@ -493,8 +563,9 @@ public class MyScreen extends BaseOwoScreen { row.padding(Insets.of(0)); row.gap(4); - row.child(UIComponents.label(Component.literal(setting.label())) - .color(Color.ofRgb(0xa3a3ac))); + row.child(UIComponents.label(Component.literal(setting.label()) + .withStyle(style -> style.withFont(new FontDescription.Resource(MC_FIVE)))) + .color(Color.ofRgb(0xFF5c5c66))); row.child(UIComponents.spacer()); row.child(setting.control()); @@ -523,7 +594,8 @@ public class MyScreen extends BaseOwoScreen { return List.of( new SettingRow("Position", SettingsControls.makeCyclingButton(GLASS_PANEL, positionStates, currentIndex)), - new SettingRow("Size", paddingSlider) + new SettingRow("", SettingsControls.makeSectionHeader(CONTENT_WIDTH - 8, "Display Options")), + new SettingRow("Padding", paddingSlider) ); } @@ -561,7 +633,6 @@ public class MyScreen extends BaseOwoScreen { new SettingsControls.ButtonState("L-BOTTOM", () -> Module_Coords.setPosition(1, 99)) ); - int currentIndex = positionIndex(Config.get().fpsX, Config.get().fpsY); FlowLayout biomeCheckbox = SettingsControls.makeCheckbox( @@ -571,11 +642,20 @@ public class MyScreen extends BaseOwoScreen { Module_Coords::setBiomeEnabled ); - return List.of( - new SettingRow("Position", SettingsControls.makeCyclingButton(GLASS_PANEL, positionStates, currentIndex)), - new SettingRow("Show Biome", biomeCheckbox) + FlowLayout sizeSlider = SettingsControls.makeSlider( + FLAT, + 80, + 0.0, 15.0, + Config.get().coordsP, + value -> (int) value + "px", + value -> Module_Coords.setCoordsPadding((int) value) ); + return List.of( + new SettingRow("Position", SettingsControls.makeCyclingButton(GLASS_PANEL, positionStates, currentIndex)), + new SettingRow("Show Biome", biomeCheckbox), + new SettingRow("Padding", sizeSlider) + ); } private List hitboxSettings() { @@ -586,7 +666,7 @@ public class MyScreen extends BaseOwoScreen { Module_Hitbox.isEnabled(), value -> { Module_Hitbox.setEnabled(value); - openModuleSettings("Hitbox", hitboxSettings()); // rebuild panel so slider appears/disappears + openModuleSettings("Hitbox", hitboxSettings()); } ); @@ -608,13 +688,52 @@ public class MyScreen extends BaseOwoScreen { return rows; } + private List effectsSettings() { + List positionStates = List.of( + new SettingsControls.ButtonState("L-TOP", () -> Module_Effects.setPosition(1, 1)), + new SettingsControls.ButtonState("R-TOP", () -> Module_Effects.setPosition(99, 1)), + new SettingsControls.ButtonState("R-BOTTOM", () -> Module_Effects.setPosition(99, 99)), + new SettingsControls.ButtonState("L-BOTTOM", () -> Module_Effects.setPosition(1, 99)) + ); + + int currentIndex = positionIndex(Config.get().effectsX, Config.get().effectsY); + + FlowLayout bgCheck = SettingsControls.makeCheckbox( + FLAT, + GLASS_PANEL, + Module_Effects.isBackgroundEnabled(), + Module_Effects::setBackgroundEnabled + ); + + FlowLayout timerCheck = SettingsControls.makeCheckbox( + FLAT, + GLASS_PANEL, + Module_Effects.isTimerEnabled(), + Module_Effects::setTimerEnabled + ); + + FlowLayout nameCheck = SettingsControls.makeCheckbox( + FLAT, + GLASS_PANEL, + Module_Effects.isNameEnabled(), + Module_Effects::setNameEnabled + ); + + return List.of( + new SettingRow("Position", SettingsControls.makeCyclingButton(GLASS_PANEL, positionStates, currentIndex)), + new SettingRow("Show Background", bgCheck), + new SettingRow("Show Timer", timerCheck), + new SettingRow("Show Name", nameCheck) + ); + } + private int positionIndex(int x, int y) { boolean right = x > 50; boolean bottom = y > 50; - if (!right && !bottom) return 0; // L-TOP - if (right && !bottom) return 1; // R-TOP - if (right && bottom) return 2; // R-BOTTOM - return 3; // L-BOTTOM + if (!right && !bottom) return 0; + if (right && !bottom) return 1; + if (right && bottom) return 2; + return 3; } @Override diff --git a/src/main/java/de/fullrisk/citrusDev/UI/QuestsPanel.java b/src/main/java/de/fullrisk/citrusDev/UI/QuestsPanel.java index 29fc253..4dd2b2a 100644 --- a/src/main/java/de/fullrisk/citrusDev/UI/QuestsPanel.java +++ b/src/main/java/de/fullrisk/citrusDev/UI/QuestsPanel.java @@ -113,7 +113,7 @@ public class QuestsPanel { summaryRow.child(UIComponents.label( Component.literal("quests " + unlockedCount + "/" + quests.size()) .withStyle(style -> style.withFont(new FontDescription.Resource(MyScreen.MC_FIVE)))) - .color(Color.ofRgb(0xa3a3ac))); + .color(Color.ofRgb(0xFF5c5c66))); content.child(summaryRow); @@ -140,22 +140,12 @@ public class QuestsPanel { return handle; } - /** - * Call this once per client tick (e.g. from ClientTickEvents.END_CLIENT_TICK) - * to keep the open panel's playtime/progress/quest state live. - * Throttled to once every 20 ticks (~1x/sec) since playtime itself only - * updates that often server-side. - */ public static void tick() { if (activeHandle != null && tickCounter++ % 20 == 0) { activeHandle.refresh(); } } - /** - * Call this from the screen's onClose() so a closed panel stops being - * refreshed and can be garbage collected. - */ public static void clearActiveHandle() { activeHandle = null; } @@ -216,7 +206,7 @@ public class QuestsPanel { textStack.child(titleLabel); var descLabel = UIComponents.label(Component.literal(quest.description())) - .color(Color.ofRgb(0xa3a3ac)) + .color(Color.ofRgb(0xFF5c5c66)) .maxWidth(entryWidth - 24); textStack.child(descLabel); diff --git a/src/main/java/de/fullrisk/citrusDev/UIHelper/DraggableHud.java b/src/main/java/de/fullrisk/citrusDev/UIHelper/DraggableHud.java index 6b04b9f..66787da 100644 --- a/src/main/java/de/fullrisk/citrusDev/UIHelper/DraggableHud.java +++ b/src/main/java/de/fullrisk/citrusDev/UIHelper/DraggableHud.java @@ -2,6 +2,7 @@ package de.fullrisk.citrusDev.UIHelper; import de.fullrisk.citrusDev.Config; import de.fullrisk.citrusDev.UI.Module_Coords; +import de.fullrisk.citrusDev.UI.Module_Effects; import de.fullrisk.citrusDev.UI.Module_FPS; import de.fullrisk.citrusDev.UI.Module_KeyStrokes; import io.wispforest.owo.ui.container.FlowLayout; @@ -191,6 +192,12 @@ public class DraggableHud { () -> c.fpsX, v -> c.fpsX = v, () -> c.fpsY, v -> c.fpsY = v, Module_FPS::isEnabled + ), + new ModuleDragConfig( + Module_Effects::getEffectsContainer, 120, 50, + () -> c.effectsX, v -> c.effectsX = v, + () -> c.effectsY, v -> c.effectsY = v, + Module_Effects::isEnabled ) }; } diff --git a/src/main/java/de/fullrisk/citrusDev/UIHelper/ModuleRow.java b/src/main/java/de/fullrisk/citrusDev/UIHelper/ModuleRow.java index 85343a7..4f6cbf8 100644 --- a/src/main/java/de/fullrisk/citrusDev/UIHelper/ModuleRow.java +++ b/src/main/java/de/fullrisk/citrusDev/UIHelper/ModuleRow.java @@ -62,7 +62,7 @@ public class ModuleRow { textStack.child(titleLabel); textStack.child(UIComponents.label(Component.literal(description)) - .color(Color.ofRgb(0xa3a3ac)) + .color(Color.ofRgb(0xFF5c5c66)) .maxWidth(130)); row.child(textStack); diff --git a/src/main/java/de/fullrisk/citrusDev/UIHelper/SettingsControls.java b/src/main/java/de/fullrisk/citrusDev/UIHelper/SettingsControls.java index fbcdbf3..bfe35fe 100644 --- a/src/main/java/de/fullrisk/citrusDev/UIHelper/SettingsControls.java +++ b/src/main/java/de/fullrisk/citrusDev/UIHelper/SettingsControls.java @@ -7,6 +7,7 @@ import io.wispforest.owo.ui.core.*; import net.minecraft.client.Minecraft; import net.minecraft.client.resources.sounds.SimpleSoundInstance; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.FontDescription; import net.minecraft.resources.Identifier; import net.minecraft.sounds.SoundEvents; @@ -18,6 +19,8 @@ import java.util.function.DoubleFunction; public class SettingsControls { + private static final Identifier MC_FIVE = Identifier.fromNamespaceAndPath("citrus-dev", "minecraft_five"); + private static final Surface GLASS_PANEL = Surface.blur(5f, 15f) .and(Surface.flat(0x40FFFFFF)) .and(Surface.outline(0x64de4b)); @@ -55,7 +58,9 @@ public class SettingsControls { button.horizontalAlignment(HorizontalAlignment.CENTER); button.padding(Insets.of(2, 2, 4, 4)); button.surface(baseSurface); - button.child(UIComponents.label(Component.literal(text)).color(Color.ofRgb(0xFFFFFF))); + button.child(UIComponents.label(Component.literal(text) + .withStyle(style -> style.withFont(new FontDescription.Resource(MC_FIVE)))) + .color(Color.ofRgb(0xFFFFFF))); button.mouseEnter().subscribe(() -> button.surface( Surface.blur(5f, 15f).and(Surface.flat(0x70FFFFFF)).and(Surface.outline(0xFFFFFFFF)) @@ -81,7 +86,8 @@ public class SettingsControls { button.horizontalAlignment(HorizontalAlignment.CENTER); button.surface(baseSurface); - var label = UIComponents.label(Component.literal(states.get(initialIndex).label())) + var label = UIComponents.label(Component.literal(states.get(initialIndex).label()) + .withStyle(style -> style.withFont(new FontDescription.Resource(MC_FIVE)))) .color(Color.ofRgb(0xFFFFFF)); button.child(label); @@ -93,7 +99,8 @@ public class SettingsControls { currentIndex[0] = (currentIndex[0] + 1) % states.size(); ButtonState newState = states.get(currentIndex[0]); - label.text(Component.literal(newState.label())); + label.text(Component.literal(newState.label()) + .withStyle(style -> style.withFont(new FontDescription.Resource(MC_FIVE)))); newState.onSelect().run(); return true; @@ -154,7 +161,8 @@ public class SettingsControls { wrapper.padding(Insets.of(2, 2, 4, 0)); wrapper.gap(2); - var valueLabel = UIComponents.label(Component.literal(labelFormatter.apply(initial))); + var valueLabel = UIComponents.label(Component.literal(labelFormatter.apply(initial)) + .withStyle(style -> style.withFont(new FontDescription.Resource(MC_FIVE)))); valueLabel.color(Color.ofRgb(0xFFFFFF)); valueLabel.horizontalSizing(Sizing.fixed(labelWidth)); wrapper.child(valueLabel); @@ -168,7 +176,8 @@ public class SettingsControls { return doubled; }); slider.onChanged().subscribe(value -> { - valueLabel.text(Component.literal(labelFormatter.apply(value))); + valueLabel.text(Component.literal(labelFormatter.apply(value)) + .withStyle(style -> style.withFont(new FontDescription.Resource(MC_FIVE)))); onChange.accept(value); }); @@ -176,4 +185,26 @@ public class SettingsControls { return wrapper; } + + public static FlowLayout makeSectionHeader(int width, String text) { + FlowLayout row = UIContainers.horizontalFlow(Sizing.fixed(width), Sizing.fixed(12)); + row.verticalAlignment(VerticalAlignment.CENTER); + row.horizontalAlignment(HorizontalAlignment.LEFT); + row.gap(4); + + FlowLayout shortLine = UIContainers.horizontalFlow(Sizing.fixed(10), Sizing.fixed(1)); + shortLine.surface(Surface.flat(0x80FFFFFF)); + row.child(shortLine); + + var label = UIComponents.label(Component.literal(text.toUpperCase()) + .withStyle(style -> style.withFont(new FontDescription.Resource(MC_FIVE)))) + .color(Color.ofRgb(0x9a9aa5)); + row.child(label); + + FlowLayout longLine = UIContainers.horizontalFlow(Sizing.fill(100), Sizing.fixed(1)); + longLine.surface(Surface.flat(0x80FFFFFF)); + row.child(longLine); + + return row; + } } \ No newline at end of file diff --git a/src/main/java/de/fullrisk/citrusDev/client/CitrusDevClient.java b/src/main/java/de/fullrisk/citrusDev/client/CitrusDevClient.java index 0e51974..ea4bc37 100644 --- a/src/main/java/de/fullrisk/citrusDev/client/CitrusDevClient.java +++ b/src/main/java/de/fullrisk/citrusDev/client/CitrusDevClient.java @@ -30,6 +30,7 @@ public class CitrusDevClient implements ClientModInitializer { Module_FPS.register(); Module_KeyStrokes.register(); Module_Coords.register(); + Module_Effects.register(); KeyMapping.Category category = KeyMapping.Category.register( @@ -57,6 +58,7 @@ public class CitrusDevClient implements ClientModInitializer { Module_KeyStrokes.tick(); Module_Coords.tick(); DraggableHud.tick(); + Module_Effects.tick(); while (openScreenKey.consumeClick()) { if (client.player != null && client.player.isShiftKeyDown()) { diff --git a/src/main/resources/assets/citrus-dev/textures/icons/effect.png b/src/main/resources/assets/citrus-dev/textures/icons/effect.png new file mode 100644 index 0000000000000000000000000000000000000000..12e227ad535d5c042fecd49d76c013b0a29888ed GIT binary patch literal 444 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4(FKU~KkuaSXBOONcNW&-~3P=f9#sFQbHn1VlJV z>28F|fddDiiaH$=8+1Cait_OAKt;L^GOk$kNB&7`X40aBIR}n^mp?3kF!X@JS%srO z?96w;2HD63}!oQEgd8-wKjSp{)-+$oG-~0O)Yo){-)gxYmOX& f>BsJ3BvEFDC$IGXG-)q;0y5sy)z4*}Q$iB}*zI7w literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/citrus-dev/textures/icons/fullbright.png b/src/main/resources/assets/citrus-dev/textures/icons/fullbright.png new file mode 100644 index 0000000000000000000000000000000000000000..71be80bd5d7d6af92f876d8a90d0790bcd1c7471 GIT binary patch literal 476 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4(FKU|j6!;uvDloAl%Ve|w4M1G|G) z3a|+|h#j7go{#`k#qj@sQcGr9O3IOy9BeIT(ws#OGB7hULnIgl&-XuTG%-A6Qhd@i zDdXR_49gn@6BG?*H8wUff|WGwe(?9D$&R!MNpWk9+Rk6!^?>cbk-(Tk2Mz$GL1r^$ zwphwfPn*F#A(pL77jE*QXZlGD5%LJWScbxFR^Rl-n+j4-lu8(`9fz54=oQn1Wy#0; z4rb0Vgd6Kr$arS{d;jGJnWi1|iRXEv^u(cMcLU5(P8(${rOw|=O}~&5d!XQNIaBk- zPyTBR4*&twRZiD))$L5X=55N4K<8SNO p*g&xY43k6M#mdPa*(G3}XJ(L^zn($wm`@By-qY33Wt~$(69A5Sy_f(1 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/citrus-dev/textures/icons/keystrokes.png b/src/main/resources/assets/citrus-dev/textures/icons/keystrokes.png new file mode 100644 index 0000000000000000000000000000000000000000..50d91f75a7d8a685a76c21e4f16730b0e0493468 GIT binary patch literal 16798 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�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 J9_wMF{tpSmw9)_o literal 0 HcmV?d00001