aboutsummaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
authorKevin <92656833+kevinthegreat1@users.noreply.github.com>2024-06-07 12:19:20 +0800
committerGitHub <noreply@github.com>2024-06-07 12:19:20 +0800
commitb657a22064a18dd5ea76187fb82b92ec682f9fc5 (patch)
treefc34a1cbfd13384653023f540e4bb4a441ede38e /src/main/java
parentcfa71fe3f18a58dce1e3a96867c7df6686d01fe5 (diff)
parent9054e87d9f5becea3addbdd60afeb6e3829143e8 (diff)
downloadSkyblocker-b657a22064a18dd5ea76187fb82b92ec682f9fc5.tar.gz
Skyblocker-b657a22064a18dd5ea76187fb82b92ec682f9fc5.tar.bz2
Skyblocker-b657a22064a18dd5ea76187fb82b92ec682f9fc5.zip
Merge pull request #691 from viciscat/events-notifications
Calendar Event Notifications
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/de/hysky/skyblocker/SkyblockerMod.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/config/SkyblockerConfigManager.java1
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/EventNotificationsCategory.java68
-rw-r--r--src/main/java/de/hysky/skyblocker/config/configs/EventNotificationsConfig.java35
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/auction/widgets/CategoryTabWidget.java28
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/events/EventNotifications.java174
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/events/EventToast.java93
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/events/JacobEventToast.java60
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListTab.java89
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListWidget.java136
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/itemlist/SearchResultsWidget.java15
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/itemlist/UpcomingEventsTab.java168
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/JacobsContestWidget.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/Utils.java18
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/config/DurationController.java70
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/config/DurationControllerWidget.java38
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/gui/SideTabButtonWidget.java39
18 files changed, 963 insertions, 76 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
index a9aab3b6..19eb395a 100644
--- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
+++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
@@ -26,6 +26,7 @@ import de.hysky.skyblocker.skyblock.end.EnderNodes;
import de.hysky.skyblocker.skyblock.end.TheEnd;
import de.hysky.skyblocker.skyblock.entity.MobBoundingBoxes;
import de.hysky.skyblocker.skyblock.fancybars.FancyStatusBars;
+import de.hysky.skyblocker.skyblock.events.EventNotifications;
import de.hysky.skyblocker.skyblock.garden.FarmingHud;
import de.hysky.skyblocker.skyblock.garden.LowerSensitivity;
import de.hysky.skyblocker.skyblock.garden.VisitorHelper;
@@ -177,6 +178,7 @@ public class SkyblockerMod implements ClientModInitializer {
Kuudra.init();
RenderHelper.init();
FancyStatusBars.init();
+ EventNotifications.init();
containerSolverManager.init();
statusBarTracker.init();
BeaconHighlighter.init();
diff --git a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
index 9c495382..c9246599 100644
--- a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
+++ b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
@@ -44,5 +44,8 @@ public class SkyblockerConfig {
public QuickNavigationConfig quickNav = new QuickNavigationConfig();
@SerialEntry
+ public EventNotificationsConfig eventNotifications = new EventNotificationsConfig();
+
+ @SerialEntry
public MiscConfig misc = new MiscConfig();
}
diff --git a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfigManager.java b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfigManager.java
index dd406b8a..f519473c 100644
--- a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfigManager.java
+++ b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfigManager.java
@@ -83,6 +83,7 @@ public class SkyblockerConfigManager {
.category(SlayersCategory.create(defaults, config))
.category(ChatCategory.create(defaults, config))
.category(QuickNavigationCategory.create(defaults, config))
+ .category(EventNotificationsCategory.create(defaults, config))
.category(MiscCategory.create(defaults, config))).generateScreen(parent);
}
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/EventNotificationsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/EventNotificationsCategory.java
new file mode 100644
index 00000000..6fd01cf8
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/config/categories/EventNotificationsCategory.java
@@ -0,0 +1,68 @@
+package de.hysky.skyblocker.config.categories;
+
+import de.hysky.skyblocker.config.ConfigUtils;
+import de.hysky.skyblocker.config.SkyblockerConfig;
+import de.hysky.skyblocker.config.configs.EventNotificationsConfig;
+import de.hysky.skyblocker.skyblock.events.EventNotifications;
+import de.hysky.skyblocker.utils.config.DurationController;
+import dev.isxander.yacl3.api.*;
+import it.unimi.dsi.fastutil.ints.IntImmutableList;
+import it.unimi.dsi.fastutil.ints.IntList;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.sound.PositionedSoundInstance;
+import net.minecraft.text.Text;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class EventNotificationsCategory {
+
+ private static boolean shouldPlaySound = false;
+
+ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig config) {
+ shouldPlaySound = false;
+ return ConfigCategory.createBuilder()
+ .name(Text.translatable("skyblocker.config.eventNotifications"))
+ .option(Option.<EventNotificationsConfig.Sound>createBuilder()
+ .binding(defaults.eventNotifications.reminderSound,
+ () -> config.eventNotifications.reminderSound,
+ sound -> config.eventNotifications.reminderSound = sound)
+ .controller(ConfigUtils::createEnumCyclingListController)
+ .name(Text.translatable("skyblocker.config.eventNotifications.notificationSound"))
+ .listener((soundOption, sound) -> {
+ if (!shouldPlaySound) {
+ shouldPlaySound = true;
+ return;
+ }
+ if (sound.getSoundEvent() != null)
+ MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(sound.getSoundEvent(), 1f, 1f));
+ })
+ .build())
+ .groups(createGroups(config))
+ .build();
+
+ }
+
+ private static List<OptionGroup> createGroups(SkyblockerConfig config) {
+ Map<String, IntList> eventsReminderTimes = config.eventNotifications.eventsReminderTimes;
+ List<OptionGroup> groups = new ArrayList<>(eventsReminderTimes.size());
+ if (eventsReminderTimes.isEmpty()) return List.of(OptionGroup.createBuilder().option(LabelOption.create(Text.translatable("skyblocker.config.eventNotifications.monologue"))).build());
+ for (Map.Entry<String, IntList> entry : eventsReminderTimes.entrySet()) {
+ groups.add(ListOption.<Integer>createBuilder()
+ .name(Text.literal(entry.getKey()))
+ .binding(EventNotifications.DEFAULT_REMINDERS, entry::getValue, integers -> entry.setValue(new IntImmutableList(integers)))
+ .controller(option -> () -> new DurationController(option)) // yea
+ .description(OptionDescription.of(Text.translatable("skyblocker.config.eventNotifications.@Tooltip[0]"),
+ Text.empty(),
+ Text.translatable("skyblocker.config.eventNotifications.@Tooltip[1]"),
+ Text.empty(),
+ Text.translatable("skyblocker.config.eventNotifications.@Tooltip[2]", entry.getKey())))
+ .initial(60)
+ .collapsed(true)
+ .build()
+ );
+ }
+ return groups;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/config/configs/EventNotificationsConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/EventNotificationsConfig.java
new file mode 100644
index 00000000..c43ae7a6
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/config/configs/EventNotificationsConfig.java
@@ -0,0 +1,35 @@
+package de.hysky.skyblocker.config.configs;
+
+import dev.isxander.yacl3.config.v2.api.SerialEntry;
+import it.unimi.dsi.fastutil.ints.IntList;
+import net.minecraft.sound.SoundEvent;
+import net.minecraft.sound.SoundEvents;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class EventNotificationsConfig {
+
+ @SerialEntry
+ public Sound reminderSound = Sound.PLING;
+
+ @SerialEntry
+ public Map<String, IntList> eventsReminderTimes = new HashMap<>();
+
+ public enum Sound {
+ NONE(null),
+ BELL(SoundEvents.BLOCK_BELL_USE),
+ DING(SoundEvents.ENTITY_ARROW_HIT_PLAYER),
+ PLING(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value()),
+ GOAT(SoundEvents.GOAT_HORN_SOUNDS.getFirst().value());
+
+ public SoundEvent getSoundEvent() {
+ return soundEvent;
+ }
+
+ final SoundEvent soundEvent;
+ Sound(SoundEvent soundEvent) {
+ this.soundEvent = soundEvent;
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/auction/widgets/CategoryTabWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/auction/widgets/CategoryTabWidget.java
index 02dbc132..a0b5f0b9 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/auction/widgets/CategoryTabWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/auction/widgets/CategoryTabWidget.java
@@ -1,42 +1,26 @@
package de.hysky.skyblocker.skyblock.auction.widgets;
import de.hysky.skyblocker.skyblock.auction.SlotClickHandler;
+import de.hysky.skyblocker.utils.render.gui.SideTabButtonWidget;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.DrawContext;
-import net.minecraft.client.gui.screen.ButtonTextures;
-import net.minecraft.client.gui.widget.ToggleButtonWidget;
import net.minecraft.client.item.TooltipType;
import net.minecraft.item.Item.TooltipContext;
import net.minecraft.item.ItemStack;
-import net.minecraft.util.Identifier;
import org.jetbrains.annotations.NotNull;
-public class CategoryTabWidget extends ToggleButtonWidget {
- private static final ButtonTextures TEXTURES = new ButtonTextures(new Identifier("recipe_book/tab"), new Identifier("recipe_book/tab_selected"));
-
- public void setIcon(@NotNull ItemStack icon) {
- this.icon = icon.copy();
- }
-
- private @NotNull ItemStack icon;
+public class CategoryTabWidget extends SideTabButtonWidget {
private final SlotClickHandler slotClick;
private int slotId = -1;
public CategoryTabWidget(@NotNull ItemStack icon, SlotClickHandler slotClick) {
- super(0, 0, 35, 27, false);
- this.icon = icon.copy(); // copy prevents item disappearing on click
+ super(0, 0, false, icon);
this.slotClick = slotClick;
- setTextures(TEXTURES);
}
@Override
public void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) {
- if (textures == null) return;
- Identifier identifier = textures.get(true, this.toggled);
- int x = getX();
- if (toggled) x -= 2;
- context.drawGuiTexture(identifier, x, this.getY(), this.width, this.height);
- context.drawItem(icon, x + 9, getY() + 5);
+ super.renderWidget(context, mouseX, mouseY, delta);
if (isMouseOver(mouseX, mouseY)) {
context.getMatrices().push();
@@ -52,8 +36,8 @@ public class CategoryTabWidget extends ToggleButtonWidget {
@Override
public void onClick(double mouseX, double mouseY) {
- if (this.toggled || slotId == -1) return;
+ if (isToggled() || slotId == -1) return;
+ super.onClick(mouseX, mouseY);
slotClick.click(slotId);
- this.setToggled(true);
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/events/EventNotifications.java b/src/main/java/de/hysky/skyblocker/skyblock/events/EventNotifications.java
new file mode 100644
index 00000000..0fd41969
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/events/EventNotifications.java
@@ -0,0 +1,174 @@
+package de.hysky.skyblocker.skyblock.events;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.mojang.brigadier.arguments.BoolArgumentType;
+import com.mojang.brigadier.arguments.IntegerArgumentType;
+import com.mojang.logging.LogUtils;
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.events.SkyblockEvents;
+import de.hysky.skyblocker.utils.Http;
+import de.hysky.skyblocker.utils.scheduler.Scheduler;
+import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
+import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.sound.PositionedSoundInstance;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+import net.minecraft.sound.SoundEvent;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+
+public class EventNotifications {
+ private static final Logger LOGGER = LogUtils.getLogger();
+
+ private static long currentTime = System.currentTimeMillis() / 1000;
+
+ public static final String JACOBS = "Jacob's Farming Contest";
+
+ public static final IntList DEFAULT_REMINDERS = IntList.of(60, 60 * 5);
+
+ public static final Map<String, ItemStack> eventIcons = new Object2ObjectOpenHashMap<>();
+
+ static {
+ eventIcons.put("Dark Auction", new ItemStack(Items.NETHER_BRICK));
+ eventIcons.put("Bonus Fishing Festival", new ItemStack(Items.FISHING_ROD));
+ eventIcons.put("Bonus Mining Fiesta", new ItemStack(Items.IRON_PICKAXE));
+ eventIcons.put(JACOBS, new ItemStack(Items.IRON_HOE));
+ eventIcons.put("New Year Celebration", new ItemStack(Items.CAKE));
+ eventIcons.put("Election Over!", new ItemStack(Items.JUKEBOX));
+ eventIcons.put("Election Booth Opens", new ItemStack(Items.JUKEBOX));
+ eventIcons.put("Spooky Festival", new ItemStack(Items.JACK_O_LANTERN));
+ eventIcons.put("Season of Jerry", new ItemStack(Items.SNOWBALL));
+ eventIcons.put("Jerry's Workshop Opens", new ItemStack(Items.SNOW_BLOCK));
+ eventIcons.put("Traveling Zoo", new ItemStack(Items.HAY_BLOCK)); // change to the custom head one day
+ }
+
+ public static void init() {
+ Scheduler.INSTANCE.scheduleCyclic(EventNotifications::timeUpdate, 20);
+
+ SkyblockEvents.JOIN.register(EventNotifications::refreshEvents);
+ ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(
+ ClientCommandManager.literal("skyblocker").then(
+ ClientCommandManager.literal("debug").then(
+ ClientCommandManager.literal("toasts").then(
+ ClientCommandManager.argument("time", IntegerArgumentType.integer(0))
+ .then(ClientCommandManager.argument("jacob", BoolArgumentType.bool()).executes(context -> {
+ long time = System.currentTimeMillis() / 1000 + context.getArgument("time", int.class);
+ if (context.getArgument("jacob", Boolean.class)) {
+ MinecraftClient.getInstance().getToastManager().add(
+ new JacobEventToast(time, "Jacob's farming contest", new String[]{"Cactus", "Cocoa Beans", "Pumpkin"})
+ );
+ } else {
+ MinecraftClient.getInstance().getToastManager().add(
+ new EventToast(time, "Jacob's or something idk", new ItemStack(Items.PAPER))
+ );
+ }
+ return 0;
+ }
+ )
+ )
+ )
+ )
+ )
+ ));
+ }
+
+ private static final Map<String, LinkedList<SkyblockEvent>> events = new Object2ObjectOpenHashMap<>();
+
+ public static Map<String, LinkedList<SkyblockEvent>> getEvents() {
+ return events;
+ }
+
+ public static void refreshEvents() {
+ CompletableFuture.supplyAsync(() -> {
+ try {
+ JsonArray jsonElements = SkyblockerMod.GSON.fromJson(Http.sendGetRequest("https://hysky.de/api/calendar"), JsonArray.class);
+ return jsonElements.asList().stream().map(JsonElement::getAsJsonObject).toList();
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker] Failed to download events list", e);
+ }
+ return List.<JsonObject>of();
+ }).thenAccept(eventsList -> {
+ events.clear();
+ for (JsonObject object : eventsList) {
+ if (object.get("timestamp").getAsLong() + object.get("duration").getAsInt() < currentTime) continue;
+ SkyblockEvent skyblockEvent = SkyblockEvent.of(object);
+ events.computeIfAbsent(object.get("event").getAsString(), s -> new LinkedList<>()).add(skyblockEvent);
+ }
+
+ for (Map.Entry<String, LinkedList<SkyblockEvent>> entry : events.entrySet()) {
+ entry.getValue().sort(Comparator.comparingLong(SkyblockEvent::start)); // Sort just in case it's not in order for some reason in API
+ //LOGGER.info("Next {} is at {}", entry.getKey(), entry.getValue().peekFirst());
+ }
+
+ for (String s : events.keySet()) {
+ SkyblockerConfigManager.get().eventNotifications.eventsReminderTimes.computeIfAbsent(s, s1 -> DEFAULT_REMINDERS);
+ }
+ }).exceptionally(EventNotifications::itBorked);
+ }
+
+ private static Void itBorked(Throwable throwable) {
+ LOGGER.error("[Skyblocker] Event loading borked, sowwy :(", throwable);
+ return null;
+ }
+
+
+ private static void timeUpdate() {
+
+ long newTime = System.currentTimeMillis() / 1000;
+ for (Map.Entry<String, LinkedList<SkyblockEvent>> entry : events.entrySet()) {
+ LinkedList<SkyblockEvent> nextEvents = entry.getValue();
+ SkyblockEvent skyblockEvent = nextEvents.peekFirst();
+ if (skyblockEvent == null) continue;
+
+ // Remove finished event
+ if (newTime > skyblockEvent.start() + skyblockEvent.duration()) {
+ nextEvents.pollFirst();
+ skyblockEvent = nextEvents.peekFirst();
+ if (skyblockEvent == null) continue;
+ }
+ String eventName = entry.getKey();
+ List<Integer> reminderTimes = SkyblockerConfigManager.get().eventNotifications.eventsReminderTimes.getOrDefault(eventName, DEFAULT_REMINDERS);
+ if (reminderTimes.isEmpty()) continue;
+
+ for (Integer reminderTime : reminderTimes) {
+ if (currentTime + reminderTime < skyblockEvent.start() && newTime + reminderTime >= skyblockEvent.start()) {
+ MinecraftClient instance = MinecraftClient.getInstance();
+ if (eventName.equals(JACOBS)) {
+ instance.getToastManager().add(
+ new JacobEventToast(skyblockEvent.start(), eventName, skyblockEvent.extras())
+ );
+ } else {
+ instance.getToastManager().add(
+ new EventToast(skyblockEvent.start(), eventName, eventIcons.getOrDefault(eventName, new ItemStack(Items.PAPER)))
+ );
+ }
+ SoundEvent soundEvent = SkyblockerConfigManager.get().eventNotifications.reminderSound.getSoundEvent();
+ if (soundEvent != null)
+ instance.getSoundManager().play(PositionedSoundInstance.master(soundEvent, 1f, 1f));
+ break;
+ }
+ }
+ }
+ currentTime = newTime;
+ }
+
+ public record SkyblockEvent(long start, int duration, String[] extras, @Nullable String warpCommand) {
+ public static SkyblockEvent of(JsonObject jsonObject) {
+ String location = jsonObject.get("location").getAsString();
+ location = location.isBlank() ? null : location;
+ return new SkyblockEvent(jsonObject.get("timestamp").getAsLong(),
+ jsonObject.get("duration").getAsInt(),
+ jsonObject.get("extras").getAsJsonArray().asList().stream().map(JsonElement::getAsString).toArray(String[]::new),
+ location);
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/events/EventToast.java b/src/main/java/de/hysky/skyblocker/skyblock/events/EventToast.java
new file mode 100644
index 00000000..567c800a
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/events/EventToast.java
@@ -0,0 +1,93 @@
+package de.hysky.skyblocker.skyblock.events;
+
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.utils.Utils;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.toast.Toast;
+import net.minecraft.client.toast.ToastManager;
+import net.minecraft.item.ItemStack;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.OrderedText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Colors;
+import net.minecraft.util.Formatting;
+import net.minecraft.util.Identifier;
+
+import java.util.List;
+
+public class EventToast implements Toast {
+ protected static final Identifier TEXTURE = new Identifier(SkyblockerMod.NAMESPACE, "notification");
+
+ private final long eventStartTime;
+
+ protected final List<OrderedText> message;
+ protected final List<OrderedText> messageNow;
+ protected final int messageWidth;
+ protected final int messageNowWidth;
+ protected final ItemStack icon;
+
+ protected boolean started;
+
+ public EventToast(long eventStartTime, String name, ItemStack icon) {
+ this.eventStartTime = eventStartTime;
+ MutableText formatted = Text.translatable("skyblocker.events.startsSoon", Text.literal(name).formatted(Formatting.YELLOW)).formatted(Formatting.WHITE);
+ TextRenderer renderer = MinecraftClient.getInstance().textRenderer;
+ message = renderer.wrapLines(formatted, 150);
+ messageWidth = message.stream().mapToInt(renderer::getWidth).max().orElse(150);
+
+ MutableText formattedNow = Text.translatable("skyblocker.events.startsNow", Text.literal(name).formatted(Formatting.YELLOW)).formatted(Formatting.WHITE);
+ messageNow = renderer.wrapLines(formattedNow, 150);
+ messageNowWidth = messageNow.stream().mapToInt(renderer::getWidth).max().orElse(150);
+ this.icon = icon;
+ this.started = eventStartTime - System.currentTimeMillis() / 1000 < 0;
+
+ }
+ @Override
+ public Visibility draw(DrawContext context, ToastManager manager, long startTime) {
+ context.drawGuiTexture(TEXTURE, 0, 0, getWidth(), getHeight());
+
+ int y = (getHeight() - getInnerContentsHeight())/2;
+ y = 2 + drawMessage(context, 30, y, Colors.WHITE);
+ drawTimer(context, 30, y);
+
+ context.drawItemWithoutEntity(icon, 8, getHeight()/2 - 8);
+ return startTime > 5_000 ? Visibility.HIDE: Visibility.SHOW;
+ }
+
+ protected int drawMessage(DrawContext context, int x, int y, int color) {
+ TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
+ for (OrderedText orderedText : started ? messageNow: message) {
+ context.drawText(textRenderer, orderedText, x, y, color, false);
+ y += textRenderer.fontHeight;
+ }
+ return y;
+ }
+
+ protected void drawTimer(DrawContext context, int x, int y) {
+ long currentTime = System.currentTimeMillis() / 1000;
+ int timeTillEvent = (int) (eventStartTime - currentTime);
+ started = timeTillEvent < 0;
+ if (started) return;
+
+ Text time = Utils.getDurationText(timeTillEvent);
+
+ TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
+ context.drawText(textRenderer, time, x, y, Colors.LIGHT_YELLOW, false);
+ }
+
+ @Override
+ public int getWidth() {
+ return (started ? messageNowWidth: messageWidth) + 30 + 6;
+ }
+
+ protected int getInnerContentsHeight() {
+ return message.size() * 9 + (started ? 0 : 9);
+ }
+
+ @Override
+ public int getHeight() {
+ return Math.max(getInnerContentsHeight() + 12 + 2, 32);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/events/JacobEventToast.java b/src/main/java/de/hysky/skyblocker/skyblock/events/JacobEventToast.java
new file mode 100644
index 00000000..43ed7d12
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/events/JacobEventToast.java
@@ -0,0 +1,60 @@
+package de.hysky.skyblocker.skyblock.events;
+
+import de.hysky.skyblocker.skyblock.tabhud.widget.JacobsContestWidget;
+import de.hysky.skyblocker.utils.render.RenderHelper;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.toast.ToastManager;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+import net.minecraft.util.Colors;
+import net.minecraft.util.math.MathHelper;
+
+public class JacobEventToast extends EventToast {
+
+ private final String[] crops;
+
+ private static final ItemStack DEFAULT_ITEM = new ItemStack(Items.IRON_HOE);
+
+ public JacobEventToast(long eventStartTime, String name, String[] crops) {
+ super(eventStartTime, name, new ItemStack(Items.IRON_HOE));
+ this.crops = crops;
+ }
+
+ @Override
+ public Visibility draw(DrawContext context, ToastManager manager, long startTime) {
+ context.drawGuiTexture(TEXTURE, 0, 0, getWidth(), getHeight());
+
+ int y = (getHeight() - getInnerContentsHeight()) / 2;
+ TextRenderer textRenderer = manager.getClient().textRenderer;
+ MatrixStack matrices = context.getMatrices();
+ if (startTime < 3_000) {
+ int k = MathHelper.floor(Math.clamp((3_000 - startTime) / 200.0f, 0.0f, 1.0f) * 255.0f) << 24 | 0x4000000;
+ y = 2 + drawMessage(context, 30, y, 0xFFFFFF | k);
+ } else {
+ int k = (~MathHelper.floor(Math.clamp((startTime - 3_000) / 200.0f, 0.0f, 1.0f) * 255.0f)) << 24 | 0x4000000;
+
+
+ String s = "Crops:";
+ int x = 30 + textRenderer.getWidth(s) + 4;
+ context.drawText(textRenderer, s, 30, 7 + (16 - textRenderer.fontHeight) / 2, Colors.WHITE, false);
+ for (int i = 0; i < crops.length; i++) {
+ context.drawItem(JacobsContestWidget.FARM_DATA.getOrDefault(crops[i], DEFAULT_ITEM), x + i * (16 + 8), 7);
+ }
+ // IDK how to make the items transparent, so I just redraw the texture on top
+ matrices.push();
+ matrices.translate(0, 0, 400f);
+ RenderHelper.renderNineSliceColored(context, TEXTURE, 0, 0, getWidth(), getHeight(), 1f, 1f, 1f, (k >> 24) / 255f);
+ matrices.pop();
+ y += textRenderer.fontHeight * message.size();
+ }
+ matrices.push();
+ matrices.translate(0, 0, 400f);
+ drawTimer(context, 30, y);
+
+ context.drawItemWithoutEntity(icon, 8, getHeight() / 2 - 8);
+ matrices.pop();
+ return startTime > 5_000 ? Visibility.HIDE : Visibility.SHOW;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListTab.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListTab.java
new file mode 100644
index 00000000..4109246d
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListTab.java
@@ -0,0 +1,89 @@
+package de.hysky.skyblocker.skyblock.itemlist;
+
+import com.mojang.blaze3d.systems.RenderSystem;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.Element;
+import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder;
+import net.minecraft.client.gui.widget.TextFieldWidget;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+import java.util.List;
+
+public class ItemListTab extends ItemListWidget.TabContainerWidget {
+
+ private SearchResultsWidget results;
+ private final MinecraftClient client;
+ private TextFieldWidget searchField;
+
+ public ItemListTab(int x, int y, MinecraftClient client, TextFieldWidget searchField) {
+ super(x, y, Text.literal("Item List Tab"));
+ this.client = client;
+ this.searchField = searchField;
+ if (ItemRepository.filesImported()) {
+ this.results = new SearchResultsWidget(this.client, x - 9, y - 9 );
+ this.results.updateSearchResult(searchField == null ? "": this.searchField.getText());
+ }
+ }
+
+ @Override
+ public List<? extends Element> children() {
+ return List.of(results, searchField);
+ }
+
+ @Override
+ protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) {
+ MatrixStack matrices = context.getMatrices();
+ matrices.push();
+ matrices.translate(0.0D, 0.0D, 100.0D);
+ RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
+ int x = getX();
+ int y = getY();
+
+ // all coordinates offseted -9
+ if (!ItemRepository.filesImported() && !this.searchField.isFocused() && this.searchField.getText().isEmpty()) {
+ Text hintText = (Text.literal("Loading...")).formatted(Formatting.ITALIC).formatted(Formatting.GRAY);
+ context.drawTextWithShadow(this.client.textRenderer, hintText, x + 16, y + 7, -1);
+ } else if (!this.searchField.isFocused() && this.searchField.getText().isEmpty()) {
+ Text hintText = (Text.translatable("gui.recipebook.search_hint")).formatted(Formatting.ITALIC).formatted(Formatting.GRAY);
+ context.drawTextWithShadow(this.client.textRenderer, hintText, x + 16, y + 7, -1);
+ } else {
+ this.searchField.render(context, mouseX, mouseY, delta);
+ }
+ if (ItemRepository.filesImported()) {
+ if (results == null) {
+ this.results = new SearchResultsWidget(this.client, x - 9, y - 9);
+ }
+ this.results.updateSearchResult(this.searchField.getText());
+ this.results.render(context, mouseX, mouseY, delta);
+ }
+ matrices.pop();
+ }
+
+ @Override
+ protected void appendClickableNarrations(NarrationMessageBuilder builder) {}
+
+ public void setSearchField(TextFieldWidget searchField) {
+ this.searchField = searchField;
+ }
+
+ @Override
+ public boolean mouseClicked(double mouseX, double mouseY, int button) {
+ if (!visible) return false;
+ if (this.searchField.mouseClicked(mouseX, mouseY, button)) {
+ this.results.closeRecipeView();
+ this.searchField.setFocused(true);
+ return true;
+ } else {
+ this.searchField.setFocused(false);
+ return this.results.mouseClicked(mouseX, mouseY, button);
+ }
+ }
+
+ @Override
+ public void drawTooltip(DrawContext context, int mouseX, int mouseY) {
+ if (this.results != null) this.results.drawTooltip(context, mouseX, mouseY);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListWidget.java
index 6120528c..a618f4df 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListWidget.java
@@ -1,103 +1,135 @@
package de.hysky.skyblocker.skyblock.itemlist;
-import com.mojang.blaze3d.systems.RenderSystem;
-
import de.hysky.skyblocker.mixins.accessors.RecipeBookWidgetAccessor;
+import de.hysky.skyblocker.utils.render.gui.SideTabButtonWidget;
+import it.unimi.dsi.fastutil.Pair;
+import it.unimi.dsi.fastutil.objects.ObjectObjectImmutablePair;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.recipebook.RecipeBookWidget;
+import net.minecraft.client.gui.tooltip.Tooltip;
+import net.minecraft.client.gui.widget.ContainerWidget;
import net.minecraft.client.gui.widget.TextFieldWidget;
-import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
import net.minecraft.screen.AbstractRecipeScreenHandler;
import net.minecraft.text.Text;
-import net.minecraft.util.Formatting;
+
+import java.util.ArrayList;
+import java.util.List;
@Environment(value = EnvType.CLIENT)
public class ItemListWidget extends RecipeBookWidget {
private int parentWidth;
private int parentHeight;
private int leftOffset;
- private TextFieldWidget searchField;
- private SearchResultsWidget results;
+
+ private TabContainerWidget currentTabContent;
+ private final List<Pair<SideTabButtonWidget, TabContainerWidget>> tabs = new ArrayList<>(2);
+ private ItemListTab itemListTab;
+
+ private static int currentTab = 0;
public ItemListWidget() {
super();
}
- public void updateSearchResult() {
- this.results.updateSearchResult(((RecipeBookWidgetAccessor) this).getSearchText());
- }
-
@Override
public void initialize(int parentWidth, int parentHeight, MinecraftClient client, boolean narrow, AbstractRecipeScreenHandler<?> craftingScreenHandler) {
super.initialize(parentWidth, parentHeight, client, narrow, craftingScreenHandler);
this.parentWidth = parentWidth;
this.parentHeight = parentHeight;
this.leftOffset = narrow ? 0 : 86;
- this.searchField = ((RecipeBookWidgetAccessor) this).getSearchField();
- int x = (this.parentWidth - 147) / 2 - this.leftOffset;
- int y = (this.parentHeight - 166) / 2;
- if (ItemRepository.filesImported()) {
- this.results = new SearchResultsWidget(this.client, x, y);
- this.updateSearchResult();
- }
+ TextFieldWidget searchField = ((RecipeBookWidgetAccessor) this).getSearchField();
+ int x = (parentWidth - 147) / 2 - leftOffset;
+ int y = (parentHeight - 166) / 2;
+
+ // Init all the tabs, content and the tab button on the left
+ tabs.clear();
+
+ // Item List
+ itemListTab = new ItemListTab(x + 9, y + 9, this.client, searchField);
+ SideTabButtonWidget itemListTabButton = new SideTabButtonWidget(x - 30, y + 3, currentTab == 0, new ItemStack(Items.CRAFTING_TABLE));
+ itemListTabButton.setTooltip(Tooltip.of(Text.literal("Item List")));
+ if (currentTab == 0) currentTabContent = itemListTab;
+ tabs.add(new ObjectObjectImmutablePair<>(
+ itemListTabButton,
+ this.itemListTab));
+
+ // Upcoming Events
+ UpcomingEventsTab upcomingEventsTab = new UpcomingEventsTab(x + 9, y + 9, this.client);
+ SideTabButtonWidget eventsTabButtonWidget = new SideTabButtonWidget(x - 30, y + 3 + 27, currentTab == 1, new ItemStack(Items.CLOCK));
+ eventsTabButtonWidget.setTooltip(Tooltip.of(Text.literal("Upcoming Events")));
+ if (currentTab == 1) currentTabContent = upcomingEventsTab;
+ tabs.add(new ObjectObjectImmutablePair<>(
+ eventsTabButtonWidget,
+ upcomingEventsTab
+ ));
+
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+ if (itemListTab != null) itemListTab.setSearchField(((RecipeBookWidgetAccessor) this).getSearchField());
}
@Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
if (this.isOpen()) {
- MatrixStack matrices = context.getMatrices();
- matrices.push();
- matrices.translate(0.0D, 0.0D, 100.0D);
- RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
- this.searchField = ((RecipeBookWidgetAccessor) this).getSearchField();
int i = (this.parentWidth - 147) / 2 - this.leftOffset;
int j = (this.parentHeight - 166) / 2;
+ // Draw the texture
context.drawTexture(TEXTURE, i, j, 1, 1, 147, 166);
- this.searchField = ((RecipeBookWidgetAccessor) this).getSearchField();
-
- if (!ItemRepository.filesImported() && !this.searchField.isFocused() && this.searchField.getText().isEmpty()) {
- Text hintText = (Text.literal("Loading...")).formatted(Formatting.ITALIC).formatted(Formatting.GRAY);
- context.drawTextWithShadow(this.client.textRenderer, hintText, i + 25, j + 14, -1);
- } else if (!this.searchField.isFocused() && this.searchField.getText().isEmpty()) {
- Text hintText = (Text.translatable("gui.recipebook.search_hint")).formatted(Formatting.ITALIC).formatted(Formatting.GRAY);
- context.drawTextWithShadow(this.client.textRenderer, hintText, i + 25, j + 14, -1);
- } else {
- this.searchField.render(context, mouseX, mouseY, delta);
- }
- if (ItemRepository.filesImported()) {
- if (results == null) {
- int x = (this.parentWidth - 147) / 2 - this.leftOffset;
- int y = (this.parentHeight - 166) / 2;
- this.results = new SearchResultsWidget(this.client, x, y);
- }
- this.updateSearchResult();
- this.results.render(context, mouseX, mouseY, delta);
+ // Draw the tab's content
+ if (currentTabContent != null) currentTabContent.render(context, mouseX, mouseY, delta);
+ // Draw the tab buttons
+ for (Pair<SideTabButtonWidget, TabContainerWidget> tab : tabs) {
+ tab.left().render(context, mouseX, mouseY, delta);
}
- matrices.pop();
+
}
}
@Override
public void drawTooltip(DrawContext context, int x, int y, int mouseX, int mouseY) {
- if (this.isOpen() && ItemRepository.filesImported() && results != null) {
- this.results.drawTooltip(context, mouseX, mouseY);
+ if (this.isOpen() && currentTabContent != null) {
+ this.currentTabContent.drawTooltip(context, mouseX, mouseY);
}
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
- if (this.isOpen() && this.client.player != null && !this.client.player.isSpectator() && ItemRepository.filesImported() && this.searchField != null && results != null) {
- if (this.searchField.mouseClicked(mouseX, mouseY, button)) {
- this.results.closeRecipeView();
- this.searchField.setFocused(true);
- return true;
- } else {
- this.searchField.setFocused(false);
- return this.results.mouseClicked(mouseX, mouseY, button);
+ if (this.isOpen() && this.client.player != null && !this.client.player.isSpectator()) {
+ // check if a tab is clicked
+ for (Pair<SideTabButtonWidget, TabContainerWidget> tab : tabs) {
+ if (tab.first().mouseClicked(mouseX, mouseY, button) && currentTabContent != tab.right()) {
+ for (Pair<SideTabButtonWidget, TabContainerWidget> tab2 : tabs) {
+ tab2.first().setToggled(false);
+ }
+ tab.first().setToggled(true);
+ currentTabContent = tab.right();
+ currentTab = tabs.indexOf(tab);
+ return true;
+ }
}
+ // click the tab content
+ if (currentTabContent != null) return currentTabContent.mouseClicked(mouseX, mouseY, button);
+ else return false;
} else return false;
}
+
+ /**
+ * A container widget but with a fixed width and height and a drawTooltip method to implement
+ */
+ public abstract static class TabContainerWidget extends ContainerWidget {
+
+ public TabContainerWidget(int x, int y, Text text) {
+ super(x, y, 131, 150, text);
+ }
+
+ public abstract void drawTooltip(DrawContext context, int mouseX, int mouseY);
+ }
} \ No newline at end of file
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/SearchResultsWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/SearchResultsWidget.java
index 1ef352e3..48d3a8f6 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/SearchResultsWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/SearchResultsWidget.java
@@ -6,6 +6,7 @@ import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.Drawable;
+import net.minecraft.client.gui.Element;
import net.minecraft.client.gui.screen.ButtonTextures;
import net.minecraft.client.gui.widget.ToggleButtonWidget;
import net.minecraft.component.DataComponentTypes;
@@ -23,7 +24,7 @@ import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-public class SearchResultsWidget implements Drawable {
+public class SearchResultsWidget implements Drawable, Element {
private static final ButtonTextures PAGE_FORWARD_TEXTURES = new ButtonTextures(new Identifier("recipe_book/page_forward"), new Identifier("recipe_book/page_forward_highlighted"));
private static final ButtonTextures PAGE_BACKWARD_TEXTURES = new ButtonTextures(new Identifier("recipe_book/page_backward"), new Identifier("recipe_book/page_backward_highlighted"));
private static final int COLS = 5;
@@ -225,4 +226,16 @@ public class SearchResultsWidget implements Drawable {
return false;
}
+ private boolean focused = false;
+
+ @Override
+ public void setFocused(boolean focused) {
+ this.focused = focused;
+ }
+
+ @Override
+ public boolean isFocused() {
+ return focused;
+ }
+
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/UpcomingEventsTab.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/UpcomingEventsTab.java
new file mode 100644
index 00000000..9552ae87
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/UpcomingEventsTab.java
@@ -0,0 +1,168 @@
+package de.hysky.skyblocker.skyblock.itemlist;
+
+import de.hysky.skyblocker.mixins.accessors.DrawContextInvoker;
+import de.hysky.skyblocker.skyblock.events.EventNotifications;
+import de.hysky.skyblocker.skyblock.tabhud.widget.JacobsContestWidget;
+import de.hysky.skyblocker.utils.Utils;
+import de.hysky.skyblocker.utils.scheduler.MessageScheduler;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.Element;
+import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder;
+import net.minecraft.client.gui.tooltip.HoveredTooltipPositioner;
+import net.minecraft.client.gui.tooltip.TooltipComponent;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Style;
+import net.minecraft.text.Text;
+import net.minecraft.util.Colors;
+import net.minecraft.util.Formatting;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+
+public class UpcomingEventsTab extends ItemListWidget.TabContainerWidget {
+ private static final ItemStack CLOCK = new ItemStack(Items.CLOCK);
+ private final MinecraftClient client;
+ private final List<EventRenderer> events;
+
+ public UpcomingEventsTab(int x, int y, MinecraftClient client) {
+ super(x, y, Text.literal("Upcoming Events Tab"));
+ this.client = client;
+ events = EventNotifications.getEvents().entrySet()
+ .stream()
+ .sorted(Comparator.comparingLong(a -> a.getValue().isEmpty() ? Long.MAX_VALUE : a.getValue().peekFirst().start()))
+ .map(stringLinkedListEntry -> new EventRenderer(stringLinkedListEntry.getKey(), stringLinkedListEntry.getValue()))
+ .toList();
+ }
+
+ @Override
+ public void drawTooltip(DrawContext context, int mouseX, int mouseY) {
+ if (hovered != null) {
+ ((DrawContextInvoker) context).invokeDrawTooltip(this.client.textRenderer, hovered.getTooltip(), mouseX, mouseY, HoveredTooltipPositioner.INSTANCE);
+ }
+ }
+
+ @Override
+ public List<? extends Element> children() {
+ return List.of();
+ }
+
+ private EventRenderer hovered = null;
+
+ @Override
+ protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) {
+ int x = getX();
+ int y = getY();
+ context.enableScissor(x, y, getRight(), getBottom());
+ context.drawItem(CLOCK, x, y + 4);
+ context.drawText(this.client.textRenderer, "Upcoming Events", x + 17, y + 7, -1, true);
+
+ int eventsY = y + 7 + 24;
+ hovered = null;
+ for (EventRenderer eventRenderer : events) {
+ eventRenderer.render(context, x + 1, eventsY, mouseX, mouseY);
+ if (isMouseOver(mouseX, mouseY) && eventRenderer.isMouseOver(mouseX, mouseY, x+1, eventsY)) hovered = eventRenderer;
+ eventsY += eventRenderer.getHeight();
+
+ }
+ context.disableScissor();
+ }
+
+ @Override
+ public boolean mouseClicked(double mouseX, double mouseY, int button) {
+ if (hovered != null && hovered.getWarpCommand() != null) {
+ MessageScheduler.INSTANCE.sendMessageAfterCooldown(hovered.getWarpCommand());
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected void appendClickableNarrations(NarrationMessageBuilder builder) {
+
+ }
+
+ public static class EventRenderer {
+
+ private final LinkedList<EventNotifications.SkyblockEvent> events;
+ private final String eventName;
+
+ public EventRenderer(String eventName, LinkedList<EventNotifications.SkyblockEvent> events) {
+ this.events = events;
+ this.eventName = eventName;
+ }
+
+ public void render(DrawContext context, int x, int y, int mouseX, int mouseY) {
+ long time = System.currentTimeMillis() / 1000;
+ TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
+ context.drawText(textRenderer, Text.literal(eventName).fillStyle(Style.EMPTY.withUnderline(isMouseOver(mouseX, mouseY, x, y))), x, y, -1, true);
+ if (events.isEmpty()) {
+ context.drawText(textRenderer, Text.literal(" ").append(Text.translatable("skyblocker.events.tab.noMore")), x, y + textRenderer.fontHeight, Colors.GRAY, false);
+ } else if (events.peekFirst().start() > time) {
+ MutableText formatted = Text.literal(" ").append(Text.translatable("skyblocker.events.tab.startsIn", Utils.getDurationText((int) (events.peekFirst().start() - time)))).formatted(Formatting.YELLOW);
+ context.drawText(textRenderer, formatted, x, y + textRenderer.fontHeight, -1, true);
+ } else {
+ MutableText formatted = Text.literal(" ").append(Text.translatable( "skyblocker.events.tab.endsIn", Utils.getDurationText((int) (events.peekFirst().start() + events.peekFirst().duration() - time)))).formatted(Formatting.GREEN);
+ context.drawText(textRenderer, formatted, x, y + textRenderer.fontHeight, -1, true);
+ }
+
+ }
+
+ public int getHeight() {
+ return 20;
+ }
+
+ public boolean isMouseOver(int mouseX, int mouseY, int x, int y) {
+ return mouseX >= x && mouseX <= x + 131 && mouseY >= y && mouseY <= y+getHeight();
+ }
+
+ public List<TooltipComponent> getTooltip() {
+ List<TooltipComponent> components = new ArrayList<>();
+ if (events.peekFirst() == null) return components;
+ if (eventName.equals(EventNotifications.JACOBS)) {
+ components.add(new JacobsTooltip(events.peekFirst().extras()));
+ }
+ //noinspection DataFlowIssue
+ if (events.peekFirst().warpCommand() != null) {
+ components.add(TooltipComponent.of(Text.translatable("skyblocker.events.tab.clickToWarp").formatted(Formatting.ITALIC).asOrderedText()));
+ }
+
+ return components;
+ }
+
+ public @Nullable String getWarpCommand() {
+ if (events.isEmpty()) return null;
+ return events.peek().warpCommand();
+ }
+ }
+
+ private record JacobsTooltip(String[] crops) implements TooltipComponent {
+
+ private static final ItemStack BARRIER = new ItemStack(Items.BARRIER);
+
+ @Override
+ public int getHeight() {
+ return 20;
+ }
+
+ @Override
+ public int getWidth(TextRenderer textRenderer) {
+ return 16 * 3 + 4;
+ }
+
+ @Override
+ public void drawItems(TextRenderer textRenderer, int x, int y, DrawContext context) {
+ for (int i = 0; i < crops.length; i++) {
+ String crop = crops[i];
+ context.drawItem(JacobsContestWidget.FARM_DATA.getOrDefault(crop, BARRIER), x + 18 * i, y + 2);
+ }
+ }
+
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/JacobsContestWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/JacobsContestWidget.java
index 24dcc229..c28c8679 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/JacobsContestWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/JacobsContestWidget.java
@@ -26,7 +26,7 @@ public class JacobsContestWidget extends Widget {
//TODO Properly match the contest placement and display it
private static final Pattern CROP_PATTERN = Pattern.compile("(?<fortune>[☘○]) (?<crop>[A-Za-z ]+).*");
- private static final Map<String, ItemStack> FARM_DATA = Map.ofEntries(
+ public static final Map<String, ItemStack> FARM_DATA = Map.ofEntries(
entry("Wheat", new ItemStack(Items.WHEAT)),
entry("Sugar Cane", new ItemStack(Items.SUGAR_CANE)),
entry("Carrot", new ItemStack(Items.CARROT)),
diff --git a/src/main/java/de/hysky/skyblocker/utils/Utils.java b/src/main/java/de/hysky/skyblocker/utils/Utils.java
index 62a3b897..7c28294f 100644
--- a/src/main/java/de/hysky/skyblocker/utils/Utils.java
+++ b/src/main/java/de/hysky/skyblocker/utils/Utils.java
@@ -19,6 +19,7 @@ import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.network.PlayerListEntry;
import net.minecraft.scoreboard.*;
+import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import org.jetbrains.annotations.NotNull;
@@ -355,6 +356,23 @@ public class Utils {
}
}
+ // TODO: Combine with `ChocolateFactorySolver.formatTime` and move into `SkyblockTime`.
+ public static Text getDurationText(int timeInSeconds) {
+ int seconds = timeInSeconds % 60;
+ int minutes = (timeInSeconds/60) % 60;
+ int hours = (timeInSeconds/3600);
+
+ MutableText time = Text.empty();
+ if (hours > 0) {
+ time.append(hours + "h").append(" ");
+ }
+ if (hours > 0 || minutes > 0) {
+ time.append(minutes + "m").append(" ");
+ }
+ time.append(seconds + "s");
+ return time;
+ }
+
private static void updateFromPlayerList(MinecraftClient client) {
if (client.getNetworkHandler() == null) {
return;
diff --git a/src/main/java/de/hysky/skyblocker/utils/config/DurationController.java b/src/main/java/de/hysky/skyblocker/utils/config/DurationController.java
new file mode 100644
index 00000000..09edcf3c
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/utils/config/DurationController.java
@@ -0,0 +1,70 @@
+package de.hysky.skyblocker.utils.config;
+
+import de.hysky.skyblocker.utils.Utils;
+import dev.isxander.yacl3.api.Option;
+import dev.isxander.yacl3.api.utils.Dimension;
+import dev.isxander.yacl3.gui.AbstractWidget;
+import dev.isxander.yacl3.gui.YACLScreen;
+import dev.isxander.yacl3.gui.controllers.string.IStringController;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public record DurationController(Option<Integer> option) implements IStringController<Integer> {
+
+ private static final Pattern secondsPattern = Pattern.compile("(^|\\s)(\\d+)s(\\s|$)");
+ private static final Pattern minutesPattern = Pattern.compile("(^|\\s)(\\d+)m(\\s|$)");
+ private static final Pattern hoursPattern = Pattern.compile("(^|\\s)(\\d+)h(\\s|$)");
+
+ @Override
+ public String getString() {
+ return Utils.getDurationText(option.pendingValue()).getString();
+ }
+
+
+ @Override
+ public void setFromString(String value) {
+ Matcher hoursMatcher = hoursPattern.matcher(value);
+ Matcher minutesMatcher = minutesPattern.matcher(value);
+ Matcher secondsMatcher = secondsPattern.matcher(value);
+
+ int result = 0;
+ if (hoursMatcher.find()) {
+ result += Integer.parseInt(hoursMatcher.group(2)) * 3600;
+ }
+ if (minutesMatcher.find()) {
+ result += Integer.parseInt(minutesMatcher.group(2)) * 60;
+ }
+ if (secondsMatcher.find()) {
+ result += Integer.parseInt(secondsMatcher.group(2));
+ }
+ option.requestSet(result);
+ }
+
+
+ @Override
+ public boolean isInputValid(String s) {
+ Matcher hoursMatcher = hoursPattern.matcher(s);
+ Matcher minutesMatcher = minutesPattern.matcher(s);
+ Matcher secondsMatcher = secondsPattern.matcher(s);
+
+ int hoursCount = 0;
+ while (hoursMatcher.find()) hoursCount++;
+ int minutesCount = 0;
+ while (minutesMatcher.find()) minutesCount++;
+ int secondsCount = 0;
+ while (secondsMatcher.find()) secondsCount++;
+
+ if (hoursCount == 0 && minutesCount == 0 && secondsCount == 0) return false;
+ if (hoursCount > 1 || minutesCount > 1 || secondsCount > 1) return false;
+ s = s.replaceAll(hoursPattern.pattern(), "");
+ s = s.replaceAll(minutesPattern.pattern(), "");
+ s = s.replaceAll(secondsPattern.pattern(), "");
+ return s.isBlank();
+ }
+
+ @Override
+ public AbstractWidget provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension) {
+ return new DurationControllerWidget(this, screen, widgetDimension);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/utils/config/DurationControllerWidget.java b/src/main/java/de/hysky/skyblocker/utils/config/DurationControllerWidget.java
new file mode 100644
index 00000000..f25cd088
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/utils/config/DurationControllerWidget.java
@@ -0,0 +1,38 @@
+package de.hysky.skyblocker.utils.config;
+
+import dev.isxander.yacl3.api.utils.Dimension;
+import dev.isxander.yacl3.gui.YACLScreen;
+import dev.isxander.yacl3.gui.controllers.string.IStringController;
+import dev.isxander.yacl3.gui.controllers.string.StringControllerElement;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+import java.util.function.Consumer;
+
+public class DurationControllerWidget extends StringControllerElement {
+
+ public DurationControllerWidget(IStringController<?> control, YACLScreen screen, Dimension<Integer> dim) {
+ super(control, screen, dim, false);
+ }
+
+ @Override
+ public void unfocus() {
+ if (control.isInputValid(inputField)) super.unfocus();
+ else modifyInput(stringBuilder -> stringBuilder.replace(0, stringBuilder.length(), control.getString()));
+ }
+
+ @Override
+ public boolean modifyInput(Consumer<StringBuilder> consumer) {
+ StringBuilder temp = new StringBuilder(inputField);
+ consumer.accept(temp);
+ inputField = temp.toString();
+ return true;
+ }
+
+ @Override
+ protected Text getValueText() {
+ Text valueText = super.getValueText();
+ boolean inputValid = control.isInputValid(valueText.getString());
+ return valueText.copy().formatted(inputValid ? Formatting.WHITE: Formatting.RED);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/utils/render/gui/SideTabButtonWidget.java b/src/main/java/de/hysky/skyblocker/utils/render/gui/SideTabButtonWidget.java
new file mode 100644
index 00000000..87da0d36
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/utils/render/gui/SideTabButtonWidget.java
@@ -0,0 +1,39 @@
+package de.hysky.skyblocker.utils.render.gui;
+
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.ButtonTextures;
+import net.minecraft.client.gui.widget.ToggleButtonWidget;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.Identifier;
+import org.jetbrains.annotations.NotNull;
+
+public class SideTabButtonWidget extends ToggleButtonWidget {
+ private static final ButtonTextures TEXTURES = new ButtonTextures(new Identifier("recipe_book/tab"), new Identifier("recipe_book/tab_selected"));
+ protected @NotNull ItemStack icon;
+
+ public void setIcon(@NotNull ItemStack icon) {
+ this.icon = icon.copy();
+ }
+
+ public SideTabButtonWidget(int x, int y, boolean toggled, @NotNull ItemStack icon) {
+ super(x, y, 35, 27, toggled);
+ this.icon = icon.copy();
+ setTextures(TEXTURES);
+ }
+
+ @Override
+ public void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) {
+ if (textures == null) return;
+ Identifier identifier = textures.get(true, this.toggled);
+ int x = getX();
+ if (toggled) x -= 2;
+ context.drawGuiTexture(identifier, x, this.getY(), this.width, this.height);
+ context.drawItem(icon, x + 9, getY() + 5);
+ }
+
+ @Override
+ public void onClick(double mouseX, double mouseY) {
+ super.onClick(mouseX, mouseY);
+ if (!isToggled()) this.setToggled(true);
+ }
+}