aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThunderblade73 <85900443+Thunderblade73@users.noreply.github.com>2024-09-13 20:23:40 +0200
committerGitHub <noreply@github.com>2024-09-13 20:23:40 +0200
commit691d2551cf722b76c1894c814d1f6c2e98fe17d6 (patch)
treec02fd9edb6c2f1f4cb8791cf7113c8d5a8594a2f
parent20081af74fd53f35746ab0d5c974bb5db92bec3b (diff)
downloadskyhanni-691d2551cf722b76c1894c814d1f6c2e98fe17d6.tar.gz
skyhanni-691d2551cf722b76c1894c814d1f6c2e98fe17d6.tar.bz2
skyhanni-691d2551cf722b76c1894c814d1f6c2e98fe17d6.zip
Feature: Carnival QoL (#2498)
Co-authored-by: CalMWolfs <94038482+CalMWolfs@users.noreply.github.com> Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com>
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/event/EventConfig.java4
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/event/carnival/CarnivalConfig.java26
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java17
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/event/carnival/CarnivalGoal.kt236
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/event/carnival/CarnivalQuickStart.kt60
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/event/carnival/CarnivalReminder.kt99
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/HypixelCommands.kt4
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/json/BaseGsonBuilder.kt4
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/json/SkyHanniTypeAdapters.kt16
9 files changed, 461 insertions, 5 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/event/EventConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/event/EventConfig.java
index 1c916bbe1..41d5771ab 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/features/event/EventConfig.java
+++ b/src/main/java/at/hannibal2/skyhanni/config/features/event/EventConfig.java
@@ -44,9 +44,8 @@ public class EventConfig {
@Expose
public GreatSpookConfig spook = new GreatSpookConfig();
- @ConfigOption(name = "Carnival", desc = "")
- @Accordion
@Expose
+ @Category(name = "The Carnival", desc = "Features for games at §eThe Carnival §7when §bFoxy §7is Mayor.")
public CarnivalConfig carnival = new CarnivalConfig();
// comment in if the event is needed again
@@ -58,5 +57,4 @@ public class EventConfig {
@Category(name = "Lobby Waypoints", desc = "Lobby Event Waypoint settings")
@Expose
public LobbyWaypointsConfig lobbyWaypoints = new LobbyWaypointsConfig();
-
}
diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/event/carnival/CarnivalConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/event/carnival/CarnivalConfig.java
index b874f7558..cad2b7ea3 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/features/event/carnival/CarnivalConfig.java
+++ b/src/main/java/at/hannibal2/skyhanni/config/features/event/carnival/CarnivalConfig.java
@@ -1,7 +1,11 @@
package at.hannibal2.skyhanni.config.features.event.carnival;
+import at.hannibal2.skyhanni.config.FeatureToggle;
+import at.hannibal2.skyhanni.config.core.config.Position;
import com.google.gson.annotations.Expose;
import io.github.notenoughupdates.moulconfig.annotations.Accordion;
+import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorBoolean;
+import io.github.notenoughupdates.moulconfig.annotations.ConfigLink;
import io.github.notenoughupdates.moulconfig.annotations.ConfigOption;
public class CarnivalConfig {
@@ -10,4 +14,26 @@ public class CarnivalConfig {
@ConfigOption(name = "Zombie Shootout", desc = "")
@Accordion
public ZombieShootoutConfig zombieShootout = new ZombieShootoutConfig();
+
+ @Expose
+ @ConfigOption(name = "Reminder Daily Tickets", desc = "Reminds you when tickets can be claimed from the carnival leader.")
+ @ConfigEditorBoolean
+ @FeatureToggle
+ public boolean reminderDailyTickets = true;
+
+ @Expose
+ @ConfigOption(name = "Show Goals", desc = "Displays the goals for this carnival event.")
+ @ConfigEditorBoolean
+ @FeatureToggle
+ public boolean showGoals = true;
+
+ @Expose
+ @ConfigLink(owner = CarnivalConfig.class, field = "showGoals")
+ public Position goalsPosition = new Position(20, 20);
+
+ @Expose
+ @ConfigOption(name = "Double Click to Start", desc = "Clicking the npc again after the npc finishes talking to start game.")
+ @ConfigEditorBoolean
+ @FeatureToggle
+ public boolean doubleClickToStart = true;
}
diff --git a/src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java b/src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java
index 5938eebdb..83e7ea47c 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java
+++ b/src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java
@@ -10,6 +10,7 @@ import at.hannibal2.skyhanni.features.combat.endernodetracker.EnderNodeTracker;
import at.hannibal2.skyhanni.features.combat.ghostcounter.GhostData;
import at.hannibal2.skyhanni.features.dungeon.CroesusChestTracker;
import at.hannibal2.skyhanni.features.dungeon.DungeonFloor;
+import at.hannibal2.skyhanni.features.event.carnival.CarnivalGoal;
import at.hannibal2.skyhanni.features.event.diana.DianaProfitTracker;
import at.hannibal2.skyhanni.features.event.diana.MythologicalCreatureTracker;
import at.hannibal2.skyhanni.features.event.hoppity.HoppityCollectionStats;
@@ -160,6 +161,22 @@ public class ProfileSpecificStorage {
}
@Expose
+ public CarnivalStorage carnival = new CarnivalStorage();
+
+ public static class CarnivalStorage {
+
+ @Expose
+ @Nullable
+ public java.time.LocalDate lastClaimedDay = null;
+
+ @Expose
+ public int carnivalYear = 0;
+
+ @Expose
+ public Map<CarnivalGoal, Boolean> goals = new HashMap<>();
+ }
+
+ @Expose
public MaxwellPowerStorage maxwell = new MaxwellPowerStorage();
public static class MaxwellPowerStorage {
diff --git a/src/main/java/at/hannibal2/skyhanni/features/event/carnival/CarnivalGoal.kt b/src/main/java/at/hannibal2/skyhanni/features/event/carnival/CarnivalGoal.kt
new file mode 100644
index 000000000..c36212a22
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/event/carnival/CarnivalGoal.kt
@@ -0,0 +1,236 @@
+package at.hannibal2.skyhanni.features.event.carnival
+
+import at.hannibal2.skyhanni.SkyHanniMod
+import at.hannibal2.skyhanni.data.Perk
+import at.hannibal2.skyhanni.data.ProfileStorageData
+import at.hannibal2.skyhanni.events.GuiRenderEvent
+import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent
+import at.hannibal2.skyhanni.events.LorenzChatEvent
+import at.hannibal2.skyhanni.events.ProfileJoinEvent
+import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule
+import at.hannibal2.skyhanni.utils.ItemUtils.getLore
+import at.hannibal2.skyhanni.utils.LorenzUtils
+import at.hannibal2.skyhanni.utils.RegexUtils.matches
+import at.hannibal2.skyhanni.utils.RenderUtils.renderRenderables
+import at.hannibal2.skyhanni.utils.SkyBlockTime
+import at.hannibal2.skyhanni.utils.renderables.Renderable
+import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern
+import net.minecraft.init.Blocks
+import net.minecraft.init.Items
+import net.minecraft.item.Item
+import net.minecraft.item.ItemStack
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import org.intellij.lang.annotations.Language
+
+private val repoGroup = RepoPattern.group("carnvial.goals")
+
+enum class CarnivalGoal(
+ private val type: GoalType,
+ @Language("RegEXP") loreLine: String,
+ @Language("RegEXP") chatLine: String,
+ val display: String,
+) {
+ FRUIT_DIGGING_PLAY(
+ GoalType.FRUIT_DIGGING,
+ "§7Play §a3 games §7of §6Fruit Digging§7.",
+ "(§8 - §r)?§7Play §r§a3 games §r§7of §r§6Fruit Digging§r§7.",
+ "Play §a3 games",
+ ),
+ FRUIT_DIGGING_SCORE(
+ GoalType.FRUIT_DIGGING,
+ "§7Reach §a3,000 score §7in a single game",
+ "(§8 - §r)?§7Reach §r§a3,000 score §r§7in a single game of §r§6Fruit Digging§r§7.",
+ "Reach §a3,000 score",
+ ),
+ DIG_APPLE(
+ GoalType.FRUIT_DIGGING,
+ "§7Dig up §a3 Apples §7in a single game of",
+ "(§8 - §r)?§7Dig up §r§a3 Apples §r§7in a single game of §r§6Fruit Digging§r§7.",
+ "Dig up §a3 Apples",
+ ),
+ UNIQUE_FRUIT(
+ GoalType.FRUIT_DIGGING,
+ "§7Dig up §a5 unique Fruits §7in a single",
+ "(§8 - §r)?§7Dig up §r§a5 unique Fruits §r§7in a single game of §r§6Fruit Digging§r§7.",
+ "Dig up §a5 unique Fruits",
+ ),
+ DRAGONFRUIT(
+ GoalType.FRUIT_DIGGING,
+ "§7Dig up a §dDragonfruit§7.",
+ "(§8 - §r)?§7Dig up a §r§dDragonfruit§r§7.",
+ "Dig up a §dDragonfruit",
+ ),
+ CATCH_A_FISH_PLAY(
+ GoalType.CATCH_A_FISH,
+ "§7Play §a3 games §7of §3Catch a Fish§7.",
+ "(§8 - §r)?§7Play §r§a3 games §r§7of §r§3Catch a Fish§r§7.",
+ "Play §a3 games",
+ ),
+ CATCH_A_FISH_SCORE(
+ GoalType.CATCH_A_FISH,
+ "§7Reach §a3,000 score §7in a single game",
+ "(§8 - §r)?§7Catch §r§a30 Fish §r§7in a single game of §r§3Catch a Fish§r§7.",
+ "Reach §a3,000 score",
+ ),
+ CATCH_FISH(
+ GoalType.CATCH_A_FISH,
+ "§7Catch §a30 Fish §7in a single game of",
+ "(§8 - §r)?§7Reach §r§a3,000 score §r§7in a single game of §r§3Catch a Fish§r§7.",
+ "Catch §a30 Fish",
+ ),
+ CATCH_YELLOW_FISH(
+ GoalType.CATCH_A_FISH,
+ "§7Catch §a5 Yellow Fish §7in a single game",
+ "(§8 - §r)?§7Catch §r§a5 Yellow Fish §r§7in a single game of §r§3Catch a Fish§r§7.",
+ "Catch §a5 Yellow Fish",
+ ),
+ CATCH_STREAK(
+ GoalType.CATCH_A_FISH,
+ "§7Reach a §a5 Catch Streak §7in a single",
+ "(§8 - §r)?§7Reach a §r§a5 Catch Streak §r§7in a single game of §r§3Catch a Fish§r§7.",
+ "Reach a §a5 Catch Streak",
+ ),
+ ZOMBIE_SHOOTOUT_PLAY(
+ GoalType.ZOMBIE_SHOOTOUT,
+ "§7Play §a3 games §7of §cZombie Shootout§7.",
+ "(§8 - §r)?§7Play §r§a3 games §r§7of §r§cZombie Shootout§r§7.",
+ "Play §a3 games",
+ ),
+ ZOMBIE_SHOOTOUT_SCORE(
+ GoalType.ZOMBIE_SHOOTOUT,
+ "§7Reach §a3,000 score §7in a single game",
+ "(§8 - §r)?§7Reach §r§a3,000 score §r§7in a single game of §r§cZombie Shootout§r§7.",
+ "Reach §a3,000 score",
+ ),
+ SHOOT_ZOMBIE(
+ GoalType.ZOMBIE_SHOOTOUT,
+ "§7Shoot §a60 Zombies §7in a single game of",
+ "(§8 - §r)?§7Shoot §r§a60 Zombies §r§7in a single game of §r§cZombie Shootout§r§7.",
+ "Shoot §a60 Zombies",
+ ),
+ SHOOT_DIAMOND_ZOMBIE(
+ GoalType.ZOMBIE_SHOOTOUT,
+ "§7Shoot §a5 Diamond Zombies §7in a single",
+ "(§8 - §r)?§7Shoot §r§a5 Diamond Zombies §r§7in a single game of §r§cZombie Shootout§r§7.",
+ "Shoot §a5 Diamond Zombies",
+ ),
+ SHOOT_LAMPS(
+ GoalType.ZOMBIE_SHOOTOUT,
+ "§7Shoot §a5 Redstone Lamps §7in a single",
+ "(§8 - §r)?§7Shoot §r§a5 Redstone Lamps §r§7in a single game of §r§cZombie Shootout§r§7.",
+ "Shoot §a5 Redstone Lamps",
+ ),
+ ;
+
+ private val patternKeyName = name.lowercase().replace("_", ".")
+
+ private val lorePattern by repoGroup.pattern("lore.$patternKeyName", loreLine)
+ private val chatPattern by repoGroup.pattern("chat.$patternKeyName", chatLine)
+
+ private var isReached: Boolean
+ get() {
+ val year = SkyBlockTime.now().year
+ if (year != storage?.carnivalYear) {
+ storage?.goals?.clear()
+ storage?.carnivalYear = year
+ }
+ return storage?.goals?.get(this) ?: false
+ }
+ set(value) {
+ val year = SkyBlockTime.now().year
+ if (year != storage?.carnivalYear) {
+ storage?.goals?.clear()
+ storage?.carnivalYear = year
+ }
+ storage?.goals?.set(this, value)
+ dirty = true
+ }
+
+ @SkyHanniModule
+ companion object {
+
+ init {
+ entries.forEach {
+ it.chatPattern
+ it.lorePattern
+ }
+ }
+
+ private val config get() = SkyHanniMod.feature.event.carnival
+ private val storage get() = ProfileStorageData.profileSpecific?.carnival
+
+ private val inventoryPattern by repoGroup.pattern("inventory", "Carnival Goals")
+
+ private val completePattern by repoGroup.pattern("complete", "§a§lCOMPLETE")
+
+ private var dirty = true
+
+ private fun getEntry(item: Item, lore: List<String>): CarnivalGoal? =
+ entries.filter { it.type.item == item }.firstOrNull { it.lorePattern.matches(lore.firstOrNull()) }
+
+ @SubscribeEvent
+ fun onProfileJoin(event: ProfileJoinEvent) {
+ dirty = true
+ }
+
+ @SubscribeEvent
+ fun onInventoryFullyOpened(event: InventoryFullyOpenedEvent) {
+ if (!isEnabled()) return
+ if (!inventoryPattern.matches(event.inventoryName)) return
+ for (stack in event.inventoryItems.values) {
+ val lore = stack.getLore()
+ val goal = getEntry(stack.item, lore) ?: continue
+ val lastLine = lore.last()
+ goal.isReached = completePattern.matches(lastLine)
+ }
+ }
+
+ @SubscribeEvent
+ fun onLorenzChat(event: LorenzChatEvent) {
+ if (!isEnabled()) return
+ entries.firstOrNull { it.chatPattern.matches(event.message) }?.isReached = true
+ }
+
+ private var display = emptyList<Renderable>()
+
+ @SubscribeEvent
+ fun onGuiRenderGuiOverlayRender(event: GuiRenderEvent.GuiOverlayRenderEvent) {
+ if (!isEnabled()) return
+ if (dirty) {
+ display = buildList {
+ GoalType.entries.map { it.fullDisplay }.forEach { list -> addAll(list) }
+ }
+ dirty = false
+ }
+ config.goalsPosition.renderRenderables(display, posLabel = "Carnival Goals")
+ }
+
+ fun isEnabled() =
+ LorenzUtils.inSkyBlock && config.showGoals && Perk.CHIVALROUS_CARNIVAL.isActive && LorenzUtils.skyBlockArea == "Carnival"
+
+ private enum class GoalType(val item: Item, display: String) {
+ FRUIT_DIGGING(Item.getItemFromBlock(Blocks.sand), "§6Fruit Digging"),
+ CATCH_A_FISH(Items.fish, "§3Catch a Fish"),
+ ZOMBIE_SHOOTOUT(Items.arrow, "§cZombie Shootout");
+
+ val singleDisplay by lazy {
+ Renderable.horizontalContainer(
+ listOf(
+ Renderable.itemStack(ItemStack(item)),
+ Renderable.string(display),
+ ),
+ )
+ }
+
+ val fullDisplay: List<Renderable>
+ get() {
+ val goals = getGoals.filterNot { it.isReached }
+ if (goals.isEmpty()) return emptyList()
+ return listOf(singleDisplay) + goals.map { Renderable.string(" " + it.display) }
+ }
+
+ val getGoals get() = CarnivalGoal.entries.filter { it.type == this }
+ }
+ }
+
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/features/event/carnival/CarnivalQuickStart.kt b/src/main/java/at/hannibal2/skyhanni/features/event/carnival/CarnivalQuickStart.kt
new file mode 100644
index 000000000..ee2a9028a
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/event/carnival/CarnivalQuickStart.kt
@@ -0,0 +1,60 @@
+package at.hannibal2.skyhanni.features.event.carnival
+
+import at.hannibal2.skyhanni.SkyHanniMod
+import at.hannibal2.skyhanni.data.Perk
+import at.hannibal2.skyhanni.events.EntityClickEvent
+import at.hannibal2.skyhanni.events.LorenzChatEvent
+import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule
+import at.hannibal2.skyhanni.utils.HypixelCommands
+import at.hannibal2.skyhanni.utils.LorenzUtils
+import at.hannibal2.skyhanni.utils.MobUtils.mob
+import at.hannibal2.skyhanni.utils.RegexUtils.matches
+import at.hannibal2.skyhanni.utils.SimpleTimeMark
+import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern
+import net.minecraft.entity.EntityLivingBase
+import net.minecraft.util.ChatComponentText
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import kotlin.time.Duration.Companion.seconds
+
+@SkyHanniModule
+object CarnivalQuickStart {
+
+ private val config get() = SkyHanniMod.feature.event.carnival.doubleClickToStart
+
+ /** REGEX-TEST: §eSelect an option: §r\n§e ➜ §a[Sure thing, partner!] §r\n§e ➜ §b[Could ya tell me the rules again?] §r\n§e ➜ §c[I'd like to do somthin' else fer now.]
+ * */
+ private val chatPattern by RepoPattern.pattern("carnival.select.option.chat", "§eSelect an option:.*")
+
+ private val repoGroup = RepoPattern.group("carnival.npcs")
+
+ private val pirate by repoGroup.pattern("pirate", "Carnival Pirateman")
+ private val fisher by repoGroup.pattern("fisher", "Carnival Fisherman")
+ private val cowboy by repoGroup.pattern("cowboy", "Carnival Cowboy")
+
+ private var lastChat = SimpleTimeMark.farPast()
+
+ @SubscribeEvent
+ fun onEntityClick(event: EntityClickEvent) {
+ if (!isEnabled()) return
+ if (lastChat.passedSince() > 5.0.seconds) return
+ val mob = (event.clickedEntity as? EntityLivingBase)?.mob ?: return
+ val type = when {
+ cowboy.matches(mob.name) -> "carnival_cowboy"
+ fisher.matches(mob.name) -> "carnival_fisherman"
+ pirate.matches(mob.name) -> "carnival_pirateman"
+ else -> return
+ }
+ HypixelCommands.npcOption(type, "r_2_1")
+ event.cancel()
+ }
+
+ @SubscribeEvent
+ fun onLorenzChat(event: LorenzChatEvent) {
+ if (!isEnabled()) return
+ // IDK what is wrong here, but it does not work with event.message
+ if (!chatPattern.matches((event.chatComponent as? ChatComponentText)?.chatComponentText_TextValue)) return
+ lastChat = SimpleTimeMark.now()
+ }
+
+ fun isEnabled() = LorenzUtils.inSkyBlock && config && Perk.CHIVALROUS_CARNIVAL.isActive && LorenzUtils.skyBlockArea == "Carnival"
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/features/event/carnival/CarnivalReminder.kt b/src/main/java/at/hannibal2/skyhanni/features/event/carnival/CarnivalReminder.kt
new file mode 100644
index 000000000..eff751661
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/event/carnival/CarnivalReminder.kt
@@ -0,0 +1,99 @@
+package at.hannibal2.skyhanni.features.event.carnival
+
+import at.hannibal2.skyhanni.SkyHanniMod
+import at.hannibal2.skyhanni.data.Perk
+import at.hannibal2.skyhanni.data.ProfileStorageData
+import at.hannibal2.skyhanni.events.LorenzChatEvent
+import at.hannibal2.skyhanni.events.ProfileJoinEvent
+import at.hannibal2.skyhanni.events.SecondPassedEvent
+import at.hannibal2.skyhanni.features.fame.ReminderUtils
+import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule
+import at.hannibal2.skyhanni.utils.ChatUtils
+import at.hannibal2.skyhanni.utils.HypixelCommands
+import at.hannibal2.skyhanni.utils.LorenzUtils
+import at.hannibal2.skyhanni.utils.RegexUtils.matches
+import at.hannibal2.skyhanni.utils.SimpleTimeMark
+import at.hannibal2.skyhanni.utils.SimpleTimeMark.Companion.fromNow
+import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.time.ZoneOffset
+import java.time.ZonedDateTime
+import kotlin.time.Duration.Companion.minutes
+import kotlin.time.Duration.Companion.seconds
+
+@SkyHanniModule
+object CarnivalReminder {
+
+ private val config get() = SkyHanniMod.feature.event.carnival
+ private val storage get() = ProfileStorageData.profileSpecific?.carnival
+
+ private var nextCheckTime = SimpleTimeMark.farFuture()
+
+ private var claimedToday = false
+
+ private var lastClaimedDay
+ get() = storage?.lastClaimedDay
+ set(value) {
+ storage?.lastClaimedDay = value
+ }
+
+ private val repoGroup = RepoPattern.group("carnival.tickets")
+
+ /** REGEX-TEST: §aYou claimed §r§aCarnival Ticket §r§8x25§r§a!
+ */
+ private val ticketClaimedPattern by repoGroup.pattern("claimed", "§aYou claimed §r§aCarnival Ticket §r§8x25§r§a!")
+
+ /** REGEX-TEST: §e[NPC] §aCarnival Leader§f: §rYou've already claimed your §aCarnival Tickets §ffor §btoday§f, but I'm happy to answer any questions you might have.
+ */
+ private val alreadyClaimedPattern by repoGroup.pattern(
+ "already",
+ "§e\\[NPC\\] §aCarnival Leader§f: §rYou've already claimed your §aCarnival Tickets §ffor §btoday§f, but I'm happy to answer any questions you might have.",
+ )
+
+ @SubscribeEvent
+ fun onSecondPassedEvent(event: SecondPassedEvent) {
+ if (!isEnabled() || nextCheckTime.isInFuture()) return
+ check()
+ }
+
+ @SubscribeEvent
+ fun onProfileJoin(event: ProfileJoinEvent) {
+ claimedToday = false
+ if (!isEnabled()) return
+ nextCheckTime = 30.0.seconds.fromNow()
+ checkDate()
+ check()
+ }
+
+ @SubscribeEvent
+ fun onLorenzChat(event: LorenzChatEvent) {
+ if (!isEnabled() && !claimedToday) return
+ if (!ticketClaimedPattern.matches(event.message) && !alreadyClaimedPattern.matches(event.message)) return
+ claimedToday = true
+ lastClaimedDay = ZonedDateTime.now(ZoneOffset.UTC).toLocalDate()
+ }
+
+ private fun checkDate() {
+ val currentDay = ZonedDateTime.now(ZoneOffset.UTC).toLocalDate()
+ val lastClaimedDay = lastClaimedDay
+
+ claimedToday = !(lastClaimedDay == null || currentDay.isAfter(lastClaimedDay))
+ }
+
+ fun check() {
+ if (claimedToday) {
+ checkDate()
+ } else if (!ReminderUtils.isBusy()) {
+ ChatUtils.clickToActionOrDisable(
+ "Carnival Tickets are ready to be claimed!",
+ config::reminderDailyTickets,
+ "warp to The Carnival",
+ ) {
+ HypixelCommands.warp("carnival")
+ }
+ nextCheckTime = 5.0.minutes.fromNow()
+ }
+ }
+
+ fun isEnabled() = LorenzUtils.inSkyBlock && config.reminderDailyTickets && Perk.CHIVALROUS_CARNIVAL.isActive
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/HypixelCommands.kt b/src/main/java/at/hannibal2/skyhanni/utils/HypixelCommands.kt
index 176afe8c0..c148e93e8 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/HypixelCommands.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/HypixelCommands.kt
@@ -32,6 +32,10 @@ object HypixelCommands {
send("recipe $itemName")
}
+ fun npcOption(npc: String, answer: String) {
+ send("selectnpcoption $npc $answer")
+ }
+
fun warp(warp: String) {
send("warp $warp")
}
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/json/BaseGsonBuilder.kt b/src/main/java/at/hannibal2/skyhanni/utils/json/BaseGsonBuilder.kt
index bc9f706f8..5b3e8c8e1 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/json/BaseGsonBuilder.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/json/BaseGsonBuilder.kt
@@ -11,6 +11,7 @@ import at.hannibal2.skyhanni.utils.tracker.SkyHanniTracker
import com.google.gson.GsonBuilder
import io.github.notenoughupdates.moulconfig.observer.PropertyTypeAdapterFactory
import net.minecraft.item.ItemStack
+import java.time.LocalDate
import java.util.UUID
object BaseGsonBuilder {
@@ -28,9 +29,10 @@ object BaseGsonBuilder {
.registerTypeAdapter(IslandType::class.java, SkyHanniTypeAdapters.ISLAND_TYPE.nullSafe())
.registerTypeAdapter(
SkyHanniTracker.DefaultDisplayMode::class.java,
- SkyHanniTypeAdapters.TRACKER_DISPLAY_MODE.nullSafe()
+ SkyHanniTypeAdapters.TRACKER_DISPLAY_MODE.nullSafe(),
)
.registerTypeAdapter(SimpleTimeMark::class.java, SkyHanniTypeAdapters.TIME_MARK.nullSafe())
+ .registerTypeAdapter(LocalDate::class.java, SkyHanniTypeAdapters.LOCALE_DATE.nullSafe())
.enableComplexMapKeySerialization()
fun lenientGson(): GsonBuilder = gson()
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/json/SkyHanniTypeAdapters.kt b/src/main/java/at/hannibal2/skyhanni/utils/json/SkyHanniTypeAdapters.kt
index 7859853ca..2048be522 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/json/SkyHanniTypeAdapters.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/json/SkyHanniTypeAdapters.kt
@@ -18,9 +18,11 @@ import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import net.minecraft.item.ItemStack
+import java.time.LocalDate
import java.util.UUID
object SkyHanniTypeAdapters {
+
val NEU_ITEMSTACK: TypeAdapter<ItemStack> = SimpleStringTypeAdapter(NEUItems::saveNBTData, NEUItems::loadNBTData)
val UUID: TypeAdapter<UUID> = SimpleStringTypeAdapter(
@@ -72,7 +74,19 @@ object SkyHanniTypeAdapters {
val ISLAND_TYPE = SimpleStringTypeAdapter.forEnum<IslandType>()
val RARITY = SimpleStringTypeAdapter.forEnum<LorenzRarity>()
- inline fun <reified T> GsonBuilder.registerTypeAdapter(
+ val LOCALE_DATE = object : TypeAdapter<LocalDate>() {
+ override fun write(out: JsonWriter, value: LocalDate) {
+ out.value(value.toString())
+ }
+
+ override fun read(reader: JsonReader): LocalDate {
+ return LocalDate.parse(reader.nextString())
+ }
+ }
+
+ inline
+
+ fun <reified T> GsonBuilder.registerTypeAdapter(
crossinline write: (JsonWriter, T) -> Unit,
crossinline read: (JsonReader) -> T,
): GsonBuilder {