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 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 = 0x64de4b; private static Handle activeHandle; private static int tickCounter = 0; private record QuestData(String title, String description, int requiredPlaytime) { boolean isUnlocked() { return PlaytimeTracker.getCurrentPlaytimeSeconds() >= requiredPlaytime; } } public static class Handle { private final LabelComponent playtimeLabel; private final LabelComponent percentLabel; private final FlowLayout barFill; private final int barWidth; private final List quests; private final Set notifiedTitles = new HashSet<>(); private Handle(LabelComponent playtimeLabel, LabelComponent percentLabel, FlowLayout barFill, int barWidth, List quests) { this.playtimeLabel = playtimeLabel; this.percentLabel = percentLabel; this.barFill = barFill; this.barWidth = barWidth; this.quests = quests; 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 quests = quests(); int playtime = PlaytimeTracker.getCurrentPlaytimeSeconds(); int maxPlaytime = 360; int progress = Math.min(playtime * 100 / maxPlaytime, 100); int barWidth = contentWidth - 8; long unlockedCount = quests.stream().filter(QuestData::isUnlocked).count(); FlowLayout statusBar = UIContainers.horizontalFlow(Sizing.fixed(barWidth), Sizing.fixed(20)); statusBar.gap(4); statusBar.padding(Insets.of(4)); statusBar.verticalAlignment(VerticalAlignment.CENTER); statusBar.horizontalAlignment(HorizontalAlignment.LEFT); statusBar.surface(MainUI.GLASS_PANEL); LabelComponent playtimeLabel = UIComponents.label(Component.literal(formatPlaytime(playtime))); playtimeLabel.color(Color.ofRgb(0xFFFFFF)); statusBar.child(playtimeLabel); statusBar.child(UIComponents.spacer()); LabelComponent percentLabel = UIComponents.label(Component.literal(progress + "%")); percentLabel.color(Color.ofRgb(ACCENT)); statusBar.child(percentLabel); content.child(statusBar); FlowLayout barBg = UIContainers.horizontalFlow(Sizing.fixed(barWidth), Sizing.fixed(6)); barBg.surface(Surface.flat(0x30FFFFFF).and(Surface.outline(0x30FFFFFF))); barBg.horizontalAlignment(HorizontalAlignment.LEFT); barBg.gap(0); barBg.padding(Insets.of(0)); int fillWidth = Math.max(barWidth * progress / 100, progress > 0 ? 2 : 0); FlowLayout barFill = UIContainers.horizontalFlow(Sizing.fixed(fillWidth), Sizing.fixed(6)); barFill.surface(Surface.flat(0xFF000000 | ACCENT)); barBg.child(barFill); content.child(barBg); FlowLayout summaryRow = UIContainers.horizontalFlow(Sizing.fixed(barWidth), Sizing.content()); summaryRow.padding(Insets.of(3, 0, 2, 2)); summaryRow.child(UIComponents.label( Component.literal("quests " + unlockedCount + "/" + quests.size()) .withStyle(style -> style.withFont(new FontDescription.Resource(MainUI.MC_FIVE)))) .color(Color.ofRgb(0xFF5c5c66))); content.child(summaryRow); FlowLayout questList = UIContainers.verticalFlow(Sizing.fixed(barWidth), Sizing.content()); questList.gap(3); questList.padding(Insets.of(2, 0, 2, 2)); for (QuestData quest : quests) { questList.child(buildQuestEntry(quest, contentWidth)); } int usedHeight = 20 + 6 + 18 + 6; int scrollHeight = contentHeight - usedHeight; var scrollArea = UIContainers.verticalScroll( Sizing.fixed(barWidth), Sizing.fixed(scrollHeight), questList ); content.child(scrollArea); Handle handle = new Handle(playtimeLabel, percentLabel, barFill, barWidth, quests); activeHandle = handle; return handle; } public static void tick() { if (activeHandle != null && tickCounter++ % 20 == 0) { activeHandle.refresh(); } } public static void clearActiveHandle() { activeHandle = null; } private static String formatPlaytime(int seconds) { int hours = seconds / 3600; int minutes = (seconds % 3600) / 60; int secs = seconds % 60; if (hours > 0) { return hours + "h " + minutes + "m " + secs + "s"; } else if (minutes > 0) { return minutes + "m " + secs + "s"; } else { return secs + "s"; } } private static List quests() { return List.of( new QuestData("first steps", "Play for 5 minutes", 300), new QuestData("getting started", "Play for 30 minutes", 1800), new QuestData("dedicated player", "Play for 1 hour", 3600), new QuestData("veteran", "Play for 3 hours", 10800) ); } private static FlowLayout buildQuestEntry(QuestData quest, int contentWidth) { boolean unlocked = quest.isUnlocked(); int entryWidth = contentWidth - 12; FlowLayout row = UIContainers.horizontalFlow(Sizing.fixed(entryWidth), Sizing.fixed(32)); row.gap(6); row.padding(Insets.of(4)); row.verticalAlignment(VerticalAlignment.CENTER); row.horizontalAlignment(HorizontalAlignment.LEFT); row.surface(unlocked ? Surface.flat(0x1AFFFFFF).and(Surface.outline(0x30 << 24 | ACCENT)) : Surface.flat(0x12000000)); FlowLayout iconBox = UIContainers.verticalFlow(Sizing.fixed(12), Sizing.fixed(12)); iconBox.surface(unlocked ? Surface.flat(0xFF000000 | ACCENT) : Surface.flat(0x40FFFFFF)); iconBox.horizontalAlignment(HorizontalAlignment.CENTER); iconBox.verticalAlignment(VerticalAlignment.CENTER); iconBox.child(UIComponents.label(Component.literal("")) .color(Color.ofRgb(0x1c1c1c))); row.child(iconBox); FlowLayout textStack = UIContainers.verticalFlow(Sizing.fixed(entryWidth - 24), Sizing.content()); textStack.gap(1); textStack.horizontalAlignment(HorizontalAlignment.LEFT); textStack.verticalAlignment(VerticalAlignment.TOP); var titleLabel = UIComponents.label(Component.literal(quest.title()) .withStyle(style -> style.withFont(new FontDescription.Resource(MainUI.MC_FIVE)))) .color(Color.ofRgb(unlocked ? 0xFFFFFF : 0x797b86)); textStack.child(titleLabel); var descLabel = UIComponents.label(Component.literal(quest.description())) .color(Color.ofRgb(0xFF5c5c66)) .maxWidth(entryWidth - 24); textStack.child(descLabel); row.child(textStack); return row; } }