Added Quests and Real Settings (yay)

This commit is contained in:
2026-07-11 10:46:47 +02:00
parent d3fc829dee
commit 652fe728e1
7 changed files with 392 additions and 77 deletions
@@ -0,0 +1,130 @@
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.KeyMapping;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
import de.fullrisk.citrusDev.Config;
public class Module_KeyStrokes {
private static FlowLayout keyContainer;
private static FlowLayout topRow;
private static FlowLayout bottomRow;
private static FlowLayout wBox, aBox, sBox, dBox;
private static LabelComponent wLabel, aLabel, sLabel, dLabel;
static boolean toggle = true;
private static boolean shown = false;
public static boolean isEnabled() {
return toggle;
}
public static void setEnabled(boolean value) {
toggle = value;
}
private static final Config CONFIG = Config.get();
private static final Surface GLASS_PANEL = Surface.blur(5f, 15f)
.and(Surface.flat(0x40FFFFFF));
private static final int KEY_SIZE = 16;
private static final int PRESSED_COLOR = 0xFF64de4b;
private static final int IDLE_COLOR = 0x40FFFFFF;
public static void setPosition(int x, int y) {
if (keyContainer != null) {
keyContainer.positioning(Positioning.relative(x, y));
}
CONFIG.keyX = x;
CONFIG.keyY = y;
CONFIG.save();
}
public static void register() {
Hud.add(Identifier.fromNamespaceAndPath("citrus-dev", "keystrokes_display"), () -> {
if (keyContainer == null) {
wLabel = UIComponents.label(Component.literal("W"));
aLabel = UIComponents.label(Component.literal("A"));
sLabel = UIComponents.label(Component.literal("S"));
dLabel = UIComponents.label(Component.literal("D"));
wBox = makeKeyBox(wLabel);
aBox = makeKeyBox(aLabel);
sBox = makeKeyBox(sLabel);
dBox = makeKeyBox(dLabel);
topRow = UIContainers.horizontalFlow(Sizing.content(), Sizing.content());
topRow.gap(2);
topRow.horizontalAlignment(HorizontalAlignment.CENTER);
topRow.child(UIContainers.horizontalFlow(Sizing.fixed(KEY_SIZE), Sizing.fixed(KEY_SIZE))); // spacer
topRow.child(wBox);
topRow.child(UIContainers.horizontalFlow(Sizing.fixed(KEY_SIZE), Sizing.fixed(KEY_SIZE))); // spacer
bottomRow = UIContainers.horizontalFlow(Sizing.content(), Sizing.content());
bottomRow.gap(2);
bottomRow.horizontalAlignment(HorizontalAlignment.CENTER);
bottomRow.child(aBox);
bottomRow.child(sBox);
bottomRow.child(dBox);
keyContainer = UIContainers.verticalFlow(Sizing.content(), Sizing.content());
keyContainer.gap(2);
keyContainer.verticalAlignment(VerticalAlignment.CENTER);
keyContainer.horizontalAlignment(HorizontalAlignment.CENTER);
keyContainer.padding(Insets.of(0));
keyContainer.positioning(Positioning.relative(CONFIG.keyX, CONFIG.keyY));
}
return keyContainer;
});
}
private static FlowLayout makeKeyBox(LabelComponent label) {
FlowLayout box = UIContainers.verticalFlow(Sizing.fixed(KEY_SIZE), Sizing.fixed(KEY_SIZE));
box.horizontalAlignment(HorizontalAlignment.CENTER);
box.verticalAlignment(VerticalAlignment.CENTER);
box.surface(Surface.flat(IDLE_COLOR));
box.child(label);
return box;
}
public static void tick() {
if (keyContainer == null) return;
if (toggle && !shown) {
keyContainer.clearChildren(); // <-- safety net against duplicate adds
keyContainer.surface(GLASS_PANEL);
keyContainer.padding(Insets.of(Config.get().fpsP));
keyContainer.child(topRow);
keyContainer.child(bottomRow);
shown = true;
} else if (!toggle && shown) {
keyContainer.clearChildren();
keyContainer.surface(Surface.flat(0));
keyContainer.padding(Insets.of(0));
shown = false;
}
if (!toggle) return;
Minecraft client = Minecraft.getInstance();
KeyMapping up = client.options.keyUp;
KeyMapping left = client.options.keyLeft;
KeyMapping down = client.options.keyDown;
KeyMapping right = client.options.keyRight;
wBox.surface(Surface.flat(up.isDown() ? PRESSED_COLOR : IDLE_COLOR));
aBox.surface(Surface.flat(left.isDown() ? PRESSED_COLOR : IDLE_COLOR));
sBox.surface(Surface.flat(down.isDown() ? PRESSED_COLOR : IDLE_COLOR));
dBox.surface(Surface.flat(right.isDown() ? PRESSED_COLOR : IDLE_COLOR));
}
}
@@ -56,6 +56,7 @@ public class MyScreen extends BaseOwoScreen<FlowLayout> {
private FlowLayout content;
private final String initialModule;
public MyScreen() {
this(null);
}
@@ -130,8 +131,8 @@ public class MyScreen extends BaseOwoScreen<FlowLayout> {
rootComponent.child(panel);
if ("FPS".equals(initialModule)) {
openModuleSettings("FPS", fpsModuleSettings());
if (initialModule != null) {
openModuleByKey(initialModule);
} else {
buildGeneralPanel(content);
}
@@ -249,72 +250,51 @@ public class MyScreen extends BaseOwoScreen<FlowLayout> {
}
private void buildGeneralPanel(FlowLayout content) {
FlowLayout iconButton = UIContainers.horizontalFlow(Sizing.content(), Sizing.content());
iconButton.gap(8);
iconButton.verticalAlignment(VerticalAlignment.TOP);
iconButton.horizontalAlignment(HorizontalAlignment.CENTER);
iconButton.padding(Insets.of(6));
iconButton.horizontalSizing(Sizing.fixed(0));
iconButton.verticalSizing(Sizing.fixed(0));
iconButton.surface(toggle ? GLASS_PANEL : DISABLED);
iconButton.child(UIComponents.texture(
Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/block_culling_file.png"),
0, 0, 18, 12, 18, 12
)).horizontalAlignment(HorizontalAlignment.LEFT)
.verticalAlignment(VerticalAlignment.CENTER);
FlowLayout textStack = UIContainers.verticalFlow(Sizing.content(), Sizing.content());
textStack.gap(2);
textStack.horizontalAlignment(HorizontalAlignment.LEFT);
textStack.verticalAlignment(VerticalAlignment.CENTER);
var fpsLabel = UIComponents.label(
Component.literal("FPS")
.withStyle(style -> style.withFont(new FontDescription.Resource(MC_FIVE)))
).color(Color.ofRgb(toggle ? 0xffffff : 0x797b86));
textStack.child(fpsLabel);
textStack.child(UIComponents.label(
Component.literal("Displays your FPS"))
.color(Color.ofRgb(0xa3a3ac))
.maxWidth(130)
);
iconButton.child(textStack);
iconButton.child(UIComponents.spacer());
FlowLayout settingsButton = SettingsControls.makeSmallButton(
FLAT,
FlowLayout fpsRow = ModuleRow.build(
GLASS_PANEL, DISABLED, FLAT,
Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/block_culling_file.png"),
18, 12,
MC_FIVE,
"FPS",
"Displays your FPS",
new ModuleRow.ToggleState() {
public boolean get() { return toggle; }
public void set(boolean value) { toggle = value; }
},
Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/settings.png"),
() -> {
Config.get().lastModule = "FPS";
Config.get().save();
openModuleSettings("FPS", fpsModuleSettings());
}
},
"Module Enabled", "FPS counter is now active",
"Module Disabled", "FPS counter is now inactive"
);
iconButton.child(settingsButton);
iconButton.mouseEnter().subscribe(() -> iconButton.surface(toggle ? GLASS_PANEL : DISABLED));
iconButton.mouseLeave().subscribe(() -> iconButton.surface(toggle ? GLASS_PANEL : DISABLED));
FlowLayout keyStrokesRow = ModuleRow.build(
GLASS_PANEL, DISABLED, FLAT,
Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/block_culling_file.png"),
18, 12,
MC_FIVE,
"Keystrokes",
"Current Key Presses",
new ModuleRow.ToggleState() {
public boolean get() { return Module_KeyStrokes.isEnabled(); }
public void set(boolean value) { Module_KeyStrokes.setEnabled(value); }
},
Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/settings.png"),
() -> {
Config.get().lastModule = "Key";
Config.get().save();
openModuleSettings("Key", keyStrokesSettings());
},
"Module Enabled", "Keystrokes is now active",
"Module Disabled", "KeyStrokes is now inactive"
);
content.child(keyStrokesRow);
content.child(fpsRow);
iconButton.mouseDown().subscribe((click, doubled) -> {
toggle = !toggle;
fpsLabel.color(Color.ofRgb(toggle ? 0xffffff : 0x797b86));
iconButton.surface(toggle ? GLASS_PANEL : DISABLED);
CitrusToast.show("Module Enabled", "FPS counter is now active");
Minecraft.getInstance().getSoundManager().play(
SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK.value(), 2.0F, 2)
);
return true;
});
iconButton.horizontalSizing().animate(150, Easing.CUBIC, Sizing.fixed(200)).forwards();
iconButton.verticalSizing().animate(150, Easing.CUBIC, Sizing.fixed(35)).forwards();
content.child(iconButton);
}
@@ -366,6 +346,14 @@ public class MyScreen extends BaseOwoScreen<FlowLayout> {
content.child(scrollArea);
}
private void openModuleByKey(String key) {
switch (key) {
case "FPS" -> openModuleSettings("FPS", fpsModuleSettings());
case "Key" -> openModuleSettings("Keystrokes", keyStrokesSettings());
default -> buildGeneralPanel(content);
}
}
private FlowLayout makeSettingRow(SettingRow setting) {
FlowLayout row = UIContainers.horizontalFlow(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.fixed(16));
row.verticalAlignment(VerticalAlignment.CENTER);
@@ -398,12 +386,48 @@ public class MyScreen extends BaseOwoScreen<FlowLayout> {
value -> Module_FPS.fpsContainer.gap((int) value)
);
int currentIndex = positionIndex(Config.get().fpsX, Config.get().fpsY);
return List.of(
new SettingRow("Position", SettingsControls.makeCyclingButton(GLASS_PANEL, positionStates)),
new SettingRow("Position", SettingsControls.makeCyclingButton(GLASS_PANEL, positionStates, currentIndex)),
new SettingRow("Size", sizeSlider)
);
}
private List<SettingRow> keyStrokesSettings() {
List<SettingsControls.ButtonState> positionStates = List.of(
new SettingsControls.ButtonState("L-TOP", () -> Module_KeyStrokes.setPosition(1, 1)),
new SettingsControls.ButtonState("R-TOP", () -> Module_KeyStrokes.setPosition(99, 1)),
new SettingsControls.ButtonState("R-BOTTOM", () -> Module_KeyStrokes.setPosition(99, 99)),
new SettingsControls.ButtonState("L-BOTTOM", () -> Module_KeyStrokes.setPosition(1, 99))
);
FlowLayout sizeSlider = SettingsControls.makeSlider(
FLAT,
80,
0.0, 20.0,
4.0,
value -> (int) value + "px",
value -> {} // fixed: was Module_FPS.fpsContainer.gap(...)
);
int currentIndex = positionIndex(Config.get().keyX, Config.get().keyY);
return List.of(
new SettingRow("Position", SettingsControls.makeCyclingButton(GLASS_PANEL, positionStates, currentIndex)),
new SettingRow("Size", sizeSlider)
);
}
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
}
@Override
public boolean isPauseScreen() {
return false;
@@ -1,5 +1,6 @@
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;
@@ -7,12 +8,15 @@ import io.wispforest.owo.ui.core.*;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.FontDescription;
import de.fullrisk.citrusDev.client.PlaytimeTracker;
import de.fullrisk.citrusDev.UIHelper.CitrusToast;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class QuestsPanel {
private static final int ACCENT = 0xbaff86;
private static final int ACCENT = 0x64de4b;
private record QuestData(String title, String description, int requiredPlaytime) {
boolean isUnlocked() {
@@ -20,7 +24,50 @@ public class QuestsPanel {
}
}
public static void build(FlowLayout content, int contentWidth, int contentHeight) {
public static class Handle {
private final LabelComponent playtimeLabel;
private final LabelComponent percentLabel;
private final FlowLayout barFill;
private final int barWidth;
private final List<QuestData> quests;
private final Set<String> notifiedTitles = new HashSet<>();
private Handle(LabelComponent playtimeLabel, LabelComponent percentLabel, FlowLayout barFill, int barWidth, List<QuestData> quests) {
this.playtimeLabel = playtimeLabel;
this.percentLabel = percentLabel;
this.barFill = barFill;
this.barWidth = barWidth;
this.quests = quests;
// seed with whatever's already unlocked on open, so we don't spam
// toasts for quests the player unlocked before opening this screen
for (QuestData quest : quests) {
if (quest.isUnlocked()) {
notifiedTitles.add(quest.title());
}
}
}
public void refresh() {
int playtime = PlaytimeTracker.getCurrentPlaytimeSeconds();
int maxPlaytime = 360;
int progress = Math.min(playtime * 100 / maxPlaytime, 100);
playtimeLabel.text(Component.literal(formatPlaytime(playtime)));
percentLabel.text(Component.literal(progress + "%"));
int fillWidth = Math.max(barWidth * progress / 100, progress > 0 ? 2 : 0);
barFill.horizontalSizing(Sizing.fixed(fillWidth));
for (QuestData quest : quests) {
if (quest.isUnlocked() && notifiedTitles.add(quest.title())) {
CitrusToast.show("Quest Unlocked", quest.title());
}
}
}
}
public static Handle build(FlowLayout content, int contentWidth, int contentHeight) {
List<QuestData> quests = quests();
int playtime = PlaytimeTracker.getCurrentPlaytimeSeconds();
int maxPlaytime = 360;
@@ -28,7 +75,6 @@ public class QuestsPanel {
int barWidth = contentWidth - 8;
long unlockedCount = quests.stream().filter(QuestData::isUnlocked).count();
// --- status row: playtime + percentage ---
FlowLayout statusBar = UIContainers.horizontalFlow(Sizing.fixed(barWidth), Sizing.fixed(20));
statusBar.gap(4);
statusBar.padding(Insets.of(4));
@@ -36,15 +82,17 @@ public class QuestsPanel {
statusBar.horizontalAlignment(HorizontalAlignment.LEFT);
statusBar.surface(MyScreen.GLASS_PANEL);
statusBar.child(UIComponents.label(Component.literal("Playtime: " + formatPlaytime(playtime)))
.color(Color.ofRgb(0xFFFFFF)));
LabelComponent playtimeLabel = UIComponents.label(Component.literal(formatPlaytime(playtime)));
playtimeLabel.color(Color.ofRgb(0xFFFFFF));
statusBar.child(playtimeLabel);
statusBar.child(UIComponents.spacer());
statusBar.child(UIComponents.label(Component.literal(progress + "%"))
.color(Color.ofRgb(ACCENT)));
LabelComponent percentLabel = UIComponents.label(Component.literal(progress + "%"));
percentLabel.color(Color.ofRgb(ACCENT));
statusBar.child(percentLabel);
content.child(statusBar);
// --- progress bar ---
FlowLayout barBg = UIContainers.horizontalFlow(Sizing.fixed(barWidth), Sizing.fixed(6));
barBg.surface(Surface.flat(0x30FFFFFF).and(Surface.outline(0x30FFFFFF)));
barBg.horizontalAlignment(HorizontalAlignment.LEFT);
@@ -58,7 +106,6 @@ public class QuestsPanel {
content.child(barBg);
// --- unlocked count summary ---
FlowLayout summaryRow = UIContainers.horizontalFlow(Sizing.fixed(barWidth), Sizing.content());
summaryRow.padding(Insets.of(3, 0, 2, 2));
@@ -69,7 +116,6 @@ public class QuestsPanel {
content.child(summaryRow);
// --- quest list (scrollable) ---
FlowLayout questList = UIContainers.verticalFlow(Sizing.fixed(barWidth), Sizing.content());
questList.gap(3);
questList.padding(Insets.of(2, 0, 2, 2));
@@ -78,7 +124,7 @@ public class QuestsPanel {
questList.child(buildQuestEntry(quest, contentWidth));
}
int usedHeight = 20 + 6 + 18 + 6; // statusBar + barBg + summaryRow + gaps
int usedHeight = 20 + 6 + 18 + 6;
int scrollHeight = contentHeight - usedHeight;
var scrollArea = UIContainers.verticalScroll(
@@ -87,6 +133,8 @@ public class QuestsPanel {
questList
);
content.child(scrollArea);
return new Handle(playtimeLabel, percentLabel, barFill, barWidth, quests);
}
private static String formatPlaytime(int seconds) {
@@ -130,7 +178,7 @@ public class QuestsPanel {
: Surface.flat(0x40FFFFFF));
iconBox.horizontalAlignment(HorizontalAlignment.CENTER);
iconBox.verticalAlignment(VerticalAlignment.CENTER);
iconBox.child(UIComponents.label(Component.literal(unlocked ? "" : ""))
iconBox.child(UIComponents.label(Component.literal(""))
.color(Color.ofRgb(0x1c1c1c)));
row.child(iconBox);