Skip to content

Commit

Permalink
Merge pull request #691 from viciscat/events-notifications
Browse files Browse the repository at this point in the history
Calendar Event Notifications
  • Loading branch information
kevinthegreat1 authored Jun 7, 2024
2 parents cfa71fe + 9054e87 commit b657a22
Show file tree
Hide file tree
Showing 21 changed files with 989 additions and 76 deletions.
2 changes: 2 additions & 0 deletions src/main/java/de/hysky/skyblocker/SkyblockerMod.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
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;
Expand Down Expand Up @@ -177,6 +178,7 @@ public void onInitializeClient() {
Kuudra.init();
RenderHelper.init();
FancyStatusBars.init();
EventNotifications.init();
containerSolverManager.init();
statusBarTracker.init();
BeaconHighlighter.init();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ public class SkyblockerConfig {
@SerialEntry
public QuickNavigationConfig quickNav = new QuickNavigationConfig();

@SerialEntry
public EventNotificationsConfig eventNotifications = new EventNotificationsConfig();

@SerialEntry
public MiscConfig misc = new MiscConfig();
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public static Screen createGUI(Screen parent) {
.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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -52,8 +36,8 @@ public void setSlotId(int slotId) {

@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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Loading

0 comments on commit b657a22

Please sign in to comment.