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 1c916bbe15ce..41d5771ab475 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 b874f7558d8e..cad2b7ea3d72 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 5938eebdb326..83e7ea47c254 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.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; @@ -159,6 +160,22 @@ public static class PositionChange { public ChocolateFactoryStrayTracker.Data strayTracker = new ChocolateFactoryStrayTracker.Data(); } + @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 goals = new HashMap<>(); + } + @Expose public MaxwellPowerStorage maxwell = new 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 000000000000..c36212a22d8b --- /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): 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() + + @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 + 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 000000000000..ee2a9028ae7e --- /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 000000000000..eff7516617b1 --- /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 176afe8c0860..c148e93e80fd 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 bc9f706f8c2f..5b3e8c8e1f58 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 7859853ca45f..2048be522061 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 = SimpleStringTypeAdapter(NEUItems::saveNBTData, NEUItems::loadNBTData) val UUID: TypeAdapter = SimpleStringTypeAdapter( @@ -72,7 +74,19 @@ object SkyHanniTypeAdapters { val ISLAND_TYPE = SimpleStringTypeAdapter.forEnum() val RARITY = SimpleStringTypeAdapter.forEnum() - inline fun GsonBuilder.registerTypeAdapter( + val LOCALE_DATE = object : TypeAdapter() { + override fun write(out: JsonWriter, value: LocalDate) { + out.value(value.toString()) + } + + override fun read(reader: JsonReader): LocalDate { + return LocalDate.parse(reader.nextString()) + } + } + + inline + + fun GsonBuilder.registerTypeAdapter( crossinline write: (JsonWriter, T) -> Unit, crossinline read: (JsonReader) -> T, ): GsonBuilder {