Ported kill stats and rare item drop storage to sqlite from json

This fixes lengthy and memory intensive server shutdowns
Existing global_kill_stats.json will be imported on first run
This commit is contained in:
sirdabalot 2025-02-12 12:17:02 +00:00 committed by Ryan
parent 212ce57580
commit 62a7dd4324
8 changed files with 415 additions and 388 deletions

1
Server/.gitignore vendored
View file

@ -2,6 +2,7 @@ bin/**
out/** out/**
data/logs/** data/logs/**
data/profile/** data/profile/**
data/playerstats/**
.idea/** .idea/**
/bin /bin
.DS_Store** .DS_Store**

View file

@ -1,217 +0,0 @@
package core
import core.game.system.SystemShutdownHook
import core.game.system.mysql.SQLManager
import core.game.world.map.Location
import core.tools.mysql.Database
import core.tools.secondsToTicks
import org.json.simple.JSONObject
import java.io.File
import java.math.BigInteger
/**
* A class holding various variables for the server.
* @author Ceikry
*/
class ServerConstants {
companion object {
@JvmField
var SHUTDOWN_HOOK: Thread = Thread(SystemShutdownHook())
@JvmField
var DATA_PATH: String? = null
//path to the cache
@JvmField
var CACHE_PATH: String? = null
//path for the server store (obsolete, but kept for the sake of system sanity.)
@JvmField
var STORE_PATH: String? = null
//path for player saves
@JvmField
var PLAYER_SAVE_PATH: String? = null
@JvmField
var PLAYER_ATTRIBUTE_PATH = "ish";
//path to the various config files, such as npc_spawns.json
var CONFIG_PATH: String? = null
@JvmField
var GRAND_EXCHANGE_DATA_PATH: String? = null
@JvmField
var RDT_DATA_PATH: String? = null
@JvmField
var OBJECT_PARSER_PATH: String? = null
@JvmField
var SCRIPTS_PATH: String? = null
@JvmField
var DIALOGUE_SCRIPTS_PATH: String? = null
@JvmField
var LOGS_PATH: String? = null
@JvmField
var BOT_DATA_PATH: String? = null
//the max number of players.
@JvmField
var MAX_PLAYERS = 0
//the max number of NPCs
@JvmField
var MAX_NPCS = 0
//the location where new players are placed on login.
@JvmField
var START_LOCATION: Location? = null
//Location for all home teleports/respawn location
@JvmField
var HOME_LOCATION: Location? = null
//the name for the database
@JvmField
var DATABASE_NAME: String? = null
//username for the database
@JvmField
var DATABASE_USER: String? = null
//password for the database
@JvmField
var DATABASE_PASS: String? = null
//address for the database
@JvmField
var DATABASE_ADDRESS: String? = null
@JvmField
var DATABASE_PORT: String? = null
@JvmField
var WRITE_LOGS: Boolean = false
@JvmField
var BANK_SIZE: Int = 496
@JvmField
var GE_AUTOSAVE_FREQUENCY = secondsToTicks(3600) //1 hour
@JvmField
var GE_AUTOSTOCK_ENABLED = false
//location names for the ::to command.
val TELEPORT_DESTINATIONS = arrayOf(
arrayOf(Location.create(2974, 4383, 2), "corp", "corporal", "corporeal"),
arrayOf(Location.create(2659, 2649, 0), "pc", "pest control", "pest"),
arrayOf(Location.create(3293, 3184, 0), "al kharid", "alkharid", "kharid"),
arrayOf(Location.create(3222, 3217, 0), "lumbridge", "lumby"),
arrayOf(Location.create(3110, 3168, 0), "wizard tower", "wizards tower", "tower", "wizards"),
arrayOf(Location.create(3083, 3249, 0), "draynor", "draynor village"),
arrayOf(Location.create(3019, 3244, 0), "port sarim", "sarim"),
arrayOf(Location.create(2956, 3209, 0), "rimmington"),
arrayOf(Location.create(2965, 3380, 0), "fally", "falador"),
arrayOf(Location.create(2895, 3436, 0), "taverley"),
arrayOf(Location.create(3080, 3423, 0), "barbarian village", "barb"),
arrayOf(Location.create(3213, 3428, 0), "varrock"),
arrayOf(Location.create(3164, 3485, 0), "grand exchange", "ge"),
arrayOf(Location.create(2917, 3175, 0), "karamja"),
arrayOf(Location.create(2450, 5165, 0), "tzhaar"),
arrayOf(Location.create(2795, 3177, 0), "brimhaven"),
arrayOf(Location.create(2849, 2961, 0), "shilo village", "shilo"),
arrayOf(Location.create(2605, 3093, 0), "yanille"),
arrayOf(Location.create(2663, 3305, 0), "ardougne", "ardy"),
arrayOf(Location.create(2450, 3422, 0), "gnome stronghold", "gnome"),
arrayOf(Location.create(2730, 3485, 0), "camelot", "cammy", "seers"),
arrayOf(Location.create(2805, 3435, 0), "catherby"),
arrayOf(Location.create(2659, 3657, 0), "rellekka"),
arrayOf(Location.create(2890, 3676, 0), "trollheim"),
arrayOf(Location.create(2914, 3746, 0), "godwars", "gwd", "god wars"),
arrayOf(Location.create(3180, 3684, 0), "bounty hunter", "bh"),
arrayOf(Location.create(3272, 3687, 0), "clan wars", "clw"),
arrayOf(Location.create(3090, 3957, 0), "mage arena", "mage", "magearena", "arena"),
arrayOf(Location.create(3069, 10257, 0), "king black dragon", "kbd"),
arrayOf(Location.create(3359, 3416, 0), "digsite"),
arrayOf(Location.create(3488, 3489, 0), "canifis"),
arrayOf(Location.create(3428, 3526, 0), "slayer tower", "slayer"),
arrayOf(Location.create(3502, 9483, 2), "kalphite queen", "kq", "kalphite hive", "kalphite"),
arrayOf(Location.create(3233, 2913, 0), "pyramid"),
arrayOf(Location.create(3419, 2917, 0), "nardah"),
arrayOf(Location.create(3482, 3090, 0), "uzer"),
arrayOf(Location.create(3358, 2970, 0), "pollnivneach", "poln"),
arrayOf(Location.create(3305, 2788, 0), "sophanem"),
arrayOf(Location.create(2898, 3544, 0), "burthorpe", "burthorp"),
arrayOf(Location.create(3088, 3491, 0), "edge", "edgeville"),
arrayOf(Location.create(3169, 3034, 0), "bedabin"),
arrayOf(Location.create(3565, 3289, 0), "barrows"),
arrayOf(Location.create(3016, 3513, 0), "bkf", "black knights fortress"),
arrayOf(Location.create(3052, 3481, 0), "monastery")
)
@JvmField
var DATABASE: Database? = null
//if SQL is enabled
@JvmField
var MYSQL = true
//the server name
@JvmField
var SERVER_NAME: String = ""
//The RSA_KEY for the server.
@JvmField
var EXPONENT = BigInteger("52317200263721308660411803146360972546561037484450290559823448967617618536819222494429186211525706853703641369936136465589036631055945454547936148730495933263344792588795811788941129493188907621550836988152620502378278134421731002382361670176785306598134280732756356458964850508114958769985438054979422820241")
//The MODULUS for the server.
@JvmField
var MODULUS = BigInteger("96982303379631821170939875058071478695026608406924780574168393250855797534862289546229721580153879336741968220328805101128831071152160922518190059946555203865621183480223212969502122536662721687753974815205744569357388338433981424032996046420057284324856368815997832596174397728134370577184183004453899764051")
/**
* Parses a JSONObject and retrieves the values for all settings in this file.
* @author Ceikry
* @param data : The JSONObject to parse.
*/
fun parse(data: JSONObject) {
MAX_PLAYERS = data["max_players"].toString().toInt()
MAX_NPCS = data["max_npcs"].toString().toInt()
START_LOCATION = JSONUtils.parseLocation(data["new_player_location"].toString())
HOME_LOCATION = JSONUtils.parseLocation(data["home_location"].toString())
DATA_PATH = JSONUtils.parsePath(data["data_path"].toString())
CACHE_PATH = JSONUtils.parsePath(data["cache_path"].toString())
STORE_PATH = JSONUtils.parsePath(data["store_path"].toString())
PLAYER_SAVE_PATH = JSONUtils.parsePath(data["save_path"].toString())
CONFIG_PATH = JSONUtils.parsePath(data["configs_path"].toString())
PLAYER_ATTRIBUTE_PATH = PLAYER_SAVE_PATH + "attributes" + File.separator
GRAND_EXCHANGE_DATA_PATH = JSONUtils.parsePath(data["grand_exchange_data_path"].toString())
BOT_DATA_PATH = JSONUtils.parsePath(data["bot_data_path"].toString())
RDT_DATA_PATH = JSONUtils.parsePath(data["rare_drop_table_path"].toString())
OBJECT_PARSER_PATH = JSONUtils.parsePath(data["object_parser_path"].toString())
SCRIPTS_PATH = JSONUtils.parsePath(data["scripts_path"].toString())
DIALOGUE_SCRIPTS_PATH = JSONUtils.parsePath(data["dialogue_scripts_path"].toString())
if(data.containsKey("logs_path")){
LOGS_PATH = data["logs_path"].toString()
}
if(data.containsKey("writeLogs")){
WRITE_LOGS = data["writeLogs"] as Boolean
}
DATABASE_NAME = data["database_name"].toString()
DATABASE_USER = data["database_username"].toString()
DATABASE_PASS = data["database_password"].toString()
DATABASE_ADDRESS = data["database_address"].toString()
DATABASE_PORT = data["database_port"].toString()
DATABASE = Database(DATABASE_ADDRESS, DATABASE_NAME, DATABASE_USER, DATABASE_PASS)
}
}
}

View file

@ -10,7 +10,7 @@ import content.global.skill.slayer.SlayerManager
import content.global.skill.slayer.Tasks import content.global.skill.slayer.Tasks
import content.global.skill.summoning.familiar.BurdenBeast import content.global.skill.summoning.familiar.BurdenBeast
import core.ServerConstants import core.ServerConstants
import core.api.utils.GlobalKillCounter import core.api.utils.PlayerStatsCounter
import core.api.utils.Vector import core.api.utils.Vector
import core.cache.def.impl.AnimationDefinition import core.cache.def.impl.AnimationDefinition
import core.cache.def.impl.ItemDefinition import core.cache.def.impl.ItemDefinition
@ -2075,7 +2075,7 @@ fun sendItemSelect (player: Player, vararg options: String, keepAlive: Boolean =
fun announceIfRare(player: Player, item: Item) { fun announceIfRare(player: Player, item: Item) {
if (item.definition.getConfiguration(ItemConfigParser.RARE_ITEM, false)) { if (item.definition.getConfiguration(ItemConfigParser.RARE_ITEM, false)) {
sendNews("${player.username} has just received: ${item.amount} x ${item.name}.") sendNews("${player.username} has just received: ${item.amount} x ${item.name}.")
GlobalKillCounter.incrementRareDrop(player, item) PlayerStatsCounter.incrementRareDrop(player, item)
} }
} }

View file

@ -1,120 +0,0 @@
package core.api.utils
import core.api.ShutdownListener
import core.api.StartupListener
import core.game.node.entity.player.Player
import core.tools.SystemLogger
import java.io.File
import java.io.FileReader
import java.io.FileWriter
import org.json.simple.JSONObject
import org.json.simple.parser.JSONParser
import core.ServerConstants
import core.api.log
import core.game.node.item.Item
import core.tools.Log
import core.tools.SystemLogger.logShutdown
import core.tools.SystemLogger.logStartup
class GlobalKillCounter : StartupListener, ShutdownListener {
override fun startup() {
logStartup("Parsing Global Kill Counts")
val file = File(ServerConstants.DATA_PATH + File.separator + "global_kill_stats.json")
if(!file.exists()) {
return
}
val reader = FileReader(file)
val parser = JSONParser()
try {
val data = parser.parse(reader) as JSONObject
val tmp_kills = data.get("kills")
populate(kills, tmp_kills)
val tmp_rare_drops = data.get("rare_drops")
populate(rare_drops, tmp_rare_drops)
} catch (e: Exception){
log(this::class.java, Log.ERR, "Failed parsing ${file.name} - stack trace below.")
e.printStackTrace()
}
}
override fun shutdown() {
logShutdown("Saving Global Kill Counts")
val data = JSONObject()
data.put("kills", saveField(kills))
data.put("rare_drops", saveField(rare_drops))
val file = File(ServerConstants.DATA_PATH + File.separator + "global_kill_stats.json")
FileWriter(file).use { it.write(data.toJSONString()); it.flush(); it.close() }
}
companion object {
val kills: HashMap<String, HashMap<Long, Long>> = HashMap()
val rare_drops: HashMap<String, HashMap<Long, Long>> = HashMap()
@JvmStatic
fun populate(field: HashMap<String, HashMap<Long, Long>>, obj: Any?) {
if(obj != null && obj is JSONObject) {
for((player, tmp_kc) in obj.asIterable()) {
if(player is String) {
val kc: HashMap<Long, Long> = HashMap()
for((npc_id, count) in (tmp_kc as JSONObject).asIterable()) {
kc.put(java.lang.Long.parseLong(npc_id as String), count as Long)
}
field.put(player, kc)
}
}
}
}
@JvmStatic
fun saveField(field: HashMap<String, HashMap<Long, Long>>): JSONObject {
val tmp_kills = JSONObject()
for((player, kc) in field.asIterable()) {
val tmp_kc = JSONObject()
for((id, count) in kc.asIterable()) {
tmp_kc.put(id, count)
}
tmp_kills.put(player, tmp_kc)
}
return tmp_kills
}
@JvmStatic
fun save() {
}
@JvmStatic
fun incrementKills(player: Player, npc_id: Int) {
val player_kills = kills.getOrPut(player.username, { HashMap() })
val old_amount = player_kills.getOrElse(npc_id.toLong(), { 0 })
player_kills.put(npc_id.toLong(), 1 + old_amount)
}
@JvmStatic
fun incrementRareDrop(player: Player, item: Item) {
val player_drops = rare_drops.getOrPut(player.username, { HashMap() })
val old_amount = player_drops.getOrElse(item.id.toLong(), { 0 })
player_drops.put(item.id.toLong(), item.amount + old_amount)
}
@JvmStatic
fun getKills(player: Player, npc_id: Int): Long {
return kills.getOrElse(player.username, { HashMap() }).getOrElse(npc_id.toLong(), { 0 })
}
@JvmStatic
fun getKills(player: Player, npc_ids: IntArray): Long {
var sum: Long = 0
for(npc_id in npc_ids) {
sum += getKills(player, npc_id)
}
return sum
}
@JvmStatic
fun getRareDrops(player: Player, item_id: Int): Long {
return rare_drops.getOrElse(player.username, { HashMap() }).getOrElse(item_id.toLong(), { 0 })
}
}
}

View file

@ -0,0 +1,253 @@
package core.api.utils
import core.api.StartupListener
import core.game.node.entity.player.Player
import java.io.File
import java.io.FileReader
import org.json.simple.JSONObject
import org.json.simple.parser.JSONParser
import core.ServerConstants
import core.api.log
import core.game.node.item.Item
import core.game.world.GameWorld
import core.integrations.sqlite.SQLiteProvider
import core.tools.Log
import core.tools.SystemLogger.logStartup
import kotlin.io.path.Path
class PlayerStatsCounter(
private val dbPath: String = Path(ServerConstants.DATA_PATH ?: "", "playerstats", "player_stats.db").toString()
) : StartupListener {
override fun startup() {
logStartup("Loading Player Stats")
db = SQLiteProvider(dbPath, expectedTables)
db.initTables()
if (!tableHasData()) { // TODO: Remove check, porter and raw inserts once SQLite tracking is proven and the live server has been updated
portLegacyKillCounterJsonToSQLite()
}
}
companion object {
lateinit var db: SQLiteProvider
private fun resolveUIDFromPlayerUsername(playerUsername: String): Int {
return GameWorld.accountStorage.getAccountInfo(playerUsername).uid
}
private fun portLegacyKillCounterJsonToSQLite() {
val file = File(Path(ServerConstants.DATA_PATH ?: "", "global_kill_stats.json").toString())
if (!file.exists()) {
return
}
val reader = FileReader(file)
val parser = JSONParser()
try {
val data = parser.parse(reader) as JSONObject
val json_kills = data.get("kills")
if (json_kills != null && json_kills is JSONObject) {
var progress = 1
val totalPlayers = json_kills.size
for ((player, killStats) in json_kills.asIterable()) {
log(
PlayerStatsCounter::class.java,
Log.INFO,
"Porting kill counters for player $progress/$totalPlayers"
)
if (player is String) {
val playerUid = resolveUIDFromPlayerUsername(player)
log(
PlayerStatsCounter::class.java,
Log.INFO,
"Player $player ($playerUid)"
)
for ((npc_id, count) in (killStats as JSONObject).asIterable()) {
log(
PlayerStatsCounter::class.java,
Log.INFO,
"Inserting kill for $player ($playerUid, $npc_id, $count)"
)
incrementKills(
playerUid,
(npc_id as String).toInt(),
count as Long
)
}
}
progress++
}
}
val json_rare_drops = data.get("rare_drops")
if (json_rare_drops != null && json_rare_drops is JSONObject) {
var progress = 1
val totalPlayers = json_rare_drops.size
for ((player, rareDrops) in json_rare_drops.asIterable()) {
log(
PlayerStatsCounter::class.java,
Log.INFO,
"Porting rare drops for player $progress/$totalPlayers"
)
if (player is String) {
val playerUid = resolveUIDFromPlayerUsername(player)
log(
PlayerStatsCounter::class.java,
Log.INFO,
"Player $player ($playerUid)"
)
for ((item_id, count) in (rareDrops as JSONObject).asIterable()) {
log(
PlayerStatsCounter::class.java,
Log.INFO,
"Inserting rare drop for $player ($playerUid, $item_id, $count)"
)
incrementRareDrop(
playerUid,
(item_id as String).toInt(),
count as Long
)
}
}
progress++
}
}
} catch (e: Exception) {
log(this::class.java, Log.ERR, "Failed parsing ${file.name} - stack trace below.")
e.printStackTrace()
}
}
private val killsTableDefinition = """
CREATE TABLE kills(
player_uid INTEGER,
entity_id INTEGER,
kills INTEGER,
PRIMARY KEY(player_uid, entity_id))
""".trimIndent()
private val rareDropsTableDefinition = """
CREATE TABLE rare_drops(
player_uid INTEGER,
item_id INTEGER,
amount INTEGER,
PRIMARY KEY (player_uid, item_id))
""".trimIndent()
private val getKillsRowCountSql = """
SELECT COUNT(1) FROM kills;
""".trimIndent()
private val insertOrIncrementKillSql = """
INSERT INTO kills (player_uid, entity_id, kills)
VALUES (?, ?, ?)
ON CONFLICT(player_uid, entity_id) DO UPDATE SET kills = kills + ?;
""".trimIndent()
private val insertOrIncrementRareDropSql = """
INSERT INTO rare_drops (player_uid, item_id, amount)
VALUES (?, ?, ?)
ON CONFLICT(player_uid, item_id) DO UPDATE SET amount = amount + ?;
""".trimIndent()
private val getRareDropsSql = """
SELECT amount FROM rare_drops WHERE player_uid=? AND item_id=?
""".trimIndent()
private fun createGetKillsSql(countOfEntitiesToSearchFor: Int): String {
if (countOfEntitiesToSearchFor < 1) {
throw Exception("Should be at least 1")
}
val entitySearchParameterMarkers = "?" + ",?".repeat(countOfEntitiesToSearchFor - 1)
return """
SELECT SUM(kills) FROM kills WHERE player_uid=? AND entity_id IN ($entitySearchParameterMarkers)
""".trimIndent()
}
private val expectedTables = hashMapOf(
"kills" to killsTableDefinition,
"rare_drops" to rareDropsTableDefinition,
)
private fun tableHasData(): Boolean {
var hasData = false
db.run { conn ->
val statement = conn.prepareStatement(getKillsRowCountSql)
val result = statement.executeQuery()
if (result.next()) {
val rowCount = result.getInt(1)
hasData = rowCount > 0
}
}
return hasData
}
@JvmStatic
private fun incrementKills(playerUid: Int, npcId: Int, kills: Long) {
db.run { conn ->
val statement = conn.prepareStatement(insertOrIncrementKillSql)
statement.setInt(1, playerUid)
statement.setInt(2, npcId)
statement.setLong(3, kills)
statement.setLong(4, kills)
statement.execute()
}
}
@JvmStatic
fun incrementKills(player: Player, npcId: Int) {
incrementKills(player.details.uid, npcId, 1)
}
@JvmStatic
private fun incrementRareDrop(playerUid: Int, itemId: Int, amount: Long) {
db.run { conn ->
val statement = conn.prepareStatement(insertOrIncrementRareDropSql)
statement.setInt(1, playerUid)
statement.setInt(2, itemId)
statement.setLong(3, amount)
statement.setLong(4, amount)
statement.execute()
}
}
@JvmStatic
fun incrementRareDrop(player: Player, item: Item) {
incrementRareDrop(player.details.uid, item.id, item.amount.toLong())
}
@JvmStatic
fun getKills(player: Player, npcIds: IntArray): Long {
var kills: Long = 0
db.run { conn ->
val statement = conn.prepareStatement(createGetKillsSql(npcIds.size))
statement.setInt(1, player.details.uid)
for (npcIdParamIndex in npcIds.indices) {
// +2 because the statement parameterIndexes start at 1 and the first param is the player uid
statement.setInt(npcIdParamIndex + 2, npcIds[npcIdParamIndex])
}
val result = statement.executeQuery()
if (result.next()) {
kills = result.getLong(1)
}
}
return kills
}
@JvmStatic
fun getRareDrops(player: Player, itemId: Int): Long {
var rareDropsForItem: Long = 0
db.run { conn ->
val statement = conn.prepareStatement(getRareDropsSql)
statement.setInt(1, player.details.uid)
statement.setInt(2, itemId)
val result = statement.executeQuery()
if (result.next()) {
rareDropsForItem = result.getLong(1)
}
}
return rareDropsForItem
}
}
}

View file

@ -8,7 +8,6 @@ import core.game.interaction.MovementPulse;
import core.game.node.entity.Entity; import core.game.node.entity.Entity;
import core.game.node.entity.combat.BattleState; import core.game.node.entity.combat.BattleState;
import core.game.node.entity.combat.spell.CombatSpell; import core.game.node.entity.combat.spell.CombatSpell;
import core.game.node.entity.combat.CombatPulse;
import core.game.node.entity.combat.CombatStyle; import core.game.node.entity.combat.CombatStyle;
import core.game.node.entity.combat.spell.DefaultCombatSpell; import core.game.node.entity.combat.spell.DefaultCombatSpell;
import core.game.node.entity.combat.equipment.WeaponInterface; import core.game.node.entity.combat.equipment.WeaponInterface;
@ -29,7 +28,7 @@ import core.game.world.update.flag.context.Animation;
import core.game.world.update.flag.context.Graphics; import core.game.world.update.flag.context.Graphics;
import core.game.world.update.flag.*; import core.game.world.update.flag.*;
import core.tools.RandomFunction; import core.tools.RandomFunction;
import core.api.utils.GlobalKillCounter; import core.api.utils.PlayerStatsCounter;
import core.api.utils.Vector; import core.api.utils.Vector;
import core.game.shops.Shops; import core.game.shops.Shops;
import core.game.node.entity.combat.CombatSwingHandler; import core.game.node.entity.combat.CombatSwingHandler;
@ -573,7 +572,7 @@ public class NPC extends Entity {
Player p = !(killer instanceof Player) ? null : (Player) killer; Player p = !(killer instanceof Player) ? null : (Player) killer;
if (p != null) { if (p != null) {
p.incrementAttribute("/save:" + STATS_BASE + ":" + STATS_ENEMIES_KILLED); p.incrementAttribute("/save:" + STATS_BASE + ":" + STATS_ENEMIES_KILLED);
GlobalKillCounter.incrementKills(p, originalId); PlayerStatsCounter.incrementKills(p, originalId);
} }
handleDrops(p, killer); handleDrops(p, killer);
if (!isRespawn()) if (!isRespawn())

View file

@ -11,7 +11,7 @@ import core.game.node.entity.player.Player
import core.plugin.Initializable import core.plugin.Initializable
import org.rs09.consts.Items import org.rs09.consts.Items
import org.rs09.consts.NPCs import org.rs09.consts.NPCs
import core.api.utils.GlobalKillCounter import core.api.utils.PlayerStatsCounter
import core.game.system.command.Privilege import core.game.system.command.Privilege
import core.game.world.repository.Repository import core.game.world.repository.Repository
import java.util.* import java.util.*
@ -89,69 +89,69 @@ class StatsCommandSet : CommandSet(Privilege.STANDARD) {
} }
1 -> { 1 -> {
when(i) { when(i) {
97 -> sendLine(player, "Turoths: ${GlobalKillCounter.getKills(queryPlayer, TUROTH_IDS)}", i) 97 -> sendLine(player, "Turoths: ${PlayerStatsCounter.getKills(queryPlayer, TUROTH_IDS)}", i)
68 -> sendLine(player, "Kurasks: ${GlobalKillCounter.getKills(queryPlayer, KURASK_IDS)}", i) 68 -> sendLine(player, "Kurasks: ${PlayerStatsCounter.getKills(queryPlayer, KURASK_IDS)}", i)
69 -> sendLine(player, "Leaf-bladed swords: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.LEAF_BLADED_SWORD_13290)}", i) 69 -> sendLine(player, "Leaf-bladed swords: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.LEAF_BLADED_SWORD_13290)}", i)
70 -> sendLine(player, SPACER,i) 70 -> sendLine(player, SPACER,i)
71 -> sendLine(player, "Gargoyles: ${GlobalKillCounter.getKills(queryPlayer, GARGOYLE_IDS)}", i) 71 -> sendLine(player, "Gargoyles: ${PlayerStatsCounter.getKills(queryPlayer, GARGOYLE_IDS)}", i)
72 -> sendLine(player, "Granite mauls: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.GRANITE_MAUL_4153)}", i) 72 -> sendLine(player, "Granite mauls: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.GRANITE_MAUL_4153)}", i)
73 -> sendLine(player, SPACER,i) 73 -> sendLine(player, SPACER,i)
74 -> sendLine(player, "Spiritual mages: ${GlobalKillCounter.getKills(queryPlayer, SPIRITUAL_MAGE_IDS)}", i) 74 -> sendLine(player, "Spiritual mages: ${PlayerStatsCounter.getKills(queryPlayer, SPIRITUAL_MAGE_IDS)}", i)
75 -> sendLine(player, "Dragon boots: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.DRAGON_BOOTS_11732)}", i) 75 -> sendLine(player, "Dragon boots: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.DRAGON_BOOTS_11732)}", i)
76 -> sendLine(player, SPACER,i) 76 -> sendLine(player, SPACER,i)
77 -> sendLine(player, "Abyssal demons: ${GlobalKillCounter.getKills(queryPlayer, NPCs.ABYSSAL_DEMON_1615)}", i) 77 -> sendLine(player, "Abyssal demons: ${PlayerStatsCounter.getKills(queryPlayer, intArrayOf(NPCs.ABYSSAL_DEMON_1615))}", i)
78 -> sendLine(player, "Abyssal whips: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.ABYSSAL_WHIP_4151)}", i) 78 -> sendLine(player, "Abyssal whips: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.ABYSSAL_WHIP_4151)}", i)
79 -> sendLine(player, SPACER,i) 79 -> sendLine(player, SPACER,i)
80 -> sendLine(player, "Dark beasts: ${GlobalKillCounter.getKills(queryPlayer, NPCs.DARK_BEAST_2783)}", i) 80 -> sendLine(player, "Dark beasts: ${PlayerStatsCounter.getKills(queryPlayer, intArrayOf(NPCs.DARK_BEAST_2783))}", i)
81 -> sendLine(player, "Dark bows: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.DARK_BOW_11235)}", i) 81 -> sendLine(player, "Dark bows: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.DARK_BOW_11235)}", i)
82 -> sendLine(player, "Green Dragons: ${GlobalKillCounter.getKills(queryPlayer, GREEN_DRAGON_IDS)}", i) 82 -> sendLine(player, "Green Dragons: ${PlayerStatsCounter.getKills(queryPlayer, GREEN_DRAGON_IDS)}", i)
83 -> sendLine(player, "Blue Dragons: ${GlobalKillCounter.getKills(queryPlayer, BLUE_DRAGON_IDS)}", i) 83 -> sendLine(player, "Blue Dragons: ${PlayerStatsCounter.getKills(queryPlayer, BLUE_DRAGON_IDS)}", i)
84 -> sendLine(player, "Red Dragons: ${GlobalKillCounter.getKills(queryPlayer, RED_DRAGON_IDS)}", i) 84 -> sendLine(player, "Red Dragons: ${PlayerStatsCounter.getKills(queryPlayer, RED_DRAGON_IDS)}", i)
85 -> sendLine(player, "Black Dragons: ${GlobalKillCounter.getKills(queryPlayer, BLACK_DRAGON_IDS)}", i) 85 -> sendLine(player, "Black Dragons: ${PlayerStatsCounter.getKills(queryPlayer, BLACK_DRAGON_IDS)}", i)
86 -> sendLine(player, SPACER,i) 86 -> sendLine(player, SPACER,i)
87 -> sendLine(player, "Bronze Dragons: ${GlobalKillCounter.getKills(queryPlayer, BRONZE_DRAGON_IDS)}", i) 87 -> sendLine(player, "Bronze Dragons: ${PlayerStatsCounter.getKills(queryPlayer, BRONZE_DRAGON_IDS)}", i)
88 -> sendLine(player, "Iron Dragons: ${GlobalKillCounter.getKills(queryPlayer, IRON_DRAGON_IDS)}", i) 88 -> sendLine(player, "Iron Dragons: ${PlayerStatsCounter.getKills(queryPlayer, IRON_DRAGON_IDS)}", i)
89 -> sendLine(player, "Steel Dragons: ${GlobalKillCounter.getKills(queryPlayer, STEEL_DRAGON_IDS)}", i) 89 -> sendLine(player, "Steel Dragons: ${PlayerStatsCounter.getKills(queryPlayer, STEEL_DRAGON_IDS)}", i)
90 -> sendLine(player, "Mithril Dragons: ${GlobalKillCounter.getKills(queryPlayer, MITHRIL_DRAGON_IDS)}", i) 90 -> sendLine(player, "Mithril Dragons: ${PlayerStatsCounter.getKills(queryPlayer, MITHRIL_DRAGON_IDS)}", i)
91 -> sendLine(player, "Skeletal Wyverns: ${GlobalKillCounter.getKills(queryPlayer, SKELETAL_WYVERN_IDS)}", i) 91 -> sendLine(player, "Skeletal Wyverns: ${PlayerStatsCounter.getKills(queryPlayer, SKELETAL_WYVERN_IDS)}", i)
92 -> sendLine(player, SPACER,i) 92 -> sendLine(player, SPACER,i)
93 -> sendLine(player, "Draconic visages: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.DRACONIC_VISAGE_11286)}", i) 93 -> sendLine(player, "Draconic visages: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.DRACONIC_VISAGE_11286)}", i)
else -> sendLine(player,"",i) else -> sendLine(player,"",i)
} }
} }
2 -> { 2 -> {
when(i) { when(i) {
97 -> sendLine(player, "Ahrim's hood: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.AHRIMS_HOOD_4708)}", i) 97 -> sendLine(player, "Ahrim's hood: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.AHRIMS_HOOD_4708)}", i)
68 -> sendLine(player, "Ahrim's staff: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.AHRIMS_STAFF_4710)}", i) 68 -> sendLine(player, "Ahrim's staff: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.AHRIMS_STAFF_4710)}", i)
69 -> sendLine(player, "Ahrim's robetop: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.AHRIMS_ROBETOP_4712)}", i) 69 -> sendLine(player, "Ahrim's robetop: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.AHRIMS_ROBETOP_4712)}", i)
70 -> sendLine(player, "Ahrim's robeskirt: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.AHRIMS_ROBESKIRT_4714)}", i) 70 -> sendLine(player, "Ahrim's robeskirt: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.AHRIMS_ROBESKIRT_4714)}", i)
71 -> sendLine(player, SPACER,i) 71 -> sendLine(player, SPACER,i)
72 -> sendLine(player, "Dharok's helm: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.DHAROKS_HELM_4716)}", i) 72 -> sendLine(player, "Dharok's helm: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.DHAROKS_HELM_4716)}", i)
73 -> sendLine(player, "Dharok's greataxe: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.DHAROKS_GREATAXE_4718)}", i) 73 -> sendLine(player, "Dharok's greataxe: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.DHAROKS_GREATAXE_4718)}", i)
74 -> sendLine(player, "Dharok's platebody: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.DHAROKS_PLATEBODY_4720)}", i) 74 -> sendLine(player, "Dharok's platebody: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.DHAROKS_PLATEBODY_4720)}", i)
75 -> sendLine(player, "Dharok's platelegs: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.DHAROKS_PLATELEGS_4722)}", i) 75 -> sendLine(player, "Dharok's platelegs: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.DHAROKS_PLATELEGS_4722)}", i)
76 -> sendLine(player, SPACER,i) 76 -> sendLine(player, SPACER,i)
77 -> sendLine(player, "Guthan's helm: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.GUTHANS_HELM_4724)}", i) 77 -> sendLine(player, "Guthan's helm: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.GUTHANS_HELM_4724)}", i)
78 -> sendLine(player, "Guthan's warspear: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.GUTHANS_WARSPEAR_4726)}", i) 78 -> sendLine(player, "Guthan's warspear: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.GUTHANS_WARSPEAR_4726)}", i)
79 -> sendLine(player, "Guthan's platebody: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.GUTHANS_PLATEBODY_4728)}", i) 79 -> sendLine(player, "Guthan's platebody: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.GUTHANS_PLATEBODY_4728)}", i)
80 -> sendLine(player, "Guthan's chainskirt: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.GUTHANS_CHAINSKIRT_4730)}", i) 80 -> sendLine(player, "Guthan's chainskirt: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.GUTHANS_CHAINSKIRT_4730)}", i)
82 -> sendLine(player, "Karil's coif: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.KARILS_COIF_4732)}", i) 82 -> sendLine(player, "Karil's coif: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.KARILS_COIF_4732)}", i)
83 -> sendLine(player, "Karil's crossbow: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.KARILS_CROSSBOW_4734)}", i) 83 -> sendLine(player, "Karil's crossbow: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.KARILS_CROSSBOW_4734)}", i)
84 -> sendLine(player, "Karil's leathertop: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.KARILS_LEATHERTOP_4736)}", i) 84 -> sendLine(player, "Karil's leathertop: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.KARILS_LEATHERTOP_4736)}", i)
85 -> sendLine(player, "Karil's leatherskirt: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.KARILS_LEATHERSKIRT_4738)}", i) 85 -> sendLine(player, "Karil's leatherskirt: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.KARILS_LEATHERSKIRT_4738)}", i)
86 -> sendLine(player, SPACER,i) 86 -> sendLine(player, SPACER,i)
87 -> sendLine(player, "Torag's helm: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.TORAGS_HELM_4745)}", i) 87 -> sendLine(player, "Torag's helm: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.TORAGS_HELM_4745)}", i)
88 -> sendLine(player, "Torag's hammers: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.TORAGS_HAMMERS_4747)}", i) 88 -> sendLine(player, "Torag's hammers: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.TORAGS_HAMMERS_4747)}", i)
89 -> sendLine(player, "Torag's platebody: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.TORAGS_PLATEBODY_4749)}", i) 89 -> sendLine(player, "Torag's platebody: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.TORAGS_PLATEBODY_4749)}", i)
90 -> sendLine(player, "Torag's platelegs: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.TORAGS_PLATELEGS_4751)}", i) 90 -> sendLine(player, "Torag's platelegs: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.TORAGS_PLATELEGS_4751)}", i)
91 -> sendLine(player, SPACER,i) 91 -> sendLine(player, SPACER,i)
92 -> sendLine(player, "Verac's helm: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.VERACS_HELM_4753)}", i) 92 -> sendLine(player, "Verac's helm: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.VERACS_HELM_4753)}", i)
93 -> sendLine(player, "Verac's flail: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.VERACS_FLAIL_4755)}", i) 93 -> sendLine(player, "Verac's flail: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.VERACS_FLAIL_4755)}", i)
94 -> sendLine(player, "Verac's brassard: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.VERACS_BRASSARD_4757)}", i) 94 -> sendLine(player, "Verac's brassard: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.VERACS_BRASSARD_4757)}", i)
95 -> sendLine(player, "Verac's plateskirt: ${GlobalKillCounter.getRareDrops(queryPlayer, Items.VERACS_PLATESKIRT_4759)}", i) 95 -> sendLine(player, "Verac's plateskirt: ${PlayerStatsCounter.getRareDrops(queryPlayer, Items.VERACS_PLATESKIRT_4759)}", i)
else -> sendLine(player,"",i) else -> sendLine(player,"",i)
} }
} }

View file

@ -0,0 +1,111 @@
import core.api.utils.PlayerStatsCounter
import core.game.node.item.Item
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import java.io.File
@TestInstance(TestInstance.Lifecycle.PER_CLASS) class PlayerStatsCounterTests {
companion object {
private const val TEST_DB_PATH = "player_stats_test.db"
private val counter = PlayerStatsCounter(TEST_DB_PATH)
init {
TestUtils.preTestSetup()
counter.startup()
}
@AfterAll @JvmStatic fun cleanup() {
File(TEST_DB_PATH).delete()
}
}
@Test fun testKillIncrementShouldAddOneKillForNewPlayer() {
val testPlayer = TestUtils.getMockPlayer("test_kill_inc")
val testNPCId = 10
val oldKills = PlayerStatsCounter.getKills(testPlayer, IntArray(testNPCId))
Assertions.assertEquals(0, oldKills)
PlayerStatsCounter.incrementKills(testPlayer,testNPCId)
val newKills = PlayerStatsCounter.getKills(testPlayer, intArrayOf(testNPCId))
Assertions.assertEquals(1, newKills)
}
@Test fun testGetKillShouldReturnKillsForSinglePlayer() {
val testPlayer = TestUtils.getMockPlayer("kill_single_player_1")
val testPlayer2 = TestUtils.getMockPlayer("kill_single_player_2")
val testNPCId = 10
PlayerStatsCounter.incrementKills(testPlayer,testNPCId)
PlayerStatsCounter.incrementKills(testPlayer2,testNPCId)
PlayerStatsCounter.incrementKills(testPlayer2,testNPCId)
val kills = PlayerStatsCounter.getKills(testPlayer2, intArrayOf(testNPCId))
Assertions.assertEquals(2, kills)
}
@Test fun testGetKillShouldReturnSumForAllNPCsForAGivenPlayer() {
val testPlayer = TestUtils.getMockPlayer("test_sum_npcs")
val testNPCId1 = 10
val testNPCId2 = 11
val testNPCId3 = 12
PlayerStatsCounter.incrementKills(testPlayer,testNPCId1)
PlayerStatsCounter.incrementKills(testPlayer,testNPCId2)
PlayerStatsCounter.incrementKills(testPlayer,testNPCId2)
PlayerStatsCounter.incrementKills(testPlayer,testNPCId2)
PlayerStatsCounter.incrementKills(testPlayer,testNPCId3)
PlayerStatsCounter.incrementKills(testPlayer,testNPCId3)
val killsForNPCs1And2 = PlayerStatsCounter.getKills(testPlayer, intArrayOf(testNPCId1, testNPCId2))
Assertions.assertEquals(4, killsForNPCs1And2)
val killsForAllNPCs = PlayerStatsCounter.getKills(testPlayer, intArrayOf(testNPCId1, testNPCId2, testNPCId3))
Assertions.assertEquals(6, killsForAllNPCs)
}
@Test fun testRewardIncrementShouldAddOneRewardForNewPlayer() {
val testPlayer = TestUtils.getMockPlayer("test_reward_inc")
val itemId = 10
val oldRareDrops = PlayerStatsCounter.getRareDrops(testPlayer, itemId)
Assertions.assertEquals(0, oldRareDrops)
PlayerStatsCounter.incrementRareDrop(testPlayer, Item(itemId))
val newRareDrops = PlayerStatsCounter.getRareDrops(testPlayer, itemId)
Assertions.assertEquals(1, newRareDrops)
}
@Test fun testGetRewardsShouldReturnRewardsForSinglePlayer() {
val testPlayer = TestUtils.getMockPlayer("reward_single_player_1")
val testPlayer2 = TestUtils.getMockPlayer("reward_single_player_2")
val testItemId = 10
PlayerStatsCounter.incrementRareDrop(testPlayer,Item(testItemId))
PlayerStatsCounter.incrementRareDrop(testPlayer2,Item(testItemId))
PlayerStatsCounter.incrementRareDrop(testPlayer2,Item(testItemId))
val rareDrops = PlayerStatsCounter.getRareDrops(testPlayer2, testItemId)
Assertions.assertEquals(2, rareDrops)
}
@Test fun testGetRewardsShouldHonourItemAmountWhenIncrementing() {
val testPlayer = TestUtils.getMockPlayer("test_reward_inc_itemamount")
val itemId = 10
val itemAmount: Long = 5
val oldRareDrops = PlayerStatsCounter.getRareDrops(testPlayer, itemId)
Assertions.assertEquals(0, oldRareDrops)
PlayerStatsCounter.incrementRareDrop(testPlayer, Item(itemId, itemAmount.toInt()))
val newRareDrops = PlayerStatsCounter.getRareDrops(testPlayer, itemId)
Assertions.assertEquals(itemAmount, newRareDrops)
}
}