aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/moe/nea/ledger/modules
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2024-12-06 19:27:07 +0100
committerLinnea Gräf <nea@nea.moe>2024-12-06 19:28:23 +0100
commitf7507f384459b57460af899bf9ceae4f52f1ea21 (patch)
tree410d70c0a35de852278b03ac9243080d7e0a0490 /src/main/kotlin/moe/nea/ledger/modules
parent2c9132d0c755964800b710ce43c8feebd44f1299 (diff)
downloadLocalTransactionLedger-f7507f384459b57460af899bf9ceae4f52f1ea21.tar.gz
LocalTransactionLedger-f7507f384459b57460af899bf9ceae4f52f1ea21.tar.bz2
LocalTransactionLedger-f7507f384459b57460af899bf9ceae4f52f1ea21.zip
refactor: Add DI and packages
Diffstat (limited to 'src/main/kotlin/moe/nea/ledger/modules')
-rw-r--r--src/main/kotlin/moe/nea/ledger/modules/AuctionHouseDetection.kt100
-rw-r--r--src/main/kotlin/moe/nea/ledger/modules/BankDetection.kt42
-rw-r--r--src/main/kotlin/moe/nea/ledger/modules/BazaarDetection.kt47
-rw-r--r--src/main/kotlin/moe/nea/ledger/modules/BazaarOrderDetection.kt78
-rw-r--r--src/main/kotlin/moe/nea/ledger/modules/BitsDetection.kt58
-rw-r--r--src/main/kotlin/moe/nea/ledger/modules/BitsShopDetection.kt59
-rw-r--r--src/main/kotlin/moe/nea/ledger/modules/DungeonChestDetection.kt121
-rw-r--r--src/main/kotlin/moe/nea/ledger/modules/MinionDetection.kt54
-rw-r--r--src/main/kotlin/moe/nea/ledger/modules/NpcDetection.kt46
9 files changed, 605 insertions, 0 deletions
diff --git a/src/main/kotlin/moe/nea/ledger/modules/AuctionHouseDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/AuctionHouseDetection.kt
new file mode 100644
index 0000000..cbbff12
--- /dev/null
+++ b/src/main/kotlin/moe/nea/ledger/modules/AuctionHouseDetection.kt
@@ -0,0 +1,100 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.events.BeforeGuiAction
+import moe.nea.ledger.events.ChatReceived
+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.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.client.gui.inventory.GuiChest
+import net.minecraft.inventory.ContainerChest
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.util.regex.Pattern
+
+class AuctionHouseDetection @Inject constructor(val ledger: LedgerLogger, val ids: ItemIdProvider) {
+ data class LastViewedItem(
+ val count: Int,
+ val id: String,
+ )
+ /*
+ You collected 8,712,000 coins from selling Ultimate Carrot Candy Upgrade to [VIP] kodokush in an auction!
+ You collected 60,000 coins from selling Walnut to [MVP++] Alea1337 in an auction!
+ You purchased 2x Walnut for 69 coins!
+ You purchased ◆ Ice Rune I for 4,000 coins!
+ */
+
+ val collectSold =
+ Pattern.compile("You collected (?<coins>$SHORT_NUMBER_PATTERN) coins? from selling (?<what>.*) to (?<buyer>.*) in an auction!")
+ val purchased =
+ Pattern.compile("You purchased (?:(?<amount>[0-9]+)x )?(?<what>.*) for (?<coins>$SHORT_NUMBER_PATTERN) coins!")
+ var lastViewedItems: MutableList<LastViewedItem> = mutableListOf()
+
+ @SubscribeEvent
+ fun onEvent(event: ChatReceived) {
+ collectSold.useMatcher(event.message) {
+ val lastViewedItem = lastViewedItems.removeLastOrNull()
+ ledger.logEntry(
+ LedgerEntry(
+ "AUCTION_SOLD",
+ event.timestamp,
+ parseShortNumber(group("coins")),
+ lastViewedItem?.id,
+ lastViewedItem?.count
+ )
+ )
+ }
+ purchased.useMatcher(event.message) {
+ ledger.logEntry(
+ LedgerEntry(
+ "AUCTION_BOUGHT",
+ event.timestamp,
+ parseShortNumber(group("coins")),
+ ids.findForName(group("what")),
+ group("amount")?.toInt() ?: 1
+ )
+ )
+ }
+ }
+
+ @SubscribeEvent
+ fun onBeforeAuctionCollected(event: BeforeGuiAction) {
+ val chest = (event.gui as? GuiChest) ?: return
+ val slots = chest.inventorySlots as ContainerChest
+ val name = slots.lowerChestInventory.displayName.unformattedText.unformattedString()
+
+ if (name == "BIN Auction View" || name == "Auction View") {
+ handleCollectSingleAuctionView(slots)
+ }
+ if (name == "Manage Auctions") {
+ handleCollectMultipleAuctionsView(slots)
+ }
+ }
+
+ private fun handleCollectMultipleAuctionsView(slots: ContainerChest) {
+ lastViewedItems =
+ (0 until slots.lowerChestInventory.sizeInventory)
+ .mapNotNull { slots.lowerChestInventory.getStackInSlot(it) }
+ .filter {
+ it.getLore().contains("§7Status: §aSold!") // BINs
+ || it.getLore().contains("§7Status: §aEnded!") // Auctions
+ }
+ .mapNotNull { LastViewedItem(it.stackSize, it.getInternalId() ?: return@mapNotNull null) }
+ .toMutableList()
+ }
+
+
+ fun handleCollectSingleAuctionView(slots: ContainerChest) {
+ val soldItem = slots.lowerChestInventory.getStackInSlot(9 + 4) ?: return
+ val id = soldItem.getInternalId() ?: return
+ val count = soldItem.stackSize
+ lastViewedItems = mutableListOf(LastViewedItem(count, id))
+ }
+
+
+} \ No newline at end of file
diff --git a/src/main/kotlin/moe/nea/ledger/modules/BankDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/BankDetection.kt
new file mode 100644
index 0000000..8d0fd81
--- /dev/null
+++ b/src/main/kotlin/moe/nea/ledger/modules/BankDetection.kt
@@ -0,0 +1,42 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.LedgerEntry
+import moe.nea.ledger.LedgerLogger
+import moe.nea.ledger.SHORT_NUMBER_PATTERN
+import moe.nea.ledger.events.ChatReceived
+import moe.nea.ledger.parseShortNumber
+import moe.nea.ledger.useMatcher
+import moe.nea.ledger.utils.Inject
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.util.regex.Pattern
+
+
+class BankDetection @Inject constructor(val ledger: LedgerLogger) {
+ val withdrawPattern =
+ Pattern.compile("^(You have withdrawn|Withdrew) (?<amount>$SHORT_NUMBER_PATTERN) coins?! (?:There's now|You now have) (?<newtotal>$SHORT_NUMBER_PATTERN) coins? (?:left in the account!|in your account!)$")
+ val depositPattern =
+ Pattern.compile("^(?:You have deposited|Deposited) (?<amount>$SHORT_NUMBER_PATTERN) coins?! (?:There's now|You now have) (?<newtotal>$SHORT_NUMBER_PATTERN) coins? (?:in your account!|in the account!)$")
+
+ @SubscribeEvent
+ fun onChat(event: ChatReceived) {
+ withdrawPattern.useMatcher(event.message) {
+ ledger.logEntry(
+ LedgerEntry(
+ "BANK_WITHDRAW",
+ event.timestamp,
+ parseShortNumber(group("amount")),
+ )
+ )
+ }
+ depositPattern.useMatcher(event.message) {
+ ledger.logEntry(
+ LedgerEntry(
+ "BANK_DEPOSIT",
+ event.timestamp,
+ parseShortNumber(group("amount")),
+ )
+ )
+ }
+ }
+
+}
diff --git a/src/main/kotlin/moe/nea/ledger/modules/BazaarDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/BazaarDetection.kt
new file mode 100644
index 0000000..01c8bbc
--- /dev/null
+++ b/src/main/kotlin/moe/nea/ledger/modules/BazaarDetection.kt
@@ -0,0 +1,47 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.events.ChatReceived
+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.parseShortNumber
+import moe.nea.ledger.useMatcher
+import moe.nea.ledger.utils.Inject
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.util.regex.Pattern
+
+class BazaarDetection @Inject constructor(val ledger: LedgerLogger, val ids: ItemIdProvider) {
+
+ val instaBuyPattern =
+ Pattern.compile("\\[Bazaar\\] Bought (?<count>$SHORT_NUMBER_PATTERN)x (?<what>.*) for (?<coins>$SHORT_NUMBER_PATTERN) coins!")
+ val instaSellPattern =
+ Pattern.compile("\\[Bazaar\\] Sold (?<count>$SHORT_NUMBER_PATTERN)x (?<what>.*) for (?<coins>$SHORT_NUMBER_PATTERN) coins!")
+
+
+ @SubscribeEvent
+ fun onInstSellChat(event: ChatReceived) {
+ instaBuyPattern.useMatcher(event.message) {
+ ledger.logEntry(
+ LedgerEntry(
+ "BAZAAR_BUY_INSTANT",
+ event.timestamp,
+ parseShortNumber(group("coins")),
+ ids.findForName(group("what")),
+ parseShortNumber(group("count")).toInt(),
+ )
+ )
+ }
+ instaSellPattern.useMatcher(event.message) {
+ ledger.logEntry(
+ LedgerEntry(
+ "BAZAAR_SELL_INSTANT",
+ event.timestamp,
+ parseShortNumber(group("coins")),
+ ids.findForName(group("what")),
+ parseShortNumber(group("count")).toInt(),
+ )
+ )
+ }
+ }
+}
diff --git a/src/main/kotlin/moe/nea/ledger/modules/BazaarOrderDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/BazaarOrderDetection.kt
new file mode 100644
index 0000000..7e611ac
--- /dev/null
+++ b/src/main/kotlin/moe/nea/ledger/modules/BazaarOrderDetection.kt
@@ -0,0 +1,78 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.events.ChatReceived
+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.mixin.AccessorGuiEditSign
+import moe.nea.ledger.parseShortNumber
+import moe.nea.ledger.useMatcher
+import moe.nea.ledger.utils.Inject
+import net.minecraft.client.gui.inventory.GuiEditSign
+import net.minecraftforge.client.event.GuiScreenEvent
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.util.regex.Pattern
+
+class BazaarOrderDetection @Inject constructor(val ledger: LedgerLogger, val ids: ItemIdProvider) {
+
+ val buyOrderClaimed =
+ Pattern.compile("\\[Bazaar] Claimed (?<amount>$SHORT_NUMBER_PATTERN)x (?<what>.*) worth (?<coins>$SHORT_NUMBER_PATTERN) coins? bought for $SHORT_NUMBER_PATTERN each!")
+ val sellOrderClaimed =
+ Pattern.compile("\\[Bazaar] Claimed (?<coins>$SHORT_NUMBER_PATTERN) coins? from selling (?<amount>$SHORT_NUMBER_PATTERN)x (?<what>.*) at $SHORT_NUMBER_PATTERN each!")
+ val orderFlipped =
+ Pattern.compile("\\[Bazaar] Order Flipped! (?<amount>$SHORT_NUMBER_PATTERN)x (?<what>.*) for (?<coins>$SHORT_NUMBER_PATTERN) coins? of total expected profit.")
+ val previousPricePattern =
+ Pattern.compile("(?<price>$SHORT_NUMBER_PATTERN)/u")
+ var lastFlippedPreviousPrice = 0.0
+
+ @SubscribeEvent
+ fun detectSignFlip(event: GuiScreenEvent.InitGuiEvent) {
+ val gui = event.gui
+ if (gui !is GuiEditSign) return
+ gui as AccessorGuiEditSign
+ val text = gui.tileEntity_ledger.signText
+ if (text[2].unformattedText != "Previous price:") return
+ previousPricePattern.useMatcher(text[3].unformattedText) {
+ lastFlippedPreviousPrice = parseShortNumber(group("price"))
+ }
+ }
+
+ @SubscribeEvent
+ fun detectBuyOrders(event: ChatReceived) {
+ orderFlipped.useMatcher(event.message) {
+ val amount = parseShortNumber(group("amount")).toInt()
+ ledger.logEntry(
+ LedgerEntry(
+ "BAZAAR_BUY_ORDER",
+ event.timestamp,
+ lastFlippedPreviousPrice * amount,
+ ids.findForName(group("what")),
+ amount,
+ )
+ )
+ }
+ buyOrderClaimed.useMatcher(event.message) {
+ ledger.logEntry(
+ LedgerEntry(
+ "BAZAAR_BUY_ORDER",
+ event.timestamp,
+ parseShortNumber(group("coins")),
+ ids.findForName(group("what")),
+ parseShortNumber(group("amount")).toInt(),
+ )
+ )
+ }
+ sellOrderClaimed.useMatcher(event.message) {
+ ledger.logEntry(
+ LedgerEntry(
+ "BAZAAR_SELL_ORDER",
+ event.timestamp,
+ parseShortNumber(group("coins")),
+ ids.findForName(group("what")),
+ parseShortNumber(group("amount")).toInt(),
+ )
+ )
+ }
+ }
+}
diff --git a/src/main/kotlin/moe/nea/ledger/modules/BitsDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/BitsDetection.kt
new file mode 100644
index 0000000..22b5392
--- /dev/null
+++ b/src/main/kotlin/moe/nea/ledger/modules/BitsDetection.kt
@@ -0,0 +1,58 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.events.ChatReceived
+import moe.nea.ledger.events.LateWorldLoadEvent
+import moe.nea.ledger.LedgerEntry
+import moe.nea.ledger.LedgerLogger
+import moe.nea.ledger.SHORT_NUMBER_PATTERN
+import moe.nea.ledger.ScoreboardUtil
+import moe.nea.ledger.parseShortNumber
+import moe.nea.ledger.unformattedString
+import moe.nea.ledger.useMatcher
+import moe.nea.ledger.utils.Inject
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.time.Instant
+
+class BitsDetection @Inject constructor(val ledger: LedgerLogger) {
+
+ var lastBits = -1
+
+ val bitScoreboardRegex = "Bits: (?<purse>$SHORT_NUMBER_PATTERN)".toPattern()
+
+ @SubscribeEvent
+ fun onWorldSwitch(event: LateWorldLoadEvent) {
+ ScoreboardUtil.getScoreboardStrings().forEach {
+ bitScoreboardRegex.useMatcher<Unit>(it.unformattedString()) {
+ val bits = parseShortNumber(group("purse")).toInt()
+ if (lastBits != bits) {
+ ledger.logEntry(
+ LedgerEntry(
+ "BITS_PURSE_STATUS",
+ Instant.now(),
+ 0.0,
+ null,
+ bits
+ )
+ )
+ lastBits = bits
+ }
+ return
+ }
+ }
+ }
+
+ @SubscribeEvent
+ fun onEvent(event: ChatReceived) {
+ if (event.message.startsWith("You consumed a Booster Cookie!")) {
+ ledger.logEntry(
+ LedgerEntry(
+ "BOOSTER_COOKIE_ATE",
+ Instant.now(),
+ 0.0,
+ null,
+ null,
+ )
+ )
+ }
+ }
+}
diff --git a/src/main/kotlin/moe/nea/ledger/modules/BitsShopDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/BitsShopDetection.kt
new file mode 100644
index 0000000..2033d8d
--- /dev/null
+++ b/src/main/kotlin/moe/nea/ledger/modules/BitsShopDetection.kt
@@ -0,0 +1,59 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.events.ChatReceived
+import moe.nea.ledger.events.GuiClickEvent
+import moe.nea.ledger.LedgerEntry
+import moe.nea.ledger.LedgerLogger
+import moe.nea.ledger.SHORT_NUMBER_PATTERN
+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.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.time.Instant
+
+class BitsShopDetection @Inject constructor(val ledger: LedgerLogger) {
+
+
+ data class BitShopEntry(
+ val id: String,
+ val bitPrice: Int,
+ val timestamp: Long = System.currentTimeMillis()
+ )
+
+ var lastClickedBitShopItem: BitShopEntry? = null
+ var bitCostPattern = "(?<cost>$SHORT_NUMBER_PATTERN) Bits".toPattern()
+
+ @SubscribeEvent
+ fun recordLastBitPrice(event: GuiClickEvent) {
+ val slot = event.slotIn ?: return
+ val name = slot.inventory.displayName.unformattedText.unformattedString()
+ if (name != "Community Shop" && !name.startsWith("Bits Shop"))
+ return
+ val stack = slot.stack ?: return
+ val id = stack.getInternalId() ?: return
+ val bitPrice = stack.getLore()
+ .firstNotNullOfOrNull { bitCostPattern.useMatcher(it.unformattedString()) { parseShortNumber(group("cost")).toInt() } }
+ ?: return
+ lastClickedBitShopItem = BitShopEntry(id, bitPrice)
+ }
+
+ @SubscribeEvent
+ fun onChat(event: ChatReceived) {
+ if (event.message.startsWith("You bought ")) {
+ val lastBit = lastClickedBitShopItem ?: return
+ if (System.currentTimeMillis() - lastBit.timestamp > 5000) return
+ ledger.logEntry(
+ LedgerEntry(
+ "COMMUNITY_SHOP_BUY", Instant.now(),
+ lastBit.bitPrice.toDouble(),
+ lastBit.id,
+ 1
+ )
+ )
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/main/kotlin/moe/nea/ledger/modules/DungeonChestDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/DungeonChestDetection.kt
new file mode 100644
index 0000000..4bdd37c
--- /dev/null
+++ b/src/main/kotlin/moe/nea/ledger/modules/DungeonChestDetection.kt
@@ -0,0 +1,121 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.events.ChatReceived
+import moe.nea.ledger.events.GuiClickEvent
+import moe.nea.ledger.LedgerEntry
+import moe.nea.ledger.LedgerLogger
+import moe.nea.ledger.SHORT_NUMBER_PATTERN
+import moe.nea.ledger.getDisplayNameU
+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.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import net.minecraftforge.fml.common.gameevent.TickEvent
+import java.time.Instant
+import java.util.regex.Pattern
+
+class DungeonChestDetection @Inject constructor(val logger: LedgerLogger) {
+
+ /*{
+ id: "minecraft:chest",
+ Count: 1b,
+ tag: {
+ display: {
+ Lore: ["§7Purchase this chest to receive the", "§7rewards above. You can only open", "§7one chest per Dungeons run -", "§7choose wisely!", "", "§7Cost", "§625,000 Coins", "§9Dungeon Chest Key", "", "§7§cNOTE: Coins are withdrawn from your", "§cbank if you don't have enough in", "§cyour purse."],
+ Name: "§aOpen Reward Chest"
+ }
+ },
+ Damage: 0s
+ }
+
+ {
+ id: "minecraft:feather",
+ Count: 1b,
+ tag: {
+ overrideMeta: 1b,
+ ench: [],
+ HideFlags: 254,
+ display: {
+ Lore: ["§7Consume a §9Kismet Feather §7to reroll", "§7the loot within this chest.", "", "§7You may only use a feather once", "§7per dungeon run.", "", "§eClick to reroll this chest!"],
+ Name: "§aReroll Chest"
+ },
+ AttributeModifiers: []
+ },
+ Damage: 0s
+}
+ */
+ val costPattern = Pattern.compile("(?<cost>$SHORT_NUMBER_PATTERN) Coins")
+
+
+ data class ChestCost(
+ val cost: Double,
+ val openTimestamp: Long,
+ val hasKey: Boolean,
+ )
+
+ var lastOpenedChest: ChestCost? = null
+
+ @SubscribeEvent
+ fun onKismetClick(event: GuiClickEvent) {
+ val slot = event.slotIn ?: return
+ if (!slot.inventory.displayName.unformattedText.unformattedString().endsWith(" Chest")) return
+ val stack = slot.stack ?: return
+ if (stack.getDisplayNameU() == "§aReroll Chest") {
+ logger.logEntry(
+ LedgerEntry(
+ "KISMET_REROLL",
+ Instant.now(),
+ 0.0,
+ itemId = "KISMET_FEATHER",
+ itemAmount = 1
+ )
+ )
+ }
+ }
+
+ @SubscribeEvent
+ fun onRewardChestClick(event: GuiClickEvent) {
+ val slot = event.slotIn ?: return
+ if (!slot.inventory.displayName.unformattedText.unformattedString().endsWith(" Chest")) return
+ val stack = slot.stack ?: return
+ 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)
+ }
+
+ @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(
+ "DUNGEON_CHEST_OPEN",
+ Instant.ofEpochMilli(toOpen.openTimestamp),
+ toOpen.cost,
+ itemId = if (toOpen.hasKey) "DUNGEON_CHEST_KEY" else null
+ )
+ )
+ }
+
+ @SubscribeEvent
+ fun onTick(event: TickEvent) {
+ val toOpen = lastOpenedChest
+ if (toOpen != null && toOpen.openTimestamp + 1000L < System.currentTimeMillis()) {
+ completeTransaction(toOpen)
+ }
+ }
+}
diff --git a/src/main/kotlin/moe/nea/ledger/modules/MinionDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/MinionDetection.kt
new file mode 100644
index 0000000..73d06fa
--- /dev/null
+++ b/src/main/kotlin/moe/nea/ledger/modules/MinionDetection.kt
@@ -0,0 +1,54 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.events.BeforeGuiAction
+import moe.nea.ledger.events.ChatReceived
+import moe.nea.ledger.ExpiringValue
+import moe.nea.ledger.LedgerEntry
+import moe.nea.ledger.LedgerLogger
+import moe.nea.ledger.ROMAN_NUMBER_PATTERN
+import moe.nea.ledger.SHORT_NUMBER_PATTERN
+import moe.nea.ledger.parseRomanNumber
+import moe.nea.ledger.parseShortNumber
+import moe.nea.ledger.unformattedString
+import moe.nea.ledger.useMatcher
+import moe.nea.ledger.utils.Inject
+import net.minecraft.client.gui.inventory.GuiChest
+import net.minecraft.inventory.ContainerChest
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.time.Instant
+import kotlin.time.Duration.Companion.seconds
+
+class MinionDetection @Inject constructor(val ledger: LedgerLogger) {
+ // §aYou received §r§6367,516.8 coins§r§a!
+ val hopperCollectPattern = "You received (?<amount>$SHORT_NUMBER_PATTERN) coins?!".toPattern()
+ val minionNamePattern = "(?<name>.*) Minion (?<level>$ROMAN_NUMBER_PATTERN)".toPattern()
+
+ var lastOpenedMinion = ExpiringValue.empty<String>()
+
+ @SubscribeEvent
+ fun onBeforeClaim(event: BeforeGuiAction) {
+ val container = event.gui as? GuiChest ?: return
+ val inv = (container.inventorySlots as ContainerChest).lowerChestInventory
+ val invName = inv.displayName.unformattedText.unformattedString()
+ minionNamePattern.useMatcher(invName) {
+ val name = group("name")
+ val level = parseRomanNumber(group("level"))
+ lastOpenedMinion = ExpiringValue(name.uppercase().replace(" ", "_") + "_" + level)
+ }
+ }
+
+
+ @SubscribeEvent
+ fun onChat(event: ChatReceived) {
+ hopperCollectPattern.useMatcher(event.message) {
+ val minionName = lastOpenedMinion.consume(3.seconds)
+ ledger.logEntry(LedgerEntry(
+ "AUTOMERCHANT_PROFIT_COLLECT",
+ Instant.now(),
+ parseShortNumber(group("amount")),
+ minionName, // TODO: switch to its own column idk
+ ))
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/main/kotlin/moe/nea/ledger/modules/NpcDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/NpcDetection.kt
new file mode 100644
index 0000000..68f0257
--- /dev/null
+++ b/src/main/kotlin/moe/nea/ledger/modules/NpcDetection.kt
@@ -0,0 +1,46 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.events.ChatReceived
+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.parseShortNumber
+import moe.nea.ledger.useMatcher
+import moe.nea.ledger.utils.Inject
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.util.regex.Pattern
+
+class NpcDetection @Inject constructor(val ledger: LedgerLogger, val ids: ItemIdProvider) {
+
+ val npcBuyPattern =
+ Pattern.compile("You bought (back )?(?<what>.*?) (x(?<count>$SHORT_NUMBER_PATTERN) )?for (?<coins>$SHORT_NUMBER_PATTERN) Coins!")
+ val npcSellPattern =
+ Pattern.compile("You sold (?<what>.*) (x(?<count>$SHORT_NUMBER_PATTERN) )?for (?<coins>$SHORT_NUMBER_PATTERN) Coins!")
+
+ @SubscribeEvent
+ fun onNpcBuy(event: ChatReceived) {
+ npcBuyPattern.useMatcher(event.message) {
+ ledger.logEntry(
+ LedgerEntry(
+ "NPC_BUY",
+ event.timestamp,
+ parseShortNumber(group("coins")),
+ ids.findForName(group("what")),
+ group("count")?.let(::parseShortNumber)?.toInt() ?: 1,
+ )
+ )
+ }
+ npcSellPattern.useMatcher(event.message) {
+ ledger.logEntry(
+ LedgerEntry(
+ "NPC_SELL",
+ event.timestamp,
+ parseShortNumber(group("coins")),
+ ids.findForName(group("what")),
+ group("count")?.let(::parseShortNumber)?.toInt() ?: 1,
+ )
+ )
+ }
+ }
+}