From d708dca108dcbfe3d67dfe90c27de9cdb41184a6 Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Sat, 7 Dec 2024 00:50:33 +0100 Subject: feat: Add SQLITE database entry logging --- src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt | 4 +- src/main/kotlin/moe/nea/ledger/ItemUtil.kt | 4 +- src/main/kotlin/moe/nea/ledger/Ledger.kt | 4 +- src/main/kotlin/moe/nea/ledger/LedgerLogger.kt | 126 +++++++++++--- src/main/kotlin/moe/nea/ledger/UUIDUtil.kt | 39 +++++ .../kotlin/moe/nea/ledger/database/DBLogEntry.kt | 19 +++ .../kotlin/moe/nea/ledger/database/DBSchema.kt | 122 ++++++++++++- .../kotlin/moe/nea/ledger/database/DBUpgrade.kt | 68 ++++++++ .../kotlin/moe/nea/ledger/database/Database.kt | 41 ++++- .../kotlin/moe/nea/ledger/database/Upgrades.kt | 20 +++ src/main/kotlin/moe/nea/ledger/database/schema.dot | 23 +++ .../nea/ledger/modules/AuctionHouseDetection.kt | 152 +++++++++-------- .../kotlin/moe/nea/ledger/modules/BankDetection.kt | 15 +- .../moe/nea/ledger/modules/BazaarDetection.kt | 71 ++++---- .../moe/nea/ledger/modules/BazaarOrderDetection.kt | 133 ++++++++------- .../kotlin/moe/nea/ledger/modules/BitsDetection.kt | 21 ++- .../moe/nea/ledger/modules/BitsShopDetection.kt | 18 +- .../nea/ledger/modules/DungeonChestDetection.kt | 189 +++++++++++---------- .../moe/nea/ledger/modules/MinionDetection.kt | 21 ++- .../kotlin/moe/nea/ledger/modules/NpcDetection.kt | 31 +++- src/main/kotlin/moe/nea/ledger/utils/DI.kt | 2 + 21 files changed, 797 insertions(+), 326 deletions(-) create mode 100644 src/main/kotlin/moe/nea/ledger/UUIDUtil.kt create mode 100644 src/main/kotlin/moe/nea/ledger/database/DBLogEntry.kt create mode 100644 src/main/kotlin/moe/nea/ledger/database/DBUpgrade.kt create mode 100644 src/main/kotlin/moe/nea/ledger/database/Upgrades.kt create mode 100644 src/main/kotlin/moe/nea/ledger/database/schema.dot (limited to 'src/main') diff --git a/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt b/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt index 65580fa..43f2cb7 100644 --- a/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt +++ b/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt @@ -22,7 +22,7 @@ class ItemIdProvider { MinecraftForge.EVENT_BUS.post(BeforeGuiAction(event.gui)) } - private val knownNames = mutableMapOf() + private val knownNames = mutableMapOf() @SubscribeEvent fun saveInventoryIds(event: BeforeGuiAction) { @@ -45,7 +45,7 @@ class ItemIdProvider { } } - fun findForName(name: String): String? { + fun findForName(name: String): ItemId? { return knownNames[name] } diff --git a/src/main/kotlin/moe/nea/ledger/ItemUtil.kt b/src/main/kotlin/moe/nea/ledger/ItemUtil.kt index 892127d..b82c97f 100644 --- a/src/main/kotlin/moe/nea/ledger/ItemUtil.kt +++ b/src/main/kotlin/moe/nea/ledger/ItemUtil.kt @@ -4,11 +4,11 @@ import net.minecraft.item.ItemStack import net.minecraft.nbt.NBTTagCompound -fun ItemStack.getInternalId(): String? { +fun ItemStack.getInternalId(): ItemId? { val nbt = this.tagCompound ?: NBTTagCompound() val extraAttributes = nbt.getCompoundTag("ExtraAttributes") val id = extraAttributes.getString("id") - return id.takeIf { it.isNotBlank() } + return id.takeIf { it.isNotBlank() }?.let(::ItemId) } diff --git a/src/main/kotlin/moe/nea/ledger/Ledger.kt b/src/main/kotlin/moe/nea/ledger/Ledger.kt index 54ad6e7..f85a6b0 100644 --- a/src/main/kotlin/moe/nea/ledger/Ledger.kt +++ b/src/main/kotlin/moe/nea/ledger/Ledger.kt @@ -79,7 +79,6 @@ class Ledger { @Mod.EventHandler fun init(event: FMLInitializationEvent) { logger.info("Initializing ledger") - Database.init() val di = DI() di.registerSingleton(this) @@ -97,11 +96,14 @@ class Ledger { NpcDetection::class.java, LogChatCommand::class.java, ConfigCommand::class.java, + Database::class.java ) di.instantiateAll() di.getAllInstances().forEach(MinecraftForge.EVENT_BUS::register) di.getAllInstances().filterIsInstance() .forEach { ClientCommandHandler.instance.registerCommand(it) } + + di.provide().loadAndUpgrade() } var lastJoin = -1L diff --git a/src/main/kotlin/moe/nea/ledger/LedgerLogger.kt b/src/main/kotlin/moe/nea/ledger/LedgerLogger.kt index c35d203..045c6b1 100644 --- a/src/main/kotlin/moe/nea/ledger/LedgerLogger.kt +++ b/src/main/kotlin/moe/nea/ledger/LedgerLogger.kt @@ -3,7 +3,11 @@ package moe.nea.ledger import com.google.gson.Gson import com.google.gson.JsonArray import com.google.gson.JsonObject +import moe.nea.ledger.database.DBItemEntry +import moe.nea.ledger.database.DBLogEntry +import moe.nea.ledger.database.Database import moe.nea.ledger.events.ChatReceived +import moe.nea.ledger.utils.Inject import net.minecraft.client.Minecraft import net.minecraft.util.ChatComponentText import net.minecraftforge.fml.common.eventhandler.SubscribeEvent @@ -11,7 +15,8 @@ import net.minecraftforge.fml.common.gameevent.TickEvent.ClientTickEvent import java.io.File import java.text.SimpleDateFormat import java.time.Instant -import java.util.* +import java.util.Date +import java.util.UUID class LedgerLogger { fun printOut(text: String) { @@ -21,30 +26,32 @@ class LedgerLogger { val profileIdPattern = "Profile ID: (?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})".toPattern() - var currentProfile: String? = null + var currentProfile: UUID? = null var shouldLog by Ledger.managedConfig.instance.debug::logEntries + @Inject + lateinit var database: Database + @SubscribeEvent fun onProfileSwitch(event: ChatReceived) { profileIdPattern.useMatcher(event.message) { - currentProfile = group("profile") + currentProfile = UUID.fromString(group("profile")) } } fun printToChat(entry: LedgerEntry) { + val items = entry.items.joinToString("\n§e") { " - ${it.direction} ${it.count}x ${it.itemId}" } printOut( """ §e================= TRANSACTION START §eTYPE: §a${entry.transactionType} §eTIMESTAMP: §a${entry.timestamp} - §eTOTAL VALUE: §a${entry.totalTransactionCoins} - §eITEM ID: §a${entry.itemId} - §eITEM AMOUNT: §a${entry.itemAmount} + §e%s §ePROFILE: §a${currentProfile} §e================= TRANSACTION END - """.trimIndent() + """.trimIndent().replace("%s", items) ) } @@ -79,6 +86,21 @@ class LedgerLogger { if (shouldLog) printToChat(entry) Ledger.logger.info("Logging entry of type ${entry.transactionType}") + val transactionId = UUIDUtil.createULIDAt(entry.timestamp) + DBLogEntry.insert(database.connection) { + it[DBLogEntry.profileId] = currentProfile ?: UUIDUtil.NIL_UUID + it[DBLogEntry.playerId] = UUIDUtil.getPlayerUUID() + it[DBLogEntry.type] = entry.transactionType + it[DBLogEntry.transactionId] = transactionId + } + entry.items.forEach { change -> + DBItemEntry.insert(database.connection) { + it[DBItemEntry.transactionId] = transactionId + it[DBItemEntry.mode] = change.direction + it[DBItemEntry.size] = change.count + it[DBItemEntry.itemId] = change.itemId + } + } entries.add(entry.intoJson(currentProfile)) commit() } @@ -110,28 +132,92 @@ class LedgerLogger { } } +enum class TransactionType { + AUCTION_BOUGHT, + AUCTION_SOLD, + AUTOMERCHANT_PROFIT_COLLECT, + BANK_DEPOSIT, + BANK_WITHDRAW, + BAZAAR_BUY_INSTANT, + BAZAAR_BUY_ORDER, + BAZAAR_SELL_INSTANT, + BAZAAR_SELL_ORDER, + BITS_PURSE_STATUS, + BOOSTER_COOKIE_ATE, + COMMUNITY_SHOP_BUY, + DUNGEON_CHEST_OPEN, + KISMET_REROLL, + NPC_BUY, + NPC_SELL, +} + +@JvmInline +value class ItemId( + val string: String +) { + companion object { + val COINS = ItemId("SKYBLOCK_COIN") + val BITS = ItemId("SKYBLOCK_BIT") + val NIL = ItemId("SKYBLOCK_NIL") + val DUNGEON_CHEST_KEY = ItemId("DUNGEON_CHEST_KEY") + val BOOSTER_COOKIE = ItemId("BOOSTER_COOKIE") + val KISMET_FEATHER = ItemId("KISMET_FEATHER") + } +} + + +data class ItemChange( + val itemId: ItemId, + val count: Double, + val direction: ChangeDirection, +) { + enum class ChangeDirection { + GAINED, + TRANSFORM, + SYNC, + CATALYST, + LOST; + } + + companion object { + fun gainCoins(number: Double): ItemChange { + return gain(ItemId.COINS, number) + } + + fun gain(itemId: ItemId, amount: Number): ItemChange { + return ItemChange(itemId, amount.toDouble(), ChangeDirection.GAINED) + } + + fun lose(itemId: ItemId, amount: Number): ItemChange { + return ItemChange(itemId, amount.toDouble(), ChangeDirection.LOST) + } + + fun loseCoins(number: Double): ItemChange { + return lose(ItemId.COINS, number) + } + } +} data class LedgerEntry( - val transactionType: String, + val transactionType: TransactionType, val timestamp: Instant, - val totalTransactionCoins: Double, - val itemId: String? = null, - val itemAmount: Int? = null, + val items: List, ) { - fun intoJson(profileId: String?): JsonObject { + fun intoJson(profileId: UUID?): JsonObject { + val coinAmount = items.find { it.itemId == ItemId.COINS || it.itemId == ItemId.BITS }?.count + val nonCoins = items.find { it.itemId != ItemId.COINS && it.itemId != ItemId.BITS } return JsonObject().apply { - addProperty("transactionType", transactionType) + addProperty("transactionType", transactionType.name) addProperty("timestamp", timestamp.toEpochMilli().toString()) - addProperty("totalTransactionValue", totalTransactionCoins) - addProperty("itemId", itemId ?: "") - addProperty("itemAmount", itemAmount ?: 0) - addProperty("profileId", profileId) + addProperty("totalTransactionValue", coinAmount) + addProperty("itemId", nonCoins?.itemId?.string ?: "") + addProperty("itemAmount", nonCoins?.count ?: 0.0) + addProperty("profileId", profileId.toString()) addProperty( "playerId", - (Minecraft.getMinecraft().thePlayer?.uniqueID?.toString() ?: lastKnownUUID) - .also { lastKnownUUID = it }) + UUIDUtil.getPlayerUUID().toString() + ) } } } -var lastKnownUUID = "null" diff --git a/src/main/kotlin/moe/nea/ledger/UUIDUtil.kt b/src/main/kotlin/moe/nea/ledger/UUIDUtil.kt new file mode 100644 index 0000000..ddfb8c2 --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/UUIDUtil.kt @@ -0,0 +1,39 @@ +package moe.nea.ledger + +import com.mojang.util.UUIDTypeAdapter +import io.azam.ulidj.ULID +import net.minecraft.client.Minecraft +import java.time.Instant +import java.util.UUID +import kotlin.random.Random + +object UUIDUtil { + @JvmInline + value class ULIDWrapper( + val wrapped: String + ) { + init { + require(ULID.isValid(wrapped)) + } + } + + fun parseDashlessUuid(string: String) = UUIDTypeAdapter.fromString(string) + val NIL_UUID = UUID(0L, 0L) + fun getPlayerUUID(): UUID { + val currentUUID = Minecraft.getMinecraft().thePlayer?.uniqueID + ?: Minecraft.getMinecraft().session?.playerID?.let(::parseDashlessUuid) + ?: lastKnownUUID + lastKnownUUID = currentUUID + return currentUUID + } + + fun createULIDAt(timestamp: Instant): ULIDWrapper { + return ULIDWrapper(ULID.generate( + timestamp.toEpochMilli(), + Random.nextBytes(10) + )) + } + + private var lastKnownUUID: UUID = NIL_UUID + +} \ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/database/DBLogEntry.kt b/src/main/kotlin/moe/nea/ledger/database/DBLogEntry.kt new file mode 100644 index 0000000..77ac215 --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/database/DBLogEntry.kt @@ -0,0 +1,19 @@ +package moe.nea.ledger.database + +import moe.nea.ledger.ItemChange +import moe.nea.ledger.ItemId +import moe.nea.ledger.TransactionType + +object DBLogEntry : Table("LogEntry") { + val transactionId = column("transactionId", DBUlid) + val type = column("type", DBEnum()) + val profileId = column("profileId", DBUuid) + val playerId = column("playerId", DBUuid) +} + +object DBItemEntry : Table("ItemEntry") { + val transactionId = column("transactionId", DBUlid) // TODO: add foreign keys + val mode = column("mode", DBEnum()) + val itemId = column("item", DBString.mapped(ItemId::string, ::ItemId)) + val size = column("size", DBDouble) +} diff --git a/src/main/kotlin/moe/nea/ledger/database/DBSchema.kt b/src/main/kotlin/moe/nea/ledger/database/DBSchema.kt index 5c9099c..dee99e4 100644 --- a/src/main/kotlin/moe/nea/ledger/database/DBSchema.kt +++ b/src/main/kotlin/moe/nea/ledger/database/DBSchema.kt @@ -1,10 +1,11 @@ package moe.nea.ledger.database +import moe.nea.ledger.UUIDUtil import java.sql.Connection import java.sql.PreparedStatement import java.sql.ResultSet -import java.sql.Timestamp import java.time.Instant +import java.util.UUID interface DBSchema { val tables: List @@ -16,6 +17,54 @@ interface DBType { fun get(result: ResultSet, index: Int): T fun set(stmt: PreparedStatement, index: Int, value: T) fun getName(): String = javaClass.simpleName + fun mapped( + from: (R) -> T, + to: (T) -> R, + ): DBType { + return object : DBType { + override fun getName(): String { + return "Mapped(${this@DBType.getName()})" + } + + override val dbType: String + get() = this@DBType.dbType + + override fun get(result: ResultSet, index: Int): R { + return to(this@DBType.get(result, index)) + } + + override fun set(stmt: PreparedStatement, index: Int, value: R) { + this@DBType.set(stmt, index, from(value)) + } + } + } +} + +object DBUuid : DBType { + override val dbType: String + get() = "TEXT" + + override fun get(result: ResultSet, index: Int): UUID { + return UUIDUtil.parseDashlessUuid(result.getString(index)) + } + + override fun set(stmt: PreparedStatement, index: Int, value: UUID) { + stmt.setString(index, value.toString()) + } +} + +object DBUlid : DBType { + override val dbType: String + get() = "TEXT" + + override fun get(result: ResultSet, index: Int): UUIDUtil.ULIDWrapper { + val text = result.getString(index) + return UUIDUtil.ULIDWrapper(text) + } + + override fun set(stmt: PreparedStatement, index: Int, value: UUIDUtil.ULIDWrapper) { + stmt.setString(index, value.wrapped) + } } object DBString : DBType { @@ -31,6 +80,45 @@ object DBString : DBType { } } +class DBEnum>( + val type: Class, +) : DBType { + companion object { + inline operator fun > invoke(): DBEnum { + return DBEnum(T::class.java) + } + } + + override val dbType: String + get() = "TEXT" + + override fun getName(): String { + return "DBEnum(${type.simpleName})" + } + + override fun set(stmt: PreparedStatement, index: Int, value: T) { + stmt.setString(index, value.name) + } + + override fun get(result: ResultSet, index: Int): T { + val name = result.getString(index) + return java.lang.Enum.valueOf(type, name) + } +} + +object DBDouble : DBType { + override val dbType: String + get() = "DOUBLE" + + override fun get(result: ResultSet, index: Int): Double { + return result.getDouble(index) + } + + override fun set(stmt: PreparedStatement, index: Int, value: Double) { + stmt.setDouble(index, value) + } +} + object DBInt : DBType { override val dbType: String get() = "INTEGER" @@ -57,12 +145,12 @@ object DBInstant : DBType { } } -// TODO: add table class Column @Deprecated("Use Table.column instead") constructor(val name: String, val type: DBType) { val sqlName get() = "`$name`" } interface Constraint { + val affectedColumns: Collection> fun asSQL(): String } @@ -71,6 +159,9 @@ class UniqueConstraint(val columns: List>) : Constraint { require(columns.isNotEmpty()) } + override val affectedColumns: Collection> + get() = columns + override fun asSQL(): String { return "UNIQUE (${columns.joinToString() { it.sqlName }})" } @@ -111,18 +202,37 @@ abstract class Table(val name: String) { println(string) } - fun createIfNotExists(connection: Connection) { + fun createIfNotExists( + connection: Connection, + filteredColumns: List> = columns + ) { val properties = mutableListOf() - for (column in columns) { + for (column in filteredColumns) { properties.add("${column.sqlName} ${column.type.dbType}") } + val columnSet = filteredColumns.toSet() for (constraint in constraints) { - properties.add(constraint.asSQL()) + if (columnSet.containsAll(constraint.affectedColumns)) { + properties.add(constraint.asSQL()) + } } - connection.prepareAndLog("CREATE TABLE IF NOT EXISTS `$name` (" + properties.joinToString() + ")") + connection.prepareAndLog("CREATE TABLE IF NOT EXISTS $sqlName (" + properties.joinToString() + ")") .execute() } + fun alterTableAddColumns( + connection: Connection, + newColumns: List> + ) { + for (column in newColumns) { + connection.prepareAndLog("ALTER TABLE $sqlName ADD ${column.sqlName} ${column.type.dbType}") + .execute() + } + for (constraint in constraints) { + // TODO: automatically add constraints, maybe (or maybe move constraints into the upgrade schema) + } + } + enum class OnConflict { FAIL, IGNORE, diff --git a/src/main/kotlin/moe/nea/ledger/database/DBUpgrade.kt b/src/main/kotlin/moe/nea/ledger/database/DBUpgrade.kt new file mode 100644 index 0000000..7d1782a --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/database/DBUpgrade.kt @@ -0,0 +1,68 @@ +package moe.nea.ledger.database + +import java.sql.Connection + +interface DBUpgrade { + val toVersion: Long + val fromVersion get() = toVersion - 1 + fun performUpgrade(connection: Connection) + + companion object { + + fun performUpgrades( + connection: Connection, + upgrades: Iterable, + ) { + for (upgrade in upgrades) { + upgrade.performUpgrade(connection) + } + } + + fun performUpgradeChain( + connection: Connection, + from: Long, to: Long, + upgrades: Iterable, + afterEach: (newVersion: Long) -> Unit, + ) { + val table = buildLookup(upgrades) + for (version in (from + 1)..(to)) { + val currentUpgrades = table[version] ?: listOf() + println("Scheduled ${currentUpgrades.size} upgrades to reach DB version $version") + performUpgrades(connection, currentUpgrades) + afterEach(version) + } + } + + fun buildLookup(upgrades: Iterable): Map> { + return upgrades.groupBy { it.toVersion } + } + + fun createTable(to: Long, table: Table, vararg columns: Column<*>): DBUpgrade { + require(columns.all { it in table.columns }) + return of("Create table ${table}", to) { + table.createIfNotExists(it, columns.toList()) + } + } + + fun addColumns(to: Long, table: Table, vararg columns: Column<*>): DBUpgrade { + return of("Add columns to table $table", to) { + table.alterTableAddColumns(it, columns.toList()) + } + } + + fun of(name: String, to: Long, block: (Connection) -> Unit): DBUpgrade { + return object : DBUpgrade { + override val toVersion: Long + get() = to + + override fun performUpgrade(connection: Connection) { + block(connection) + } + + override fun toString(): String { + return name + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/database/Database.kt b/src/main/kotlin/moe/nea/ledger/database/Database.kt index 9dc4e00..a77ea30 100644 --- a/src/main/kotlin/moe/nea/ledger/database/Database.kt +++ b/src/main/kotlin/moe/nea/ledger/database/Database.kt @@ -1,10 +1,11 @@ package moe.nea.ledger.database import moe.nea.ledger.Ledger +import java.sql.Connection import java.sql.DriverManager -object Database { - val connection = DriverManager.getConnection("jdbc:sqlite:${Ledger.dataFolder.resolve("database.db")}") +class Database { + lateinit var connection: Connection object MetaTable : Table("LedgerMeta") { val key = column("key", DBString) @@ -15,14 +16,38 @@ object Database { } } - fun init() { + data class MetaKey(val name: String) { + companion object { + val DATABASE_VERSION = MetaKey("databaseVersion") + val LAST_LAUNCH = MetaKey("lastLaunch") + } + } + + fun setMetaKey(key: MetaKey, value: String) { + MetaTable.insert(connection, Table.OnConflict.REPLACE) { + it[MetaTable.key] = key.name + it[MetaTable.value] = value + } + } + + val databaseVersion: Long = 1 + + fun loadAndUpgrade() { + connection = DriverManager.getConnection("jdbc:sqlite:${Ledger.dataFolder.resolve("database.db")}") MetaTable.createIfNotExists(connection) - val meta = MetaTable.selectAll(connection).associate { it[MetaTable.key] to it[MetaTable.value] } - val lastLaunch = meta["lastLaunch"]?.toLong() ?: 0L + val meta = MetaTable.selectAll(connection).associate { MetaKey(it[MetaTable.key]) to it[MetaTable.value] } + val lastLaunch = meta[MetaKey.LAST_LAUNCH]?.toLong() ?: 0L println("Last launch $lastLaunch") - MetaTable.insert(connection, Table.OnConflict.REPLACE) { - it[MetaTable.key] = "lastLaunch" - it[MetaTable.value] = System.currentTimeMillis().toString() + setMetaKey(MetaKey.LAST_LAUNCH, System.currentTimeMillis().toString()) + + val oldVersion = meta[MetaKey.DATABASE_VERSION]?.toLong() ?: -1 + println("Old Database Version: $oldVersion; Current version: $databaseVersion") + // TODO: create a backup if there is a db version upgrade happening + DBUpgrade.performUpgradeChain( + connection, oldVersion, databaseVersion, + Upgrades().upgrades + ) { version -> + setMetaKey(MetaKey.DATABASE_VERSION, version.toString()) } } diff --git a/src/main/kotlin/moe/nea/ledger/database/Upgrades.kt b/src/main/kotlin/moe/nea/ledger/database/Upgrades.kt new file mode 100644 index 0000000..e83abe7 --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/database/Upgrades.kt @@ -0,0 +1,20 @@ +package moe.nea.ledger.database + +class Upgrades { + val upgrades = mutableListOf() + + fun add(upgrade: DBUpgrade) = upgrades.add(upgrade) + + init { + add(DBUpgrade.createTable( + 0, DBLogEntry, + DBLogEntry.type, DBLogEntry.playerId, DBLogEntry.profileId, + DBLogEntry.transactionId)) + add(DBUpgrade.createTable( + 0, DBItemEntry, + DBItemEntry.itemId, DBItemEntry.size, DBItemEntry.mode, DBItemEntry.transactionId + )) + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/database/schema.dot b/src/main/kotlin/moe/nea/ledger/database/schema.dot new file mode 100644 index 0000000..d932f6a --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/database/schema.dot @@ -0,0 +1,23 @@ +digraph { + node [shape=plain]; + rankdir=LR; + entry [label=< +
+ + + + + +
Log Entry
playerId
profileId
timestamp
Type
+ >]; + item [label=< + + + + + + +
Item Stack
Transaction
Item ID
Count
Transfer Direction
+ >]; +// item:transaction -> entry; +} \ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/modules/AuctionHouseDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/AuctionHouseDetection.kt index cbbff12..0d9b0cb 100644 --- a/src/main/kotlin/moe/nea/ledger/modules/AuctionHouseDetection.kt +++ b/src/main/kotlin/moe/nea/ledger/modules/AuctionHouseDetection.kt @@ -1,11 +1,14 @@ package moe.nea.ledger.modules -import moe.nea.ledger.events.BeforeGuiAction -import moe.nea.ledger.events.ChatReceived +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 @@ -18,83 +21,88 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent import java.util.regex.Pattern class AuctionHouseDetection @Inject constructor(val ledger: LedgerLogger, val ids: ItemIdProvider) { - data class LastViewedItem( - val count: Int, - val id: String, - ) - /* - You collected 8,712,000 coins from selling Ultimate Carrot Candy Upgrade to [VIP] kodokush in an auction! - You collected 60,000 coins from selling Walnut to [MVP++] Alea1337 in an auction! - You purchased 2x Walnut for 69 coins! - You purchased ◆ Ice Rune I for 4,000 coins! - */ + 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 collectSold = - Pattern.compile("You collected (?$SHORT_NUMBER_PATTERN) coins? from selling (?.*) to (?.*) in an auction!") - val purchased = - Pattern.compile("You purchased (?:(?[0-9]+)x )?(?.*) for (?$SHORT_NUMBER_PATTERN) coins!") - var lastViewedItems: MutableList = mutableListOf() + val collectSold = + Pattern.compile("You collected (?$SHORT_NUMBER_PATTERN) coins? from selling (?.*) to (?.*) in an auction!") + val purchased = + Pattern.compile("You purchased (?:(?[0-9]+)x )?(?.*) for (?$SHORT_NUMBER_PATTERN) coins!") + var lastViewedItems: MutableList = mutableListOf() - @SubscribeEvent - fun onEvent(event: ChatReceived) { - collectSold.useMatcher(event.message) { - val lastViewedItem = lastViewedItems.removeLastOrNull() - ledger.logEntry( - LedgerEntry( - "AUCTION_SOLD", - event.timestamp, - parseShortNumber(group("coins")), - lastViewedItem?.id, - lastViewedItem?.count - ) - ) - } - purchased.useMatcher(event.message) { - ledger.logEntry( - LedgerEntry( - "AUCTION_BOUGHT", - event.timestamp, - parseShortNumber(group("coins")), - ids.findForName(group("what")), - group("amount")?.toInt() ?: 1 - ) - ) - } - } + @SubscribeEvent + fun 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() + @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) - } - } + 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() - } + 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)) - } + fun handleCollectSingleAuctionView(slots: ContainerChest) { + val soldItem = slots.lowerChestInventory.getStackInSlot(9 + 4) ?: return + val id = soldItem.getInternalId() ?: return + val count = soldItem.stackSize + lastViewedItems = mutableListOf(LastViewedItem(count, id)) + } } \ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/modules/BankDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/BankDetection.kt index 8d0fd81..e9a6c26 100644 --- a/src/main/kotlin/moe/nea/ledger/modules/BankDetection.kt +++ b/src/main/kotlin/moe/nea/ledger/modules/BankDetection.kt @@ -1,8 +1,11 @@ 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 @@ -22,18 +25,22 @@ class BankDetection @Inject constructor(val ledger: LedgerLogger) { withdrawPattern.useMatcher(event.message) { ledger.logEntry( LedgerEntry( - "BANK_WITHDRAW", + TransactionType.BANK_WITHDRAW, event.timestamp, - parseShortNumber(group("amount")), + listOf(ItemChange(ItemId.COINS, + parseShortNumber(group("amount")), + ItemChange.ChangeDirection.TRANSFORM)), ) ) } depositPattern.useMatcher(event.message) { ledger.logEntry( LedgerEntry( - "BANK_DEPOSIT", + TransactionType.BANK_DEPOSIT, event.timestamp, - parseShortNumber(group("amount")), + listOf(ItemChange(ItemId.COINS, + parseShortNumber(group("amount")), + ItemChange.ChangeDirection.TRANSFORM)), ) ) } diff --git a/src/main/kotlin/moe/nea/ledger/modules/BazaarDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/BazaarDetection.kt index 01c8bbc..522beed 100644 --- a/src/main/kotlin/moe/nea/ledger/modules/BazaarDetection.kt +++ b/src/main/kotlin/moe/nea/ledger/modules/BazaarDetection.kt @@ -1,10 +1,13 @@ package moe.nea.ledger.modules -import moe.nea.ledger.events.ChatReceived +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.Inject @@ -13,35 +16,43 @@ import java.util.regex.Pattern class BazaarDetection @Inject constructor(val ledger: LedgerLogger, val ids: ItemIdProvider) { - val instaBuyPattern = - Pattern.compile("\\[Bazaar\\] Bought (?$SHORT_NUMBER_PATTERN)x (?.*) for (?$SHORT_NUMBER_PATTERN) coins!") - val instaSellPattern = - Pattern.compile("\\[Bazaar\\] Sold (?$SHORT_NUMBER_PATTERN)x (?.*) for (?$SHORT_NUMBER_PATTERN) coins!") + val instaBuyPattern = + Pattern.compile("\\[Bazaar\\] Bought (?$SHORT_NUMBER_PATTERN)x (?.*) for (?$SHORT_NUMBER_PATTERN) coins!") + val instaSellPattern = + Pattern.compile("\\[Bazaar\\] Sold (?$SHORT_NUMBER_PATTERN)x (?.*) for (?$SHORT_NUMBER_PATTERN) coins!") - @SubscribeEvent - fun onInstSellChat(event: ChatReceived) { - instaBuyPattern.useMatcher(event.message) { - ledger.logEntry( - LedgerEntry( - "BAZAAR_BUY_INSTANT", - event.timestamp, - parseShortNumber(group("coins")), - ids.findForName(group("what")), - parseShortNumber(group("count")).toInt(), - ) - ) - } - instaSellPattern.useMatcher(event.message) { - ledger.logEntry( - LedgerEntry( - "BAZAAR_SELL_INSTANT", - event.timestamp, - parseShortNumber(group("coins")), - ids.findForName(group("what")), - parseShortNumber(group("count")).toInt(), - ) - ) - } - } + @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/src/main/kotlin/moe/nea/ledger/modules/BazaarOrderDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/BazaarOrderDetection.kt index 7e611ac..8b33fb1 100644 --- a/src/main/kotlin/moe/nea/ledger/modules/BazaarOrderDetection.kt +++ b/src/main/kotlin/moe/nea/ledger/modules/BazaarOrderDetection.kt @@ -1,10 +1,13 @@ package moe.nea.ledger.modules -import moe.nea.ledger.events.ChatReceived +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 @@ -16,63 +19,77 @@ import java.util.regex.Pattern class BazaarOrderDetection @Inject constructor(val ledger: LedgerLogger, val ids: ItemIdProvider) { - val buyOrderClaimed = - Pattern.compile("\\[Bazaar] Claimed (?$SHORT_NUMBER_PATTERN)x (?.*) worth (?$SHORT_NUMBER_PATTERN) coins? bought for $SHORT_NUMBER_PATTERN each!") - val sellOrderClaimed = - Pattern.compile("\\[Bazaar] Claimed (?$SHORT_NUMBER_PATTERN) coins? from selling (?$SHORT_NUMBER_PATTERN)x (?.*) at $SHORT_NUMBER_PATTERN each!") - val orderFlipped = - Pattern.compile("\\[Bazaar] Order Flipped! (?$SHORT_NUMBER_PATTERN)x (?.*) for (?$SHORT_NUMBER_PATTERN) coins? of total expected profit.") - val previousPricePattern = - Pattern.compile("(?$SHORT_NUMBER_PATTERN)/u") - var lastFlippedPreviousPrice = 0.0 + val buyOrderClaimed = + Pattern.compile("\\[Bazaar] Claimed (?$SHORT_NUMBER_PATTERN)x (?.*) worth (?$SHORT_NUMBER_PATTERN) coins? bought for $SHORT_NUMBER_PATTERN each!") + val sellOrderClaimed = + Pattern.compile("\\[Bazaar] Claimed (?$SHORT_NUMBER_PATTERN) coins? from selling (?$SHORT_NUMBER_PATTERN)x (?.*) at $SHORT_NUMBER_PATTERN each!") + val orderFlipped = + Pattern.compile("\\[Bazaar] Order Flipped! (?$SHORT_NUMBER_PATTERN)x (?.*) for (?$SHORT_NUMBER_PATTERN) coins? of total expected profit.") + val previousPricePattern = + Pattern.compile("(?$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 detectSignFlip(event: GuiScreenEvent.InitGuiEvent) { + val gui = event.gui + if (gui !is GuiEditSign) return + gui as AccessorGuiEditSign + val text = gui.tileEntity_ledger.signText + if (text[2].unformattedText != "Previous price:") return + previousPricePattern.useMatcher(text[3].unformattedText) { + lastFlippedPreviousPrice = parseShortNumber(group("price")) + } + } - @SubscribeEvent - fun detectBuyOrders(event: ChatReceived) { - orderFlipped.useMatcher(event.message) { - val amount = parseShortNumber(group("amount")).toInt() - ledger.logEntry( - LedgerEntry( - "BAZAAR_BUY_ORDER", - event.timestamp, - lastFlippedPreviousPrice * amount, - ids.findForName(group("what")), - amount, - ) - ) - } - buyOrderClaimed.useMatcher(event.message) { - ledger.logEntry( - LedgerEntry( - "BAZAAR_BUY_ORDER", - event.timestamp, - parseShortNumber(group("coins")), - ids.findForName(group("what")), - parseShortNumber(group("amount")).toInt(), - ) - ) - } - sellOrderClaimed.useMatcher(event.message) { - ledger.logEntry( - LedgerEntry( - "BAZAAR_SELL_ORDER", - event.timestamp, - parseShortNumber(group("coins")), - ids.findForName(group("what")), - parseShortNumber(group("amount")).toInt(), - ) - ) - } - } + @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/src/main/kotlin/moe/nea/ledger/modules/BitsDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/BitsDetection.kt index 22b5392..2872f99 100644 --- a/src/main/kotlin/moe/nea/ledger/modules/BitsDetection.kt +++ b/src/main/kotlin/moe/nea/ledger/modules/BitsDetection.kt @@ -1,11 +1,14 @@ 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.parseShortNumber import moe.nea.ledger.unformattedString import moe.nea.ledger.useMatcher @@ -27,11 +30,11 @@ class BitsDetection @Inject constructor(val ledger: LedgerLogger) { if (lastBits != bits) { ledger.logEntry( LedgerEntry( - "BITS_PURSE_STATUS", - Instant.now(), - 0.0, - null, - bits + TransactionType.BITS_PURSE_STATUS, + Instant.now(), + listOf( + ItemChange(ItemId.BITS, bits.toDouble(), ItemChange.ChangeDirection.SYNC) + ) ) ) lastBits = bits @@ -46,11 +49,11 @@ class BitsDetection @Inject constructor(val ledger: LedgerLogger) { if (event.message.startsWith("You consumed a Booster Cookie!")) { ledger.logEntry( LedgerEntry( - "BOOSTER_COOKIE_ATE", + TransactionType.BOOSTER_COOKIE_ATE, Instant.now(), - 0.0, - null, - null, + listOf( + ItemChange.lose(ItemId.BOOSTER_COOKIE, 1) + ) ) ) } diff --git a/src/main/kotlin/moe/nea/ledger/modules/BitsShopDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/BitsShopDetection.kt index 2033d8d..8fd4588 100644 --- a/src/main/kotlin/moe/nea/ledger/modules/BitsShopDetection.kt +++ b/src/main/kotlin/moe/nea/ledger/modules/BitsShopDetection.kt @@ -1,10 +1,13 @@ 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.getInternalId import moe.nea.ledger.getLore import moe.nea.ledger.parseShortNumber @@ -18,7 +21,8 @@ class BitsShopDetection @Inject constructor(val ledger: LedgerLogger) { data class BitShopEntry( - val id: String, + val id: ItemId, + val stackSize: Int, val bitPrice: Int, val timestamp: Long = System.currentTimeMillis() ) @@ -37,7 +41,7 @@ class BitsShopDetection @Inject constructor(val ledger: LedgerLogger) { val bitPrice = stack.getLore() .firstNotNullOfOrNull { bitCostPattern.useMatcher(it.unformattedString()) { parseShortNumber(group("cost")).toInt() } } ?: return - lastClickedBitShopItem = BitShopEntry(id, bitPrice) + lastClickedBitShopItem = BitShopEntry(id, stack.stackSize, bitPrice) } @SubscribeEvent @@ -47,10 +51,12 @@ class BitsShopDetection @Inject constructor(val ledger: LedgerLogger) { if (System.currentTimeMillis() - lastBit.timestamp > 5000) return ledger.logEntry( LedgerEntry( - "COMMUNITY_SHOP_BUY", Instant.now(), - lastBit.bitPrice.toDouble(), - lastBit.id, - 1 + TransactionType.COMMUNITY_SHOP_BUY, + Instant.now(), + listOf( + ItemChange.lose(ItemId.BITS, lastBit.bitPrice.toDouble()), + ItemChange.gain(lastBit.id, lastBit.stackSize) + ) ) ) } diff --git a/src/main/kotlin/moe/nea/ledger/modules/DungeonChestDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/DungeonChestDetection.kt index 4bdd37c..5598174 100644 --- a/src/main/kotlin/moe/nea/ledger/modules/DungeonChestDetection.kt +++ b/src/main/kotlin/moe/nea/ledger/modules/DungeonChestDetection.kt @@ -1,10 +1,13 @@ package moe.nea.ledger.modules -import moe.nea.ledger.events.ChatReceived -import moe.nea.ledger.events.GuiClickEvent +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.events.GuiClickEvent import moe.nea.ledger.getDisplayNameU import moe.nea.ledger.getLore import moe.nea.ledger.parseShortNumber @@ -18,104 +21,106 @@ import java.util.regex.Pattern class DungeonChestDetection @Inject constructor(val logger: LedgerLogger) { - /*{ - id: "minecraft:chest", - Count: 1b, - tag: { - display: { - Lore: ["§7Purchase this chest to receive the", "§7rewards above. You can only open", "§7one chest per Dungeons run -", "§7choose wisely!", "", "§7Cost", "§625,000 Coins", "§9Dungeon Chest Key", "", "§7§cNOTE: Coins are withdrawn from your", "§cbank if you don't have enough in", "§cyour purse."], - Name: "§aOpen Reward Chest" - } - }, - Damage: 0s - } + /*{ + id: "minecraft:chest", + Count: 1b, + tag: { + display: { + Lore: ["§7Purchase this chest to receive the", "§7rewards above. You can only open", "§7one chest per Dungeons run -", "§7choose wisely!", "", "§7Cost", "§625,000 Coins", "§9Dungeon Chest Key", "", "§7§cNOTE: Coins are withdrawn from your", "§cbank if you don't have enough in", "§cyour purse."], + Name: "§aOpen Reward Chest" + } + }, + Damage: 0s + } - { - id: "minecraft:feather", - Count: 1b, - tag: { - overrideMeta: 1b, - ench: [], - HideFlags: 254, - display: { - Lore: ["§7Consume a §9Kismet Feather §7to reroll", "§7the loot within this chest.", "", "§7You may only use a feather once", "§7per dungeon run.", "", "§eClick to reroll this chest!"], - Name: "§aReroll Chest" - }, - AttributeModifiers: [] - }, - Damage: 0s + { + id: "minecraft:feather", + Count: 1b, + tag: { + overrideMeta: 1b, + ench: [], + HideFlags: 254, + display: { + Lore: ["§7Consume a §9Kismet Feather §7to reroll", "§7the loot within this chest.", "", "§7You may only use a feather once", "§7per dungeon run.", "", "§eClick to reroll this chest!"], + Name: "§aReroll Chest" + }, + AttributeModifiers: [] + }, + Damage: 0s } - */ - val costPattern = Pattern.compile("(?$SHORT_NUMBER_PATTERN) Coins") + */ + val costPattern = Pattern.compile("(?$SHORT_NUMBER_PATTERN) Coins") - data class ChestCost( - val cost: Double, - val openTimestamp: Long, - val hasKey: Boolean, - ) + data class ChestCost( + val cost: Double, + val openTimestamp: Long, + val hasKey: Boolean, + ) - var lastOpenedChest: ChestCost? = null + var lastOpenedChest: ChestCost? = null - @SubscribeEvent - fun onKismetClick(event: GuiClickEvent) { - val slot = event.slotIn ?: return - if (!slot.inventory.displayName.unformattedText.unformattedString().endsWith(" Chest")) return - val stack = slot.stack ?: return - if (stack.getDisplayNameU() == "§aReroll Chest") { - logger.logEntry( - LedgerEntry( - "KISMET_REROLL", - Instant.now(), - 0.0, - itemId = "KISMET_FEATHER", - itemAmount = 1 - ) - ) - } - } + @SubscribeEvent + fun 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(ItemId.KISMET_FEATHER, 1) + ) + ) + ) + } + } - @SubscribeEvent - fun onRewardChestClick(event: GuiClickEvent) { - val slot = event.slotIn ?: return - if (!slot.inventory.displayName.unformattedText.unformattedString().endsWith(" Chest")) return - val stack = slot.stack ?: return - val name = stack.getDisplayNameU() - if (name != "§aOpen Reward Chest") return - val lore = stack.getLore() - val costIndex = lore.indexOf("§7Cost") - if (costIndex < 0 || costIndex + 1 !in lore.indices) return - val cost = costPattern.useMatcher(lore[costIndex + 1].unformattedString()) { - parseShortNumber(group("cost")) - } ?: 0.0 // Free chest! - val hasKey = lore.contains("§9Dungeon Chest Key") - lastOpenedChest?.let(::completeTransaction) - lastOpenedChest = ChestCost(cost, System.currentTimeMillis(), hasKey) - } + @SubscribeEvent + fun onRewardChestClick(event: GuiClickEvent) { + val slot = event.slotIn ?: return + if (!slot.inventory.displayName.unformattedText.unformattedString().endsWith(" Chest")) return + val stack = slot.stack ?: return + val name = stack.getDisplayNameU() + if (name != "§aOpen Reward Chest") return + val lore = stack.getLore() + val costIndex = lore.indexOf("§7Cost") + if (costIndex < 0 || costIndex + 1 !in lore.indices) return + val cost = costPattern.useMatcher(lore[costIndex + 1].unformattedString()) { + parseShortNumber(group("cost")) + } ?: 0.0 // Free chest! + val hasKey = lore.contains("§9Dungeon Chest Key") + lastOpenedChest?.let(::completeTransaction) + lastOpenedChest = ChestCost(cost, System.currentTimeMillis(), hasKey) + } - @SubscribeEvent - fun onChatMessage(event: ChatReceived) { - if (event.message == "You don't have that many coins in the bank!") - lastOpenedChest = null - } + @SubscribeEvent + fun onChatMessage(event: ChatReceived) { + if (event.message == "You don't have that many coins in the bank!") + lastOpenedChest = null + } - fun completeTransaction(toOpen: ChestCost) { - lastOpenedChest = null - logger.logEntry( - LedgerEntry( - "DUNGEON_CHEST_OPEN", - Instant.ofEpochMilli(toOpen.openTimestamp), - toOpen.cost, - itemId = if (toOpen.hasKey) "DUNGEON_CHEST_KEY" else null - ) - ) - } + fun completeTransaction(toOpen: ChestCost) { + lastOpenedChest = null + logger.logEntry( + LedgerEntry( + TransactionType.DUNGEON_CHEST_OPEN, + Instant.ofEpochMilli(toOpen.openTimestamp), + listOfNotNull( + if (toOpen.hasKey) ItemChange.lose(ItemId.DUNGEON_CHEST_KEY, 1) else null, + ItemChange.loseCoins(toOpen.cost) + ), + ) + ) + } - @SubscribeEvent - fun onTick(event: TickEvent) { - val toOpen = lastOpenedChest - if (toOpen != null && toOpen.openTimestamp + 1000L < System.currentTimeMillis()) { - completeTransaction(toOpen) - } - } + @SubscribeEvent + fun onTick(event: TickEvent) { + val toOpen = lastOpenedChest + if (toOpen != null && toOpen.openTimestamp + 1000L < System.currentTimeMillis()) { + completeTransaction(toOpen) + } + } } diff --git a/src/main/kotlin/moe/nea/ledger/modules/MinionDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/MinionDetection.kt index 73d06fa..1b48095 100644 --- a/src/main/kotlin/moe/nea/ledger/modules/MinionDetection.kt +++ b/src/main/kotlin/moe/nea/ledger/modules/MinionDetection.kt @@ -1,12 +1,15 @@ package moe.nea.ledger.modules -import moe.nea.ledger.events.BeforeGuiAction -import moe.nea.ledger.events.ChatReceived import moe.nea.ledger.ExpiringValue +import moe.nea.ledger.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 @@ -23,7 +26,7 @@ class MinionDetection @Inject constructor(val ledger: LedgerLogger) { val hopperCollectPattern = "You received (?$SHORT_NUMBER_PATTERN) coins?!".toPattern() val minionNamePattern = "(?.*) Minion (?$ROMAN_NUMBER_PATTERN)".toPattern() - var lastOpenedMinion = ExpiringValue.empty() + var lastOpenedMinion = ExpiringValue.empty() @SubscribeEvent fun onBeforeClaim(event: BeforeGuiAction) { @@ -33,7 +36,9 @@ class MinionDetection @Inject constructor(val ledger: LedgerLogger) { minionNamePattern.useMatcher(invName) { val name = group("name") val level = parseRomanNumber(group("level")) - lastOpenedMinion = ExpiringValue(name.uppercase().replace(" ", "_") + "_" + level) + lastOpenedMinion = ExpiringValue( + ItemId(name.uppercase().replace(" ", "_") + .replace("MINION", "GENERATOR") + "_" + level)) } } @@ -43,10 +48,12 @@ class MinionDetection @Inject constructor(val ledger: LedgerLogger) { hopperCollectPattern.useMatcher(event.message) { val minionName = lastOpenedMinion.consume(3.seconds) ledger.logEntry(LedgerEntry( - "AUTOMERCHANT_PROFIT_COLLECT", + TransactionType.AUTOMERCHANT_PROFIT_COLLECT, Instant.now(), - parseShortNumber(group("amount")), - minionName, // TODO: switch to its own column idk + listOf( + ItemChange.gainCoins(parseShortNumber(group("amount"))), + ItemChange(minionName ?: ItemId.NIL, 1.0, ItemChange.ChangeDirection.CATALYST) + ), )) } } diff --git a/src/main/kotlin/moe/nea/ledger/modules/NpcDetection.kt b/src/main/kotlin/moe/nea/ledger/modules/NpcDetection.kt index 68f0257..341dd12 100644 --- a/src/main/kotlin/moe/nea/ledger/modules/NpcDetection.kt +++ b/src/main/kotlin/moe/nea/ledger/modules/NpcDetection.kt @@ -1,10 +1,13 @@ package moe.nea.ledger.modules -import moe.nea.ledger.events.ChatReceived +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.Inject @@ -23,22 +26,32 @@ class NpcDetection @Inject constructor(val ledger: LedgerLogger, val ids: ItemId npcBuyPattern.useMatcher(event.message) { ledger.logEntry( LedgerEntry( - "NPC_BUY", + TransactionType.NPC_BUY, event.timestamp, - parseShortNumber(group("coins")), - ids.findForName(group("what")), - group("count")?.let(::parseShortNumber)?.toInt() ?: 1, + 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( - "NPC_SELL", + TransactionType.NPC_SELL, event.timestamp, - parseShortNumber(group("coins")), - ids.findForName(group("what")), - group("count")?.let(::parseShortNumber)?.toInt() ?: 1, + listOf( + ItemChange.gainCoins(parseShortNumber(group("coins"))), + ItemChange.lose( + ids.findForName(group("what")) ?: ItemId.NIL, + group("count")?.let(::parseShortNumber)?.toInt() ?: 1, + ) + ) ) ) } diff --git a/src/main/kotlin/moe/nea/ledger/utils/DI.kt b/src/main/kotlin/moe/nea/ledger/utils/DI.kt index 1114127..abd6272 100644 --- a/src/main/kotlin/moe/nea/ledger/utils/DI.kt +++ b/src/main/kotlin/moe/nea/ledger/utils/DI.kt @@ -31,6 +31,8 @@ class DI { return internalProvide(type, element) } + inline fun provide(): T = provide(T::class.java) + fun register(type: Class, provider: BaseDIProvider) { providers[type] = provider } -- cgit