aboutsummaryrefslogtreecommitdiff
path: root/mod/src/main/kotlin/moe/nea/ledger/modules
diff options
context:
space:
mode:
Diffstat (limited to 'mod/src/main/kotlin/moe/nea/ledger/modules')
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/AccessorySwapperDetection.kt34
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/AllowanceDetection.kt37
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/AuctionHouseDetection.kt143
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/BankDetection.kt49
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/BazaarDetection.kt58
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/BazaarOrderDetection.kt95
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/BitsDetection.kt62
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/BitsShopDetection.kt66
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/ChestDetection.kt48
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/DragonEyePlacementDetection.kt47
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/DragonSacrificeDetection.kt72
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/DungeonChestDetection.kt95
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/ExternalDataProvider.kt43
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/EyedropsDetection.kt35
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/ForgeDetection.kt48
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/GambleDetection.kt62
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/GodPotionDetection.kt35
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/GodPotionMixinDetection.kt38
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/KatDetection.kt95
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/KuudraChestDetection.kt45
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/MineshaftCorpseDetection.kt81
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/MinionDetection.kt61
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/NpcDetection.kt111
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/UpdateChecker.kt167
-rw-r--r--mod/src/main/kotlin/moe/nea/ledger/modules/VisitorDetection.kt87
25 files changed, 1714 insertions, 0 deletions
diff --git a/mod/src/main/kotlin/moe/nea/ledger/modules/AccessorySwapperDetection.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/AccessorySwapperDetection.kt
new file mode 100644
index 0000000..1c228ff
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/AccessorySwapperDetection.kt
@@ -0,0 +1,34 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.ItemChange
+import moe.nea.ledger.LedgerEntry
+import moe.nea.ledger.LedgerLogger
+import moe.nea.ledger.TransactionType
+import moe.nea.ledger.events.ChatReceived
+import moe.nea.ledger.gen.ItemIds
+import moe.nea.ledger.useMatcher
+import moe.nea.ledger.utils.di.Inject
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+class AccessorySwapperDetection {
+
+ val swapperUsed = "Swapped .* enrichments to .*!".toPattern()
+
+ @Inject
+ lateinit var logger: LedgerLogger
+
+ @SubscribeEvent
+ fun onChat(event: ChatReceived) {
+ swapperUsed.useMatcher(event.message) {
+ logger.logEntry(
+ LedgerEntry(
+ TransactionType.ACCESSORIES_SWAPPING,
+ event.timestamp,
+ listOf(
+ ItemChange.lose(ItemIds.TALISMAN_ENRICHMENT_SWAPPER, 1)
+ )
+ )
+ )
+ }
+ }
+} \ No newline at end of file
diff --git a/mod/src/main/kotlin/moe/nea/ledger/modules/AllowanceDetection.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/AllowanceDetection.kt
new file mode 100644
index 0000000..cd48d45
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/AllowanceDetection.kt
@@ -0,0 +1,37 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.ItemChange
+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.parseShortNumber
+import moe.nea.ledger.useMatcher
+import moe.nea.ledger.utils.di.Inject
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.util.regex.Pattern
+
+class AllowanceDetection {
+
+ val allowancePattern =
+ Pattern.compile("ALLOWANCE! You earned (?<coins>$SHORT_NUMBER_PATTERN) coins!")
+
+ @Inject
+ lateinit var logger: LedgerLogger
+
+ @SubscribeEvent
+ fun onAllowanceGain(event: ChatReceived) {
+ allowancePattern.useMatcher(event.message) {
+ logger.logEntry(
+ LedgerEntry(
+ TransactionType.ALLOWANCE_GAIN,
+ event.timestamp,
+ listOf(
+ ItemChange.gainCoins(parseShortNumber(group("coins"))),
+ )
+ )
+ )
+ }
+ }
+}
diff --git a/mod/src/main/kotlin/moe/nea/ledger/modules/AuctionHouseDetection.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/AuctionHouseDetection.kt
new file mode 100644
index 0000000..d02095d
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/AuctionHouseDetection.kt
@@ -0,0 +1,143 @@
+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.BeforeGuiAction
+import moe.nea.ledger.events.ChatReceived
+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.di.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
+import kotlin.time.Duration.Companion.seconds
+
+class AuctionHouseDetection @Inject constructor(val ledger: LedgerLogger, val ids: ItemIdProvider) {
+ data class LastViewedItem(
+ val count: Int,
+ val id: ItemId,
+ )
+ /*
+ 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 createAuctionScreen = "Confirm( BIN)? Auction".toPattern()
+ val auctionCreationCostPattern = "Cost: (?<cost>$SHORT_NUMBER_PATTERN) coins?".toPattern()
+
+ val auctionCreatedChatPattern = "(BIN )?Auction started for .*".toPattern()
+
+ var lastCreationCost: ExpiringValue<Double> = ExpiringValue.empty()
+
+ @SubscribeEvent
+ fun onCreateAuctionClick(event: BeforeGuiAction) {
+ val slots = event.chestSlots ?: return
+ if (!createAuctionScreen.asPredicate().test(slots.lowerChestInventory.name)) return
+ val auctionSlot = slots.lowerChestInventory.getStackInSlot(9 + 2) ?: return
+ val creationCost = auctionSlot.getLore().firstNotNullOfOrNull {
+ auctionCreationCostPattern.useMatcher(it.unformattedString()) { parseShortNumber(group("cost")) }
+ }
+ if (creationCost != null) {
+ lastCreationCost = ExpiringValue(creationCost)
+ }
+ }
+
+ @SubscribeEvent
+ fun onCreateAuctionChat(event: ChatReceived) {
+ auctionCreatedChatPattern.useMatcher(event.message) {
+ lastCreationCost.consume(3.seconds)?.let { cost ->
+ ledger.logEntry(LedgerEntry(
+ TransactionType.AUCTION_LISTING_CHARGE,
+ event.timestamp,
+ listOf(ItemChange.loseCoins(cost))
+ ))
+ }
+ }
+ }
+
+ 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(
+ TransactionType.AUCTION_SOLD,
+ event.timestamp,
+ listOfNotNull(
+ ItemChange.gainCoins(parseShortNumber(group("coins"))),
+ lastViewedItem?.let { ItemChange.lose(it.id, it.count) }
+ ),
+ )
+ )
+ }
+ purchased.useMatcher(event.message) {
+ ledger.logEntry(
+ LedgerEntry(
+ TransactionType.AUCTION_BOUGHT,
+ event.timestamp,
+ listOf(
+ ItemChange.loseCoins(parseShortNumber(group("coins"))),
+ ItemChange.gain(
+ ids.findForName(group("what")) ?: ItemId.NIL,
+ 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/mod/src/main/kotlin/moe/nea/ledger/modules/BankDetection.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/BankDetection.kt
new file mode 100644
index 0000000..928d30c
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/BankDetection.kt
@@ -0,0 +1,49 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.ItemChange
+import moe.nea.ledger.ItemId
+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.parseShortNumber
+import moe.nea.ledger.useMatcher
+import moe.nea.ledger.utils.di.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(
+ TransactionType.BANK_WITHDRAW,
+ event.timestamp,
+ listOf(ItemChange(ItemId.COINS,
+ parseShortNumber(group("amount")),
+ ItemChange.ChangeDirection.TRANSFORM)),
+ )
+ )
+ }
+ depositPattern.useMatcher(event.message) {
+ ledger.logEntry(
+ LedgerEntry(
+ TransactionType.BANK_DEPOSIT,
+ event.timestamp,
+ listOf(ItemChange(ItemId.COINS,
+ parseShortNumber(group("amount")),
+ ItemChange.ChangeDirection.TRANSFORM)),
+ )
+ )
+ }
+ }
+
+}
diff --git a/mod/src/main/kotlin/moe/nea/ledger/modules/BazaarDetection.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/BazaarDetection.kt
new file mode 100644
index 0000000..0f1fc2c
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/BazaarDetection.kt
@@ -0,0 +1,58 @@
+package moe.nea.ledger.modules
+
+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.parseShortNumber
+import moe.nea.ledger.useMatcher
+import moe.nea.ledger.utils.di.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(
+ TransactionType.BAZAAR_BUY_INSTANT,
+ event.timestamp,
+ listOf(
+ ItemChange.loseCoins(parseShortNumber(group("coins"))),
+ ItemChange.gain(
+ ids.findForName(group("what")) ?: ItemId.NIL,
+ parseShortNumber(group("count"))
+ )
+ )
+ )
+ )
+ }
+ instaSellPattern.useMatcher(event.message) {
+ ledger.logEntry(
+ LedgerEntry(
+ TransactionType.BAZAAR_SELL_INSTANT,
+ event.timestamp,
+ listOf(
+ ItemChange.gainCoins(parseShortNumber(group("coins"))),
+ ItemChange.lose(
+ ids.findForName(group("what")) ?: ItemId.NIL,
+ parseShortNumber(group("count"))
+ )
+ ),
+ )
+ )
+ }
+ }
+}
diff --git a/mod/src/main/kotlin/moe/nea/ledger/modules/BazaarOrderDetection.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/BazaarOrderDetection.kt
new file mode 100644
index 0000000..330ee1d
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/BazaarOrderDetection.kt
@@ -0,0 +1,95 @@
+package moe.nea.ledger.modules
+
+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.mixin.AccessorGuiEditSign
+import moe.nea.ledger.parseShortNumber
+import moe.nea.ledger.useMatcher
+import moe.nea.ledger.utils.di.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(
+ TransactionType.BAZAAR_BUY_ORDER,
+ event.timestamp,
+ listOf(
+ ItemChange.loseCoins(lastFlippedPreviousPrice * amount),
+ ItemChange.gain(
+ ids.findForName(group("what")) ?: ItemId.NIL,
+ amount,
+ )
+ )
+ )
+ )
+ }
+ buyOrderClaimed.useMatcher(event.message) {
+ ledger.logEntry(
+ LedgerEntry(
+ TransactionType.BAZAAR_BUY_ORDER,
+ event.timestamp,
+ listOf(
+ ItemChange.loseCoins(parseShortNumber(group("coins"))),
+ ItemChange.gain(
+ ids.findForName(group("what")) ?: ItemId.NIL,
+ parseShortNumber(group("amount"))
+ )
+ ),
+ )
+ )
+ }
+ sellOrderClaimed.useMatcher(event.message) {
+ ledger.logEntry(
+ LedgerEntry(
+ TransactionType.BAZAAR_SELL_ORDER,
+ event.timestamp,
+ listOf(
+ ItemChange.gainCoins(
+ parseShortNumber(group("coins"))
+ ),
+ ItemChange.lose(
+ ids.findForName(group("what")) ?: ItemId.NIL,
+ parseShortNumber(group("amount")),
+ )
+ ),
+ )
+ )
+ }
+ }
+}
diff --git a/mod/src/main/kotlin/moe/nea/ledger/modules/BitsDetection.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/BitsDetection.kt
new file mode 100644
index 0000000..44a0050
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/BitsDetection.kt
@@ -0,0 +1,62 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.ItemChange
+import moe.nea.ledger.ItemId
+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.TransactionType
+import moe.nea.ledger.gen.ItemIds
+import moe.nea.ledger.parseShortNumber
+import moe.nea.ledger.unformattedString
+import moe.nea.ledger.useMatcher
+import moe.nea.ledger.utils.di.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(
+ TransactionType.BITS_PURSE_STATUS,
+ Instant.now(),
+ listOf(
+ ItemChange(ItemIds.SKYBLOCK_BIT, bits.toDouble(), ItemChange.ChangeDirection.SYNC)
+ )
+ )
+ )
+ lastBits = bits
+ }
+ return
+ }
+ }
+ }
+
+ @SubscribeEvent
+ fun onEvent(event: ChatReceived) {
+ if (event.message.startsWith("You consumed a Booster Cookie!")) {
+ ledger.logEntry(
+ LedgerEntry(
+ TransactionType.BOOSTER_COOKIE_ATE,
+ Instant.now(),
+ listOf(
+ ItemChange.lose(ItemIds.BOOSTER_COOKIE, 1)
+ )
+ )
+ )
+ }
+ }
+}
diff --git a/mod/src/main/kotlin/moe/nea/ledger/modules/BitsShopDetection.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/BitsShopDetection.kt
new file mode 100644
index 0000000..553bebf
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/BitsShopDetection.kt
@@ -0,0 +1,66 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.ItemChange
+import moe.nea.ledger.ItemId
+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.TransactionType
+import moe.nea.ledger.gen.ItemIds
+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.di.Inject
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.time.Instant
+
+class BitsShopDetection @Inject constructor(val ledger: LedgerLogger) {
+
+
+ data class BitShopEntry(
+ val id: ItemId,
+ val stackSize: Int,
+ 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, stack.stackSize, 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(
+ TransactionType.COMMUNITY_SHOP_BUY,
+ Instant.now(),
+ listOf(
+ ItemChange.lose(ItemIds.SKYBLOCK_BIT, lastBit.bitPrice.toDouble()),
+ ItemChange.gain(lastBit.id, lastBit.stackSize)
+ )
+ )
+ )
+ }
+ }
+
+} \ No newline at end of file
diff --git a/mod/src/main/kotlin/moe/nea/ledger/modules/ChestDetection.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/ChestDetection.kt
new file mode 100644
index 0000000..cca02e1
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/ChestDetection.kt
@@ -0,0 +1,48 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.ItemChange
+import moe.nea.ledger.ItemId
+import moe.nea.ledger.ItemIdProvider
+import moe.nea.ledger.getDisplayNameU
+import moe.nea.ledger.getInternalId
+import moe.nea.ledger.getLore
+import moe.nea.ledger.unformattedString
+import moe.nea.ledger.utils.di.Inject
+import net.minecraft.init.Blocks
+import net.minecraft.inventory.Slot
+import net.minecraft.item.Item
+import java.time.Instant
+
+abstract class ChestDetection {
+ data class ChestCost(
+ val diff: List<ItemChange>,
+ val timestamp: Instant,
+ )
+
+ @Inject
+ lateinit var itemIdProvider: ItemIdProvider
+ fun scrapeChestReward(rewardSlot: Slot): ChestCost? {
+ val inventory = rewardSlot.inventory
+ if (!inventory.displayName.unformattedText.unformattedString()
+ .endsWith(" Chest")
+ ) return null
+ val rewardStack = rewardSlot.stack ?: return null
+ val name = rewardStack.getDisplayNameU()
+ if (name != "§aOpen Reward Chest") return null
+ val lore = rewardStack.getLore()
+ val cost = itemIdProvider.findCostItemsFromSpan(lore)
+ val gain = (9..18)
+ .mapNotNull { inventory.getStackInSlot(it) }
+ .filter { it.item != Item.getItemFromBlock(Blocks.stained_glass_pane) }
+ .map {
+ it.getInternalId()?.withStackSize(it.stackSize)
+ ?: itemIdProvider.findStackableItemByName(it.displayName)
+ ?: ItemId.NIL.withStackSize(it.stackSize)
+ }
+ return ChestCost(
+ cost.map { ItemChange.lose(it.first, it.second) } + gain.map { ItemChange.gain(it.first, it.second) },
+ Instant.now()
+ )
+ }
+
+} \ No newline at end of file
diff --git a/mod/src/main/kotlin/moe/nea/ledger/modules/DragonEyePlacementDetection.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/DragonEyePlacementDetection.kt
new file mode 100644
index 0000000..e389ffb
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/DragonEyePlacementDetection.kt
@@ -0,0 +1,47 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.ItemChange
+import moe.nea.ledger.ItemId
+import moe.nea.ledger.LedgerEntry
+import moe.nea.ledger.LedgerLogger
+import moe.nea.ledger.TransactionType
+import moe.nea.ledger.events.ChatReceived
+import moe.nea.ledger.events.WorldSwitchEvent
+import moe.nea.ledger.gen.ItemIds
+import moe.nea.ledger.useMatcher
+import moe.nea.ledger.utils.di.Inject
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+class DragonEyePlacementDetection {
+ val eyePlaced = "☬ You placed a Summoning Eye!( Brace yourselves!)? \\(./.\\)".toPattern()
+//☬ You placed a Summoning Eye! Brace yourselves! (8/8)
+ var eyeCount = 0
+
+ @SubscribeEvent
+ fun onWorldSwap(event: WorldSwitchEvent) {
+ eyeCount = 0
+ }
+
+ @SubscribeEvent
+ fun onRetrieveEye(event: ChatReceived) {
+ if (event.message == "You recovered a Summoning Eye!") {
+ eyeCount--
+ }
+ eyePlaced.useMatcher(event.message) {
+ eyeCount++
+ }
+ if (event.message == "Your Sleeping Eyes have been awoken by the magic of the Dragon. They are now Remnants of the Eye!") {
+ logger.logEntry(LedgerEntry(
+ TransactionType.WYRM_EVOKED,
+ event.timestamp,
+ listOf(
+ ItemChange.lose(ItemIds.SUMMONING_EYE, eyeCount)
+ )
+ ))
+ eyeCount = 0
+ }
+ }
+
+ @Inject
+ lateinit var logger: LedgerLogger
+} \ No newline at end of file
diff --git a/mod/src/main/kotlin/moe/nea/ledger/modules/DragonSacrificeDetection.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/DragonSacrificeDetection.kt
new file mode 100644
index 0000000..574cfcf
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/DragonSacrificeDetection.kt
@@ -0,0 +1,72 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.DebouncedValue
+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.gen.ItemIds
+import moe.nea.ledger.parseShortNumber
+import moe.nea.ledger.useMatcher
+import moe.nea.ledger.utils.di.Inject
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import net.minecraftforge.fml.common.gameevent.TickEvent
+import kotlin.time.Duration.Companion.seconds
+
+class DragonSacrificeDetection {
+ //SACRIFICE! You turned Holy Dragon Boots into 30 Dragon Essence!
+ //BONUS LOOT! You also received 17x Holy Dragon Fragment from your sacrifice!
+ @Inject
+ lateinit var itemIdProvider: ItemIdProvider
+
+ @Inject
+ lateinit var logger: LedgerLogger
+
+ val sacrificePattern =
+ "SACRIFICE! You turned (?<sacrifice>.*) into (?<amount>$SHORT_NUMBER_PATTERN) Dragon Essence!".toPattern()
+ val bonusLootPattern = "BONUS LOOT! You also received (?<bonus>.*) from your sacrifice!".toPattern()
+
+ var lastSacrifice: DebouncedValue<LedgerEntry> = DebouncedValue.farFuture()
+
+
+ @SubscribeEvent
+ fun onChat(event: ChatReceived) {
+ sacrificePattern.useMatcher(event.message) {
+ val sacrifice = itemIdProvider.findForName(group("sacrifice")) ?: return
+ val lootEssence = parseShortNumber(group("amount"))
+ consume(lastSacrifice.replace())
+ lastSacrifice = DebouncedValue(LedgerEntry(
+ TransactionType.DRACONIC_SACRIFICE,
+ event.timestamp,
+ listOf(
+ ItemChange.lose(sacrifice, 1),
+ ItemChange.gain(ItemIds.ESSENCE_DRAGON, lootEssence)
+ )
+ ))
+ }
+ bonusLootPattern.useMatcher(event.message) {
+ val bonusItem = itemIdProvider.findStackableItemByName(
+ group("bonus"), true
+ ) ?: return
+ lastSacrifice.replace()?.let {
+ consume(
+ it.copy(items = it.items + ItemChange.unpairGain(bonusItem))
+ )
+ }
+ }
+ }
+
+ @SubscribeEvent
+ fun onTick(event: TickEvent) {
+ consume(lastSacrifice.consume(4.seconds))
+ }
+
+ fun consume(entry: LedgerEntry?) {
+ if (entry != null)
+ logger.logEntry(entry)
+ }
+} \ No newline at end of file
diff --git a/mod/src/main/kotlin/moe/nea/ledger/modules/DungeonChestDetection.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/DungeonChestDetection.kt
new file mode 100644
index 0000000..e747be9
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/DungeonChestDetection.kt
@@ -0,0 +1,95 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.ExpiringValue
+import moe.nea.ledger.ItemChange
+import moe.nea.ledger.LedgerEntry
+import moe.nea.ledger.LedgerLogger
+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.gen.ItemIds
+import moe.nea.ledger.getDisplayNameU
+import moe.nea.ledger.unformattedString
+import moe.nea.ledger.useMatcher
+import moe.nea.ledger.utils.di.Inject
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.time.Instant
+import java.util.concurrent.locks.ReentrantLock
+import kotlin.time.Duration.Companion.seconds
+
+class DungeonChestDetection @Inject constructor(val logger: LedgerLogger) : ChestDetection() {
+
+ @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(
+ TransactionType.KISMET_REROLL,
+ Instant.now(),
+ listOf(
+ ItemChange.lose(ItemIds.KISMET_FEATHER, 1)
+ )
+ )
+ )
+ }
+ }
+
+
+ var lastOpenedChest = ExpiringValue.empty<ChestCost>()
+
+ @SubscribeEvent
+ fun supplyExtraIds(event: ExtraSupplyIdEvent) {
+ event.store("Dungeon Chest Key", ItemIds.DUNGEON_CHEST_KEY)
+ event.store("Kismet Feather", ItemIds.KISMET_FEATHER)
+ }
+
+ @SubscribeEvent
+ fun onRewardChestClick(event: GuiClickEvent) {
+ lastOpenedChest = ExpiringValue(scrapeChestReward(event.slotIn ?: return) ?: return)
+ }
+
+ class Mutex<T>(defaultValue: T) {
+ private var value: T = defaultValue
+ val lock = ReentrantLock()
+
+ fun getUnsafeLockedValue(): T {
+ if (!lock.isHeldByCurrentThread)
+ error("Accessed unsafe locked value, without holding the lock.")
+ return value
+ }
+
+ fun <R> withLock(func: (T) -> R): R {
+ lock.lockInterruptibly()
+ try {
+ val ret = func(value)
+ if (ret === value) {
+ error("Please don't smuggle out the locked value. If this is unintentional, please append a `Unit` instruction to the end of your `withLock` call: `.withLock { /* your existing code */; Unit }`.")
+ }
+ return ret
+ } finally {
+ lock.unlock()
+ }
+ }
+ }
+
+ val rewardMessage = " (WOOD|GOLD|DIAMOND|EMERALD|OBSIDIAN|BEDROCK) CHEST REWARDS".toPattern()
+
+ @SubscribeEvent
+ fun onChatMessage(event: ChatReceived) {
+ 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,
+ chest.timestamp,
+ chest.diff,
+ ))
+ }
+ }
+}
diff --git a/mod/src/main/kotlin/moe/nea/ledger/modules/ExternalDataProvider.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/ExternalDataProvider.kt
new file mode 100644
index 0000000..93bb453
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/ExternalDataProvider.kt
@@ -0,0 +1,43 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.events.InitializationComplete
+import moe.nea.ledger.events.SupplyDebugInfo
+import moe.nea.ledger.utils.GsonUtil
+import moe.nea.ledger.utils.di.Inject
+import moe.nea.ledger.utils.network.Request
+import moe.nea.ledger.utils.network.RequestUtil
+import net.minecraftforge.common.MinecraftForge
+import net.minecraftforge.fml.common.eventhandler.Event
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.util.concurrent.CompletableFuture
+
+class ExternalDataProvider @Inject constructor(
+ val requestUtil: RequestUtil
+) {
+
+ fun createAuxillaryDataRequest(path: String): Request {
+ return requestUtil.createRequest("https://github.com/nea89o/ledger-auxiliary-data/raw/refs/heads/master/$path")
+ }
+
+ private val itemNameFuture: CompletableFuture<Map<String, String>> = CompletableFuture.supplyAsync {
+ val request = createAuxillaryDataRequest("data/item_names.json")
+ val response = request.execute(requestUtil)
+ val nameMap = response.json(GsonUtil.typeToken<Map<String, String>>())
+ return@supplyAsync nameMap
+ }
+
+ lateinit var itemNames: Map<String, String>
+
+ class DataLoaded(val provider: ExternalDataProvider) : Event()
+
+ @SubscribeEvent
+ fun onDebugData(debugInfo: SupplyDebugInfo) {
+ debugInfo.record("externalItemsLoaded", itemNameFuture.isDone && !itemNameFuture.isCompletedExceptionally)
+ }
+
+ @SubscribeEvent
+ fun onInitComplete(event: InitializationComplete) {
+ itemNames = itemNameFuture.join()
+ MinecraftForge.EVENT_BUS.post(DataLoaded(this))
+ }
+} \ No newline at end of file
diff --git a/mod/src/main/kotlin/moe/nea/ledger/modules/EyedropsDetection.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/EyedropsDetection.kt
new file mode 100644
index 0000000..04dbe80
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/EyedropsDetection.kt
@@ -0,0 +1,35 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.ItemChange
+import moe.nea.ledger.ItemId
+import moe.nea.ledger.LedgerEntry
+import moe.nea.ledger.LedgerLogger
+import moe.nea.ledger.TransactionType
+import moe.nea.ledger.events.ChatReceived
+import moe.nea.ledger.gen.ItemIds
+import moe.nea.ledger.useMatcher
+import moe.nea.ledger.utils.di.Inject
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+class EyedropsDetection {
+
+ val capsaicinEyedropsUsed = "You applied the eyedrops on the minion and ran out!".toPattern()
+
+ @Inject
+ lateinit var logger: LedgerLogger
+
+ @SubscribeEvent
+ fun onChat(event: ChatReceived) {
+ capsaicinEyedropsUsed.useMatcher(event.message) {
+ logger.logEntry(
+ LedgerEntry(
+ TransactionType.CAPSAICIN_EYEDROPS_USED,
+ event.timestamp,
+ listOf(
+ ItemChange.lose(ItemIds.CAPSAICIN_EYEDROPS_NO_CHARGES, 1)
+ )
+ )
+ )
+ }
+ }
+} \ No newline at end of file
diff --git a/mod/src/main/kotlin/moe/nea/ledger/modules/ForgeDetection.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/ForgeDetection.kt
new file mode 100644
index 0000000..95811ed
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/ForgeDetection.kt
@@ -0,0 +1,48 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.ItemChange
+import moe.nea.ledger.LedgerEntry
+import moe.nea.ledger.LedgerLogger
+import moe.nea.ledger.TransactionType
+import moe.nea.ledger.events.GuiClickEvent
+import moe.nea.ledger.getInternalId
+import moe.nea.ledger.matches
+import moe.nea.ledger.unformattedString
+import moe.nea.ledger.utils.di.Inject
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.time.Instant
+
+class ForgeDetection {
+ val furnaceSlot = 9 + 4
+ val furnaceName = "Forge Slot.*".toPattern()
+
+ @SubscribeEvent
+ fun onClick(event: GuiClickEvent) {
+ val slot = event.slotIn ?: return
+ val clickedItem = slot.stack ?: return
+ if (clickedItem.displayName.unformattedString() != "Confirm") return
+ val furnaceSlotName = slot.inventory.getStackInSlot(furnaceSlot)?.displayName?.unformattedString() ?: return
+ if (!furnaceName.matches(furnaceSlotName))
+ return
+ val cl = (0 until slot.inventory.sizeInventory - 9)
+ .mapNotNull {
+ val stack = slot.inventory.getStackInSlot(it) ?: return@mapNotNull null
+ val x = it % 9
+ if (x == 4) return@mapNotNull null
+ ItemChange(
+ stack.getInternalId() ?: return@mapNotNull null,
+ stack.stackSize.toDouble(),
+ if (x < 4) ItemChange.ChangeDirection.LOST else ItemChange.ChangeDirection.GAINED
+ )
+ }
+ logger.logEntry(LedgerEntry(
+ TransactionType.FORGED,
+ Instant.now(),
+ cl,
+ ))
+ }
+
+ @Inject
+ lateinit var logger: LedgerLogger
+
+} \ No newline at end of file
diff --git a/mod/src/main/kotlin/moe/nea/ledger/modules/GambleDetection.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/GambleDetection.kt
new file mode 100644
index 0000000..a8f79c1
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/GambleDetection.kt
@@ -0,0 +1,62 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.ItemChange
+import moe.nea.ledger.ItemId
+import moe.nea.ledger.LedgerEntry
+import moe.nea.ledger.LedgerLogger
+import moe.nea.ledger.TransactionType
+import moe.nea.ledger.events.ChatReceived
+import moe.nea.ledger.gen.ItemIds
+import moe.nea.ledger.useMatcher
+import moe.nea.ledger.utils.di.Inject
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+class GambleDetection {
+
+ val dieRolled =
+ "Your (?<isHighClass>High Class )?Archfiend Dice rolled a (?<face>[1-7])!.*"
+ .toPattern()
+
+ @Inject
+ lateinit var logger: LedgerLogger
+
+ @SubscribeEvent
+ fun onChat(event: ChatReceived) {
+ dieRolled.useMatcher(event.message) {
+ val isLowClass = group("isHighClass").isNullOrBlank()
+ val item = if (isLowClass) ItemIds.ARCHFIEND_DICE else ItemIds.HIGH_CLASS_ARCHFIEND_DICE
+ val face = group("face")
+ val rollCost = if (isLowClass) 666_000.0 else 6_600_000.0
+ if (face == "7") {
+ logger.logEntry(LedgerEntry(
+ TransactionType.DIE_ROLLED,
+ event.timestamp,
+ listOf(
+ ItemChange.lose(item, 1),
+ ItemChange.loseCoins(rollCost),
+ ItemChange.gain(ItemIds.DYE_ARCHFIEND, 1),
+ )
+ ))
+ } else if (face == "6") {
+ logger.logEntry(LedgerEntry(
+ TransactionType.DIE_ROLLED,
+ event.timestamp,
+ listOf(
+ ItemChange.lose(item, 1),
+ ItemChange.loseCoins(rollCost),
+ ItemChange.gainCoins(if (isLowClass) 15_000_000.0 else 100_000_000.0),
+ )
+ ))
+ } else {
+ logger.logEntry(LedgerEntry(
+ TransactionType.DIE_ROLLED,
+ event.timestamp,
+ listOf(
+ ItemChange(item, 1.0, ItemChange.ChangeDirection.CATALYST),
+ ItemChange.loseCoins(rollCost),
+ )
+ ))
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/mod/src/main/kotlin/moe/nea/ledger/modules/GodPotionDetection.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/GodPotionDetection.kt
new file mode 100644
index 0000000..ae86519
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/GodPotionDetection.kt
@@ -0,0 +1,35 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.ItemChange
+import moe.nea.ledger.ItemId
+import moe.nea.ledger.LedgerEntry
+import moe.nea.ledger.LedgerLogger
+import moe.nea.ledger.TransactionType
+import moe.nea.ledger.events.ChatReceived
+import moe.nea.ledger.gen.ItemIds
+import moe.nea.ledger.useMatcher
+import moe.nea.ledger.utils.di.Inject
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+class GodPotionDetection {
+
+ val godPotionDrank = "(SIP|SLURP|GULP|CHUGALUG)! The God Potion grants you powers for .*!".toPattern()
+
+ @Inject
+ lateinit var logger: LedgerLogger
+
+ @SubscribeEvent
+ fun onChat(event: ChatReceived) {
+ godPotionDrank.useMatcher(event.message) {
+ logger.logEntry(
+ LedgerEntry(
+ TransactionType.GOD_POTION_DRANK,
+ event.timestamp,
+ listOf(
+ ItemChange.lose(ItemIds.GOD_POTION_2, 1)
+ )
+ )
+ )
+ }
+ }
+} \ No newline at end of file
diff --git a/mod/src/main/kotlin/moe/nea/ledger/modules/GodPotionMixinDetection.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/GodPotionMixinDetection.kt
new file mode 100644
index 0000000..b96a24a
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/GodPotionMixinDetection.kt
@@ -0,0 +1,38 @@
+package moe.nea.ledger.modules
+
+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.TransactionType
+import moe.nea.ledger.events.ChatReceived
+import moe.nea.ledger.useMatcher
+import moe.nea.ledger.utils.di.Inject
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+class GodPotionMixinDetection {
+
+ val godPotionMixinDrank = "SCHLURP! The (effects of the )?(?<what>.*?) (grants you effects|have been extended by) .*! They will pause if your God Potion expires.".toPattern()
+
+ @Inject
+ lateinit var logger: LedgerLogger
+
+ @Inject
+ lateinit var itemIdProvider: ItemIdProvider
+
+ @SubscribeEvent
+ fun onChat(event: ChatReceived) {
+ godPotionMixinDrank.useMatcher(event.message) {
+ logger.logEntry(
+ LedgerEntry(
+ TransactionType.GOD_POTION_MIXIN_DRANK,
+ event.timestamp,
+ listOf(
+ ItemChange.lose(itemIdProvider.findForName(group("what")) ?: ItemId.NIL, 1)
+ )
+ )
+ )
+ }
+ }
+} \ No newline at end of file
diff --git a/mod/src/main/kotlin/moe/nea/ledger/modules/KatDetection.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/KatDetection.kt
new file mode 100644
index 0000000..eda5aba
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/KatDetection.kt
@@ -0,0 +1,95 @@
+package moe.nea.ledger.modules
+
+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.TransactionType
+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.useMatcher
+import moe.nea.ledger.utils.di.Inject
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+
+class KatDetection {
+ @Inject
+ lateinit var log: LedgerLogger
+
+ @Inject
+ lateinit var itemIdProvider: ItemIdProvider
+
+ val giftNameToIdMap = mapOf(
+ "flower" to ItemId("KAT_FLOWER"),
+ "bouquet" to ItemId("KAT_BOUQUET"),
+ )
+ val katGift = "\\[NPC\\] Kat: A (?<gift>.*)\\? For me\\? How sweet!".toPattern()
+
+ @SubscribeEvent
+ fun onChat(event: ChatReceived) {
+ katGift.useMatcher(event.message) {
+ val giftName = group("gift")
+ val giftId = giftNameToIdMap[giftName]
+ log.logEntry(LedgerEntry(
+ TransactionType.KAT_TIMESKIP,
+ event.timestamp,
+ listOf(
+ ItemChange.lose(giftId ?: ItemId.NIL, 1)
+ )
+ ))
+ }
+ }
+
+ val confirmSlot = 9 + 9 + 4
+ val petSlot = 9 + 4
+
+ data class PetUpgrade(
+ val beforePetId: ItemId,
+ val cost: List<Pair<ItemId, Double>>
+ )
+
+ var lastPetUpgradeScheduled: PetUpgrade? = null
+
+ @SubscribeEvent
+ fun onClick(event: BeforeGuiAction) {
+ val slots = event.chestSlots ?: return
+ val petItem = slots.lowerChestInventory.getStackInSlot(petSlot) ?: return
+ val beforePetId = petItem.getInternalId() ?: return
+ val confirmItem = slots.lowerChestInventory.getStackInSlot(confirmSlot) ?: return
+ val lore = confirmItem.getLore()
+ val cost = itemIdProvider.findCostItemsFromSpan(lore)
+ lastPetUpgradeScheduled = PetUpgrade(beforePetId, cost)
+ }
+
+ val petUpgradeDialogue = "\\[NPC\\] Kat: I'll get your (?<type>.*) upgraded to (?<tier>.*) in no time!".toPattern()
+ fun upgradePetTier(itemId: ItemId): ItemId {
+ val str = itemId.string.split(";", limit = 2)
+ if (str.size == 2) {
+ val (type, tier) = str
+ val tierT = tier.toIntOrNull()
+ if (tierT != null)
+ return ItemId(type + ";" + (tierT + 1))
+ }
+ return itemId
+ }
+
+ @SubscribeEvent
+ fun onPetUpgrade(event: ChatReceived) {
+ petUpgradeDialogue.useMatcher(event.message) {
+ val upgrade = lastPetUpgradeScheduled ?: return
+ lastPetUpgradeScheduled = null
+ log.logEntry(LedgerEntry(
+ TransactionType.KAT_UPGRADE,
+ event.timestamp,
+ listOf(
+ ItemChange.lose(upgrade.beforePetId, 1),
+ ItemChange.gain(upgradePetTier(upgrade.beforePetId), 1),
+ ) + upgrade.cost.map { ItemChange.lose(it.first, it.second) },
+ ))
+ }
+ }
+
+} \ No newline at end of file
diff --git a/mod/src/main/kotlin/moe/nea/ledger/modules/KuudraChestDetection.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/KuudraChestDetection.kt
new file mode 100644
index 0000000..e0e9322
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/KuudraChestDetection.kt
@@ -0,0 +1,45 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.ItemChange
+import moe.nea.ledger.ItemId
+import moe.nea.ledger.LedgerEntry
+import moe.nea.ledger.LedgerLogger
+import moe.nea.ledger.TransactionType
+import moe.nea.ledger.events.GuiClickEvent
+import moe.nea.ledger.getInternalId
+import moe.nea.ledger.utils.di.Inject
+import net.minecraft.client.Minecraft
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+class KuudraChestDetection : ChestDetection() {
+ // TODO: extra essence for kuudra pet (how?), item SALVAGE detection
+ // TODO: save uuid along side item id
+
+ val kuudraKeyPattern = "KUUDRA_.*_TIER_KEY".toPattern()
+
+ @Inject
+ lateinit var log: LedgerLogger
+
+ @Inject
+ lateinit var minecraft: Minecraft
+ fun hasKey(keyItem: ItemId): Boolean {
+ val p = minecraft.thePlayer ?: return false
+ return p.inventory.mainInventory.any { it?.getInternalId() == keyItem }
+ }
+
+ @SubscribeEvent
+ fun onRewardChestClick(event: GuiClickEvent) {
+ val diffs = scrapeChestReward(event.slotIn ?: return) ?: return
+ val requiredKey = diffs.diff.find {
+ it.direction == ItemChange.ChangeDirection.LOST && kuudraKeyPattern.asPredicate().test(it.itemId.string)
+ }?.itemId
+ if (requiredKey != null && !hasKey(requiredKey)) {
+ return
+ }
+ log.logEntry(LedgerEntry(
+ TransactionType.KUUDRA_CHEST_OPEN,
+ diffs.timestamp,
+ diffs.diff,
+ ))
+ }
+} \ No newline at end of file
diff --git a/mod/src/main/kotlin/moe/nea/ledger/modules/MineshaftCorpseDetection.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/MineshaftCorpseDetection.kt
new file mode 100644
index 0000000..60b06ae
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/MineshaftCorpseDetection.kt
@@ -0,0 +1,81 @@
+package moe.nea.ledger.modules
+
+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.TransactionType
+import moe.nea.ledger.events.ChatReceived
+import moe.nea.ledger.matches
+import moe.nea.ledger.useMatcher
+import moe.nea.ledger.utils.BorderedTextTracker
+import moe.nea.ledger.utils.di.Inject
+
+class MineshaftCorpseDetection : BorderedTextTracker() {
+ /*
+[23:39:47] [Client thread/INFO]: [CHAT] §r§a§l▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬§r
+[23:39:47] [Client thread/INFO]: [CHAT] §r §r§b§l§r§9§lLAPIS §r§b§lCORPSE LOOT! §r
+[23:39:47] [Client thread/INFO]: [CHAT] §r §r§a§lREWARDS§r
+[23:39:47] [Client thread/INFO]: [CHAT] §r §r§5+100 HOTM Experience§r
+[23:39:47] [Client thread/INFO]: [CHAT] §r §r§a§r§aGreen Goblin Egg§r
+[23:39:47] [Client thread/INFO]: [CHAT] §r §r§9Enchanted Glacite §r§8x2§r
+[23:39:47] [Client thread/INFO]: [CHAT] §r §r§9☠ Fine Onyx Gemstone§r
+[23:39:47] [Client thread/INFO]: [CHAT] §r §r§a☠ Flawed Onyx Gemstone §r§8x20§r
+[23:39:47] [Client thread/INFO]: [CHAT] §r §r§a☘ Flawed Peridot Gemstone §r§8x40§r
+[23:39:47] [Client thread/INFO]: [CHAT] §r §r§bGlacite Powder §r§8x500§r
+[23:39:47] [Client thread/INFO]: [CHAT] §e[SkyHanni] Profit for §9Lapis Corpse§e: §678k§r
+[23:39:47] [Client thread/INFO]: [CHAT] §r§a§l▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬§r
+ */
+
+ val corpseEnterMessage = " (?<corpseKind>.*) CORPSE LOOT!".toPattern()
+
+ override fun shouldEnter(event: ChatReceived): Boolean {
+ return corpseEnterMessage.matches(event.message)
+ }
+
+ override fun shouldExit(event: ChatReceived): Boolean {
+ return genericBorderExit.matches(event.message)
+ }
+
+ override fun onBorderedTextFinished(enclosed: List<ChatReceived>) {
+ val rewards = enclosed.asSequence()
+ .dropWhile { it.message != " REWARDS" }
+ .drop(1)
+ .mapNotNull {
+ itemIdProvider.findStackableItemByName(it.message, true)
+ }
+ .map { ItemChange.unpairGain(it) }
+ .toMutableList()
+ val introMessage = enclosed.first()
+ val corpseTyp = corpseEnterMessage.useMatcher(introMessage.message) {
+ group("corpseKind")
+ }!!
+ val keyTyp = corpseNameToKey[corpseTyp]
+ if (keyTyp == null) {
+ errorUtil.reportAdHoc("Unknown corpse type $corpseTyp")
+ } else if (keyTyp != ItemId.NIL) {
+ rewards.add(ItemChange.lose(keyTyp, 1))
+ }
+ logger.logEntry(
+ LedgerEntry(
+ TransactionType.CORPSE_DESECRATED,
+ introMessage.timestamp,
+ rewards
+ )
+ )
+ }
+
+ val corpseNameToKey = mapOf(
+ "LAPIS" to ItemId.NIL,
+ "VANGUARD" to ItemId("SKELETON_KEY"),
+ "UMBER" to ItemId("UMBER_KEY"),
+ "TUNGSTEN" to ItemId("TUNGSTEN_KEY"),
+ )
+
+ @Inject
+ lateinit var logger: LedgerLogger
+
+ @Inject
+ lateinit var itemIdProvider: ItemIdProvider
+} \ No newline at end of file
diff --git a/mod/src/main/kotlin/moe/nea/ledger/modules/MinionDetection.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/MinionDetection.kt
new file mode 100644
index 0000000..6999c7f
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/MinionDetection.kt
@@ -0,0 +1,61 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.ExpiringValue
+import moe.nea.ledger.ItemChange
+import moe.nea.ledger.ItemId
+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.TransactionType
+import moe.nea.ledger.events.BeforeGuiAction
+import moe.nea.ledger.events.ChatReceived
+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.di.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<ItemId>()
+
+ @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(
+ ItemId(name.uppercase().replace(" ", "_")
+ .replace("MINION", "GENERATOR") + "_" + level))
+ }
+ }
+
+
+ @SubscribeEvent
+ fun onChat(event: ChatReceived) {
+ hopperCollectPattern.useMatcher(event.message) {
+ val minionName = lastOpenedMinion.consume(3.seconds)
+ ledger.logEntry(LedgerEntry(
+ TransactionType.AUTOMERCHANT_PROFIT_COLLECT,
+ Instant.now(),
+ listOf(
+ ItemChange.gainCoins(parseShortNumber(group("amount"))),
+ ItemChange(minionName ?: ItemId.NIL, 1.0, ItemChange.ChangeDirection.CATALYST)
+ ),
+ ))
+ }
+ }
+
+} \ No newline at end of file
diff --git a/mod/src/main/kotlin/moe/nea/ledger/modules/NpcDetection.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/NpcDetection.kt
new file mode 100644
index 0000000..95b8aa5
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/NpcDetection.kt
@@ -0,0 +1,111 @@
+package moe.nea.ledger.modules
+
+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.asIterable
+import moe.nea.ledger.events.BeforeGuiAction
+import moe.nea.ledger.events.ChatReceived
+import moe.nea.ledger.events.ExtraSupplyIdEvent
+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.ErrorUtil
+import moe.nea.ledger.utils.di.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?!")
+
+ // You bought InfiniDirt™ Wand!
+ // You bought Prismapump x4!
+ val npcBuyWithItemPattern =
+ "You bought (?<what>.*?)!".toPattern()
+ var storedPurchases = mutableMapOf<String, List<ItemChange>>()
+
+ @SubscribeEvent
+ fun onClick(event: BeforeGuiAction) {
+ (event.chestSlots?.lowerChestInventory?.asIterable() ?: listOf())
+ .filterNotNull().forEach {
+ val name = it.getDisplayNameU().unformattedString()
+ val id = it.getInternalId() ?: return@forEach
+ val count = it.stackSize
+ val cost = ids.findCostItemsFromSpan(it.getLore())
+ storedPurchases[name] = listOf(ItemChange.gain(id, count)) + cost.map { ItemChange.unpairLose(it) }
+ }
+ }
+
+ @SubscribeEvent
+ fun addChocolate(event: ExtraSupplyIdEvent) {
+ event.store("Chocolate", ItemId("SKYBLOCK_CHOCOLATE"))
+ }
+
+ @Inject
+ lateinit var errorUtil: ErrorUtil
+
+ @SubscribeEvent
+ fun onBarteredItemBought(event: ChatReceived) {
+ npcBuyWithItemPattern.useMatcher(event.message) {
+ val changes = storedPurchases[group("what")]
+ if (changes == null) {
+ errorUtil.reportAdHoc("Item bought for items without associated cost")
+ }
+ storedPurchases.clear()
+ ledger.logEntry(
+ LedgerEntry(
+ TransactionType.NPC_BUY,
+ event.timestamp,
+ changes ?: listOf()
+ )
+ )
+ }
+ }
+
+ @SubscribeEvent
+ fun onNpcBuy(event: ChatReceived) {
+ npcBuyPattern.useMatcher(event.message) {
+ ledger.logEntry(
+ LedgerEntry(
+ TransactionType.NPC_BUY,
+ event.timestamp,
+ listOf(
+ ItemChange.loseCoins(
+ parseShortNumber(group("coins")),
+ ),
+ ItemChange.gain(
+ ids.findForName(group("what")) ?: ItemId.NIL,
+ group("count")?.let(::parseShortNumber) ?: 1,
+ )
+ )
+ )
+ )
+ }
+ npcSellPattern.useMatcher(event.message) {
+ ledger.logEntry(
+ LedgerEntry(
+ TransactionType.NPC_SELL,
+ event.timestamp,
+ listOf(
+ ItemChange.gainCoins(parseShortNumber(group("coins"))),
+ ItemChange.lose(
+ ids.findForName(group("what")) ?: ItemId.NIL,
+ group("count")?.let(::parseShortNumber)?.toInt() ?: 1,
+ )
+ )
+ )
+ )
+ }
+ }
+}
diff --git a/mod/src/main/kotlin/moe/nea/ledger/modules/UpdateChecker.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/UpdateChecker.kt
new file mode 100644
index 0000000..0d89ca1
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/UpdateChecker.kt
@@ -0,0 +1,167 @@
+package moe.nea.ledger.modules
+
+import com.google.gson.JsonElement
+import com.google.gson.JsonPrimitive
+import moe.nea.ledger.DevUtil
+import moe.nea.ledger.LedgerLogger
+import moe.nea.ledger.TriggerCommand
+import moe.nea.ledger.config.LedgerConfig
+import moe.nea.ledger.config.MainOptions
+import moe.nea.ledger.events.RegistrationFinishedEvent
+import moe.nea.ledger.events.TriggerEvent
+import moe.nea.ledger.gen.BuildConfig
+import moe.nea.ledger.utils.ErrorUtil
+import moe.nea.ledger.utils.MinecraftExecutor
+import moe.nea.ledger.utils.di.Inject
+import moe.nea.ledger.utils.network.RequestUtil
+import moe.nea.libautoupdate.CurrentVersion
+import moe.nea.libautoupdate.GithubReleaseUpdateData
+import moe.nea.libautoupdate.GithubReleaseUpdateSource
+import moe.nea.libautoupdate.PotentialUpdate
+import moe.nea.libautoupdate.UpdateContext
+import moe.nea.libautoupdate.UpdateData
+import moe.nea.libautoupdate.UpdateTarget
+import moe.nea.libautoupdate.UpdateUtils
+import net.minecraft.util.ChatComponentText
+import net.minecraft.util.ChatStyle
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.util.concurrent.CompletableFuture
+
+class UpdateChecker @Inject constructor(
+ val errorUtil: ErrorUtil,
+ val requestUtil: RequestUtil,
+) {
+
+ @Inject
+ lateinit var minecraftExecutor: MinecraftExecutor
+
+ val updater = UpdateContext(
+ NightlyAwareGithubUpdateSource("nea89o", "LocalTransactionLedger"),
+ if (DevUtil.isDevEnv) UpdateTarget { listOf() }
+ else UpdateTarget.deleteAndSaveInTheSameFolder(UpdateChecker::class.java),
+ object : CurrentVersion {
+ override fun display(): String {
+ return BuildConfig.VERSION
+ }
+
+ override fun isOlderThan(element: JsonElement?): Boolean {
+ if (element !is JsonPrimitive || !element.isString) return true
+ val newHash = element.asString // TODO: change once i support non nightly update streams
+ val length = minOf(newHash.length, BuildConfig.GIT_COMMIT.length)
+ if (newHash.substring(0, length).equals(BuildConfig.GIT_COMMIT.substring(0, length), ignoreCase = true))
+ return false
+ return true
+ }
+
+
+ override fun toString(): String {
+ return "{gitversion:${BuildConfig.GIT_COMMIT}, version:${BuildConfig.FULL_VERSION}}"
+ }
+ },
+ "ledger"
+ )
+
+ class NightlyAwareGithubUpdateSource(owner: String, repository: String) :
+ GithubReleaseUpdateSource(owner, repository) {
+ override fun selectUpdate(updateStream: String, releases: List<GithubRelease>): UpdateData? {
+ if (updateStream == "nightly") {
+ return findAsset(releases.find { it.tagName == "nightly" })
+ }
+ return super.selectUpdate(updateStream, releases.filter { it.tagName != "nightly" })
+ }
+
+ val releaseRegex = "commit: `(?<hash>[a-f0-9]+)`".toPattern()
+
+ override fun findAsset(release: GithubRelease?): UpdateData? {
+ val update = super.findAsset(release) as GithubReleaseUpdateData? ?: return null
+ return GithubReleaseUpdateData(
+ update.versionName,
+ releaseRegex.matcher(update.releaseDescription)
+ .takeIf { it.find() }
+ ?.run { group("hash") }
+ ?.let(::JsonPrimitive)
+ ?: update.versionNumber,
+ update.sha256,
+ update.download,
+ update.releaseDescription,
+ update.targetCommittish,
+ update.createdAt,
+ update.publishedAt,
+ update.htmlUrl
+ )
+ }
+ }
+
+ init {
+ UpdateUtils.patchConnection {
+ this.requestUtil.enhanceConnection(it)
+ }
+ }
+
+ var latestUpdate: PotentialUpdate? = null
+ var hasNotified = false
+
+ @SubscribeEvent
+ fun onStartup(event: RegistrationFinishedEvent) {
+ if (config.main.updateCheck == MainOptions.UpdateCheckBehaviour.NONE) return
+ launchUpdateCheck()
+ }
+
+ fun launchUpdateCheck() {
+ errorUtil.listenToFuture(
+ updater.checkUpdate("nightly")
+ .thenAcceptAsync(
+ {
+ latestUpdate = it
+ informAboutUpdates(it)
+ }, minecraftExecutor)
+ )
+ }
+
+ @Inject
+ lateinit var config: LedgerConfig
+
+ @Inject
+ lateinit var triggerCommand: TriggerCommand
+
+ val installTrigger = "execute-download"
+
+ @Inject
+ lateinit var logger: LedgerLogger
+ fun informAboutUpdates(potentialUpdate: PotentialUpdate) {
+ if (hasNotified) return
+ hasNotified = true
+ if (!potentialUpdate.isUpdateAvailable) return
+ logger.printOut(
+ ChatComponentText("§aThere is a new update for LocalTransactionLedger. Click here to automatically download and install it.")
+ .setChatStyle(ChatStyle().setChatClickEvent(triggerCommand.getTriggerCommandLine(installTrigger))))
+ if (config.main.updateCheck == MainOptions.UpdateCheckBehaviour.FULL) {
+ downloadUpdate()
+ }
+ }
+
+ var updateFuture: CompletableFuture<Void>? = null
+
+ fun downloadUpdate() {
+ val l = latestUpdate ?: return
+ if (updateFuture != null) return
+ // TODO: inject into findAsset to overwrite the tag id with the commit id
+ logger.printOut("§aTrying to download ledger update ${l.update.versionName}")
+ updateFuture =
+ latestUpdate?.launchUpdate()
+ ?.thenAcceptAsync(
+ {
+ logger.printOut("§aLedger update downloaded. It will automatically apply after your next restart.")
+ }, minecraftExecutor)
+ ?.let(errorUtil::listenToFuture)
+ }
+
+ @SubscribeEvent
+ fun onTrigger(event: TriggerEvent) {
+ if (event.action == installTrigger) {
+ event.isCanceled = true
+ downloadUpdate()
+ }
+ }
+
+} \ No newline at end of file
diff --git a/mod/src/main/kotlin/moe/nea/ledger/modules/VisitorDetection.kt b/mod/src/main/kotlin/moe/nea/ledger/modules/VisitorDetection.kt
new file mode 100644
index 0000000..f457ae4
--- /dev/null
+++ b/mod/src/main/kotlin/moe/nea/ledger/modules/VisitorDetection.kt
@@ -0,0 +1,87 @@
+package moe.nea.ledger.modules
+
+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.ExtraSupplyIdEvent
+import moe.nea.ledger.events.GuiClickEvent
+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.di.Inject
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.time.Instant
+
+class VisitorDetection {
+ @Inject
+ lateinit var logger: LedgerLogger
+
+ @Inject
+ lateinit var idProvider: ItemIdProvider
+
+ @SubscribeEvent
+ fun parseFromItem(event: GuiClickEvent) {
+ val stack = event.slotIn?.stack ?: return
+
+ val displayName = stack.getDisplayNameU()
+ if (displayName != "§aAccept Offer") return
+ val lore = stack.getLore()
+ if (!lore.contains("§eClick to give!")) return
+
+ val rewards = lore
+ .asSequence()
+ .dropWhile { it != "§7Rewards:" }.drop(1)
+ .takeWhile { it != "" }
+ .mapNotNull { parseGardenLoreLine(it) }
+ .map { ItemChange.gain(it.first, it.second) }
+ .toList()
+
+ val cost = lore
+ .asSequence()
+ .dropWhile { it != "§7Items Required:" }.drop(1)
+ .takeWhile { it != "" }
+ .mapNotNull { parseGardenLoreLine(it) }
+ .map { ItemChange.lose(it.first, it.second) }
+ .toList()
+
+ logger.logEntry(LedgerEntry(
+ TransactionType.VISITOR_BARGAIN,
+ Instant.now(),
+ cost + rewards
+ ))
+ }
+
+ private fun parseGardenLoreLine(rewardLine: String): Pair<ItemId, Double>? {
+ val f = rewardLine.unformattedString().trim()
+ return idProvider.findStackableItemByName(f, true)
+ }
+
+
+ @SubscribeEvent
+ fun supplyNames(event: ExtraSupplyIdEvent) {
+ event.store("Carrot", ItemId("CARROT_ITEM"))
+ event.store("Potato", ItemId("POTATO_ITEM"))
+ event.store("Jacob's Ticket", ItemId("JACOBS_TICKET"))
+ event.store("Cocoa Beans", ItemId("INK_SACK:3"))
+ event.store("Enchanted Cocoa Beans", ItemId("ENCHANTED_COCOA"))
+ event.store("Enchanted Red Mushroom Block", ItemId("ENCHANTED_HUGE_MUSHROOM_2"))
+ event.store("Enchanted Brown Mushroom Block", ItemId("ENCHANTED_HUGE_MUSHROOM_1"))
+ event.store("Nether Wart", ItemId("NETHER_STALK"))
+ event.store("Enchanted Nether Wart", ItemId("ENCHANTED_NETHER_STALK"))
+ event.store("Mutant Nether Wart", ItemId("MUTANT_NETHER_STALK"))
+ event.store("Jack o' Lantern", ItemId("JACK_O_LANTERN"))
+ event.store("Cactus Green", ItemId("INK_SACK:2"))
+ event.store("Hay Bale", ItemId("HAY_BLOCK"))
+ event.store("Rabbit's Foot", ItemId("RABBIT_FOOT"))
+ event.store("Raw Porkchop", ItemId("PORK"))
+ event.store("Raw Rabbit", ItemId("RABBIT"))
+ event.store("White Wool", ItemId("WOOL"))
+ event.store("Copper Dye", ItemId("DYE_COPPER"))
+ }
+} \ No newline at end of file