diff --git a/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt b/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt index fbd93a4d840f..f021fe326d3b 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt +++ b/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt @@ -63,6 +63,7 @@ import at.hannibal2.skyhanni.features.misc.MarkedPlayerManager import at.hannibal2.skyhanni.features.misc.discordrpc.DiscordRPCManager import at.hannibal2.skyhanni.features.misc.limbo.LimboTimeTracker import at.hannibal2.skyhanni.features.misc.massconfiguration.DefaultConfigFeatures +import at.hannibal2.skyhanni.features.misc.reminders.ReminderManager import at.hannibal2.skyhanni.features.misc.update.UpdateManager import at.hannibal2.skyhanni.features.misc.visualwords.VisualWordGui import at.hannibal2.skyhanni.features.rift.area.westvillage.VerminTracker @@ -170,6 +171,7 @@ object Commands { { DefaultConfigFeatures.onCommand(it) }, DefaultConfigFeatures::onComplete, ) + registerCommand("shremind", "Set a reminder for yourself") { ReminderManager.command(it) } registerCommand("shwords", "Opens the config list for modifying visual words") { openVisualWords() } } diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/misc/MiscConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/misc/MiscConfig.java index 95d3281ec51f..54f0c7c50afd 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/misc/MiscConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/misc/MiscConfig.java @@ -107,6 +107,11 @@ public class MiscConfig { @Accordion public PatcherCoordsWaypointConfig patcherCoordsWaypoint = new PatcherCoordsWaypointConfig(); + @Expose + @ConfigOption(name = "Reminders", desc = "") + @Accordion + public RemindersConfig reminders = new RemindersConfig(); + @Expose @ConfigOption(name = "Show Outside SkyBlock", desc = "Show these features outside of SkyBlock.") @ConfigEditorDraggableList diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/misc/RemindersConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/misc/RemindersConfig.java new file mode 100644 index 000000000000..fb7a9b785db8 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/misc/RemindersConfig.java @@ -0,0 +1,21 @@ +package at.hannibal2.skyhanni.config.features.misc; + +import com.google.gson.annotations.Expose; +import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorBoolean; +import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorSlider; +import io.github.notenoughupdates.moulconfig.annotations.ConfigOption; + +public class RemindersConfig { + @Expose + @ConfigOption(name = "Auto Delete Reminders", desc = "Automatically deletes reminders after they have been shown once.") + @ConfigEditorBoolean + public boolean autoDeleteReminders = false; + + @Expose + @ConfigOption( + name = "Reminder Interval", + desc = "The interval in minutes in which reminders are shown again, after they have been shown once." + ) + @ConfigEditorSlider(minValue = 0f, maxValue = 60f, minStep = 1f) + public float interval = 5f; +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/storage/Storage.java b/src/main/java/at/hannibal2/skyhanni/config/storage/Storage.java index c37ba1dd0993..252c9d8bb53e 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/storage/Storage.java +++ b/src/main/java/at/hannibal2/skyhanni/config/storage/Storage.java @@ -1,5 +1,6 @@ package at.hannibal2.skyhanni.config.storage; +import at.hannibal2.skyhanni.features.misc.reminders.Reminder; import at.hannibal2.skyhanni.features.misc.visualwords.VisualWord; import at.hannibal2.skyhanni.utils.LorenzVec; import at.hannibal2.skyhanni.utils.tracker.SkyHanniTracker; @@ -50,4 +51,7 @@ public class Storage { @Expose public List blacklistedUsers = new ArrayList<>(); + + @Expose + public Map reminders = new HashMap<>(); } diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/reminders/Reminder.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/reminders/Reminder.kt new file mode 100644 index 000000000000..4dd08eafd33a --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/reminders/Reminder.kt @@ -0,0 +1,37 @@ +package at.hannibal2.skyhanni.features.misc.reminders + +import at.hannibal2.skyhanni.utils.SimpleTimeMark +import com.google.gson.annotations.Expose +import java.time.Instant +import java.time.LocalDate +import java.time.ZoneId +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter +import java.time.format.FormatStyle +import java.util.Locale +import kotlin.time.Duration + +data class Reminder( + @Expose var reason: String, + @Expose var remindAt: SimpleTimeMark, + @Expose var lastReminder: SimpleTimeMark = SimpleTimeMark.farPast(), +) { + + fun formatShort(): String { + val time = getRemindTime() + val date = time.toLocalDate() + if (date.isEqual(LocalDate.now())) { + return time.format(DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withDefaultLocale()) + } + return date.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withDefaultLocale()) + } + + fun formatFull(): String = getRemindTime().format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).withDefaultLocale()) + + fun shouldRemind(interval: Duration) = remindAt.isInPast() && lastReminder.passedSince() >= interval + + private fun getRemindTime(): ZonedDateTime = Instant.ofEpochMilli(remindAt.toMillis()).atZone(ZoneId.systemDefault()) + + private fun DateTimeFormatter.withDefaultLocale(): DateTimeFormatter = withLocale(Locale.getDefault()) +} + diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/reminders/ReminderManager.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/reminders/ReminderManager.kt new file mode 100644 index 000000000000..472aac2de6be --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/reminders/ReminderManager.kt @@ -0,0 +1,249 @@ +package at.hannibal2.skyhanni.features.misc.reminders + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.SecondPassedEvent +import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule +import at.hannibal2.skyhanni.utils.ChatUtils +import at.hannibal2.skyhanni.utils.SimpleTimeMark +import at.hannibal2.skyhanni.utils.StringUtils +import at.hannibal2.skyhanni.utils.TimeUtils +import at.hannibal2.skyhanni.utils.TimeUtils.format +import at.hannibal2.skyhanni.utils.TimeUtils.minutes +import at.hannibal2.skyhanni.utils.chat.Text +import at.hannibal2.skyhanni.utils.chat.Text.asComponent +import at.hannibal2.skyhanni.utils.chat.Text.center +import at.hannibal2.skyhanni.utils.chat.Text.command +import at.hannibal2.skyhanni.utils.chat.Text.fitToChat +import at.hannibal2.skyhanni.utils.chat.Text.hover +import at.hannibal2.skyhanni.utils.chat.Text.send +import at.hannibal2.skyhanni.utils.chat.Text.style +import at.hannibal2.skyhanni.utils.chat.Text.suggest +import at.hannibal2.skyhanni.utils.chat.Text.wrap +import net.minecraft.util.EnumChatFormatting +import net.minecraft.util.IChatComponent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds + +@SkyHanniModule +object ReminderManager { + + private const val REMINDERS_PER_PAGE = 10 + + // Random numbers chosen, this will be used to delete the old list and action messages + private const val REMINDERS_LIST_ID = -546745 + private const val REMINDERS_ACTION_ID = -546746 + private const val REMINDERS_MESSAGE_ID = -546747 + + private val storage get() = SkyHanniMod.feature.storage.reminders + private val config get() = SkyHanniMod.feature.misc.reminders + + private var listPage = 1 + + private fun getSortedReminders() = storage.entries.sortedBy { it.value.remindAt } + + private fun sendMessage(message: String) = Text.join("§e[Reminder]", " ", message).send(REMINDERS_ACTION_ID) + + private fun createDivider() = Text.HYPHEN.fitToChat().style { + strikethrough = true + color = EnumChatFormatting.BLUE + } + + private fun parseDuration(text: String): Duration? = try { + val duration = TimeUtils.getDuration(text) + if (duration <= 1.seconds) null else duration + } catch (e: Exception) { + null + } + + private fun listReminders(page: Int) { + val reminders = getSortedReminders() + val maxPage = (reminders.size + REMINDERS_PER_PAGE - 1) / REMINDERS_PER_PAGE + + listPage = page.coerceIn(0, maxPage) + + val text: MutableList = mutableListOf() + + text.add(createDivider()) + + text.add( + Text.join( + if (listPage > 1) "§6§l<<".asComponent { + hover = "§eClick to view page ${listPage - 1}".asComponent() + command = "/shremind list ${listPage - 1}" + } else null, + " ", + "§6Reminders (Page $listPage of $maxPage)", + " ", + if (listPage < maxPage) "§6§l>>".asComponent { + hover = "§eClick to view page ${listPage + 1}".asComponent() + command = "/shremind list ${listPage + 1}" + } else null, + ).center(), + ) + + if (reminders.isNotEmpty()) { + for (i in (listPage - 1) * REMINDERS_PER_PAGE until listPage * REMINDERS_PER_PAGE) { + if (i >= reminders.size) break + val (id, reminder) = reminders[i] + + text.add( + Text.join( + "§c✕".asComponent { + hover = "§7Click to remove".asComponent() + command = "/shremind remove -l $id" + }.wrap("§8[", "§8]"), + " ", + "§e✎".asComponent { + hover = "§7Click to start editing".asComponent() + suggest = "/shremind edit -l $id ${reminder.reason} " + }.wrap("§8[", "§8]"), + " ", + "§6${reminder.formatShort()}".asComponent { + hover = "§7${reminder.formatFull()}".asComponent() + }.wrap("§8[", "§8]"), + " ", + "§7${reminder.reason}", + ), + ) + } + } else { + text.add(Text.EMPTY) + text.add("§cNo reminders found.".asComponent().center()) + text.add(Text.EMPTY) + } + + text.add(createDivider()) + + Text.join(*text.toTypedArray(), separator = Text.NEWLINE).send(REMINDERS_LIST_ID) + } + + private fun createReminder(args: Array) { + if (args.size < 2) return help() + + val time = parseDuration(args.first()) ?: return ChatUtils.userError("Invalid time format") + val reminder = args.drop(1).joinToString(" ") + val remindAt = SimpleTimeMark.now().plus(time) + + storage[StringUtils.generateRandomId()] = Reminder(reminder, remindAt) + sendMessage("§6Reminder set for ${time.format()}") + } + + private fun actionReminder( + args: List, + command: String, + vararg arguments: String, + action: (List, Reminder) -> String, + ) { + val argumentText = arguments.joinToString(" ") + if (args.size < arguments.size) return ChatUtils.userError("/shremind $command $argumentText") + + if (args.first() == "-l") { + if (args.size < arguments.size + 1) return ChatUtils.userError("/shremind $command -l $argumentText") + if (storage[args.drop(1).first()] == null) return ChatUtils.userError("Reminder not found!") + action(args.drop(2), storage[args.drop(1).first()]!!).apply { + listReminders(listPage) + sendMessage(this) + } + } else if (storage[args.first()] == null) { + return ChatUtils.userError("Reminder not found!") + } else { + sendMessage(action(args.drop(1), storage[args.first()]!!)) + } + } + + private fun removeReminder(args: List) = actionReminder( + args, + "remove", + "[id]", + ) { _, reminder -> + storage.values.remove(reminder) + "§cReminder deleted." + } + + private fun editReminder(args: List) = actionReminder( + args, + "edit", + "[id]", + "[reminder]", + ) { arguments, reminder -> + reminder.reason = arguments.joinToString(" ") + "§6Reminder edited." + } + + private fun moveReminder(args: List) = actionReminder( + args, + "move", + "[id]", + "[time]", + ) { arguments, reminder -> + val time = parseDuration(arguments.first()) ?: return@actionReminder "§cInvalid time format!" + reminder.remindAt = SimpleTimeMark.now().plus(time) + "§6Reminder moved to ${time.format()}" + } + + private fun help() { + createDivider().send() + "§6SkyHanni Reminder Commands:".asComponent().send() + "§e/shremind