diff options
Diffstat (limited to 'mod/src/main/kotlin/moe/nea/ledger/modules')
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 |