aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2024-12-19 20:18:39 +0100
committerLinnea Gräf <nea@nea.moe>2024-12-19 20:18:39 +0100
commiteda44cb8743c709c15a7ed03381d05e43728e647 (patch)
tree557a3bd4f096584158b46921f4826208396b4baa /src/main/kotlin
parenta046b198645811fe1b7db129942505c379aabb03 (diff)
downloadLocalTransactionLedger-eda44cb8743c709c15a7ed03381d05e43728e647.tar.gz
LocalTransactionLedger-eda44cb8743c709c15a7ed03381d05e43728e647.tar.bz2
LocalTransactionLedger-eda44cb8743c709c15a7ed03381d05e43728e647.zip
feat: Add corpse loot detection
Diffstat (limited to 'src/main/kotlin')
-rw-r--r--src/main/kotlin/moe/nea/ledger/ItemChange.kt6
-rw-r--r--src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt35
-rw-r--r--src/main/kotlin/moe/nea/ledger/Ledger.kt2
-rw-r--r--src/main/kotlin/moe/nea/ledger/NumberUtil.kt8
-rw-r--r--src/main/kotlin/moe/nea/ledger/TransactionType.kt1
-rw-r--r--src/main/kotlin/moe/nea/ledger/events/ChatReceived.kt2
-rw-r--r--src/main/kotlin/moe/nea/ledger/modules/MineshaftCorpseDetection.kt74
-rw-r--r--src/main/kotlin/moe/nea/ledger/modules/VisitorDetection.kt24
-rw-r--r--src/main/kotlin/moe/nea/ledger/utils/BorderedTextTracker.kt41
9 files changed, 167 insertions, 26 deletions
diff --git a/src/main/kotlin/moe/nea/ledger/ItemChange.kt b/src/main/kotlin/moe/nea/ledger/ItemChange.kt
index 834cd2b..186a4e3 100644
--- a/src/main/kotlin/moe/nea/ledger/ItemChange.kt
+++ b/src/main/kotlin/moe/nea/ledger/ItemChange.kt
@@ -54,6 +54,12 @@ data class ItemChange(
return gain(ItemId.COINS, number)
}
+ fun unpair(direction: ChangeDirection, pair: Pair<ItemId, Double>): ItemChange {
+ return ItemChange(pair.first, pair.second, direction)
+ }
+
+ fun unpairGain(pair: Pair<ItemId, Double>) = unpair(ChangeDirection.GAINED, pair)
+
fun gain(itemId: ItemId, amount: Number): ItemChange {
return ItemChange(itemId, amount.toDouble(), ChangeDirection.GAINED)
}
diff --git a/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt b/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt
index a84aac8..c269c3d 100644
--- a/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt
+++ b/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt
@@ -86,11 +86,15 @@ class ItemIdProvider {
fun findForName(name: String, fallbackToGenerated: Boolean = true): ItemId? {
var id = knownNames[name]
if (id == null && fallbackToGenerated) {
- id = ItemId(name.uppercase().replace(" ", "_"))
+ id = generateName(name)
}
return id
}
+ fun generateName(name: String): ItemId {
+ return ItemId(name.uppercase().replace(" ", "_"))
+ }
+
private val coinRegex = "(?<amount>$SHORT_NUMBER_PATTERN) Coins?".toPattern()
private val stackedItemRegex = "(?<name>.*) x(?<count>$SHORT_NUMBER_PATTERN)".toPattern()
private val essenceRegex = "(?<essence>.*) Essence x(?<count>$SHORT_NUMBER_PATTERN)".toPattern()
@@ -103,14 +107,41 @@ class ItemIdProvider {
.toList()
}
+ private val etherialRewardPattern = "\\+(?<amount>${SHORT_NUMBER_PATTERN})x? (?<what>.*)".toPattern()
+
fun findStackableItemByName(name: String, fallbackToGenerated: Boolean = false): Pair<ItemId, Double>? {
- val properName = name.unformattedString()
+ val properName = name.unformattedString().trim()
if (properName == "FREE" || properName == "This Chest is Free!") {
return Pair(ItemId.COINS, 0.0)
}
coinRegex.useMatcher(properName) {
return Pair(ItemId.COINS, parseShortNumber(group("amount")))
}
+ etherialRewardPattern.useMatcher(properName) {
+ val id = when (val id = group("what")) {
+ "Copper" -> ItemId.COPPER
+ "Bits" -> ItemId.BITS
+ "Garden Experience" -> ItemId.GARDEN
+ "Farming XP" -> ItemId.FARMING
+ "Gold Essence" -> ItemId.GOLD_ESSENCE
+ "Gemstone Powder" -> ItemId.GEMSTONE_POWDER
+ "Mithril Powder" -> ItemId.MITHRIL_POWDER
+ "Pelts" -> ItemId.PELT
+ "Fine Flour" -> ItemId.FINE_FLOUR
+ else -> {
+ id.ifDropLast(" Experience") {
+ ItemId.skill(generateName(it).string)
+ } ?: id.ifDropLast(" XP") {
+ ItemId.skill(generateName(it).string)
+ } ?: id.ifDropLast(" Powder") {
+ ItemId("SKYBLOCK_POWDER_${generateName(it).string}")
+ } ?: id.ifDropLast(" Essence") {
+ ItemId("ESSENCE_${generateName(it).string}")
+ } ?: generateName(id)
+ }
+ }
+ return Pair(id, parseShortNumber(group("amount")))
+ }
essenceRegex.useMatcher(properName) {
return Pair(ItemId("ESSENCE_${group("essence").uppercase()}"),
parseShortNumber(group("count")))
diff --git a/src/main/kotlin/moe/nea/ledger/Ledger.kt b/src/main/kotlin/moe/nea/ledger/Ledger.kt
index 58a8b3d..2d41a73 100644
--- a/src/main/kotlin/moe/nea/ledger/Ledger.kt
+++ b/src/main/kotlin/moe/nea/ledger/Ledger.kt
@@ -18,6 +18,7 @@ import moe.nea.ledger.modules.DungeonChestDetection
import moe.nea.ledger.modules.ExternalDataProvider
import moe.nea.ledger.modules.KatDetection
import moe.nea.ledger.modules.KuudraChestDetection
+import moe.nea.ledger.modules.MineshaftCorpseDetection
import moe.nea.ledger.modules.MinionDetection
import moe.nea.ledger.modules.NpcDetection
import moe.nea.ledger.modules.VisitorDetection
@@ -115,6 +116,7 @@ class Ledger {
LedgerLogger::class.java,
LogChatCommand::class.java,
MinionDetection::class.java,
+ MineshaftCorpseDetection::class.java,
NpcDetection::class.java,
QueryCommand::class.java,
RequestUtil::class.java,
diff --git a/src/main/kotlin/moe/nea/ledger/NumberUtil.kt b/src/main/kotlin/moe/nea/ledger/NumberUtil.kt
index 008cfbf..438f342 100644
--- a/src/main/kotlin/moe/nea/ledger/NumberUtil.kt
+++ b/src/main/kotlin/moe/nea/ledger/NumberUtil.kt
@@ -75,9 +75,17 @@ fun parseShortNumber(string: String): Double {
return k.toDouble() * scalarMultiplier
}
+fun Pattern.matches(string: String): Boolean = matcher(string).matches()
inline fun <T> Pattern.useMatcher(string: String, block: Matcher.() -> T): T? =
matcher(string).takeIf { it.matches() }?.let(block)
+fun <T> String.ifDropLast(suffix: String, block: (String) -> T): T? {
+ if (endsWith(suffix)) {
+ return block(dropLast(suffix.length))
+ }
+ return null
+}
+
fun String.unformattedString(): String = replace("§.".toRegex(), "")
val timeFormat: DateTimeFormatter = DateTimeFormatterBuilder()
diff --git a/src/main/kotlin/moe/nea/ledger/TransactionType.kt b/src/main/kotlin/moe/nea/ledger/TransactionType.kt
index b615fcd..4e903d8 100644
--- a/src/main/kotlin/moe/nea/ledger/TransactionType.kt
+++ b/src/main/kotlin/moe/nea/ledger/TransactionType.kt
@@ -14,6 +14,7 @@ enum class TransactionType {
BITS_PURSE_STATUS,
BOOSTER_COOKIE_ATE,
COMMUNITY_SHOP_BUY,
+ CORPSE_DESECRATED,
DUNGEON_CHEST_OPEN,
KAT_TIMESKIP,
KAT_UPGRADE,
diff --git a/src/main/kotlin/moe/nea/ledger/events/ChatReceived.kt b/src/main/kotlin/moe/nea/ledger/events/ChatReceived.kt
index e88c7a0..a352c27 100644
--- a/src/main/kotlin/moe/nea/ledger/events/ChatReceived.kt
+++ b/src/main/kotlin/moe/nea/ledger/events/ChatReceived.kt
@@ -10,6 +10,6 @@ data class ChatReceived(
val timestamp: Instant = Instant.now()
) : Event() {
constructor(event: ClientChatReceivedEvent) : this(
- event.message.unformattedText.unformattedString()
+ event.message.unformattedText.unformattedString().trimEnd()
)
} \ No newline at end of file
diff --git a/src/main/kotlin/moe/nea/ledger/modules/MineshaftCorpseDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/MineshaftCorpseDetection.kt
new file mode 100644
index 0000000..85c9ad6
--- /dev/null
+++ b/src/main/kotlin/moe/nea/ledger/modules/MineshaftCorpseDetection.kt
@@ -0,0 +1,74 @@
+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
+
+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>) {
+ // TODO: test this once profile swapping has been re-enabled
+ 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
+ )
+ lateinit var logger: LedgerLogger
+ lateinit var itemIdProvider: ItemIdProvider
+} \ No newline at end of file
diff --git a/src/main/kotlin/moe/nea/ledger/modules/VisitorDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/VisitorDetection.kt
index 2ee581c..f457ae4 100644
--- a/src/main/kotlin/moe/nea/ledger/modules/VisitorDetection.kt
+++ b/src/main/kotlin/moe/nea/ledger/modules/VisitorDetection.kt
@@ -59,29 +59,7 @@ class VisitorDetection {
private fun parseGardenLoreLine(rewardLine: String): Pair<ItemId, Double>? {
val f = rewardLine.unformattedString().trim()
- return parseSpecialReward(f)
- ?: idProvider.findStackableItemByName(f, true)
- }
-
- private val specialRewardRegex = "\\+(?<amount>${SHORT_NUMBER_PATTERN})x? (?<what>.*)".toPattern()
-
- private fun parseSpecialReward(specialLine: String): Pair<ItemId, Double>? {
- specialRewardRegex.useMatcher(specialLine) {
- val id = when (group("what")) {
- "Copper" -> ItemId.COPPER
- "Bits" -> ItemId.BITS
- "Garden Experience" -> ItemId.GARDEN
- "Farming XP" -> ItemId.FARMING
- "Gold Essence" -> ItemId.GOLD_ESSENCE
- "Gemstone Powder" -> ItemId.GEMSTONE_POWDER
- "Mithril Powder" -> ItemId.MITHRIL_POWDER
- "Pelts" -> ItemId.PELT
- "Fine Flour" -> ItemId.FINE_FLOUR
- else -> ItemId.NIL
- }
- return Pair(id, parseShortNumber(group("amount")))
- }
- return null
+ return idProvider.findStackableItemByName(f, true)
}
diff --git a/src/main/kotlin/moe/nea/ledger/utils/BorderedTextTracker.kt b/src/main/kotlin/moe/nea/ledger/utils/BorderedTextTracker.kt
new file mode 100644
index 0000000..9e621e8
--- /dev/null
+++ b/src/main/kotlin/moe/nea/ledger/utils/BorderedTextTracker.kt
@@ -0,0 +1,41 @@
+package moe.nea.ledger.utils
+
+import moe.nea.ledger.events.ChatReceived
+import moe.nea.ledger.utils.di.Inject
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+abstract class BorderedTextTracker {
+
+ val genericBorderExit = "▬{10,}".toPattern()
+
+ @Inject
+ lateinit var errorUtil: ErrorUtil
+ var stack: MutableList<ChatReceived>? = null
+
+
+ @SubscribeEvent
+ fun receiveText(event: ChatReceived) {
+ if (stack != null && shouldExit(event)) {
+ exit()
+ return
+ }
+ if (shouldEnter(event)) {
+ if (stack != null) {
+ errorUtil.reportAdHoc("Double entered a bordered message")
+ exit()
+ }
+ stack = mutableListOf()
+ }
+ stack?.add(event)
+ }
+
+ private fun exit() {
+ onBorderedTextFinished(stack!!)
+ stack = null
+ }
+
+ abstract fun shouldEnter(event: ChatReceived): Boolean
+ abstract fun shouldExit(event: ChatReceived): Boolean
+ abstract fun onBorderedTextFinished(enclosed: List<ChatReceived>)
+
+} \ No newline at end of file