-
-
Notifications
You must be signed in to change notification settings - Fork 84
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #691 from viciscat/events-notifications
Calendar Event Notifications
- Loading branch information
Showing
21 changed files
with
989 additions
and
76 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
68 changes: 68 additions & 0 deletions
68
src/main/java/de/hysky/skyblocker/config/categories/EventNotificationsCategory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
35 changes: 35 additions & 0 deletions
35
src/main/java/de/hysky/skyblocker/config/configs/EventNotificationsConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
174 changes: 174 additions & 0 deletions
174
src/main/java/de/hysky/skyblocker/skyblock/events/EventNotifications.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
Oops, something went wrong.