aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2024-10-05 12:06:23 +0200
committerLinnea Gräf <nea@nea.moe>2024-10-05 12:06:23 +0200
commit977620f1b5218cc8a041742f970974a4bfff29cc (patch)
tree214dad2ae3f9e59304dc20e5891278d01d301629
parentc689c21f3757faaa43afa2402e9e49a06c1e894f (diff)
downloadmoney-ledger-master.tar.gz
money-ledger-master.tar.bz2
money-ledger-master.zip
Add minion hopper trackerHEADmaster
-rw-r--r--build.gradle.kts7
-rw-r--r--src/main/kotlin/moe/nea/ledger/ExpiringValue.kt28
-rw-r--r--src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt74
-rw-r--r--src/main/kotlin/moe/nea/ledger/Ledger.kt1
-rw-r--r--src/main/kotlin/moe/nea/ledger/MinionDetection.kt42
-rw-r--r--src/main/kotlin/moe/nea/ledger/NumberUtil.kt68
-rw-r--r--src/test/kotlin/moe/nea/ledger/NumberUtilKtTest.kt17
7 files changed, 184 insertions, 53 deletions
diff --git a/build.gradle.kts b/build.gradle.kts
index c29d146..5ac39e7 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -6,7 +6,7 @@ plugins {
id("gg.essential.loom") version "0.10.0.+"
id("dev.architectury.architectury-pack200") version "0.1.3"
id("com.github.johnrengelman.shadow") version "8.1.1"
- kotlin("jvm") version "1.8.21"
+ kotlin("jvm") version "2.0.20"
}
val baseGroup: String by project
@@ -84,10 +84,15 @@ dependencies {
shadowImpl("org.xerial:sqlite-jdbc:3.45.3.0")
shadowImpl("org.notenoughupdates.moulconfig:legacy:3.0.0-beta.9")
runtimeOnly("me.djtheredstoner:DevAuth-forge-legacy:1.1.2")
+ testImplementation("org.junit.jupiter:junit-jupiter:5.9.2")
}
// Tasks:
+tasks.test {
+ useJUnitPlatform()
+}
+
tasks.withType(JavaCompile::class) {
options.encoding = "UTF-8"
}
diff --git a/src/main/kotlin/moe/nea/ledger/ExpiringValue.kt b/src/main/kotlin/moe/nea/ledger/ExpiringValue.kt
new file mode 100644
index 0000000..dac4751
--- /dev/null
+++ b/src/main/kotlin/moe/nea/ledger/ExpiringValue.kt
@@ -0,0 +1,28 @@
+package moe.nea.ledger
+
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.nanoseconds
+
+class ExpiringValue<T>(private val value: T) {
+ val lastSeenAt: Long = System.nanoTime()
+ val age get() = (System.nanoTime() - lastSeenAt).nanoseconds
+ var taken = false
+ fun get(expiry: Duration): T? {
+ return if (!taken && expiry > age) value
+ else null
+ }
+
+ companion object {
+ fun <T> empty(): ExpiringValue<T> {
+ val value = ExpiringValue(Unit)
+ value.take()
+ @Suppress("UNCHECKED_CAST")
+ return value as ExpiringValue<T>
+ }
+ }
+
+ fun consume(expiry: Duration): T? = get(expiry).also { take() }
+ fun take() {
+ taken = true
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt b/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt
index 3da0a07..fa0d8fa 100644
--- a/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt
+++ b/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt
@@ -6,44 +6,46 @@ import net.minecraft.nbt.NBTTagCompound
import net.minecraftforge.client.event.GuiScreenEvent
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import org.lwjgl.input.Mouse
class ItemIdProvider {
- @SubscribeEvent
- fun onMouseInput(event: GuiScreenEvent.MouseInputEvent.Pre) {
- MinecraftForge.EVENT_BUS.post(BeforeGuiAction(event.gui))
- }
-
- @SubscribeEvent
- fun onKeyInput(event: GuiScreenEvent.KeyboardInputEvent.Pre) {
- MinecraftForge.EVENT_BUS.post(BeforeGuiAction(event.gui))
- }
-
- private val knownNames = mutableMapOf<String, String>()
-
- @SubscribeEvent
- fun saveInventoryIds(event: BeforeGuiAction) {
- val chest = (event.gui as? GuiChest) ?: return
- val slots = chest.inventorySlots as ContainerChest
- val chestName = slots.lowerChestInventory.name.unformattedString()
- val isOrderMenu = chestName == "Your Bazaar Orders" || chestName == "Co-op Bazaar Orders"
- slots.inventorySlots.forEach {
- val stack = it.stack ?: return@forEach
- val nbt = stack.tagCompound ?: NBTTagCompound()
- val display = nbt.getCompoundTag("display")
- var name = display.getString("Name").unformattedString()
- if (isOrderMenu)
- name = name.removePrefix("BUY ").removePrefix("SELL ")
- name = name.trim()
- val id = stack.getInternalId()
- if (id != null && name.isNotBlank()) {
- knownNames[name] = id
- }
- }
- }
-
- fun findForName(name: String): String? {
- return knownNames[name]
- }
+ @SubscribeEvent
+ fun onMouseInput(event: GuiScreenEvent.MouseInputEvent.Pre) {
+ if (Mouse.getEventButton() == -1) return
+ MinecraftForge.EVENT_BUS.post(BeforeGuiAction(event.gui))
+ }
+
+ @SubscribeEvent
+ fun onKeyInput(event: GuiScreenEvent.KeyboardInputEvent.Pre) {
+ MinecraftForge.EVENT_BUS.post(BeforeGuiAction(event.gui))
+ }
+
+ private val knownNames = mutableMapOf<String, String>()
+
+ @SubscribeEvent
+ fun saveInventoryIds(event: BeforeGuiAction) {
+ val chest = (event.gui as? GuiChest) ?: return
+ val slots = chest.inventorySlots as ContainerChest
+ val chestName = slots.lowerChestInventory.name.unformattedString()
+ val isOrderMenu = chestName == "Your Bazaar Orders" || chestName == "Co-op Bazaar Orders"
+ slots.inventorySlots.forEach {
+ val stack = it.stack ?: return@forEach
+ val nbt = stack.tagCompound ?: NBTTagCompound()
+ val display = nbt.getCompoundTag("display")
+ var name = display.getString("Name").unformattedString()
+ if (isOrderMenu)
+ name = name.removePrefix("BUY ").removePrefix("SELL ")
+ name = name.trim()
+ val id = stack.getInternalId()
+ if (id != null && name.isNotBlank()) {
+ knownNames[name] = id
+ }
+ }
+ }
+
+ fun findForName(name: String): String? {
+ return knownNames[name]
+ }
} \ No newline at end of file
diff --git a/src/main/kotlin/moe/nea/ledger/Ledger.kt b/src/main/kotlin/moe/nea/ledger/Ledger.kt
index 96cddab..784c111 100644
--- a/src/main/kotlin/moe/nea/ledger/Ledger.kt
+++ b/src/main/kotlin/moe/nea/ledger/Ledger.kt
@@ -106,6 +106,7 @@ class Ledger {
AuctionHouseDetection(ledger, ids),
BitsDetection(ledger),
BitsShop(ledger),
+ MinionDetection(ledger),
).forEach(MinecraftForge.EVENT_BUS::register)
}
diff --git a/src/main/kotlin/moe/nea/ledger/MinionDetection.kt b/src/main/kotlin/moe/nea/ledger/MinionDetection.kt
new file mode 100644
index 0000000..bb2a187
--- /dev/null
+++ b/src/main/kotlin/moe/nea/ledger/MinionDetection.kt
@@ -0,0 +1,42 @@
+package moe.nea.ledger
+
+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(val ledger: LedgerLogger) {
+ // §aYou received §r§6367,516.8 coins§r§a!
+ val hopperCollectPattern = "You received (?<amount>$SHORT_NUMBER_PATTERN) coins?!".toPattern()
+ val minionNamePattern = "(?<name>.*) Minion (?<level>$ROMAN_NUMBER_PATTERN)".toPattern()
+
+ var lastOpenedMinion = ExpiringValue.empty<String>()
+
+ @SubscribeEvent
+ fun onBeforeClaim(event: BeforeGuiAction) {
+ val container = event.gui as? GuiChest ?: return
+ val inv = (container.inventorySlots as ContainerChest).lowerChestInventory
+ val invName = inv.displayName.unformattedText.unformattedString()
+ minionNamePattern.useMatcher(invName) {
+ val name = group("name")
+ val level = parseRomanNumber(group("level"))
+ lastOpenedMinion = ExpiringValue(name.uppercase().replace(" ", "_") + "_" + level)
+ }
+ }
+
+
+ @SubscribeEvent
+ fun onChat(event: ChatReceived) {
+ hopperCollectPattern.useMatcher(event.message) {
+ val minionName = lastOpenedMinion.consume(3.seconds)
+ ledger.logEntry(LedgerEntry(
+ "AUTOMERCHANT_PROFIT_COLLECT",
+ Instant.now(),
+ parseShortNumber(group("amount")),
+ minionName, // TODO: switch to its own column idk
+ ))
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/main/kotlin/moe/nea/ledger/NumberUtil.kt b/src/main/kotlin/moe/nea/ledger/NumberUtil.kt
index d26b047..b0e47db 100644
--- a/src/main/kotlin/moe/nea/ledger/NumberUtil.kt
+++ b/src/main/kotlin/moe/nea/ledger/NumberUtil.kt
@@ -6,28 +6,64 @@ import java.util.regex.Pattern
// language=regexp
val SHORT_NUMBER_PATTERN = "[0-9]+(?:,[0-9]+)*(?:\\.[0-9]+)?[kKmMbB]?"
+// language=regexp
+val ROMAN_NUMBER_PATTERN = "[IVXLCDM]+"
+
+val romanNumbers = mapOf(
+ 'I' to 1,
+ 'V' to 5,
+ 'X' to 10,
+ 'L' to 50,
+ 'C' to 100,
+ 'D' to 500,
+ 'M' to 1000
+)
+
+fun parseRomanNumber(string: String): Int {
+ var smallestSeenSoFar = Int.MAX_VALUE
+ var lastSeenOfSmallest = 0
+ var amount = 0
+ for (c in string) {
+ val cV = romanNumbers[c]!!
+ if (cV == smallestSeenSoFar) {
+ lastSeenOfSmallest++
+ amount += cV
+ } else if (cV < smallestSeenSoFar) {
+ smallestSeenSoFar = cV
+ amount += cV
+ lastSeenOfSmallest = 1
+ } else {
+ amount -= lastSeenOfSmallest * smallestSeenSoFar * 2
+ smallestSeenSoFar = cV
+ amount += cV
+ lastSeenOfSmallest = 1
+ }
+ }
+ return amount
+}
+
val siScalars = mapOf(
- 'k' to 1_000.0,
- 'K' to 1_000.0,
- 'm' to 1_000_000.0,
- 'M' to 1_000_000.0,
- 'b' to 1_000_000_000.0,
- 'B' to 1_000_000_000.0,
+ 'k' to 1_000.0,
+ 'K' to 1_000.0,
+ 'm' to 1_000_000.0,
+ 'M' to 1_000_000.0,
+ 'b' to 1_000_000_000.0,
+ 'B' to 1_000_000_000.0,
)
fun parseShortNumber(string: String): Double {
- var k = string.replace(",", "")
- val scalar = k.last()
- var scalarMultiplier = siScalars[scalar]
- if (scalarMultiplier == null) {
- scalarMultiplier = 1.0
- } else {
- k = k.dropLast(1)
- }
- return k.toDouble() * scalarMultiplier
+ var k = string.replace(",", "")
+ val scalar = k.last()
+ var scalarMultiplier = siScalars[scalar]
+ if (scalarMultiplier == null) {
+ scalarMultiplier = 1.0
+ } else {
+ k = k.dropLast(1)
+ }
+ return k.toDouble() * scalarMultiplier
}
inline fun <T> Pattern.useMatcher(string: String, block: Matcher.() -> T): T? =
- matcher(string).takeIf { it.matches() }?.let(block)
+ matcher(string).takeIf { it.matches() }?.let(block)
fun String.unformattedString(): String = replace("§.".toRegex(), "") \ No newline at end of file
diff --git a/src/test/kotlin/moe/nea/ledger/NumberUtilKtTest.kt b/src/test/kotlin/moe/nea/ledger/NumberUtilKtTest.kt
new file mode 100644
index 0000000..4068a42
--- /dev/null
+++ b/src/test/kotlin/moe/nea/ledger/NumberUtilKtTest.kt
@@ -0,0 +1,17 @@
+package moe.nea.ledger
+
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+
+class NumberUtilKtTest {
+ @Test
+ fun parseRomanNumberTest() {
+ assertEquals(4, parseRomanNumber("IV"))
+ assertEquals(1, parseRomanNumber("I"))
+ assertEquals(14, parseRomanNumber("XIV"))
+ assertEquals(3, parseRomanNumber("III"))
+ assertEquals(8, parseRomanNumber("IIX"))
+ assertEquals(500, parseRomanNumber("DM"))
+ assertEquals(2024, parseRomanNumber("MMXXIV"))
+ }
+} \ No newline at end of file