From 0ca988c907c7e8e26029f59cc098e6be5e008ee5 Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Sat, 7 Dec 2024 22:30:51 +0100 Subject: feat: Add dungeon chest loot detection --- .../nea/ledger/events/RegistrationFinishedEvent.kt | 7 ++ src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt | 29 ++++++- src/main/kotlin/moe/nea/ledger/ItemUtil.kt | 10 +++ src/main/kotlin/moe/nea/ledger/Ledger.kt | 2 + src/main/kotlin/moe/nea/ledger/LedgerLogger.kt | 4 + .../moe/nea/ledger/events/ExtraSupplyIdEvent.kt | 12 +++ .../nea/ledger/modules/DungeonChestDetection.kt | 91 +++++++++++----------- .../kotlin/moe/nea/ledger/modules/KatDetection.kt | 7 +- 8 files changed, 109 insertions(+), 53 deletions(-) create mode 100644 src/main/java/moe/nea/ledger/events/RegistrationFinishedEvent.kt create mode 100644 src/main/kotlin/moe/nea/ledger/events/ExtraSupplyIdEvent.kt (limited to 'src/main') diff --git a/src/main/java/moe/nea/ledger/events/RegistrationFinishedEvent.kt b/src/main/java/moe/nea/ledger/events/RegistrationFinishedEvent.kt new file mode 100644 index 0000000..d36e0c7 --- /dev/null +++ b/src/main/java/moe/nea/ledger/events/RegistrationFinishedEvent.kt @@ -0,0 +1,7 @@ +package moe.nea.ledger.events + +import net.minecraftforge.fml.common.eventhandler.Event + +class RegistrationFinishedEvent : Event() { + +} diff --git a/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt b/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt index f582015..f4a0232 100644 --- a/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt +++ b/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt @@ -1,6 +1,8 @@ package moe.nea.ledger import moe.nea.ledger.events.BeforeGuiAction +import moe.nea.ledger.events.ExtraSupplyIdEvent +import moe.nea.ledger.events.RegistrationFinishedEvent import net.minecraft.client.Minecraft import net.minecraft.item.ItemStack import net.minecraft.nbt.NBTTagCompound @@ -25,6 +27,11 @@ class ItemIdProvider { private val knownNames = mutableMapOf() + @SubscribeEvent + fun onTick(event: RegistrationFinishedEvent) { + MinecraftForge.EVENT_BUS.post(ExtraSupplyIdEvent(knownNames::put)) + } + @SubscribeEvent(priority = EventPriority.HIGH) fun savePlayerInventoryIds(event: BeforeGuiAction) { val player = Minecraft.getMinecraft().thePlayer ?: return @@ -66,14 +73,30 @@ class ItemIdProvider { } private val coinRegex = "(?$SHORT_NUMBER_PATTERN) Coins?".toPattern() - private val stackedItem = "(?.*) x(?$SHORT_NUMBER_PATTERN)".toPattern() + private val stackedItemRegex = "(?.*) x(?$SHORT_NUMBER_PATTERN)".toPattern() + private val essenceRegex = "(?.*) Essence x(?$SHORT_NUMBER_PATTERN)".toPattern() - fun findFromLore(name: String): Pair? { + fun findCostItemsFromSpan(lore: List): List> { + return lore.iterator().asSequence() + .dropWhile { it.unformattedString() != "Cost" }.drop(1) + .takeWhile { it != "" } + .map { findStackableItemByName(it) ?: Pair(ItemId.NIL, 1.0) } + .toList() + } + + fun findStackableItemByName(name: String): Pair? { val properName = name.unformattedString() + if (properName == "FREE") { + return Pair(ItemId.COINS, 0.0) + } coinRegex.useMatcher(properName) { return Pair(ItemId.COINS, parseShortNumber(group("amount"))) } - stackedItem.useMatcher(properName) { + essenceRegex.useMatcher(properName) { + return Pair(ItemId("ESSENCE_${group("essence").uppercase()}"), + parseShortNumber(group("count"))) + } + stackedItemRegex.useMatcher(properName) { val item = findForName(group("name")) if (item != null) { val count = parseShortNumber(group("count")) diff --git a/src/main/kotlin/moe/nea/ledger/ItemUtil.kt b/src/main/kotlin/moe/nea/ledger/ItemUtil.kt index 38c2b50..cadcb66 100644 --- a/src/main/kotlin/moe/nea/ledger/ItemUtil.kt +++ b/src/main/kotlin/moe/nea/ledger/ItemUtil.kt @@ -16,9 +16,19 @@ fun ItemStack.getInternalId(): ItemId? { if (id == "PET") { id = getPetId() ?: id } + if (id == "ENCHANTED_BOOK") { + id = getEnchanments().entries.singleOrNull()?.let { + "${it.key};${it.value}".uppercase() + } + } return id?.let(::ItemId) } +fun ItemStack.getEnchanments(): Map { + val enchantments = getExtraAttributes().getCompoundTag("enchantments") + return enchantments.keySet.associateWith { enchantments.getInteger(it) } +} + class PetInfo { var type: String? = null var tier: String? = null diff --git a/src/main/kotlin/moe/nea/ledger/Ledger.kt b/src/main/kotlin/moe/nea/ledger/Ledger.kt index 8a4b850..ee15a83 100644 --- a/src/main/kotlin/moe/nea/ledger/Ledger.kt +++ b/src/main/kotlin/moe/nea/ledger/Ledger.kt @@ -6,6 +6,7 @@ import moe.nea.ledger.config.LedgerConfig import moe.nea.ledger.database.Database import moe.nea.ledger.events.ChatReceived import moe.nea.ledger.events.LateWorldLoadEvent +import moe.nea.ledger.events.RegistrationFinishedEvent import moe.nea.ledger.modules.AuctionHouseDetection import moe.nea.ledger.modules.BankDetection import moe.nea.ledger.modules.BazaarDetection @@ -110,6 +111,7 @@ class Ledger { .forEach { ClientCommandHandler.instance.registerCommand(it) } di.provide().loadAndUpgrade() + MinecraftForge.EVENT_BUS.post(RegistrationFinishedEvent()) } var lastJoin = -1L diff --git a/src/main/kotlin/moe/nea/ledger/LedgerLogger.kt b/src/main/kotlin/moe/nea/ledger/LedgerLogger.kt index 76b8741..e8c15ea 100644 --- a/src/main/kotlin/moe/nea/ledger/LedgerLogger.kt +++ b/src/main/kotlin/moe/nea/ledger/LedgerLogger.kt @@ -157,6 +157,10 @@ enum class TransactionType { value class ItemId( val string: String ) { + fun singleItem(): Pair { + return Pair(this, 1.0) + } + companion object { val COINS = ItemId("SKYBLOCK_COIN") val BITS = ItemId("SKYBLOCK_BIT") diff --git a/src/main/kotlin/moe/nea/ledger/events/ExtraSupplyIdEvent.kt b/src/main/kotlin/moe/nea/ledger/events/ExtraSupplyIdEvent.kt new file mode 100644 index 0000000..d040961 --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/events/ExtraSupplyIdEvent.kt @@ -0,0 +1,12 @@ +package moe.nea.ledger.events + +import moe.nea.ledger.ItemId +import net.minecraftforge.fml.common.eventhandler.Event + +class ExtraSupplyIdEvent( + private val store: (String, ItemId) -> Unit +) : Event() { + fun store(name: String, id: ItemId) { + store.invoke(name, id) + } +} diff --git a/src/main/kotlin/moe/nea/ledger/modules/DungeonChestDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/DungeonChestDetection.kt index 5598174..c50e9eb 100644 --- a/src/main/kotlin/moe/nea/ledger/modules/DungeonChestDetection.kt +++ b/src/main/kotlin/moe/nea/ledger/modules/DungeonChestDetection.kt @@ -1,23 +1,26 @@ package moe.nea.ledger.modules +import moe.nea.ledger.ExpiringValue import moe.nea.ledger.ItemChange import moe.nea.ledger.ItemId +import moe.nea.ledger.ItemIdProvider import moe.nea.ledger.LedgerEntry import moe.nea.ledger.LedgerLogger -import moe.nea.ledger.SHORT_NUMBER_PATTERN import moe.nea.ledger.TransactionType import moe.nea.ledger.events.ChatReceived +import moe.nea.ledger.events.ExtraSupplyIdEvent import moe.nea.ledger.events.GuiClickEvent import moe.nea.ledger.getDisplayNameU +import moe.nea.ledger.getInternalId import moe.nea.ledger.getLore -import moe.nea.ledger.parseShortNumber import moe.nea.ledger.unformattedString import moe.nea.ledger.useMatcher import moe.nea.ledger.utils.Inject +import net.minecraft.init.Blocks +import net.minecraft.item.Item import net.minecraftforge.fml.common.eventhandler.SubscribeEvent -import net.minecraftforge.fml.common.gameevent.TickEvent import java.time.Instant -import java.util.regex.Pattern +import kotlin.time.Duration.Companion.seconds class DungeonChestDetection @Inject constructor(val logger: LedgerLogger) { @@ -49,16 +52,8 @@ class DungeonChestDetection @Inject constructor(val logger: LedgerLogger) { Damage: 0s } */ - val costPattern = Pattern.compile("(?$SHORT_NUMBER_PATTERN) Coins") - - - data class ChestCost( - val cost: Double, - val openTimestamp: Long, - val hasKey: Boolean, - ) - - var lastOpenedChest: ChestCost? = null + @Inject + lateinit var itemIdProvider: ItemIdProvider @SubscribeEvent fun onKismetClick(event: GuiClickEvent) { @@ -78,6 +73,19 @@ class DungeonChestDetection @Inject constructor(val logger: LedgerLogger) { } } + data class ChestCost( + val diff: List, + val timestamp: Instant, + ) + + var lastOpenedChest = ExpiringValue.empty() + + @SubscribeEvent + fun supplyExtraIds(event: ExtraSupplyIdEvent) { + event.store("Dungeon Chest Key", ItemId("DUNGEON_CHEST_KEY")) + event.store("Kismet Feather", ItemId("KISMET_FEATHER")) + } + @SubscribeEvent fun onRewardChestClick(event: GuiClickEvent) { val slot = event.slotIn ?: return @@ -86,41 +94,36 @@ class DungeonChestDetection @Inject constructor(val logger: LedgerLogger) { val name = stack.getDisplayNameU() if (name != "§aOpen Reward Chest") return val lore = stack.getLore() - val costIndex = lore.indexOf("§7Cost") - if (costIndex < 0 || costIndex + 1 !in lore.indices) return - val cost = costPattern.useMatcher(lore[costIndex + 1].unformattedString()) { - parseShortNumber(group("cost")) - } ?: 0.0 // Free chest! - val hasKey = lore.contains("§9Dungeon Chest Key") - lastOpenedChest?.let(::completeTransaction) - lastOpenedChest = ChestCost(cost, System.currentTimeMillis(), hasKey) + val cost = itemIdProvider.findCostItemsFromSpan(lore) + val gain = (9..18) + .mapNotNull { slot.inventory.getStackInSlot(it) } + .filter { it.item != Item.getItemFromBlock(Blocks.stained_glass_pane) } + .map { + it.getInternalId()?.singleItem() + ?: itemIdProvider.findStackableItemByName(it.displayName) + ?: Pair(ItemId.NIL, it.stackSize.toDouble()) + } + lastOpenedChest = ExpiringValue(ChestCost( + cost.map { ItemChange.lose(it.first, it.second) } + + gain.map { ItemChange.gain(it.first, it.second) }, + Instant.now() + )) } + val rewardMessage = " .* CHEST REWARDS".toPattern() + @SubscribeEvent fun onChatMessage(event: ChatReceived) { - if (event.message == "You don't have that many coins in the bank!") - lastOpenedChest = null - } - - fun completeTransaction(toOpen: ChestCost) { - lastOpenedChest = null - logger.logEntry( - LedgerEntry( + if (event.message == "You don't have that many coins in the bank!") { + lastOpenedChest.take() + } + rewardMessage.useMatcher(event.message) { + val chest = lastOpenedChest.consume(3.seconds) ?: return + logger.logEntry(LedgerEntry( TransactionType.DUNGEON_CHEST_OPEN, - Instant.ofEpochMilli(toOpen.openTimestamp), - listOfNotNull( - if (toOpen.hasKey) ItemChange.lose(ItemId.DUNGEON_CHEST_KEY, 1) else null, - ItemChange.loseCoins(toOpen.cost) - ), - ) - ) - } - - @SubscribeEvent - fun onTick(event: TickEvent) { - val toOpen = lastOpenedChest - if (toOpen != null && toOpen.openTimestamp + 1000L < System.currentTimeMillis()) { - completeTransaction(toOpen) + chest.timestamp, + chest.diff, + )) } } } diff --git a/src/main/kotlin/moe/nea/ledger/modules/KatDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/KatDetection.kt index 8a2aa19..4e2e40a 100644 --- a/src/main/kotlin/moe/nea/ledger/modules/KatDetection.kt +++ b/src/main/kotlin/moe/nea/ledger/modules/KatDetection.kt @@ -10,7 +10,6 @@ import moe.nea.ledger.events.BeforeGuiAction import moe.nea.ledger.events.ChatReceived import moe.nea.ledger.getInternalId import moe.nea.ledger.getLore -import moe.nea.ledger.unformattedString import moe.nea.ledger.useMatcher import moe.nea.ledger.utils.Inject import net.minecraftforge.fml.common.eventhandler.SubscribeEvent @@ -61,11 +60,7 @@ class KatDetection { val beforePetId = petItem.getInternalId() ?: return val confirmItem = slots.lowerChestInventory.getStackInSlot(confirmSlot) ?: return val lore = confirmItem.getLore() - val cost = lore.iterator().asSequence() - .dropWhile { it.unformattedString() != "Cost" }.drop(1) - .takeWhile { it != "" } - .map { itemIdProvider.findFromLore(it) ?: Pair(ItemId.NIL, 1.0) } - .toList() + val cost = itemIdProvider.findCostItemsFromSpan(lore) lastPetUpgradeScheduled = PetUpgrade(beforePetId, cost) } -- cgit