Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ combat_def = "kalphite_queen"
xp_bonus = 0.5
hunt_mode = "aggressive"
slayer_xp = 535.5
categories = ["kalphite", "boss"]
categories = ["kalphite"]
aka = ["kq"]
respawn_delay = 50
drop_table = "kalphite_queen"
Expand All @@ -20,6 +20,7 @@ examine = "I don't think insect repellent will work..."
id = 1160
clone = "kalphite_queen"
combat_def = "kalphite_queen_airborne"
categories = ["kalphite", "boss"]
xp_bonus = 12.5

[kalphite_worker]
Expand Down
179 changes: 100 additions & 79 deletions database/src/main/kotlin/world/gregs/voidps/storage/DatabaseStorage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -491,98 +491,112 @@
}
}

/**
* Loads every table once and groups the rows by player, rather than querying each child table
* per account (a dozen queries per player).
*/
override fun accounts(): List<PlayerSave> = transaction {
val experience = ExperienceTable.selectAll().associate { it[ExperienceTable.playerId] to experience(it) }
val levels = LevelsTable.selectAll().associate { it[LevelsTable.playerId] to levels(it) }
val variables = VariablesTable.selectAll().groupBy({ it[VariablesTable.playerId] }, { it[VariablesTable.name] to variable(it) })
val inventories = InventoriesTable.selectAll().groupBy({ it[InventoriesTable.playerId] }, ::inventory)
val offers = OffersTable.selectAll().groupBy({ it[OffersTable.playerId] }, { it })
val history = PlayerHistoryTable.selectAll().groupBy({ it[PlayerHistoryTable.playerId] }, ::history)
val kills = KillsTable.selectAll().groupBy({ it[KillsTable.playerId] }, { it[KillsTable.category] to it[KillsTable.count] })
val records = RecordsTable.selectAll().groupBy({ it[RecordsTable.playerId] }, { it[RecordsTable.type] to it[RecordsTable.millis] })
val recentEvents = RecentEventsTable.selectAll().groupBy { it[RecentEventsTable.playerId] }
AccountsTable.selectAll().map { row ->
val playerId = row[AccountsTable.id]
PlayerSave(
name = row[AccountsTable.name],
password = row[AccountsTable.passwordHash],
tile = Tile(row[AccountsTable.tile]),
experience = loadExperience(playerId),
experience = experience.getValue(playerId),
blocked = row[AccountsTable.blockedSkills].map { Skill.entries[it] },
levels = loadLevels(playerId),
levels = levels.getValue(playerId),
male = row[AccountsTable.male],
looks = row[AccountsTable.looks].toIntArray(),
colours = row[AccountsTable.colours].toIntArray(),
variables = loadVariables(playerId),
inventories = loadInventories(playerId),
variables = variables[playerId]?.toMap() ?: emptyMap(),
inventories = inventories[playerId]?.toMap() ?: emptyMap(),
friends = row[AccountsTable.friends].zip(row[AccountsTable.ranks]) { name, rank -> name to ClanRank.valueOf(rank) }.toMap(),
ignores = row[AccountsTable.ignores],
offers = loadOffers(playerId),
history = loadHistory(playerId),
kills = loadKills(playerId),
records = loadRecords(playerId),
recentEvents = loadRecentEvents(playerId),
offers = offers(offers[playerId] ?: emptyList()),
history = history[playerId] ?: emptyList(),
kills = kills[playerId]?.toMap() ?: emptyMap(),
records = records[playerId]?.toMap() ?: emptyMap(),
recentEvents = recentEvents(recentEvents[playerId] ?: emptyList()),
)
}
}

private fun loadExperience(playerId: Int): IntArray {
val it = ExperienceTable.selectAll().where { ExperienceTable.playerId eq playerId }.first()
return intArrayOf(
it[ExperienceTable.attack],
it[ExperienceTable.defence],
it[ExperienceTable.strength],
it[ExperienceTable.constitution],
it[ExperienceTable.ranged],
it[ExperienceTable.prayer],
it[ExperienceTable.magic],
it[ExperienceTable.cooking],
it[ExperienceTable.woodcutting],
it[ExperienceTable.fletching],
it[ExperienceTable.fishing],
it[ExperienceTable.firemaking],
it[ExperienceTable.crafting],
it[ExperienceTable.smithing],
it[ExperienceTable.mining],
it[ExperienceTable.herblore],
it[ExperienceTable.agility],
it[ExperienceTable.thieving],
it[ExperienceTable.slayer],
it[ExperienceTable.farming],
it[ExperienceTable.runecrafting],
it[ExperienceTable.hunter],
it[ExperienceTable.construction],
it[ExperienceTable.summoning],
it[ExperienceTable.dungeoneering],
)
}
private fun loadExperience(playerId: Int): IntArray = experience(ExperienceTable.selectAll().where { ExperienceTable.playerId eq playerId }.first())

private fun experience(it: ResultRow): IntArray = intArrayOf(
it[ExperienceTable.attack],
it[ExperienceTable.defence],
it[ExperienceTable.strength],
it[ExperienceTable.constitution],
it[ExperienceTable.ranged],
it[ExperienceTable.prayer],
it[ExperienceTable.magic],
it[ExperienceTable.cooking],
it[ExperienceTable.woodcutting],
it[ExperienceTable.fletching],
it[ExperienceTable.fishing],
it[ExperienceTable.firemaking],
it[ExperienceTable.crafting],
it[ExperienceTable.smithing],
it[ExperienceTable.mining],
it[ExperienceTable.herblore],
it[ExperienceTable.agility],
it[ExperienceTable.thieving],
it[ExperienceTable.slayer],
it[ExperienceTable.farming],
it[ExperienceTable.runecrafting],
it[ExperienceTable.hunter],
it[ExperienceTable.construction],
it[ExperienceTable.summoning],
it[ExperienceTable.dungeoneering],
)

private fun loadLevels(playerId: Int): IntArray = levels(LevelsTable.selectAll().where { LevelsTable.playerId eq playerId }.first())

private fun levels(it: ResultRow): IntArray = intArrayOf(
it[LevelsTable.attack],
it[LevelsTable.defence],
it[LevelsTable.strength],
it[LevelsTable.constitution],
it[LevelsTable.ranged],
it[LevelsTable.prayer],
it[LevelsTable.magic],
it[LevelsTable.cooking],
it[LevelsTable.woodcutting],
it[LevelsTable.fletching],
it[LevelsTable.fishing],
it[LevelsTable.firemaking],
it[LevelsTable.crafting],
it[LevelsTable.smithing],
it[LevelsTable.mining],
it[LevelsTable.herblore],
it[LevelsTable.agility],
it[LevelsTable.thieving],
it[LevelsTable.slayer],
it[LevelsTable.farming],
it[LevelsTable.runecrafting],
it[LevelsTable.hunter],
it[LevelsTable.construction],
it[LevelsTable.summoning],
it[LevelsTable.dungeoneering],
)

private fun loadLevels(playerId: Int): IntArray {
val it = LevelsTable.selectAll().where { LevelsTable.playerId eq playerId }.first()
return intArrayOf(
it[LevelsTable.attack],
it[LevelsTable.defence],
it[LevelsTable.strength],
it[LevelsTable.constitution],
it[LevelsTable.ranged],
it[LevelsTable.prayer],
it[LevelsTable.magic],
it[LevelsTable.cooking],
it[LevelsTable.woodcutting],
it[LevelsTable.fletching],
it[LevelsTable.fishing],
it[LevelsTable.firemaking],
it[LevelsTable.crafting],
it[LevelsTable.smithing],
it[LevelsTable.mining],
it[LevelsTable.herblore],
it[LevelsTable.agility],
it[LevelsTable.thieving],
it[LevelsTable.slayer],
it[LevelsTable.farming],
it[LevelsTable.runecrafting],
it[LevelsTable.hunter],
it[LevelsTable.construction],
it[LevelsTable.summoning],
it[LevelsTable.dungeoneering],
)
private fun loadVariables(playerId: Int): Map<String, Any> = VariablesTable.selectAll().where { VariablesTable.playerId eq playerId }.associate { row ->
row[VariablesTable.name] to variable(row)
}

private fun loadVariables(playerId: Int): Map<String, Any> = VariablesTable.selectAll().where { VariablesTable.playerId eq playerId }.associate { row ->
val variableName = row[VariablesTable.name]
private fun variable(row: ResultRow): Any {
val variableType = row[VariablesTable.type]

Check notice on line 598 in database/src/main/kotlin/world/gregs/voidps/storage/DatabaseStorage.kt

View workflow job for this annotation

GitHub Actions / Qodana for JVM

Variable declaration could be moved into 'when'

Variable declaration could be moved into 'when'
variableName to when (variableType) {
return when (variableType) {
TYPE_STRING -> row[VariablesTable.string]!!
TYPE_INT -> row[VariablesTable.int]!!
TYPE_BOOLEAN -> row[VariablesTable.boolean]!!
Expand All @@ -594,7 +608,9 @@
}
}

private fun loadInventories(playerId: Int): Map<String, Array<Item>> = InventoriesTable.selectAll().where { InventoriesTable.playerId eq playerId }.associate { row ->
private fun loadInventories(playerId: Int): Map<String, Array<Item>> = InventoriesTable.selectAll().where { InventoriesTable.playerId eq playerId }.associate(::inventory)

private fun inventory(row: ResultRow): Pair<String, Array<Item>> {
val inventoryName = row[InventoriesTable.inventoryName]
val itemIds = row[InventoriesTable.items]
val amounts = row[InventoriesTable.amounts]
Expand All @@ -603,12 +619,14 @@
Item(itemId, amount)
}.toTypedArray()

inventoryName to items
return inventoryName to items
}

private fun loadOffers(playerId: Int): Array<ExchangeOffer> {
private fun loadOffers(playerId: Int): Array<ExchangeOffer> = offers(OffersTable.selectAll().where { OffersTable.playerId eq playerId }.toList())

private fun offers(rows: List<ResultRow>): Array<ExchangeOffer> {
val array = Array(6) { ExchangeOffer.EMPTY }
OffersTable.selectAll().where { OffersTable.playerId eq playerId }.map { row ->
for (row in rows) {
val id = row[OffersTable.id]
val index = row[OffersTable.index]
val item = row[OffersTable.item]
Expand All @@ -622,11 +640,13 @@
return array
}

private fun loadHistory(playerId: Int): List<ExchangeHistory> = PlayerHistoryTable.selectAll().where { PlayerHistoryTable.playerId eq playerId }.map { row ->
private fun loadHistory(playerId: Int): List<ExchangeHistory> = PlayerHistoryTable.selectAll().where { PlayerHistoryTable.playerId eq playerId }.map(::history)

private fun history(row: ResultRow): ExchangeHistory {
val item = row[PlayerHistoryTable.item]
val amount = row[PlayerHistoryTable.amount]
val coins = row[PlayerHistoryTable.coins]
ExchangeHistory(item, amount, coins)
return ExchangeHistory(item, amount, coins)
}

private fun loadKills(playerId: Int): Map<String, Int> = KillsTable.selectAll().where { KillsTable.playerId eq playerId }.associate { row ->
Expand All @@ -641,8 +661,9 @@
type to millis
}

private fun loadRecentEvents(playerId: Int): List<RecentEvent> = RecentEventsTable.selectAll()
.where { RecentEventsTable.playerId eq playerId }
private fun loadRecentEvents(playerId: Int): List<RecentEvent> = recentEvents(RecentEventsTable.selectAll().where { RecentEventsTable.playerId eq playerId }.toList())

private fun recentEvents(rows: List<ResultRow>): List<RecentEvent> = rows
.sortedBy { it[RecentEventsTable.index] }
.map { row ->
val time = row[RecentEventsTable.time]
Expand Down
41 changes: 41 additions & 0 deletions database/src/test/kotlin/world/gregs/voidps/storage/StorageTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,47 @@ abstract class StorageTest {
(account.variables["favourite_numbers"] as MutableList<Any>).add(64)
}

@Test
fun `Load every account`() {
val other = save.copy(
name = "zezima",
experience = IntArray(25) { 100 },
levels = IntArray(25) { 2 },
variables = mapOf("display_name" to "Zezima", "meaning" to 7),
inventories = mapOf("inventory" to Array(28) { Item.EMPTY }.apply { this[0] = Item("coins", 5) }),
offers = emptyArray(),
history = emptyList(),
kills = mapOf("chickens" to 1),
records = emptyMap(),
recentEvents = listOf(RecentEvent(1, "First", "a"), RecentEvent(2, "Second", "b")),
)
storage.save(listOf(save, other))

val accounts = storage.accounts().associateBy { it.name }

assertEquals(setOf(save.name, other.name), accounts.keys)
for (expected in listOf(save, other)) {
val account = accounts.getValue(expected.name)
assertContentEquals(expected.experience, account.experience)
assertContentEquals(expected.levels, account.levels)
assertEquals(expected.variables, account.variables)
assertEquals(expected.inventories.keys, account.inventories.keys)
for ((key, value) in expected.inventories) {
assertContentEquals(value, account.inventories[key])
}
assertEquals(expected.offers.map { it.id }, account.offers.filter { it != ExchangeOffer.EMPTY }.map { it.id })
assertEquals(expected.history, account.history)
assertEquals(expected.kills, account.kills)
assertEquals(expected.records, account.records)
assertEquals(expected.recentEvents, account.recentEvents)
}
}

@Test
fun `No accounts gives empty list`() {
assertTrue(storage.accounts().isEmpty())
}

@Test
fun `Loading non-existent account returns null`() {
assertNull(storage.load(save.name))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,47 @@ abstract class StorageTest {
assertContentEquals(override.offers, account.offers)
}

@Test
fun `Load every account`() {
val other = save.copy(
name = "zezima",
experience = IntArray(25) { 100 },
levels = IntArray(25) { 2 },
variables = mapOf("display_name" to "Zezima", "meaning" to 7),
inventories = mapOf("inventory" to Array(28) { Item.EMPTY }.apply { this[0] = Item("coins", 5) }),
offers = emptyArray(),
history = emptyList(),
kills = mapOf("chickens" to 1),
records = emptyMap(),
recentEvents = listOf(RecentEvent(1, "First", "a"), RecentEvent(2, "Second", "b")),
)
storage.save(listOf(save, other))

val accounts = storage.accounts().associateBy { it.name }

assertEquals(setOf(save.name, other.name), accounts.keys)
for (expected in listOf(save, other)) {
val account = accounts.getValue(expected.name)
assertContentEquals(expected.experience, account.experience)
assertContentEquals(expected.levels, account.levels)
assertEquals(expected.variables, account.variables)
assertEquals(expected.inventories.keys, account.inventories.keys)
for ((key, value) in expected.inventories) {
assertContentEquals(value, account.inventories[key])
}
assertEquals(expected.offers.map { it.id }, account.offers.filter { it != ExchangeOffer.EMPTY }.map { it.id })
assertEquals(expected.history, account.history)
assertEquals(expected.kills, account.kills)
assertEquals(expected.records, account.records)
assertEquals(expected.recentEvents, account.recentEvents)
}
}

@Test
fun `No accounts gives empty list`() {
assertTrue(storage.accounts().isEmpty())
}

@Test
fun `Loading non-existent account returns null`() {
assertNull(storage.load(save.name))
Expand Down
Loading
Loading