Merge pull request #30 from SKBotNL/master
Add tests and other changes
This commit was merged in pull request #30.
This commit is contained in:
36 files changed
+2474
-779
No files matched your search
+14
-33
@@ -10,15 +10,6 @@ plugins {
|
||||
kotlin("jvm") version "2.1.21" // Import Kotlin JVM plugin.
|
||||
}
|
||||
|
||||
/* ----------------------------- Source sets --------------------------- */
|
||||
sourceSets {
|
||||
create("dev") {
|
||||
java.srcDir("src/dev/kotlin")
|
||||
compileClasspath += sourceSets.main.get().output
|
||||
runtimeClasspath += sourceSets.main.get().output
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------- Java / Kotlin -------------------------- */
|
||||
java {
|
||||
toolchain {
|
||||
@@ -27,15 +18,7 @@ java {
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(17)
|
||||
target { compilations { getByName("dev") { associateWith(target.compilations.getByName("main")) } } }
|
||||
}
|
||||
|
||||
configurations {
|
||||
named("devCompileOnly") { extendsFrom(configurations.compileOnly.get()) }
|
||||
named("devRuntimeOnly") { extendsFrom(configurations.runtimeOnly.get()) }
|
||||
}
|
||||
kotlin { jvmToolchain(17) }
|
||||
|
||||
/* ----------------------------- Metadata ------------------------------ */
|
||||
val commitHash =
|
||||
@@ -77,8 +60,21 @@ dependencies {
|
||||
implementation("it.unimi.dsi:fastutil-core:8.5.16")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib") // Import the Kotlin standard library.
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
|
||||
|
||||
testImplementation(kotlin("test"))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:6.0.3")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-params:6.0.3")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:6.0.3")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher:6.0.3")
|
||||
testImplementation("io.mockk:mockk:1.14.9")
|
||||
testImplementation("org.purpurmc.purpur:purpur-api:1.19.4-R0.1-SNAPSHOT")
|
||||
testImplementation("net.luckperms:api:5.5")
|
||||
testImplementation("org.slf4j:slf4j-nop:2.0.17")
|
||||
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2")
|
||||
}
|
||||
|
||||
tasks.test { useJUnitPlatform() }
|
||||
|
||||
/* ---------------------- Reproducible jars ---------------------------- */
|
||||
tasks.withType<AbstractArchiveTask>().configureEach { // Ensure reproducible .jars
|
||||
isPreserveFileTimestamps = false
|
||||
@@ -114,18 +110,3 @@ spotless {
|
||||
tasks.named("spotlessCheck") {
|
||||
dependsOn("spotlessApply") // Run spotless before checking if spotless ran.
|
||||
}
|
||||
|
||||
/* ----------------------------- Shadow Dev -------------------------------- */
|
||||
tasks.register<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("shadowJarDev") {
|
||||
from(sourceSets.main.get().output)
|
||||
from(sourceSets["dev"].output)
|
||||
configurations = listOf(project.configurations.runtimeClasspath.get())
|
||||
|
||||
isEnableRelocation = true
|
||||
relocationPrefix = "${project.group}.shadow"
|
||||
}
|
||||
|
||||
tasks.register("buildDev") {
|
||||
version = "$version-dev"
|
||||
dependsOn(tasks.spotlessApply, tasks.named("shadowJarDev"))
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
package net.trueog.diamondbankog
|
||||
|
||||
import kotlin.collections.toTypedArray
|
||||
import net.trueog.diamondbankog.InventoryExtensions.countDiamondBlocks
|
||||
import net.trueog.diamondbankog.InventoryExtensions.countDiamonds
|
||||
import net.trueog.diamondbankog.InventoryExtensions.countShards
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.inventory.ItemStack
|
||||
|
||||
object InventoryUtilsTest : Test {
|
||||
override val name: String
|
||||
get() = "InventoryUtilsTest"
|
||||
|
||||
override suspend fun runTests(): Array<TestResult> {
|
||||
val testResults = mutableListOf<TestResult>()
|
||||
val tests =
|
||||
arrayOf(
|
||||
::testShardRemoval,
|
||||
::testDiamondRemoval,
|
||||
::testDiamondBlockRemoval,
|
||||
::testShardAndDiamondRemoval,
|
||||
::testShardDiamondAndDiamondBlockRemoval,
|
||||
::testShardAndDiamondRemovalWithShardChange,
|
||||
::testShardDiamondAndDiamondBlockRemovalWithShardAndDiamondChange,
|
||||
::testRemoveAll,
|
||||
::testCountShards,
|
||||
::testCountDiamonds,
|
||||
::testCountDiamondBlocks,
|
||||
::testNotEnoughSpaceForChange,
|
||||
::testEmptyRemoveShards,
|
||||
)
|
||||
for (test in tests) {
|
||||
val criteriaResults = test()
|
||||
testResults += TestResult(test.name, criteriaResults)
|
||||
}
|
||||
return testResults.toTypedArray()
|
||||
}
|
||||
|
||||
suspend fun testShardRemoval(): Array<CriteriaResult> {
|
||||
val inventorySnapshot = InventorySnapshot.withContents(arrayOf(Shard.createItemStack(9)))
|
||||
val removed =
|
||||
InventorySnapshotUtils.removeShards(inventorySnapshot, 3).getOrElse {
|
||||
val noFailureCriteria = CriteriaResult("isFailure == false", "true", false)
|
||||
return arrayOf(noFailureCriteria)
|
||||
}
|
||||
|
||||
val removedCountCriteria = CriteriaResult("removed == 3", removed.toString(), removed == 3)
|
||||
val shardCount = inventorySnapshot.countShards()
|
||||
val shardCountCriteria =
|
||||
CriteriaResult("inventorySnapshot.countShards() == 6L", shardCount.toString(), shardCount == 6L)
|
||||
return arrayOf(removedCountCriteria, shardCountCriteria)
|
||||
}
|
||||
|
||||
suspend fun testDiamondRemoval(): Array<CriteriaResult> {
|
||||
val inventorySnapshot = InventorySnapshot.withContents(arrayOf(ItemStack(Material.DIAMOND, 2)))
|
||||
val removed =
|
||||
InventorySnapshotUtils.removeShards(inventorySnapshot, 9).getOrElse {
|
||||
val noFailureCriteria = CriteriaResult("isFailure == false", "true", false)
|
||||
return arrayOf(noFailureCriteria)
|
||||
}
|
||||
|
||||
val removedCountCriteria = CriteriaResult("removed == 9", removed.toString(), removed == 9)
|
||||
val diamondCount = inventorySnapshot.countDiamonds()
|
||||
val diamondCountCriteria =
|
||||
CriteriaResult("inventorySnapshot.countDiamonds() == 1L", diamondCount.toString(), diamondCount == 1L)
|
||||
return arrayOf(removedCountCriteria, diamondCountCriteria)
|
||||
}
|
||||
|
||||
suspend fun testDiamondBlockRemoval(): Array<CriteriaResult> {
|
||||
val inventorySnapshot = InventorySnapshot.withContents(arrayOf(ItemStack(Material.DIAMOND_BLOCK, 2)))
|
||||
val removed =
|
||||
InventorySnapshotUtils.removeShards(inventorySnapshot, 81).getOrElse {
|
||||
val noFailureCriteria = CriteriaResult("isFailure == false", "true", false)
|
||||
return arrayOf(noFailureCriteria)
|
||||
}
|
||||
|
||||
val removedCountCriteria = CriteriaResult("removed == 81", removed.toString(), removed == 81)
|
||||
val diamondBlockAmount = inventorySnapshot.countDiamondBlocks()
|
||||
val diamondBlockCountCriteria =
|
||||
CriteriaResult(
|
||||
"inventorySnapshot.countDiamondBlocks() == 1L",
|
||||
diamondBlockAmount.toString(),
|
||||
diamondBlockAmount == 1L,
|
||||
)
|
||||
return arrayOf(removedCountCriteria, diamondBlockCountCriteria)
|
||||
}
|
||||
|
||||
suspend fun testShardAndDiamondRemoval(): Array<CriteriaResult> {
|
||||
val inventorySnapshot =
|
||||
InventorySnapshot.withContents(arrayOf(Shard.createItemStack(2), ItemStack(Material.DIAMOND, 1)))
|
||||
val removed =
|
||||
InventorySnapshotUtils.removeShards(inventorySnapshot, 11).getOrElse {
|
||||
val noFailureCriteria = CriteriaResult("isFailure == false", "true", false)
|
||||
return arrayOf(noFailureCriteria)
|
||||
}
|
||||
|
||||
val removedCountCriteria = CriteriaResult("removed == 11", removed.toString(), removed == 11)
|
||||
val noContentsCriteria =
|
||||
CriteriaResult(
|
||||
"inventorySnapshot.all { it == null }",
|
||||
inventorySnapshot.contents.joinToString(","),
|
||||
inventorySnapshot.all { it == null },
|
||||
)
|
||||
return arrayOf(removedCountCriteria, noContentsCriteria)
|
||||
}
|
||||
|
||||
suspend fun testShardDiamondAndDiamondBlockRemoval(): Array<CriteriaResult> {
|
||||
val inventorySnapshot =
|
||||
InventorySnapshot.withContents(
|
||||
arrayOf(Shard.createItemStack(2), ItemStack(Material.DIAMOND, 1), ItemStack(Material.DIAMOND_BLOCK, 1))
|
||||
)
|
||||
val removed =
|
||||
InventorySnapshotUtils.removeShards(inventorySnapshot, 92).getOrElse {
|
||||
val noFailureCriteria = CriteriaResult("isFailure == false", "true", false)
|
||||
return arrayOf(noFailureCriteria)
|
||||
}
|
||||
|
||||
val removedCountCriteria = CriteriaResult("removed == 92", removed.toString(), removed == 92)
|
||||
val noContentsCriteria =
|
||||
CriteriaResult(
|
||||
"inventorySnapshot.all { it == null }",
|
||||
inventorySnapshot.contents.joinToString(","),
|
||||
inventorySnapshot.all { it == null },
|
||||
)
|
||||
return arrayOf(removedCountCriteria, noContentsCriteria)
|
||||
}
|
||||
|
||||
suspend fun testShardAndDiamondRemovalWithShardChange(): Array<CriteriaResult> {
|
||||
val inventorySnapshot =
|
||||
InventorySnapshot.withContents(arrayOf(Shard.createItemStack(2), ItemStack(Material.DIAMOND, 1)))
|
||||
val removed =
|
||||
InventorySnapshotUtils.removeShards(inventorySnapshot, 10).getOrElse {
|
||||
val noFailureCriteria = CriteriaResult("isFailure == false", "true", false)
|
||||
return arrayOf(noFailureCriteria)
|
||||
}
|
||||
|
||||
val removedCountCriteria = CriteriaResult("removed == 10", removed.toString(), removed == 10)
|
||||
val shardCount = inventorySnapshot.countShards()
|
||||
val oneShardCriteria =
|
||||
CriteriaResult("inventorySnapshot.countShards() == 1", shardCount.toString(), shardCount == 1L)
|
||||
val diamondCount = inventorySnapshot.countDiamonds()
|
||||
val noDiamondsCriteria =
|
||||
CriteriaResult("inventorySnapshot.countDiamonds() == 0", diamondCount.toString(), diamondCount == 0L)
|
||||
val diamondBlockCount = inventorySnapshot.countDiamondBlocks()
|
||||
val noDiamondBlocksCriteria =
|
||||
CriteriaResult(
|
||||
"inventorySnapshot.countDiamondBlocks() == 0",
|
||||
diamondBlockCount.toString(),
|
||||
diamondBlockCount == 0L,
|
||||
)
|
||||
return arrayOf(removedCountCriteria, oneShardCriteria, noDiamondsCriteria, noDiamondBlocksCriteria)
|
||||
}
|
||||
|
||||
suspend fun testShardDiamondAndDiamondBlockRemovalWithShardAndDiamondChange(): Array<CriteriaResult> {
|
||||
val inventorySnapshot =
|
||||
InventorySnapshot.withContents(
|
||||
arrayOf(Shard.createItemStack(2), ItemStack(Material.DIAMOND, 1), ItemStack(Material.DIAMOND_BLOCK, 1))
|
||||
)
|
||||
val removed =
|
||||
InventorySnapshotUtils.removeShards(inventorySnapshot, 12).getOrElse {
|
||||
val noFailureCriteria = CriteriaResult("isFailure == false", "true", false)
|
||||
return arrayOf(noFailureCriteria)
|
||||
}
|
||||
|
||||
val removedCountCriteria = CriteriaResult("removed == 12", removed.toString(), removed == 12)
|
||||
val shardCount = inventorySnapshot.countShards()
|
||||
val oneShardCriteria =
|
||||
CriteriaResult("inventorySnapshot.countShards() == 8", shardCount.toString(), shardCount == 8L)
|
||||
val diamondCount = inventorySnapshot.countDiamonds()
|
||||
val noDiamondsCriteria =
|
||||
CriteriaResult("inventorySnapshot.countDiamonds() == 8", diamondCount.toString(), diamondCount == 8L)
|
||||
val diamondBlockCount = inventorySnapshot.countDiamondBlocks()
|
||||
val noDiamondBlocksCriteria =
|
||||
CriteriaResult(
|
||||
"inventorySnapshot.countDiamondBlocks() == 0",
|
||||
diamondBlockCount.toString(),
|
||||
diamondBlockCount == 0L,
|
||||
)
|
||||
return arrayOf(removedCountCriteria, oneShardCriteria, noDiamondsCriteria, noDiamondBlocksCriteria)
|
||||
}
|
||||
|
||||
suspend fun testRemoveAll(): Array<CriteriaResult> {
|
||||
val inventorySnapshot =
|
||||
InventorySnapshot.withContents(
|
||||
arrayOf(Shard.createItemStack(2), ItemStack(Material.DIAMOND, 1), ItemStack(Material.DIAMOND_BLOCK, 1))
|
||||
)
|
||||
val removed = InventorySnapshotUtils.removeAll(inventorySnapshot)
|
||||
|
||||
val removedCountCriteria = CriteriaResult("removed == 92", removed.toString(), removed == 92)
|
||||
val noContentsCriteria =
|
||||
CriteriaResult(
|
||||
"inventorySnapshot.all { it == null }",
|
||||
inventorySnapshot.contents.joinToString(","),
|
||||
inventorySnapshot.all { it == null },
|
||||
)
|
||||
return arrayOf(removedCountCriteria, noContentsCriteria)
|
||||
}
|
||||
|
||||
suspend fun testCountShards(): Array<CriteriaResult> {
|
||||
val inventorySnapshot =
|
||||
InventorySnapshot.withContents(
|
||||
arrayOf(Shard.createItemStack(2), Shard.createItemStack(5), Shard.createItemStack(1))
|
||||
)
|
||||
val count = InventorySnapshotUtils.countShards(inventorySnapshot)
|
||||
val countCriteria = CriteriaResult("count == 8", count.toString(), count == 8)
|
||||
return arrayOf(countCriteria)
|
||||
}
|
||||
|
||||
suspend fun testCountDiamonds(): Array<CriteriaResult> {
|
||||
val inventorySnapshot =
|
||||
InventorySnapshot.withContents(
|
||||
arrayOf(ItemStack(Material.DIAMOND, 2), ItemStack(Material.DIAMOND, 5), ItemStack(Material.DIAMOND, 1))
|
||||
)
|
||||
val count = InventorySnapshotUtils.countDiamonds(inventorySnapshot)
|
||||
val countCriteria = CriteriaResult("count == 8", count.toString(), count == 8)
|
||||
return arrayOf(countCriteria)
|
||||
}
|
||||
|
||||
suspend fun testCountDiamondBlocks(): Array<CriteriaResult> {
|
||||
val inventorySnapshot =
|
||||
InventorySnapshot.withContents(
|
||||
arrayOf(
|
||||
ItemStack(Material.DIAMOND_BLOCK, 2),
|
||||
ItemStack(Material.DIAMOND_BLOCK, 5),
|
||||
ItemStack(Material.DIAMOND_BLOCK, 1),
|
||||
)
|
||||
)
|
||||
val count = InventorySnapshotUtils.countDiamondBlocks(inventorySnapshot)
|
||||
val countCriteria = CriteriaResult("count == 8", count.toString(), count == 8)
|
||||
return arrayOf(countCriteria)
|
||||
}
|
||||
|
||||
suspend fun testNotEnoughSpaceForChange(): Array<CriteriaResult> {
|
||||
val contents = mutableListOf<ItemStack?>()
|
||||
repeat(35) { contents.add(ItemStack(Material.DIRT)) }
|
||||
contents.add(ItemStack(Material.DIAMOND_BLOCK))
|
||||
val inventorySnapshot = InventorySnapshot.withContents(contents.toTypedArray())
|
||||
val result = InventorySnapshotUtils.removeShards(inventorySnapshot, 1)
|
||||
val failureCriteria = CriteriaResult("isFailure == true", result.isFailure.toString(), result.isFailure)
|
||||
return arrayOf(failureCriteria)
|
||||
}
|
||||
|
||||
suspend fun testEmptyRemoveShards(): Array<CriteriaResult> {
|
||||
val contents = emptyArray<ItemStack?>()
|
||||
val inventorySnapshot = InventorySnapshot.withContents(contents)
|
||||
val removed =
|
||||
InventorySnapshotUtils.removeShards(inventorySnapshot, 10).getOrElse {
|
||||
val noFailureCriteria = CriteriaResult("isFailure == false", "true", false)
|
||||
return arrayOf(noFailureCriteria)
|
||||
}
|
||||
val removedCriteriaResult = CriteriaResult("removed == 0", removed.toString(), removed == 0)
|
||||
return arrayOf(removedCriteriaResult)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package net.trueog.diamondbankog
|
||||
|
||||
interface Test {
|
||||
val name: String
|
||||
|
||||
suspend fun runTests(): Array<TestResult>
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package net.trueog.diamondbankog
|
||||
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage
|
||||
import org.bukkit.command.Command
|
||||
import org.bukkit.command.CommandExecutor
|
||||
import org.bukkit.command.CommandSender
|
||||
|
||||
internal class TestCommand : CommandExecutor {
|
||||
val tests: Array<Test> = arrayOf(InventoryUtilsTest)
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
|
||||
GlobalScope.launch {
|
||||
for (test in tests) {
|
||||
sender.sendMessage("${test.name}:")
|
||||
for (result in test.runTests()) {
|
||||
sender.sendMessage(MiniMessage.miniMessage().deserialize(" $result"))
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package net.trueog.diamondbankog
|
||||
|
||||
import org.bukkit.plugin.java.JavaPlugin
|
||||
|
||||
object TestCommands {
|
||||
@JvmStatic
|
||||
fun register(plugin: JavaPlugin) {
|
||||
plugin.getCommand("dbogtests")?.setExecutor(TestCommand())
|
||||
plugin.logger.warning("Registered test commands")
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package net.trueog.diamondbankog
|
||||
|
||||
data class CriteriaResult(val criteria: String, val value: String, val passed: Boolean)
|
||||
|
||||
data class TestResult(val name: String, val criteriaResults: Array<CriteriaResult>) {
|
||||
override fun toString(): String {
|
||||
val passed = criteriaResults.all { it.passed }
|
||||
return "${if (passed) "<green>" else "<red>"}Test \"$name\" ${if (passed) "passed" else "failed: ${
|
||||
criteriaResults.filter { !it.passed }.joinToString(
|
||||
", "
|
||||
) { "criteria \"${it.criteria}\" not met (was \"${it.value}\")" }
|
||||
}"}"
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as TestResult
|
||||
|
||||
if (name != other.name) return false
|
||||
if (!criteriaResults.contentEquals(other.criteriaResults)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = name.hashCode()
|
||||
result = 31 * result + criteriaResults.contentHashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ internal object AutoCompress {
|
||||
transactionLock.withLockSuspend(player.uniqueId) {
|
||||
val inventorySnapshot = runOnMainThread {
|
||||
player.inventory.lock()
|
||||
InventorySnapshot.from(player.inventory)
|
||||
InventorySnapshot.from(player.inventory, DiamondBankOG.balanceManager)
|
||||
}
|
||||
|
||||
val shardsInInventory = inventorySnapshot.countShards().toInt()
|
||||
|
||||
@@ -2,146 +2,35 @@ package net.trueog.diamondbankog
|
||||
|
||||
import java.sql.SQLException
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import net.trueog.diamondbankog.DiamondBankException.EconomyDisabledException
|
||||
import net.trueog.diamondbankog.DiamondBankException.InvalidArgumentException
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.economyDisabled
|
||||
import net.trueog.diamondbankog.ErrorHandler.handleError
|
||||
import net.trueog.diamondbankog.PostgreSQL.PlayerShards
|
||||
import net.trueog.diamondbankog.PostgreSQL.ShardType
|
||||
|
||||
internal class BalanceManager {
|
||||
val cache = Cache()
|
||||
val postgreSQL = PostgreSQL()
|
||||
val beingModified = ConcurrentHashMap<Pair<UUID, ShardType>, AtomicInteger>()
|
||||
interface BalanceManager {
|
||||
@Throws(SQLException::class, ClassNotFoundException::class) fun init()
|
||||
|
||||
@Throws(SQLException::class, ClassNotFoundException::class)
|
||||
fun init() {
|
||||
postgreSQL.initDB()
|
||||
}
|
||||
suspend fun setPlayerShards(uuid: UUID, shards: Long, type: ShardType): Result<Unit>
|
||||
|
||||
private fun increment(uuid: UUID, type: ShardType) {
|
||||
beingModified.computeIfAbsent(uuid to type) { AtomicInteger(0) }.incrementAndGet()
|
||||
}
|
||||
suspend fun addToBankShards(uuid: UUID, shards: Long): Result<Unit>
|
||||
|
||||
private fun decrement(uuid: UUID, type: ShardType) {
|
||||
beingModified.computeIfPresent(uuid to type) { _, counter ->
|
||||
val newValue = counter.decrementAndGet()
|
||||
if (newValue == 0) null else counter
|
||||
}
|
||||
}
|
||||
suspend fun subtractFromBankShards(uuid: UUID, shards: Long): Result<Unit>
|
||||
|
||||
suspend fun setPlayerShards(uuid: UUID, shards: Long, type: ShardType): Result<Unit> {
|
||||
if (type == ShardType.TOTAL) return Result.failure(InvalidArgumentException())
|
||||
if (economyDisabled) return Result.failure(EconomyDisabledException())
|
||||
suspend fun getBankShards(uuid: UUID): Result<Long>
|
||||
|
||||
increment(uuid, type)
|
||||
postgreSQL.setPlayerShards(uuid, shards, type).getOrElse {
|
||||
return Result.failure(it)
|
||||
}
|
||||
cache.setBalance(uuid, shards, type)
|
||||
decrement(uuid, type)
|
||||
return Result.success(Unit)
|
||||
}
|
||||
suspend fun getInventoryShards(uuid: UUID): Result<Long>
|
||||
|
||||
private suspend fun addToPlayerShards(uuid: UUID, shards: Long, type: ShardType): Result<Unit> {
|
||||
if (type == ShardType.TOTAL) return Result.failure(InvalidArgumentException())
|
||||
if (economyDisabled) return Result.failure(EconomyDisabledException())
|
||||
suspend fun getEnderChestShards(uuid: UUID): Result<Long>
|
||||
|
||||
increment(uuid, type)
|
||||
val newBalanceDb =
|
||||
postgreSQL.addToPlayerShards(uuid, shards, type).getOrElse {
|
||||
return Result.failure(it)
|
||||
}
|
||||
val newBalanceCache =
|
||||
cache.addBalance(uuid, shards, type).getOrElse {
|
||||
return Result.failure(it)
|
||||
}
|
||||
if (newBalanceDb != newBalanceCache) {
|
||||
handleError(IllegalStateException("Database and cache balances do not match"))
|
||||
}
|
||||
decrement(uuid, type)
|
||||
return Result.success(Unit)
|
||||
}
|
||||
suspend fun getShardTypeShards(uuid: UUID, type: ShardType): Result<Long>
|
||||
|
||||
suspend fun addToBankShards(uuid: UUID, shards: Long): Result<Unit> =
|
||||
addToPlayerShards(uuid, shards, ShardType.BANK)
|
||||
suspend fun getTotalShards(uuid: UUID): Result<Long>
|
||||
|
||||
suspend fun subtractFromBankShards(uuid: UUID, shards: Long): Result<Unit> =
|
||||
addToPlayerShards(uuid, -shards, ShardType.BANK)
|
||||
suspend fun getAllShards(uuid: UUID): Result<PlayerShards>
|
||||
|
||||
suspend fun getBankShards(uuid: UUID) = getShardTypeShards(uuid, ShardType.BANK)
|
||||
suspend fun getBaltop(offset: Int): Result<Map<UUID?, Long>>
|
||||
|
||||
suspend fun getInventoryShards(uuid: UUID) = getShardTypeShards(uuid, ShardType.INVENTORY)
|
||||
suspend fun getBaltopWithUuid(uuid: UUID): Result<Pair<Map<UUID?, Long>, Long>>
|
||||
|
||||
suspend fun getEnderChestShards(uuid: UUID) = getShardTypeShards(uuid, ShardType.ENDER_CHEST)
|
||||
|
||||
suspend fun getShardTypeShards(uuid: UUID, type: ShardType): Result<Long> {
|
||||
if ((beingModified[uuid to type]?.get() ?: 0) > 0) {
|
||||
return postgreSQL.getShardTypeShards(uuid, type)
|
||||
}
|
||||
val cacheBalance = cache.getBalance(uuid, type)
|
||||
if (cacheBalance == -1L) {
|
||||
increment(uuid, type)
|
||||
val dbBalance =
|
||||
postgreSQL.getShardTypeShards(uuid, type).getOrElse {
|
||||
return Result.failure(it)
|
||||
}
|
||||
cache.setBalance(uuid, dbBalance, type)
|
||||
decrement(uuid, type)
|
||||
return Result.success(dbBalance)
|
||||
}
|
||||
return Result.success(cacheBalance)
|
||||
}
|
||||
|
||||
suspend fun getTotalShards(uuid: UUID): Result<Long> {
|
||||
val anyBeingModified = beingModified.any { it.key.first == uuid && it.value.get() > 0 }
|
||||
if (anyBeingModified) {
|
||||
return postgreSQL.getTotalShards(uuid)
|
||||
}
|
||||
val cacheBalance = cache.getBalance(uuid, ShardType.TOTAL)
|
||||
if (cacheBalance == -1L) {
|
||||
increment(uuid, ShardType.TOTAL)
|
||||
val dbBalance =
|
||||
postgreSQL.getTotalShards(uuid).getOrElse {
|
||||
return Result.failure(it)
|
||||
}
|
||||
cache.setBalance(uuid, dbBalance, ShardType.TOTAL)
|
||||
decrement(uuid, ShardType.TOTAL)
|
||||
return Result.success(dbBalance)
|
||||
}
|
||||
return Result.success(cacheBalance)
|
||||
}
|
||||
|
||||
suspend fun getAllShards(uuid: UUID): Result<PlayerShards> {
|
||||
val anyBeingModified = beingModified.any { it.key.first == uuid && it.value.get() > 0 }
|
||||
if (anyBeingModified) {
|
||||
return postgreSQL.getAllShards(uuid)
|
||||
}
|
||||
val cacheBankBalance = cache.getBalance(uuid, ShardType.BANK)
|
||||
val cacheInventoryBalance = cache.getBalance(uuid, ShardType.INVENTORY)
|
||||
val cacheEnderChestBalance = cache.getBalance(uuid, ShardType.ENDER_CHEST)
|
||||
if (cacheBankBalance == -1L || cacheInventoryBalance == -1L || cacheEnderChestBalance == -1L) {
|
||||
increment(uuid, ShardType.TOTAL)
|
||||
val dbPlayerShards =
|
||||
postgreSQL.getAllShards(uuid).getOrElse {
|
||||
return Result.failure(it)
|
||||
}
|
||||
cache.setBalance(uuid, dbPlayerShards.bank, ShardType.BANK)
|
||||
cache.setBalance(uuid, dbPlayerShards.inventory, ShardType.INVENTORY)
|
||||
cache.setBalance(uuid, dbPlayerShards.enderChest, ShardType.ENDER_CHEST)
|
||||
decrement(uuid, ShardType.TOTAL)
|
||||
return Result.success(dbPlayerShards)
|
||||
}
|
||||
return Result.success(PlayerShards(cacheBankBalance, cacheInventoryBalance, cacheEnderChestBalance))
|
||||
}
|
||||
|
||||
suspend fun getBaltop(offset: Int): Result<Map<UUID?, Long>> = postgreSQL.getBaltop(offset)
|
||||
|
||||
suspend fun getBaltopWithUuid(uuid: UUID): Result<Pair<Map<UUID?, Long>, Long>> = postgreSQL.getBaltopWithUuid(uuid)
|
||||
|
||||
suspend fun getNumberOfRows(): Result<Long> = postgreSQL.getNumberOfRows()
|
||||
suspend fun getNumberOfRows(): Result<Long>
|
||||
|
||||
suspend fun insertTransactionLog(
|
||||
playerUuid: UUID,
|
||||
@@ -149,27 +38,13 @@ internal class BalanceManager {
|
||||
playerToUuid: UUID?,
|
||||
transactionReason: String,
|
||||
notes: String?,
|
||||
): Result<Unit> =
|
||||
postgreSQL.insertTransactionLog(playerUuid, transferredShards, playerToUuid, transactionReason, notes)
|
||||
): Result<Unit>
|
||||
|
||||
suspend fun hasEntry(uuid: UUID) = postgreSQL.hasEntry(uuid)
|
||||
suspend fun hasEntry(uuid: UUID): Result<Boolean>
|
||||
|
||||
suspend fun cacheForPlayer(uuid: UUID): Result<Unit> {
|
||||
val playerShards =
|
||||
postgreSQL.getAllShards(uuid).getOrElse {
|
||||
return Result.failure(it)
|
||||
}
|
||||
cache.setBalance(uuid, playerShards.bank, ShardType.BANK)
|
||||
cache.setBalance(uuid, playerShards.inventory, ShardType.INVENTORY)
|
||||
cache.setBalance(uuid, playerShards.enderChest, ShardType.ENDER_CHEST)
|
||||
return Result.success(Unit)
|
||||
}
|
||||
suspend fun cacheForPlayer(uuid: UUID): Result<Unit>
|
||||
|
||||
fun removeCacheForPlayer(uuid: UUID) {
|
||||
cache.removeAll(uuid)
|
||||
}
|
||||
fun removeCacheForPlayer(uuid: UUID)
|
||||
|
||||
fun shutdown() {
|
||||
postgreSQL.pool.disconnect().get()
|
||||
}
|
||||
fun shutdown()
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
package net.trueog.diamondbankog
|
||||
|
||||
import java.sql.SQLException
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import net.trueog.diamondbankog.DiamondBankException.EconomyDisabledException
|
||||
import net.trueog.diamondbankog.DiamondBankException.InvalidArgumentException
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.economyDisabled
|
||||
import net.trueog.diamondbankog.ErrorHandler.handleError
|
||||
import net.trueog.diamondbankog.PostgreSQL.PlayerShards
|
||||
import net.trueog.diamondbankog.PostgreSQL.ShardType
|
||||
|
||||
internal class CachingBalanceManager : BalanceManager {
|
||||
val cache = Cache()
|
||||
val postgreSQL = PostgreSQL()
|
||||
val beingModified = ConcurrentHashMap<Pair<UUID, ShardType>, AtomicInteger>()
|
||||
|
||||
@Throws(SQLException::class, ClassNotFoundException::class)
|
||||
override fun init() {
|
||||
postgreSQL.initDB()
|
||||
}
|
||||
|
||||
private fun increment(uuid: UUID, type: ShardType) {
|
||||
beingModified.computeIfAbsent(uuid to type) { AtomicInteger(0) }.incrementAndGet()
|
||||
}
|
||||
|
||||
private fun decrement(uuid: UUID, type: ShardType) {
|
||||
beingModified.computeIfPresent(uuid to type) { _, counter ->
|
||||
val newValue = counter.decrementAndGet()
|
||||
if (newValue == 0) null else counter
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setPlayerShards(uuid: UUID, shards: Long, type: ShardType): Result<Unit> {
|
||||
if (type == ShardType.TOTAL) return Result.failure(InvalidArgumentException())
|
||||
if (economyDisabled) return Result.failure(EconomyDisabledException())
|
||||
|
||||
increment(uuid, type)
|
||||
postgreSQL.setPlayerShards(uuid, shards, type).getOrElse {
|
||||
return Result.failure(it)
|
||||
}
|
||||
cache.setBalance(uuid, shards, type)
|
||||
decrement(uuid, type)
|
||||
return Result.success(Unit)
|
||||
}
|
||||
|
||||
private suspend fun addToPlayerShards(uuid: UUID, shards: Long, type: ShardType): Result<Unit> {
|
||||
if (type == ShardType.TOTAL) return Result.failure(InvalidArgumentException())
|
||||
if (economyDisabled) return Result.failure(EconomyDisabledException())
|
||||
|
||||
increment(uuid, type)
|
||||
val newBalanceDb =
|
||||
postgreSQL.addToPlayerShards(uuid, shards, type).getOrElse {
|
||||
return Result.failure(it)
|
||||
}
|
||||
val newBalanceCache =
|
||||
cache.addBalance(uuid, shards, type).getOrElse {
|
||||
return Result.failure(it)
|
||||
}
|
||||
if (newBalanceDb != newBalanceCache) {
|
||||
handleError(IllegalStateException("Database and cache balances do not match"))
|
||||
}
|
||||
decrement(uuid, type)
|
||||
return Result.success(Unit)
|
||||
}
|
||||
|
||||
override suspend fun addToBankShards(uuid: UUID, shards: Long): Result<Unit> =
|
||||
addToPlayerShards(uuid, shards, ShardType.BANK)
|
||||
|
||||
override suspend fun subtractFromBankShards(uuid: UUID, shards: Long): Result<Unit> =
|
||||
addToPlayerShards(uuid, -shards, ShardType.BANK)
|
||||
|
||||
override suspend fun getBankShards(uuid: UUID): Result<Long> = getShardTypeShards(uuid, ShardType.BANK)
|
||||
|
||||
override suspend fun getInventoryShards(uuid: UUID): Result<Long> = getShardTypeShards(uuid, ShardType.INVENTORY)
|
||||
|
||||
override suspend fun getEnderChestShards(uuid: UUID): Result<Long> = getShardTypeShards(uuid, ShardType.ENDER_CHEST)
|
||||
|
||||
override suspend fun getShardTypeShards(uuid: UUID, type: ShardType): Result<Long> {
|
||||
if ((beingModified[uuid to type]?.get() ?: 0) > 0) {
|
||||
return postgreSQL.getShardTypeShards(uuid, type)
|
||||
}
|
||||
val cacheBalance = cache.getBalance(uuid, type)
|
||||
if (cacheBalance == -1L) {
|
||||
increment(uuid, type)
|
||||
val dbBalance =
|
||||
postgreSQL.getShardTypeShards(uuid, type).getOrElse {
|
||||
return Result.failure(it)
|
||||
}
|
||||
cache.setBalance(uuid, dbBalance, type)
|
||||
decrement(uuid, type)
|
||||
return Result.success(dbBalance)
|
||||
}
|
||||
return Result.success(cacheBalance)
|
||||
}
|
||||
|
||||
override suspend fun getTotalShards(uuid: UUID): Result<Long> {
|
||||
val anyBeingModified = beingModified.any { it.key.first == uuid && it.value.get() > 0 }
|
||||
if (anyBeingModified) {
|
||||
return postgreSQL.getTotalShards(uuid)
|
||||
}
|
||||
val cacheBalance = cache.getBalance(uuid, ShardType.TOTAL)
|
||||
if (cacheBalance == -1L) {
|
||||
increment(uuid, ShardType.TOTAL)
|
||||
val dbBalance =
|
||||
postgreSQL.getTotalShards(uuid).getOrElse {
|
||||
return Result.failure(it)
|
||||
}
|
||||
cache.setBalance(uuid, dbBalance, ShardType.TOTAL)
|
||||
decrement(uuid, ShardType.TOTAL)
|
||||
return Result.success(dbBalance)
|
||||
}
|
||||
return Result.success(cacheBalance)
|
||||
}
|
||||
|
||||
override suspend fun getAllShards(uuid: UUID): Result<PlayerShards> {
|
||||
val anyBeingModified = beingModified.any { it.key.first == uuid && it.value.get() > 0 }
|
||||
if (anyBeingModified) {
|
||||
return postgreSQL.getAllShards(uuid)
|
||||
}
|
||||
val cacheBankBalance = cache.getBalance(uuid, ShardType.BANK)
|
||||
val cacheInventoryBalance = cache.getBalance(uuid, ShardType.INVENTORY)
|
||||
val cacheEnderChestBalance = cache.getBalance(uuid, ShardType.ENDER_CHEST)
|
||||
if (cacheBankBalance == -1L || cacheInventoryBalance == -1L || cacheEnderChestBalance == -1L) {
|
||||
increment(uuid, ShardType.TOTAL)
|
||||
val dbPlayerShards =
|
||||
postgreSQL.getAllShards(uuid).getOrElse {
|
||||
return Result.failure(it)
|
||||
}
|
||||
cache.setBalance(uuid, dbPlayerShards.bank, ShardType.BANK)
|
||||
cache.setBalance(uuid, dbPlayerShards.inventory, ShardType.INVENTORY)
|
||||
cache.setBalance(uuid, dbPlayerShards.enderChest, ShardType.ENDER_CHEST)
|
||||
decrement(uuid, ShardType.TOTAL)
|
||||
return Result.success(dbPlayerShards)
|
||||
}
|
||||
return Result.success(PlayerShards(cacheBankBalance, cacheInventoryBalance, cacheEnderChestBalance))
|
||||
}
|
||||
|
||||
override suspend fun getBaltop(offset: Int): Result<Map<UUID?, Long>> = postgreSQL.getBaltop(offset)
|
||||
|
||||
override suspend fun getBaltopWithUuid(uuid: UUID): Result<Pair<Map<UUID?, Long>, Long>> =
|
||||
postgreSQL.getBaltopWithUuid(uuid)
|
||||
|
||||
override suspend fun getNumberOfRows(): Result<Long> = postgreSQL.getNumberOfRows()
|
||||
|
||||
override suspend fun insertTransactionLog(
|
||||
playerUuid: UUID,
|
||||
transferredShards: Long,
|
||||
playerToUuid: UUID?,
|
||||
transactionReason: String,
|
||||
notes: String?,
|
||||
): Result<Unit> =
|
||||
postgreSQL.insertTransactionLog(playerUuid, transferredShards, playerToUuid, transactionReason, notes)
|
||||
|
||||
override suspend fun hasEntry(uuid: UUID): Result<Boolean> = postgreSQL.hasEntry(uuid)
|
||||
|
||||
override suspend fun cacheForPlayer(uuid: UUID): Result<Unit> {
|
||||
val playerShards =
|
||||
postgreSQL.getAllShards(uuid).getOrElse {
|
||||
return Result.failure(it)
|
||||
}
|
||||
cache.setBalance(uuid, playerShards.bank, ShardType.BANK)
|
||||
cache.setBalance(uuid, playerShards.inventory, ShardType.INVENTORY)
|
||||
cache.setBalance(uuid, playerShards.enderChest, ShardType.ENDER_CHEST)
|
||||
return Result.success(Unit)
|
||||
}
|
||||
|
||||
override fun removeCacheForPlayer(uuid: UUID) {
|
||||
cache.removeAll(uuid)
|
||||
}
|
||||
|
||||
override fun shutdown() {
|
||||
postgreSQL.pool.disconnect().get()
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,21 @@ package net.trueog.diamondbankog
|
||||
|
||||
import java.util.*
|
||||
import kotlin.math.floor
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage
|
||||
import net.trueog.diamondbankog.DiamondBankException.InsufficientFundsException
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.balanceManager
|
||||
import net.trueog.diamondbankog.DiamondBankRuntimeException.MoreThanOneDecimalDigitRuntimeException
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.OfflinePlayer
|
||||
|
||||
object CommonOperations {
|
||||
suspend fun consume(uuid: UUID, shards: Long, inventorySnapshot: InventorySnapshot): Result<Unit> {
|
||||
suspend fun consume(
|
||||
uuid: UUID,
|
||||
shards: Long,
|
||||
inventorySnapshot: InventorySnapshot,
|
||||
config: Config = DiamondBankOG.config,
|
||||
balanceManager: BalanceManager = DiamondBankOG.balanceManager,
|
||||
mm: MiniMessage = DiamondBankOG.mm,
|
||||
): Result<Unit> {
|
||||
val bankShards =
|
||||
balanceManager.getBankShards(uuid).getOrElse {
|
||||
return Result.failure(it)
|
||||
@@ -18,7 +25,7 @@ object CommonOperations {
|
||||
if (bankShards < shards) {
|
||||
val toRemoveShards = shards - bankShards
|
||||
val removedInShards =
|
||||
InventorySnapshotUtils.removeShards(inventorySnapshot, toRemoveShards)
|
||||
InventorySnapshotUtils.removeShards(inventorySnapshot, toRemoveShards, config, balanceManager, mm)
|
||||
.getOrElse {
|
||||
return Result.failure(it)
|
||||
}
|
||||
|
||||
@@ -1,88 +1,12 @@
|
||||
package net.trueog.diamondbankog
|
||||
|
||||
import java.io.File
|
||||
import kotlin.properties.Delegates
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.plugin
|
||||
import org.bukkit.configuration.file.YamlConfiguration
|
||||
|
||||
internal class Config private constructor() {
|
||||
lateinit var prefix: String
|
||||
lateinit var postgresUrl: String
|
||||
lateinit var postgresUser: String
|
||||
lateinit var postgresPassword: String
|
||||
lateinit var postgresTable: String
|
||||
lateinit var postgresLogTable: String
|
||||
var postgresLogLimit by Delegates.notNull<Int>()
|
||||
lateinit var redisUrl: String
|
||||
|
||||
companion object {
|
||||
fun create(): Config? {
|
||||
val config = Config()
|
||||
plugin.saveDefaultConfig()
|
||||
val file = File(plugin.dataFolder, "config.yml")
|
||||
val yamlConfig = YamlConfiguration.loadConfiguration(file)
|
||||
|
||||
try {
|
||||
config.prefix = yamlConfig.get("prefix") as String
|
||||
} catch (_: Exception) {
|
||||
plugin.logger.severe("Failed to parse config option \"prefix\" as a string")
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
config.postgresUrl = yamlConfig.get("postgresUrl") as String
|
||||
} catch (_: Exception) {
|
||||
plugin.logger.severe("Failed to parse config option \"postgresUrl\" as a string")
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
config.postgresUser = yamlConfig.get("postgresUser") as String
|
||||
} catch (_: Exception) {
|
||||
plugin.logger.severe("Failed to parse config option \"postgresUser\" as a string")
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
config.postgresPassword = yamlConfig.get("postgresPassword") as String
|
||||
} catch (_: Exception) {
|
||||
plugin.logger.severe("Failed to parse config option \"postgresPassword\" as a string")
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
config.postgresTable = yamlConfig.get("postgresTable") as String
|
||||
} catch (_: Exception) {
|
||||
plugin.logger.severe("Failed to parse config option \"postgresTable\" as a string")
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
config.postgresLogTable = yamlConfig.get("postgresLogTable") as String
|
||||
} catch (_: Exception) {
|
||||
plugin.logger.severe("Failed to parse config option \"postgresLogTable\" as a string")
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
config.postgresLogLimit = yamlConfig.get("postgresLogLimit") as Int
|
||||
if (config.postgresLogLimit < 1) {
|
||||
plugin.logger.severe("\"postgresLogLimit\" cannot be less than 1")
|
||||
return null
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
plugin.logger.severe("Failed to parse config option \"postgresLogLimit\" as an integer")
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
config.redisUrl = yamlConfig.get("redisUrl") as String
|
||||
} catch (_: Exception) {
|
||||
plugin.logger.severe("Failed to parse config option \"redisUrl\" as a string")
|
||||
return null
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
}
|
||||
interface Config {
|
||||
var prefix: String
|
||||
var postgresUrl: String
|
||||
var postgresUser: String
|
||||
var postgresPassword: String
|
||||
var postgresTable: String
|
||||
var postgresLogTable: String
|
||||
var postgresLogLimit: Int
|
||||
var redisUrl: String
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package net.trueog.diamondbankog
|
||||
|
||||
interface ConfigFactory {
|
||||
fun create(): Config?
|
||||
}
|
||||
@@ -19,9 +19,9 @@ internal open class DiamondBankOG : JavaPlugin() {
|
||||
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
|
||||
lateinit var plugin: DiamondBankOG
|
||||
lateinit var config: Config
|
||||
lateinit var config: YamlConfig
|
||||
lateinit var eventManager: EventManager
|
||||
lateinit var balanceManager: BalanceManager
|
||||
lateinit var balanceManager: CachingBalanceManager
|
||||
lateinit var redis: Redis
|
||||
lateinit var luckPerms: LuckPerms
|
||||
|
||||
@@ -49,7 +49,7 @@ internal open class DiamondBankOG : JavaPlugin() {
|
||||
plugin = this
|
||||
|
||||
Companion.config =
|
||||
Config.create()
|
||||
YamlConfig.create()
|
||||
?: run {
|
||||
Bukkit.getPluginManager().disablePlugin(this)
|
||||
return
|
||||
@@ -57,7 +57,7 @@ internal open class DiamondBankOG : JavaPlugin() {
|
||||
|
||||
eventManager = EventManager()
|
||||
|
||||
balanceManager = BalanceManager()
|
||||
balanceManager = CachingBalanceManager()
|
||||
try {
|
||||
balanceManager.init()
|
||||
} catch (e: Exception) {
|
||||
@@ -102,12 +102,6 @@ internal open class DiamondBankOG : JavaPlugin() {
|
||||
this.getCommand("enableeconomy")?.setExecutor(EnableEconomy())
|
||||
this.getCommand("disableeconomy")?.setExecutor(DisableEconomy())
|
||||
|
||||
try {
|
||||
val devCommandsClass = Class.forName("net.trueog.diamondbankog.TestCommands")
|
||||
val registerMethod = devCommandsClass.getMethod("register", JavaPlugin::class.java)
|
||||
registerMethod.invoke(null, this)
|
||||
} catch (_: ClassNotFoundException) {}
|
||||
|
||||
Shard.createCraftingRecipes()
|
||||
|
||||
val diamondBankAPIJava = DiamondBankAPIJava()
|
||||
|
||||
@@ -3,7 +3,6 @@ package net.trueog.diamondbankog
|
||||
import java.util.*
|
||||
import java.util.function.Consumer
|
||||
import kotlinx.coroutines.launch
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.balanceManager
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.scope
|
||||
import net.trueog.diamondbankog.ErrorHandler.handleError
|
||||
import net.trueog.diamondbankog.InventoryExtensions.countTotal
|
||||
@@ -16,10 +15,15 @@ import org.bukkit.inventory.ItemStack
|
||||
import org.bukkit.inventory.PlayerInventory
|
||||
|
||||
class InventorySnapshot
|
||||
private constructor(private val original: Inventory, private val heldItemSlot: Int, val holder: UUID) :
|
||||
Inventory by original {
|
||||
private constructor(
|
||||
private val original: Inventory,
|
||||
private val heldItemSlot: Int,
|
||||
val holder: UUID,
|
||||
val balanceManager: BalanceManager,
|
||||
) : Inventory by original {
|
||||
|
||||
companion object {
|
||||
fun from(inventory: PlayerInventory): InventorySnapshot {
|
||||
fun from(inventory: PlayerInventory, balanceManager: BalanceManager): InventorySnapshot {
|
||||
if (!Bukkit.isPrimaryThread()) {
|
||||
throw IllegalStateException("This method should only be called on the main thread")
|
||||
}
|
||||
@@ -28,13 +32,12 @@ private constructor(private val original: Inventory, private val heldItemSlot: I
|
||||
}
|
||||
val clonedInventory = Bukkit.createInventory(null, 36)
|
||||
clonedInventory.contents = inventory.contents.map { it?.clone() }.toTypedArray()
|
||||
return InventorySnapshot(clonedInventory, inventory.heldItemSlot, inventory.holder!!.uniqueId)
|
||||
}
|
||||
|
||||
fun withContents(contents: Array<ItemStack?>): InventorySnapshot {
|
||||
val clonedInventory = Bukkit.createInventory(null, 36)
|
||||
clonedInventory.contents = contents.map { it?.clone() }.toTypedArray()
|
||||
return InventorySnapshot(clonedInventory, 0, UUID.fromString("00000000-0000-0000-0000-000000000000"))
|
||||
return InventorySnapshot(
|
||||
clonedInventory,
|
||||
inventory.heldItemSlot,
|
||||
inventory.holder!!.uniqueId,
|
||||
balanceManager,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
package net.trueog.diamondbankog
|
||||
|
||||
import kotlin.math.ceil
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.balanceManager
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.config
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.mm
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.inventory.ItemStack
|
||||
|
||||
object InventorySnapshotUtils {
|
||||
/** @return the amount of shards that could be removed */
|
||||
suspend fun removeShards(inventory: InventorySnapshot, shards: Long): Result<Int> {
|
||||
suspend fun removeShards(
|
||||
inventory: InventorySnapshot,
|
||||
shards: Long,
|
||||
config: Config,
|
||||
balanceManager: BalanceManager,
|
||||
mm: MiniMessage,
|
||||
): Result<Int> {
|
||||
require(shards > 0)
|
||||
|
||||
val shards = shards.toInt()
|
||||
@@ -25,7 +29,7 @@ object InventorySnapshotUtils {
|
||||
inventory.removeItem(ItemStack(Material.DIAMOND, diamondsToBeRemoved)).values.sumOf { it.amount }
|
||||
if (notRemovedDiamonds == 0) {
|
||||
val addMap = inventory.addItem(Shard.createItemStack(shardsChange))
|
||||
if (addMap.isNotEmpty()) {
|
||||
if (addMap.values.sumOf { it.amount } != 0) {
|
||||
val excessShards = addMap.values.sumOf { it.amount }.toLong()
|
||||
balanceManager.addToBankShards(inventory.holder, excessShards).getOrElse {
|
||||
return Result.failure(it)
|
||||
@@ -33,7 +37,11 @@ object InventorySnapshotUtils {
|
||||
val player = Bukkit.getPlayer(inventory.holder)
|
||||
player?.sendMessage(
|
||||
mm.deserialize(
|
||||
"${config.prefix}<reset>: <yellow>The excess change of ${CommonOperations.shardsToDiamondsFull(excessShards)} <yellow> has been deposited into your bank account"
|
||||
"${config.prefix}<reset>: <yellow>The excess change of ${
|
||||
CommonOperations.shardsToDiamondsFull(
|
||||
excessShards
|
||||
)
|
||||
} <yellow> has been deposited into your bank account"
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -48,24 +56,28 @@ object InventorySnapshotUtils {
|
||||
if (notRemovedDiamondBlocks == 0) {
|
||||
val shardsAddMap = inventory.addItem(Shard.createItemStack(shardsChange))
|
||||
val excessShards = shardsAddMap.values.sumOf { it.amount }.toLong()
|
||||
if (shardsAddMap.isNotEmpty()) {
|
||||
if (shardsAddMap.values.sumOf { it.amount } != 0) {
|
||||
balanceManager.addToBankShards(inventory.holder, excessShards).getOrElse {
|
||||
return Result.failure(it)
|
||||
}
|
||||
}
|
||||
val diamondsAddMap = inventory.addItem(ItemStack(Material.DIAMOND, diamondsChange))
|
||||
val excessDiamondsInShards = diamondsAddMap.values.sumOf { it.amount }.toLong() * 9
|
||||
if (diamondsAddMap.isNotEmpty()) {
|
||||
if (diamondsAddMap.values.sumOf { it.amount } != 0) {
|
||||
balanceManager.addToBankShards(inventory.holder, excessDiamondsInShards).getOrElse {
|
||||
return Result.failure(it)
|
||||
}
|
||||
}
|
||||
|
||||
if (shardsAddMap.isNotEmpty() || diamondsAddMap.isNotEmpty()) {
|
||||
if (shardsAddMap.values.sumOf { it.amount } != 0 || diamondsAddMap.values.sumOf { it.amount } != 0) {
|
||||
val player = Bukkit.getPlayer(inventory.holder)
|
||||
player?.sendMessage(
|
||||
mm.deserialize(
|
||||
"${config.prefix}<reset>: <yellow>The excess change of ${CommonOperations.shardsToDiamondsFull(excessShards+excessDiamondsInShards)} <yellow> has been deposited into your bank account"
|
||||
"${config.prefix}<reset>: <yellow>The excess change of ${
|
||||
CommonOperations.shardsToDiamondsFull(
|
||||
excessShards + excessDiamondsInShards
|
||||
)
|
||||
} <yellow> has been deposited into your bank account"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package net.trueog.diamondbankog
|
||||
|
||||
import java.util.*
|
||||
import kotlinx.coroutines.future.await
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.luckPerms
|
||||
import net.luckperms.api.LuckPerms
|
||||
|
||||
internal object PlayerPrefix {
|
||||
private val legacyToMmMap =
|
||||
@@ -38,7 +38,7 @@ internal object PlayerPrefix {
|
||||
return legacyRegex.replace(text) { legacyToMmMap[it.groupValues[1].lowercase()] ?: it.value }
|
||||
}
|
||||
|
||||
suspend fun getPrefix(uuid: UUID): String {
|
||||
suspend fun getPrefix(uuid: UUID, luckPerms: LuckPerms): String {
|
||||
val user = luckPerms.userManager.getUser(uuid) ?: luckPerms.userManager.loadUser(uuid).await()
|
||||
val prefixNode = user.nodes.singleOrNull { node -> node.key.startsWith("prefix.1.") }
|
||||
if (prefixNode == null) return ""
|
||||
|
||||
@@ -12,7 +12,7 @@ import org.bukkit.inventory.ShapelessRecipe
|
||||
import org.bukkit.persistence.PersistentDataType
|
||||
|
||||
internal object Shard {
|
||||
val namespacedKey = NamespacedKey(DiamondBankOG.plugin, "diamondshard")
|
||||
val namespacedKey = NamespacedKey("diamondbank-og", "diamondshard")
|
||||
|
||||
/** amount defaults to 1 */
|
||||
fun createItemStack(amount: Int = 1): ItemStack {
|
||||
@@ -27,12 +27,12 @@ internal object Shard {
|
||||
}
|
||||
|
||||
fun createCraftingRecipes() {
|
||||
val diamondToShardKey = NamespacedKey(DiamondBankOG.plugin, "diamond_to_shard")
|
||||
val diamondToShardKey = NamespacedKey("diamondbank-og", "diamond_to_shard")
|
||||
val diamondToShardRecipe = ShapelessRecipe(diamondToShardKey, createItemStack(9))
|
||||
diamondToShardRecipe.addIngredient(Material.DIAMOND)
|
||||
Bukkit.addRecipe(diamondToShardRecipe)
|
||||
|
||||
val shardToDiamondKey = NamespacedKey(DiamondBankOG.plugin, "shard_to_diamond")
|
||||
val shardToDiamondKey = NamespacedKey("diamondbank-og", "shard_to_diamond")
|
||||
val shardToDiamondRecipe = ShapedRecipe(shardToDiamondKey, ItemStack(Material.DIAMOND))
|
||||
shardToDiamondRecipe.shape("SSS", "SSS", "SSS")
|
||||
shardToDiamondRecipe.setIngredient('S', createItemStack())
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package net.trueog.diamondbankog
|
||||
|
||||
import java.io.File
|
||||
import kotlin.properties.Delegates
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.plugin
|
||||
import org.bukkit.configuration.file.YamlConfiguration
|
||||
|
||||
internal class YamlConfig private constructor() : Config {
|
||||
override lateinit var prefix: String
|
||||
override lateinit var postgresUrl: String
|
||||
override lateinit var postgresUser: String
|
||||
override lateinit var postgresPassword: String
|
||||
override lateinit var postgresTable: String
|
||||
override lateinit var postgresLogTable: String
|
||||
override var postgresLogLimit by Delegates.notNull<Int>()
|
||||
override lateinit var redisUrl: String
|
||||
|
||||
companion object : ConfigFactory {
|
||||
override fun create(): YamlConfig? {
|
||||
val config = YamlConfig()
|
||||
plugin.saveDefaultConfig()
|
||||
val file = File(plugin.dataFolder, "config.yml")
|
||||
val yamlConfig = YamlConfiguration.loadConfiguration(file)
|
||||
|
||||
try {
|
||||
config.prefix = yamlConfig.get("prefix") as String
|
||||
} catch (_: Exception) {
|
||||
plugin.logger.severe("Failed to parse config option \"prefix\" as a string")
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
config.postgresUrl = yamlConfig.get("postgresUrl") as String
|
||||
} catch (_: Exception) {
|
||||
plugin.logger.severe("Failed to parse config option \"postgresUrl\" as a string")
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
config.postgresUser = yamlConfig.get("postgresUser") as String
|
||||
} catch (_: Exception) {
|
||||
plugin.logger.severe("Failed to parse config option \"postgresUser\" as a string")
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
config.postgresPassword = yamlConfig.get("postgresPassword") as String
|
||||
} catch (_: Exception) {
|
||||
plugin.logger.severe("Failed to parse config option \"postgresPassword\" as a string")
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
config.postgresTable = yamlConfig.get("postgresTable") as String
|
||||
} catch (_: Exception) {
|
||||
plugin.logger.severe("Failed to parse config option \"postgresTable\" as a string")
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
config.postgresLogTable = yamlConfig.get("postgresLogTable") as String
|
||||
} catch (_: Exception) {
|
||||
plugin.logger.severe("Failed to parse config option \"postgresLogTable\" as a string")
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
config.postgresLogLimit = yamlConfig.get("postgresLogLimit") as Int
|
||||
if (config.postgresLogLimit < 1) {
|
||||
plugin.logger.severe("\"postgresLogLimit\" cannot be less than 1")
|
||||
return null
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
plugin.logger.severe("Failed to parse config option \"postgresLogLimit\" as an integer")
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
config.redisUrl = yamlConfig.get("redisUrl") as String
|
||||
} catch (_: Exception) {
|
||||
plugin.logger.severe("Failed to parse config option \"redisUrl\" as a string")
|
||||
return null
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -224,7 +224,7 @@ class DiamondBankAPIJava {
|
||||
|
||||
val inventorySnapshot = runOnMainThread {
|
||||
player.inventory.lock()
|
||||
InventorySnapshot.from(player.inventory)
|
||||
InventorySnapshot.from(player.inventory, balanceManager)
|
||||
}
|
||||
|
||||
CommonOperations.consume(player.uniqueId, shards, inventorySnapshot).getOrElse {
|
||||
@@ -281,7 +281,7 @@ class DiamondBankAPIJava {
|
||||
|
||||
val inventorySnapshot = runOnMainThread {
|
||||
payer.inventory.lock()
|
||||
InventorySnapshot.from(payer.inventory)
|
||||
InventorySnapshot.from(payer.inventory, balanceManager)
|
||||
}
|
||||
|
||||
CommonOperations.consume(payer.uniqueId, shards, inventorySnapshot).getOrElse {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package net.trueog.diamondbankog.api
|
||||
|
||||
import java.util.*
|
||||
import kotlin.getOrElse
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import net.trueog.diamondbankog.CommonOperations
|
||||
import net.trueog.diamondbankog.DiamondBankException.*
|
||||
@@ -154,7 +153,7 @@ class DiamondBankAPIKotlin {
|
||||
|
||||
val inventorySnapshot = runOnMainThread {
|
||||
player.inventory.lock()
|
||||
InventorySnapshot.from(player.inventory)
|
||||
InventorySnapshot.from(player.inventory, balanceManager)
|
||||
}
|
||||
|
||||
CommonOperations.consume(player.uniqueId, shards, inventorySnapshot).getOrElse {
|
||||
@@ -208,7 +207,7 @@ class DiamondBankAPIKotlin {
|
||||
|
||||
val inventorySnapshot = runOnMainThread {
|
||||
payer.inventory.lock()
|
||||
InventorySnapshot.from(payer.inventory)
|
||||
InventorySnapshot.from(payer.inventory, balanceManager)
|
||||
}
|
||||
|
||||
CommonOperations.consume(payer.uniqueId, shards, inventorySnapshot).getOrElse {
|
||||
|
||||
@@ -3,6 +3,7 @@ package net.trueog.diamondbankog.commands
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.launch
|
||||
import net.trueog.diamondbankog.CommonOperations
|
||||
import net.trueog.diamondbankog.DiamondBankOG
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.balanceManager
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.config
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.economyDisabled
|
||||
@@ -83,7 +84,8 @@ internal class Balance : CommandExecutor {
|
||||
"${config.prefix}<reset>: <green>${
|
||||
if (if (sender is Player) sender.uniqueId == balancePlayer.uniqueId else false) "Your Balance" else "Balance of ${
|
||||
getPrefix(
|
||||
balancePlayer.uniqueId
|
||||
balancePlayer.uniqueId,
|
||||
DiamondBankOG.luckPerms,
|
||||
)
|
||||
}${balancePlayer.name}"
|
||||
}<reset><green>:\n" +
|
||||
|
||||
@@ -4,6 +4,7 @@ import kotlin.math.ceil
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.launch
|
||||
import net.trueog.diamondbankog.CommonOperations
|
||||
import net.trueog.diamondbankog.DiamondBankOG
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.balanceManager
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.config
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.economyDisabled
|
||||
@@ -97,7 +98,8 @@ internal class Balancetop : CommandExecutor {
|
||||
baltopMessage +=
|
||||
"\n<green>${baltopMessage.lines().size + offset}<reset>. ${if (playerName == player.name) "<italic>" else ""}${
|
||||
getPrefix(
|
||||
uuid
|
||||
uuid,
|
||||
DiamondBankOG.luckPerms,
|
||||
)
|
||||
}$playerName<reset>: <yellow>$diamonds <aqua>Diamonds"
|
||||
}
|
||||
@@ -157,7 +159,8 @@ internal class Balancetop : CommandExecutor {
|
||||
baltopMessage +=
|
||||
"\n<green>${baltopMessage.lines().size + (9 * (index - 1))}<reset>. ${if (playerName == player.name) "<italic>" else ""}${
|
||||
getPrefix(
|
||||
uuid
|
||||
uuid,
|
||||
DiamondBankOG.luckPerms,
|
||||
)
|
||||
}$playerName<reset>: <yellow>$diamonds <aqua>Diamonds"
|
||||
}
|
||||
|
||||
@@ -2,16 +2,16 @@ package net.trueog.diamondbankog.commands
|
||||
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.config
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage
|
||||
import net.trueog.diamondbankog.Config
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.economyDisabled
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.mm
|
||||
import org.bukkit.command.CommandSender
|
||||
import org.bukkit.entity.Player
|
||||
|
||||
object CommonCommandInterlude {
|
||||
/** @return should return */
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
fun run(sender: CommandSender, command: String): Boolean {
|
||||
fun run(sender: CommandSender, command: String, config: Config, mm: MiniMessage): Boolean {
|
||||
contract { returns(false) implies (sender is Player) }
|
||||
if (economyDisabled) {
|
||||
sender.sendMessage(
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
package net.trueog.diamondbankog.commands
|
||||
|
||||
import kotlin.math.abs
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.config
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.mm
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.scope
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.transactionLock
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage
|
||||
import net.trueog.diamondbankog.*
|
||||
import net.trueog.diamondbankog.InventoryExtensions.countDiamondBlocks
|
||||
import net.trueog.diamondbankog.InventoryExtensions.countDiamonds
|
||||
import net.trueog.diamondbankog.InventoryExtensions.countShards
|
||||
import net.trueog.diamondbankog.InventoryExtensions.lock
|
||||
import net.trueog.diamondbankog.InventoryExtensions.unlock
|
||||
import net.trueog.diamondbankog.InventorySnapshot
|
||||
import net.trueog.diamondbankog.MainThreadBlock.runOnMainThread
|
||||
import net.trueog.diamondbankog.Shard
|
||||
import net.trueog.diamondbankog.TransactionLock
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.block.ShulkerBox
|
||||
import org.bukkit.command.Command
|
||||
@@ -23,9 +19,15 @@ import org.bukkit.command.CommandSender
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import org.bukkit.inventory.meta.BlockStateMeta
|
||||
|
||||
internal class Compress : CommandExecutor {
|
||||
internal class Compress(
|
||||
val config: Config = DiamondBankOG.config,
|
||||
val balanceManager: BalanceManager = DiamondBankOG.balanceManager,
|
||||
val mm: MiniMessage = DiamondBankOG.mm,
|
||||
val scope: CoroutineScope = DiamondBankOG.scope,
|
||||
val transactionLock: TransactionLock = DiamondBankOG.transactionLock,
|
||||
) : CommandExecutor {
|
||||
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>?): Boolean {
|
||||
if (CommonCommandInterlude.run(sender, "compress")) {
|
||||
if (CommonCommandInterlude.run(sender, "compress", config, mm)) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -38,7 +40,7 @@ internal class Compress : CommandExecutor {
|
||||
transactionLock.tryWithLockSuspend(sender.uniqueId) {
|
||||
val inventorySnapshot = runOnMainThread {
|
||||
sender.inventory.lock()
|
||||
InventorySnapshot.from(sender.inventory)
|
||||
InventorySnapshot.from(sender.inventory, balanceManager)
|
||||
}
|
||||
|
||||
val (inventory, blockStateMeta, blockState) =
|
||||
@@ -147,6 +149,8 @@ internal class Compress : CommandExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
val summaryStringBuilder = StringBuilder("Compression Summary:")
|
||||
|
||||
if (changeInShards < 0) {
|
||||
val removeMap = inventory.removeItem(Shard.createItemStack(abs(changeInShards)))
|
||||
if (removeMap.isNotEmpty()) {
|
||||
@@ -158,6 +162,9 @@ internal class Compress : CommandExecutor {
|
||||
sender.inventory.unlock()
|
||||
return@tryWithLockSuspend
|
||||
}
|
||||
summaryStringBuilder.append(
|
||||
"\n<red>$changeInShards Diamond Shard${if (changeInShards != -1) "s" else ""}"
|
||||
)
|
||||
}
|
||||
|
||||
if (changeInDiamonds > 0) {
|
||||
@@ -171,6 +178,9 @@ internal class Compress : CommandExecutor {
|
||||
sender.inventory.unlock()
|
||||
return@tryWithLockSuspend
|
||||
}
|
||||
summaryStringBuilder.append(
|
||||
"\n<green>+$changeInDiamonds Diamond${if (changeInDiamonds != 1) "s" else ""}"
|
||||
)
|
||||
} else if (changeInDiamonds < 0) {
|
||||
val removeMap = inventory.removeItem(ItemStack(Material.DIAMOND, abs(changeInDiamonds)))
|
||||
if (removeMap.isNotEmpty()) {
|
||||
@@ -182,6 +192,9 @@ internal class Compress : CommandExecutor {
|
||||
sender.inventory.unlock()
|
||||
return@tryWithLockSuspend
|
||||
}
|
||||
summaryStringBuilder.append(
|
||||
"\n<red>$changeInDiamonds Diamond${if (changeInDiamonds != -1) "s" else ""}"
|
||||
)
|
||||
}
|
||||
|
||||
if (changeInDiamondBlocks > 0) {
|
||||
@@ -195,18 +208,9 @@ internal class Compress : CommandExecutor {
|
||||
sender.inventory.unlock()
|
||||
return@tryWithLockSuspend
|
||||
}
|
||||
} else if (changeInDiamondBlocks < 0) {
|
||||
val removeMap =
|
||||
inventory.removeItem(ItemStack(Material.DIAMOND_BLOCK, abs(changeInDiamondBlocks)))
|
||||
if (removeMap.isNotEmpty()) {
|
||||
sender.sendMessage(
|
||||
mm.deserialize(
|
||||
"${config.prefix}<reset>: <red>Something went wrong while trying to compress the Diamond currency items in your ${if (isShulkerBox) "shulker box" else "inventory"}."
|
||||
)
|
||||
)
|
||||
sender.inventory.unlock()
|
||||
return@tryWithLockSuspend
|
||||
}
|
||||
summaryStringBuilder.append(
|
||||
"\n<green>+$changeInDiamondBlocks Diamond Block${if (changeInDiamondBlocks != 1) "s" else ""}"
|
||||
)
|
||||
}
|
||||
|
||||
if (blockStateMeta != null && blockState != null) {
|
||||
@@ -233,44 +237,7 @@ internal class Compress : CommandExecutor {
|
||||
)
|
||||
)
|
||||
|
||||
val shardsLine =
|
||||
if (changeInShards < 0)
|
||||
"|<red>$changeInShards Diamond Shard${if (abs(changeInShards) != 1) "s" else ""}\n"
|
||||
else ""
|
||||
val diamondsLine =
|
||||
(if (changeInDiamonds > 0) "|<green>+$changeInDiamonds"
|
||||
else if (changeInDiamonds < 0) "|<red>$changeInDiamonds" else "") +
|
||||
if (changeInDiamonds != 0)
|
||||
" Diamond${
|
||||
if (abs(
|
||||
changeInDiamonds
|
||||
) != 1
|
||||
) "s" else ""
|
||||
}\n"
|
||||
else ""
|
||||
val diamondBlocksLine =
|
||||
(if (changeInDiamondBlocks > 0) "|<green>+$changeInDiamondBlocks"
|
||||
else if (changeInDiamondBlocks < 0) "|<red>$changeInDiamondBlocks" else "") +
|
||||
if (changeInDiamondBlocks != 0)
|
||||
" Diamond Block${
|
||||
if (abs(
|
||||
changeInDiamondBlocks
|
||||
) != 1
|
||||
) "s" else ""
|
||||
}"
|
||||
else ""
|
||||
|
||||
sender.sendMessage(
|
||||
mm.deserialize(
|
||||
buildString {
|
||||
append("${config.prefix}<reset>: Compression Summary:\n")
|
||||
if (shardsLine.isNotEmpty()) append(shardsLine)
|
||||
if (diamondsLine.isNotEmpty()) append(diamondsLine)
|
||||
if (diamondBlocksLine.isNotEmpty()) append(diamondBlocksLine)
|
||||
}
|
||||
.trimMargin("|")
|
||||
)
|
||||
)
|
||||
sender.sendMessage(mm.deserialize("${config.prefix}<reset>: $summaryStringBuilder"))
|
||||
}
|
||||
) {
|
||||
is TransactionLock.LockResult.Failed -> {
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
package net.trueog.diamondbankog.commands
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.launch
|
||||
import net.trueog.diamondbankog.CommonOperations
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.balanceManager
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.config
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.mm
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.scope
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.transactionLock
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage
|
||||
import net.trueog.diamondbankog.*
|
||||
import net.trueog.diamondbankog.ErrorHandler.handleError
|
||||
import net.trueog.diamondbankog.InventoryExtensions.lock
|
||||
import net.trueog.diamondbankog.InventoryExtensions.unlock
|
||||
import net.trueog.diamondbankog.InventorySnapshot
|
||||
import net.trueog.diamondbankog.InventorySnapshotUtils
|
||||
import net.trueog.diamondbankog.MainThreadBlock.runOnMainThread
|
||||
import net.trueog.diamondbankog.TransactionLock
|
||||
import org.bukkit.command.Command
|
||||
import org.bukkit.command.CommandExecutor
|
||||
import org.bukkit.command.CommandSender
|
||||
|
||||
internal class Deposit : CommandExecutor {
|
||||
internal class Deposit(
|
||||
val config: Config = DiamondBankOG.config,
|
||||
val balanceManager: BalanceManager = DiamondBankOG.balanceManager,
|
||||
val mm: MiniMessage = DiamondBankOG.mm,
|
||||
val scope: CoroutineScope = DiamondBankOG.scope,
|
||||
val transactionLock: TransactionLock = DiamondBankOG.transactionLock,
|
||||
) : CommandExecutor {
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>?): Boolean {
|
||||
if (CommonCommandInterlude.run(sender, "deposit")) {
|
||||
if (CommonCommandInterlude.run(sender, "deposit", config, mm)) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -74,21 +74,20 @@ internal class Deposit : CommandExecutor {
|
||||
transactionLock.tryWithLockSuspend(sender.uniqueId) {
|
||||
val inventorySnapshot = runOnMainThread {
|
||||
sender.inventory.lock()
|
||||
InventorySnapshot.from(sender.inventory)
|
||||
InventorySnapshot.from(sender.inventory, balanceManager)
|
||||
}
|
||||
|
||||
val removedInShards: Long =
|
||||
if (shards == -1L) {
|
||||
InventorySnapshotUtils.removeAll(inventorySnapshot).toLong()
|
||||
} else {
|
||||
InventorySnapshotUtils.removeShards(inventorySnapshot, shards)
|
||||
InventorySnapshotUtils.removeShards(inventorySnapshot, shards, config, balanceManager, mm)
|
||||
.getOrElse {
|
||||
sender.sendMessage(
|
||||
mm.deserialize(
|
||||
"${config.prefix}<reset>: <red>You do not have enough inventory space for the change."
|
||||
)
|
||||
mm.deserialize("${config.prefix}<reset>: <red>Something went wrong.")
|
||||
)
|
||||
sender.inventory.unlock()
|
||||
handleError(it)
|
||||
return@tryWithLockSuspend
|
||||
}
|
||||
.toLong()
|
||||
@@ -96,7 +95,11 @@ internal class Deposit : CommandExecutor {
|
||||
if (shards != -1L && removedInShards != shards) {
|
||||
sender.sendMessage(
|
||||
mm.deserialize(
|
||||
"${config.prefix}<reset>: <red>You do not have ${CommonOperations.shardsToDiamondsFull(shards)} <red>to deposit."
|
||||
"${config.prefix}<reset>: <red>You do not have ${
|
||||
CommonOperations.shardsToDiamondsFull(
|
||||
shards
|
||||
)
|
||||
} <red>to deposit."
|
||||
)
|
||||
)
|
||||
sender.inventory.unlock()
|
||||
@@ -120,7 +123,11 @@ internal class Deposit : CommandExecutor {
|
||||
|
||||
sender.sendMessage(
|
||||
mm.deserialize(
|
||||
"${config.prefix}<reset>: <green>Successfully deposited ${CommonOperations.shardsToDiamondsFull(removedInShards)} <green>into your bank account."
|
||||
"${config.prefix}<reset>: <green>Successfully deposited ${
|
||||
CommonOperations.shardsToDiamondsFull(
|
||||
removedInShards
|
||||
)
|
||||
} <green>into your bank account."
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package net.trueog.diamondbankog.commands
|
||||
|
||||
import net.trueog.diamondbankog.BalanceManager
|
||||
import net.trueog.diamondbankog.Config
|
||||
import net.trueog.diamondbankog.CachingBalanceManager
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.balanceManager
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.config
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.mm
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.plugin
|
||||
import net.trueog.diamondbankog.YamlConfig
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.command.Command
|
||||
import org.bukkit.command.CommandExecutor
|
||||
@@ -14,7 +14,7 @@ import org.bukkit.command.CommandSender
|
||||
internal class DiamondBankReload : CommandExecutor {
|
||||
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>?): Boolean {
|
||||
config =
|
||||
Config.create()
|
||||
YamlConfig.create()
|
||||
?: run {
|
||||
sender.sendMessage(
|
||||
mm.deserialize("<red>Failed to reload the config. Check the console for more information.")
|
||||
@@ -23,7 +23,7 @@ internal class DiamondBankReload : CommandExecutor {
|
||||
return true
|
||||
}
|
||||
|
||||
balanceManager = BalanceManager()
|
||||
balanceManager = CachingBalanceManager()
|
||||
try {
|
||||
balanceManager.init()
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -1,29 +1,31 @@
|
||||
package net.trueog.diamondbankog.commands
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.launch
|
||||
import net.trueog.diamondbankog.CommonOperations
|
||||
import net.trueog.diamondbankog.DiamondBankException
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.balanceManager
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.config
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.mm
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.scope
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.transactionLock
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage
|
||||
import net.luckperms.api.LuckPerms
|
||||
import net.trueog.diamondbankog.*
|
||||
import net.trueog.diamondbankog.ErrorHandler.handleError
|
||||
import net.trueog.diamondbankog.InventoryExtensions.lock
|
||||
import net.trueog.diamondbankog.InventoryExtensions.unlock
|
||||
import net.trueog.diamondbankog.InventorySnapshot
|
||||
import net.trueog.diamondbankog.MainThreadBlock.runOnMainThread
|
||||
import net.trueog.diamondbankog.PlayerPrefix.getPrefix
|
||||
import net.trueog.diamondbankog.TransactionLock
|
||||
import org.bukkit.command.Command
|
||||
import org.bukkit.command.CommandExecutor
|
||||
import org.bukkit.command.CommandSender
|
||||
|
||||
internal class Pay : CommandExecutor {
|
||||
internal class Pay(
|
||||
val config: Config = DiamondBankOG.config,
|
||||
val balanceManager: BalanceManager = DiamondBankOG.balanceManager,
|
||||
val mm: MiniMessage = DiamondBankOG.mm,
|
||||
val scope: CoroutineScope = DiamondBankOG.scope,
|
||||
val luckPerms: LuckPerms = DiamondBankOG.luckPerms,
|
||||
val transactionLock: TransactionLock = DiamondBankOG.transactionLock,
|
||||
) : CommandExecutor {
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>?): Boolean {
|
||||
if (CommonCommandInterlude.run(sender, "pay")) {
|
||||
if (CommonCommandInterlude.run(sender, "pay", config, mm)) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -87,32 +89,37 @@ internal class Pay : CommandExecutor {
|
||||
transactionLock.tryWithLockSuspend(sender.uniqueId) {
|
||||
val inventorySnapshot = runOnMainThread {
|
||||
sender.inventory.lock()
|
||||
InventorySnapshot.from(sender.inventory)
|
||||
InventorySnapshot.from(sender.inventory, balanceManager)
|
||||
}
|
||||
|
||||
CommonOperations.consume(sender.uniqueId, shards, inventorySnapshot).getOrElse {
|
||||
when (it) {
|
||||
is DiamondBankException.InsufficientFundsException -> {
|
||||
sender.sendMessage(
|
||||
mm.deserialize(
|
||||
"${config.prefix}<reset>: <red>You are ${CommonOperations.shardsToDiamondsFull(it.short)} <red>short for that payment."
|
||||
CommonOperations.consume(sender.uniqueId, shards, inventorySnapshot, config, balanceManager, mm)
|
||||
.getOrElse {
|
||||
when (it) {
|
||||
is DiamondBankException.InsufficientFundsException -> {
|
||||
sender.sendMessage(
|
||||
mm.deserialize(
|
||||
"${config.prefix}<reset>: <red>You are ${
|
||||
CommonOperations.shardsToDiamondsFull(
|
||||
it.short
|
||||
)
|
||||
} <red>short for that payment."
|
||||
)
|
||||
)
|
||||
)
|
||||
sender.inventory.unlock()
|
||||
return@tryWithLockSuspend
|
||||
}
|
||||
sender.inventory.unlock()
|
||||
return@tryWithLockSuspend
|
||||
}
|
||||
|
||||
else -> {
|
||||
sender.sendMessage(
|
||||
mm.deserialize(
|
||||
"${config.prefix}<reset>: <red>A severe error has occurred. Please notify a staff member."
|
||||
else -> {
|
||||
sender.sendMessage(
|
||||
mm.deserialize(
|
||||
"${config.prefix}<reset>: <red>A severe error has occurred. Please notify a staff member."
|
||||
)
|
||||
)
|
||||
)
|
||||
sender.inventory.unlock()
|
||||
return@tryWithLockSuspend
|
||||
sender.inventory.unlock()
|
||||
return@tryWithLockSuspend
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
balanceManager.addToBankShards(receiver.uniqueId, shards).getOrElse {
|
||||
handleError(it)
|
||||
@@ -132,9 +139,14 @@ internal class Pay : CommandExecutor {
|
||||
|
||||
sender.sendMessage(
|
||||
mm.deserialize(
|
||||
"${config.prefix}<reset>: <green>Successfully paid ${CommonOperations.shardsToDiamondsFull(shards)} <green>to ${
|
||||
"${config.prefix}<reset>: <green>Successfully paid ${
|
||||
CommonOperations.shardsToDiamondsFull(
|
||||
shards
|
||||
)
|
||||
} <green>to ${
|
||||
getPrefix(
|
||||
receiver.uniqueId
|
||||
receiver.uniqueId,
|
||||
luckPerms,
|
||||
)
|
||||
} ${receiver.name}<reset><green>."
|
||||
)
|
||||
@@ -146,9 +158,14 @@ internal class Pay : CommandExecutor {
|
||||
mm.deserialize(
|
||||
"${config.prefix}<reset>: <green>${
|
||||
getPrefix(
|
||||
sender.uniqueId
|
||||
sender.uniqueId,
|
||||
luckPerms,
|
||||
)
|
||||
} ${sender.name}<reset> <green>has paid you ${CommonOperations.shardsToDiamondsFull(shards)}<green>."
|
||||
} ${sender.name}<reset> <green>has paid you ${
|
||||
CommonOperations.shardsToDiamondsFull(
|
||||
shards
|
||||
)
|
||||
}<green>."
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package net.trueog.diamondbankog.commands
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.launch
|
||||
import net.trueog.diamondbankog.CommonOperations
|
||||
import net.trueog.diamondbankog.DiamondBankOG
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.balanceManager
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.config
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.economyDisabled
|
||||
@@ -80,7 +81,8 @@ internal class SetBankBalance : CommandExecutor {
|
||||
mm.deserialize(
|
||||
"${config.prefix}<reset>: <green>Successfully set the balance of ${
|
||||
getPrefix(
|
||||
player.uniqueId
|
||||
player.uniqueId,
|
||||
DiamondBankOG.luckPerms,
|
||||
)
|
||||
}${player.name}<reset> <green>to <yellow>$balance <aqua>${if (balance == 1L) "Shard" else "Shards"}<green>."
|
||||
)
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
package net.trueog.diamondbankog.commands
|
||||
|
||||
import kotlin.math.floor
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.launch
|
||||
import net.trueog.diamondbankog.CommonOperations
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.balanceManager
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.config
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.mm
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.scope
|
||||
import net.trueog.diamondbankog.DiamondBankOG.Companion.transactionLock
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage
|
||||
import net.trueog.diamondbankog.*
|
||||
import net.trueog.diamondbankog.ErrorHandler.handleError
|
||||
import net.trueog.diamondbankog.InventoryExtensions.lock
|
||||
import net.trueog.diamondbankog.InventoryExtensions.unlock
|
||||
import net.trueog.diamondbankog.InventorySnapshot
|
||||
import net.trueog.diamondbankog.MainThreadBlock.runOnMainThread
|
||||
import net.trueog.diamondbankog.Shard
|
||||
import net.trueog.diamondbankog.TransactionLock
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.command.Command
|
||||
import org.bukkit.command.CommandExecutor
|
||||
import org.bukkit.command.CommandSender
|
||||
import org.bukkit.inventory.ItemStack
|
||||
|
||||
internal class Withdraw : CommandExecutor {
|
||||
internal class Withdraw(
|
||||
val config: Config = DiamondBankOG.config,
|
||||
val balanceManager: BalanceManager = DiamondBankOG.balanceManager,
|
||||
val mm: MiniMessage = DiamondBankOG.mm,
|
||||
val scope: CoroutineScope = DiamondBankOG.scope,
|
||||
val transactionLock: TransactionLock = DiamondBankOG.transactionLock,
|
||||
) : CommandExecutor {
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>?): Boolean {
|
||||
if (CommonCommandInterlude.run(sender, "withdraw")) {
|
||||
if (CommonCommandInterlude.run(sender, "withdraw", config, mm)) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -37,10 +37,10 @@ internal class Withdraw : CommandExecutor {
|
||||
)
|
||||
return true
|
||||
}
|
||||
if (args.size != 1 && args.size != 2) {
|
||||
if (args.size != 1) {
|
||||
sender.sendMessage(
|
||||
mm.deserialize(
|
||||
"${config.prefix}<reset>: <red>Please provide the amount of <aqua>Diamonds <red>you want to withdraw. Either a number or \"all\"."
|
||||
"${config.prefix}<reset>: <red>Please (only) provide the amount of <aqua>Diamonds <red>you want to withdraw. Either a number or \"all\"."
|
||||
)
|
||||
)
|
||||
return true
|
||||
@@ -72,12 +72,14 @@ internal class Withdraw : CommandExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
sender.inventory.lock()
|
||||
val inventorySnapshot = InventorySnapshot.from(sender.inventory)
|
||||
|
||||
scope.launch {
|
||||
when (
|
||||
transactionLock.tryWithLockSuspend(sender.uniqueId) {
|
||||
val inventorySnapshot = runOnMainThread {
|
||||
sender.inventory.lock()
|
||||
InventorySnapshot.from(sender.inventory, balanceManager)
|
||||
}
|
||||
|
||||
val bankShards =
|
||||
balanceManager.getBankShards(sender.uniqueId).getOrElse {
|
||||
sender.inventory.unlock()
|
||||
@@ -100,7 +102,15 @@ internal class Withdraw : CommandExecutor {
|
||||
sender.inventory.unlock()
|
||||
sender.sendMessage(
|
||||
mm.deserialize(
|
||||
"${config.prefix}<reset>: <red>Cannot withdraw ${CommonOperations.shardsToDiamondsFull(shardsToWithdraw)} <red>because your bank only contains <yellow>${CommonOperations.shardsToDiamondsFull(bankShards)}<red>."
|
||||
"${config.prefix}<reset>: <red>Cannot withdraw ${
|
||||
CommonOperations.shardsToDiamondsFull(
|
||||
shardsToWithdraw
|
||||
)
|
||||
} <red>because your bank only contains <yellow>${
|
||||
CommonOperations.shardsToDiamondsFull(
|
||||
bankShards
|
||||
)
|
||||
}<red>."
|
||||
)
|
||||
)
|
||||
return@tryWithLockSuspend
|
||||
@@ -116,7 +126,11 @@ internal class Withdraw : CommandExecutor {
|
||||
sender.inventory.unlock()
|
||||
sender.sendMessage(
|
||||
mm.deserialize(
|
||||
"${config.prefix}<reset>: <red>You don't have enough inventory space to withdraw ${CommonOperations.shardsToDiamondsFull(shardsToWithdraw)}<red>."
|
||||
"${config.prefix}<reset>: <red>You don't have enough inventory space to withdraw ${
|
||||
CommonOperations.shardsToDiamondsFull(
|
||||
shardsToWithdraw
|
||||
)
|
||||
}<red>."
|
||||
)
|
||||
)
|
||||
return@tryWithLockSuspend
|
||||
@@ -140,7 +154,11 @@ internal class Withdraw : CommandExecutor {
|
||||
|
||||
sender.sendMessage(
|
||||
mm.deserialize(
|
||||
"${config.prefix}<reset>: <green>Successfully withdrew ${CommonOperations.shardsToDiamondsFull(shardsToWithdraw)} <green>from your bank account."
|
||||
"${config.prefix}<reset>: <green>Successfully withdrew ${
|
||||
CommonOperations.shardsToDiamondsFull(
|
||||
shardsToWithdraw
|
||||
)
|
||||
} <green>from your bank account."
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
package net.trueog.diamondbankog
|
||||
|
||||
import io.mockk.*
|
||||
import net.kyori.adventure.text.Component
|
||||
import net.trueog.diamondbankog.Utils.allImpl
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.NamespacedKey
|
||||
import org.bukkit.Server
|
||||
import org.bukkit.block.ShulkerBox
|
||||
import org.bukkit.inventory.Inventory
|
||||
import org.bukkit.inventory.InventoryHolder
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import org.bukkit.inventory.meta.BlockStateMeta
|
||||
import org.bukkit.inventory.meta.Damageable
|
||||
import org.bukkit.inventory.meta.ItemMeta
|
||||
import org.bukkit.persistence.PersistentDataType
|
||||
|
||||
object BukkitMock {
|
||||
private fun first(item: ItemStack?, inventory: Array<ItemStack?>): Int {
|
||||
if (item == null) {
|
||||
return -1
|
||||
}
|
||||
for (i in inventory.indices) {
|
||||
if (inventory[i] == null) continue
|
||||
|
||||
if (item.isSimilar(inventory[i])) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
private fun firstPartial(item: ItemStack?, inventory: Array<ItemStack?>): Int {
|
||||
val filteredItem = item?.clone()
|
||||
if (item == null) {
|
||||
return -1
|
||||
}
|
||||
for (i in inventory.indices) {
|
||||
val cItem = inventory[i]
|
||||
if (cItem != null && cItem.amount < 64 && cItem.isSimilar(filteredItem)) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
private fun firstEmpty(inventory: Array<ItemStack?>): Int {
|
||||
for (i in inventory.indices) {
|
||||
if (inventory[i] == null) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
const val MAX_AMOUNT = 64
|
||||
|
||||
fun mockBukkit(): Server {
|
||||
val server = mockk<Server>()
|
||||
mockkStatic(Bukkit::class)
|
||||
every { Bukkit.getServer() } returns server
|
||||
every { server.isPrimaryThread } returns true
|
||||
every { Bukkit.isPrimaryThread() } returns true
|
||||
|
||||
every { Bukkit.getItemFactory() } answers { server.itemFactory }
|
||||
|
||||
every { Bukkit.createInventory(anyNullable<InventoryHolder>(), any<Int>()) } answers
|
||||
{
|
||||
server.createInventory(firstArg<InventoryHolder?>(), secondArg<Int>())
|
||||
}
|
||||
|
||||
val sizeSlot = slot<Int>()
|
||||
every { server.createInventory(any(), capture(sizeSlot)) } returns createInventory()
|
||||
|
||||
every { server.itemFactory.getItemMeta(any()) } answers
|
||||
{
|
||||
val material = firstArg<Material>()
|
||||
val itemMeta =
|
||||
if (material == Material.SHULKER_BOX) {
|
||||
mockk<BlockStateMeta>()
|
||||
} else {
|
||||
mockk<Damageable>()
|
||||
}
|
||||
|
||||
val persistentDataContainerKeys = mutableSetOf<NamespacedKey>()
|
||||
var displayName: Component? = null
|
||||
every { itemMeta.clone() } returns itemMeta
|
||||
every { itemMeta.displayName(any()) } answers { displayName = firstArg<Component>() }
|
||||
every { itemMeta.displayName() } answers { displayName }
|
||||
every { itemMeta.addEnchant(any(), any(), any()) } returns true
|
||||
every { itemMeta.addItemFlags(any()) } just Runs
|
||||
every { itemMeta.persistentDataContainer.set(any(), PersistentDataType.STRING, any()) } answers
|
||||
{
|
||||
val key = firstArg<NamespacedKey>()
|
||||
persistentDataContainerKeys.add(key)
|
||||
}
|
||||
every { itemMeta.persistentDataContainer.has(any()) } answers
|
||||
{
|
||||
val key = firstArg<NamespacedKey>()
|
||||
persistentDataContainerKeys.contains(key)
|
||||
}
|
||||
every { itemMeta.persistentDataContainer.keys } answers { persistentDataContainerKeys }
|
||||
every { itemMeta.persistentDataContainer.isEmpty } answers { persistentDataContainerKeys.isEmpty() }
|
||||
|
||||
if (itemMeta is BlockStateMeta) {
|
||||
val blockState = mockk<ShulkerBox>()
|
||||
var inventory = createInventory()
|
||||
every { itemMeta.blockState } returns blockState
|
||||
every { itemMeta.blockState = any() } answers { inventory = firstArg<ShulkerBox>().inventory }
|
||||
every { blockState.inventory } returns inventory
|
||||
}
|
||||
|
||||
if (itemMeta is Damageable) {
|
||||
every { itemMeta.damage } returns 0
|
||||
}
|
||||
|
||||
itemMeta
|
||||
}
|
||||
|
||||
every { server.itemFactory.isApplicable(any(), Material.PRISMARINE_SHARD) } returns true
|
||||
every { server.itemFactory.isApplicable(any(), Material.SHULKER_BOX) } returns true
|
||||
|
||||
every { server.itemFactory.asMetaFor(any(), any<Material>()) } answers
|
||||
{
|
||||
val itemMeta = firstArg<ItemMeta>()
|
||||
val persistentDataContainerKeys = mutableSetOf<NamespacedKey>()
|
||||
val displayName = itemMeta.displayName()
|
||||
itemMeta.persistentDataContainer.keys.forEach { persistentDataContainerKeys.add(it) }
|
||||
|
||||
val material = secondArg<Material>()
|
||||
val newItemMeta =
|
||||
if (material == Material.SHULKER_BOX) {
|
||||
mockk<BlockStateMeta>()
|
||||
} else {
|
||||
mockk<Damageable>()
|
||||
}
|
||||
|
||||
every { newItemMeta.clone() } returns newItemMeta
|
||||
every { newItemMeta.displayName() } answers { displayName }
|
||||
every { newItemMeta.persistentDataContainer.has(any()) } answers
|
||||
{
|
||||
val key = firstArg<NamespacedKey>()
|
||||
persistentDataContainerKeys.contains(key)
|
||||
}
|
||||
every { newItemMeta.persistentDataContainer.isEmpty } answers { persistentDataContainerKeys.isEmpty() }
|
||||
every { newItemMeta.persistentDataContainer.keys } answers { persistentDataContainerKeys }
|
||||
|
||||
if (newItemMeta is BlockStateMeta) {
|
||||
val blockState = mockk<ShulkerBox>()
|
||||
var inventory = ((itemMeta as BlockStateMeta).blockState as ShulkerBox).inventory
|
||||
println(inventory.contents[0])
|
||||
every { newItemMeta.blockState } returns blockState
|
||||
every { newItemMeta.blockState = any() } answers { inventory = firstArg<ShulkerBox>().inventory }
|
||||
every { blockState.inventory } returns inventory
|
||||
}
|
||||
|
||||
if (newItemMeta is Damageable) {
|
||||
every { newItemMeta.damage } returns 0
|
||||
}
|
||||
|
||||
newItemMeta
|
||||
}
|
||||
|
||||
every { server.itemFactory.equals(any(), any()) } answers
|
||||
{
|
||||
val meta1 = firstArg<ItemMeta?>()
|
||||
val meta2 = secondArg<ItemMeta?>()
|
||||
if (meta1 == meta2) return@answers true
|
||||
|
||||
if (meta1 == null) {
|
||||
return@answers (meta2!!.displayName() == null &&
|
||||
!meta2.hasEnchants() &&
|
||||
meta2.persistentDataContainer.isEmpty)
|
||||
}
|
||||
if (meta2 == null) {
|
||||
return@answers (meta1.displayName() == null &&
|
||||
!meta1.hasEnchants() &&
|
||||
meta1.persistentDataContainer.isEmpty)
|
||||
}
|
||||
|
||||
meta1.displayName() == meta2.displayName() &&
|
||||
meta1.persistentDataContainer.keys == meta2.persistentDataContainer.keys
|
||||
}
|
||||
|
||||
every { @Suppress("DEPRECATION") server.itemFactory.updateMaterial(any(), Material.PRISMARINE_SHARD) } returns
|
||||
Material.PRISMARINE_SHARD
|
||||
|
||||
every { @Suppress("DEPRECATION") server.itemFactory.updateMaterial(any(), Material.SHULKER_BOX) } returns
|
||||
Material.SHULKER_BOX
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
fun createInventory(): Inventory {
|
||||
val inventory = mockk<Inventory>()
|
||||
var contents = arrayOfNulls<ItemStack>(36)
|
||||
every { inventory.contents = any<Array<ItemStack?>>() } answers { contents = firstArg<Array<ItemStack?>>() }
|
||||
every { inventory.contents } answers { contents }
|
||||
every { inventory.storageContents } answers { contents }
|
||||
every { inventory.all(any<Material>()) } answers
|
||||
{
|
||||
val material = firstArg<Material>()
|
||||
allImpl(contents, material)
|
||||
}
|
||||
every { inventory.addItem(any()) } answers { addToInventory(firstArg<Array<out ItemStack?>>(), contents) }
|
||||
every { inventory.removeItem(any()) } answers
|
||||
{
|
||||
val items = firstArg<Array<out ItemStack?>>()
|
||||
val leftover = HashMap<Int?, ItemStack?>()
|
||||
|
||||
items.forEachIndexed { index, item ->
|
||||
if (item == null) {
|
||||
throw IllegalArgumentException("ItemStack cannot be null")
|
||||
}
|
||||
|
||||
if (item.type.isAir) {
|
||||
return@forEachIndexed
|
||||
}
|
||||
var toDelete = item.amount
|
||||
|
||||
while (true) {
|
||||
val first = first(item, contents)
|
||||
|
||||
if (first == -1) {
|
||||
item.amount = toDelete
|
||||
leftover[index] = item
|
||||
break
|
||||
} else {
|
||||
val itemStack = contents[first]
|
||||
val amount = itemStack!!.amount
|
||||
|
||||
if (amount <= toDelete) {
|
||||
toDelete -= amount
|
||||
contents[first] = null
|
||||
} else {
|
||||
itemStack.amount = amount - toDelete
|
||||
contents[first] = itemStack
|
||||
toDelete = 0
|
||||
}
|
||||
}
|
||||
|
||||
if (toDelete <= 0) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
leftover
|
||||
}
|
||||
every { inventory.remove(anyNullable<Material>()) } answers
|
||||
{
|
||||
val material = firstArg<Material?>() ?: throw IllegalArgumentException("Material cannot be null")
|
||||
|
||||
contents.forEachIndexed { index, item ->
|
||||
if (item?.type == material) {
|
||||
contents[index] = null
|
||||
}
|
||||
}
|
||||
}
|
||||
every { inventory.getItem(any<Int>()) } answers { contents[firstArg()] }
|
||||
return inventory
|
||||
}
|
||||
|
||||
fun addToInventory(items: Array<out ItemStack?>, contents: Array<ItemStack?>): HashMap<Int?, ItemStack?> {
|
||||
val leftover = HashMap<Int?, ItemStack?>()
|
||||
|
||||
items.forEachIndexed { index, item ->
|
||||
if (item == null) {
|
||||
throw IllegalArgumentException("ItemStack cannot be null")
|
||||
}
|
||||
|
||||
if (item.type.isAir) {
|
||||
return@forEachIndexed
|
||||
}
|
||||
while (true) {
|
||||
val firstPartial = firstPartial(item, contents)
|
||||
|
||||
if (firstPartial == -1) {
|
||||
val firstFree = firstEmpty(contents)
|
||||
|
||||
if (firstFree == -1) {
|
||||
leftover[index] = item
|
||||
break
|
||||
} else {
|
||||
if (item.amount > MAX_AMOUNT) {
|
||||
val stack = item.clone()
|
||||
stack.amount = MAX_AMOUNT
|
||||
contents[firstFree] = stack.clone()
|
||||
item.amount = -MAX_AMOUNT
|
||||
} else {
|
||||
contents[firstFree] = item.clone()
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val partialItem = contents[firstPartial]?.clone()
|
||||
|
||||
val amount = item.amount
|
||||
val partialAmount = partialItem!!.amount
|
||||
|
||||
if (amount + partialAmount <= MAX_AMOUNT) {
|
||||
partialItem.amount = amount + partialAmount
|
||||
contents[firstPartial] = partialItem.clone()
|
||||
break
|
||||
}
|
||||
|
||||
partialItem.amount = MAX_AMOUNT
|
||||
|
||||
contents[firstPartial] = partialItem.clone()
|
||||
item.amount = amount + partialAmount - MAX_AMOUNT
|
||||
}
|
||||
}
|
||||
}
|
||||
return leftover
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
package net.trueog.diamondbankog
|
||||
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.Runs
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.every
|
||||
import io.mockk.impl.annotations.MockK
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import io.mockk.verify
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import net.kyori.adventure.text.Component
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage
|
||||
import net.trueog.diamondbankog.Constants.playerUuid
|
||||
import net.trueog.diamondbankog.InventoryExtensions.countDiamondBlocks
|
||||
import net.trueog.diamondbankog.InventoryExtensions.countDiamonds
|
||||
import net.trueog.diamondbankog.InventoryExtensions.countShards
|
||||
import net.trueog.diamondbankog.InventoryExtensions.isLocked
|
||||
import net.trueog.diamondbankog.Utils.mockPlayerInventory
|
||||
import net.trueog.diamondbankog.Utils.waitForCoroutines
|
||||
import net.trueog.diamondbankog.commands.Compress
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.Server
|
||||
import org.bukkit.block.ShulkerBox
|
||||
import org.bukkit.command.Command
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import org.bukkit.inventory.meta.BlockStateMeta
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertAll
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.Arguments
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
|
||||
class CompressTest {
|
||||
@MockK lateinit var config: Config
|
||||
|
||||
@MockK lateinit var balanceManager: BalanceManager
|
||||
|
||||
@MockK lateinit var mm: MiniMessage
|
||||
|
||||
@MockK lateinit var player: Player
|
||||
|
||||
@MockK lateinit var command: Command
|
||||
|
||||
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
MockKAnnotations.init(this)
|
||||
|
||||
every { config.prefix } returns "DiamondBank-OG"
|
||||
|
||||
val inputSlot = slot<String>()
|
||||
every { mm.deserialize(capture(inputSlot)) } answers { Component.text(inputSlot.captured) }
|
||||
|
||||
every { player.uniqueId } returns playerUuid
|
||||
every { player.world.name } returns "world"
|
||||
every { player.hasPermission("diamondbank-og.compress") } returns true
|
||||
|
||||
every { Bukkit.getPlayer("player") } returns player
|
||||
every { server.getPlayer("player") } returns player
|
||||
every { Bukkit.getPlayer(playerUuid) } returns player
|
||||
every { server.getPlayer(playerUuid) } returns player
|
||||
|
||||
every { player.sendMessage(any<Component>()) } just Runs
|
||||
|
||||
coEvery { balanceManager.setPlayerShards(any(), any(), PostgreSQL.ShardType.INVENTORY) } returns
|
||||
Result.success(Unit)
|
||||
}
|
||||
|
||||
companion object {
|
||||
lateinit var server: Server
|
||||
|
||||
@JvmStatic
|
||||
@BeforeAll
|
||||
fun setupBukkit() {
|
||||
server = BukkitMock.mockBukkit()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun compressData() =
|
||||
listOf(
|
||||
Arguments.of("Nothing", arrayOf<ItemStack>(), "<#FFA500>Nothing found to compress.", 0, 0, 0),
|
||||
Arguments.of(
|
||||
"9 shards to 1 diamond",
|
||||
arrayOf(Shard.createItemStack(9)),
|
||||
"Compression Summary:\n<red>-9 Diamond Shards\n<green>+1 Diamond",
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
),
|
||||
Arguments.of(
|
||||
"9 diamonds to 1 diamond block",
|
||||
arrayOf(ItemStack(Material.DIAMOND, 9)),
|
||||
"Compression Summary:\n<red>-9 Diamonds\n<green>+1 Diamond Block",
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
),
|
||||
Arguments.of(
|
||||
"9 diamonds and 9 shards to 1 diamond block and 1 diamond",
|
||||
arrayOf(Shard.createItemStack(9), ItemStack(Material.DIAMOND, 9)),
|
||||
"Compression Summary:\n<red>-9 Diamond Shards\n<red>-8 Diamonds\n<green>+1 Diamond Block",
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
),
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
fun overflowCompressData() =
|
||||
listOf(
|
||||
Arguments.of(
|
||||
"10 shards",
|
||||
Array(35) { ItemStack(Material.DIRT, 1) } + arrayOf(Shard.createItemStack(10)),
|
||||
"You do not have enough space in your inventory to compress all the Diamond currency items (<green>+1 <aqua>Diamonds<red>).",
|
||||
10,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
Arguments.of(
|
||||
"10 diamonds",
|
||||
Array(35) { ItemStack(Material.DIRT, 1) } + arrayOf(ItemStack(Material.DIAMOND, 10)),
|
||||
"You do not have enough space in your inventory to compress all the Diamond currency items (<green>+1 <aqua>Diamond Blocks<red>).",
|
||||
0,
|
||||
10,
|
||||
0,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@DisplayName("Successful Compress")
|
||||
@MethodSource("compressData")
|
||||
fun successfulCompress(
|
||||
@Suppress("UNUSED_PARAMETER") name: String,
|
||||
inventoryContents: Array<ItemStack?>,
|
||||
compressionSummary: String,
|
||||
invShardCount: Long,
|
||||
invDiamondCount: Long,
|
||||
invDiamondBlockCount: Long,
|
||||
) = runTest {
|
||||
val inventory = mockPlayerInventory(player, playerUuid, inventoryContents)
|
||||
|
||||
val compress = Compress(config, balanceManager, mm, scope)
|
||||
compress.onCommand(player, command, "compress", arrayOf())
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
buildList {
|
||||
add { verify { player.sendMessage(Component.text("DiamondBank-OG<reset>: $compressionSummary")) } }
|
||||
add { assertEquals(invShardCount, inventory.countShards(), "Shard count") }
|
||||
add { assertEquals(invDiamondCount, inventory.countDiamonds(), "Diamond count") }
|
||||
add { assertEquals(invDiamondBlockCount, inventory.countDiamondBlocks(), "Diamond block count") }
|
||||
add { assertFalse(inventory.isLocked(), "Inventory locked") }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@DisplayName("Shulker box Successful Compress")
|
||||
@MethodSource("compressData")
|
||||
fun successfulShulkerBoxCompress(
|
||||
@Suppress("UNUSED_PARAMETER") name: String,
|
||||
inventoryContents: Array<ItemStack>,
|
||||
compressionSummary: String,
|
||||
invShardCount: Long,
|
||||
invDiamondCount: Long,
|
||||
invDiamondBlockCount: Long,
|
||||
) = runTest {
|
||||
val shulkerBox = ItemStack(Material.SHULKER_BOX)
|
||||
val blockStateMeta = shulkerBox.itemMeta as BlockStateMeta
|
||||
val shulkerBoxState = blockStateMeta.blockState as ShulkerBox
|
||||
inventoryContents.forEach { shulkerBoxState.inventory.addItem(it) }
|
||||
blockStateMeta.blockState = shulkerBoxState
|
||||
shulkerBox.itemMeta = blockStateMeta
|
||||
|
||||
val inventory = mockPlayerInventory(player, playerUuid, arrayOf(shulkerBox))
|
||||
|
||||
val compress = Compress(config, balanceManager, mm, scope)
|
||||
compress.onCommand(player, command, "compress", arrayOf("yes"))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
buildList {
|
||||
add { verify { player.sendMessage(Component.text("DiamondBank-OG<reset>: $compressionSummary")) } }
|
||||
add { assertEquals(invShardCount, shulkerBoxState.inventory.countShards(), "Shard count") }
|
||||
add { assertEquals(invDiamondCount, shulkerBoxState.inventory.countDiamonds(), "Diamond count") }
|
||||
add {
|
||||
assertEquals(
|
||||
invDiamondBlockCount,
|
||||
shulkerBoxState.inventory.countDiamondBlocks(),
|
||||
"Diamond block count",
|
||||
)
|
||||
}
|
||||
add { assertFalse(inventory.isLocked(), "Inventory locked") }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@DisplayName("Overflow Compress")
|
||||
@MethodSource("overflowCompressData")
|
||||
fun overflowCompress(
|
||||
@Suppress("UNUSED_PARAMETER") name: String,
|
||||
inventoryContents: Array<ItemStack?>,
|
||||
error: String,
|
||||
invShardCount: Long,
|
||||
invDiamondCount: Long,
|
||||
invDiamondBlockCount: Long,
|
||||
) = runTest {
|
||||
val inventory = mockPlayerInventory(player, playerUuid, inventoryContents)
|
||||
|
||||
val compress = Compress(config, balanceManager, mm, scope)
|
||||
compress.onCommand(player, command, "compress", arrayOf())
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
buildList {
|
||||
add { verify { player.sendMessage(Component.text("DiamondBank-OG<reset>: <red>$error")) } }
|
||||
add { assertEquals(invShardCount, inventory.countShards(), "Shard count") }
|
||||
add { assertEquals(invDiamondCount, inventory.countDiamonds(), "Diamond count") }
|
||||
add { assertEquals(invDiamondBlockCount, inventory.countDiamondBlocks(), "Diamond block count") }
|
||||
add { assertFalse(inventory.isLocked(), "Inventory locked") }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Compress while in minigame should fail")
|
||||
fun compressWhileInMinigame() = runTest {
|
||||
every { player.world.name } returns "minigame_world"
|
||||
val inventory = mockPlayerInventory(player, playerUuid, arrayOf(ItemStack(Material.DIAMOND, 9)))
|
||||
|
||||
val compress = Compress(config, balanceManager, mm, scope)
|
||||
compress.onCommand(player, command, "compress", arrayOf("all"))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text("DiamondBank-OG<reset>: <red>You cannot use /compress when in a minigame.")
|
||||
)
|
||||
}
|
||||
},
|
||||
{ assertEquals(9, inventory.countDiamonds(), "Diamond count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Compress while locked should fail")
|
||||
fun compressWhileLocked() = runTest {
|
||||
val inventory = mockPlayerInventory(player, playerUuid, arrayOf(ItemStack(Material.DIAMOND, 9)))
|
||||
|
||||
val transactionLock = mockk<TransactionLock>()
|
||||
coEvery { transactionLock.tryWithLockSuspend<Unit>(any(), any()) } returns TransactionLock.LockResult.Failed
|
||||
|
||||
val compress = Compress(config, balanceManager, mm, scope, transactionLock)
|
||||
compress.onCommand(player, command, "compress", arrayOf("all"))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text("DiamondBank-OG<reset>: <red>You are currently blocked from using /compress.")
|
||||
)
|
||||
}
|
||||
},
|
||||
{ assertEquals(9, inventory.countDiamonds(), "Diamond count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Compress too many arguments should fail")
|
||||
fun compressTooManyArgs() = runTest {
|
||||
val inventory = mockPlayerInventory(player, playerUuid, arrayOf(ItemStack(Material.DIAMOND, 9)))
|
||||
|
||||
val compress = Compress(config, balanceManager, mm, scope)
|
||||
compress.onCommand(player, command, "compress", arrayOf("1", "2"))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text(
|
||||
"DiamondBank-OG<reset>: <red>Do not provide more arguments than \"yes\" if you want to compress the items in the shulker box you're holding."
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
{ assertEquals(9, inventory.countDiamonds(), "Diamond count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Compress with invalid argument should fail")
|
||||
fun compressInvalidArg() = runTest {
|
||||
val inventory = mockPlayerInventory(player, playerUuid, arrayOf(ItemStack(Material.DIAMOND, 9)))
|
||||
|
||||
val compress = Compress(config, balanceManager, mm, scope)
|
||||
compress.onCommand(player, command, "compress", arrayOf("invalid"))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{ verify { player.sendMessage(Component.text("DiamondBank-OG<reset>: <red>Invalid argument.")) } },
|
||||
{ assertEquals(9, inventory.countDiamonds(), "Diamond count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Compress with no permission should fail")
|
||||
fun compressNoPermission() = runTest {
|
||||
every { player.hasPermission("diamondbank-og.compress") } returns false
|
||||
val inventory = mockPlayerInventory(player, playerUuid, arrayOf(ItemStack(Material.DIAMOND, 9)))
|
||||
|
||||
val compress = Compress(config, balanceManager, mm, scope)
|
||||
compress.onCommand(player, command, "compress", arrayOf("1"))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text("DiamondBank-OG<reset>: <red>You do not have permission to use this command.")
|
||||
)
|
||||
}
|
||||
},
|
||||
{ assertEquals(9, inventory.countDiamonds(), "Diamond count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Compress with yes while not holding a shulker box should fail")
|
||||
fun compressYesNotHoldingShulkerBox() = runTest {
|
||||
val inventory = mockPlayerInventory(player, playerUuid, arrayOf(ItemStack(Material.DIAMOND, 9)))
|
||||
|
||||
val compress = Compress(config, balanceManager, mm, scope)
|
||||
compress.onCommand(player, command, "compress", arrayOf("yes"))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(Component.text("DiamondBank-OG<reset>: <red>You are not holding a shulker box."))
|
||||
}
|
||||
},
|
||||
{ assertEquals(9, inventory.countDiamonds(), "Diamond count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Compress without yes while holding a shulker box should fail")
|
||||
fun compressNoYesHoldingShulkerBox() = runTest {
|
||||
val inventory = mockPlayerInventory(player, playerUuid, arrayOf(ItemStack(Material.SHULKER_BOX)))
|
||||
|
||||
val compress = Compress(config, balanceManager, mm, scope)
|
||||
compress.onCommand(player, command, "compress", arrayOf())
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text(
|
||||
"DiamondBank-OG<reset>: <#FFA500>Are you sure you want to compress the Diamond currency items in the shulker box you're holding? If so, run \"/compress yes\""
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package net.trueog.diamondbankog
|
||||
|
||||
import java.util.*
|
||||
|
||||
object Constants {
|
||||
val playerUuid: UUID = UUID.fromString("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")
|
||||
val otherPlayerUuid: UUID = UUID.fromString("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb")
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
package net.trueog.diamondbankog
|
||||
|
||||
import io.mockk.*
|
||||
import io.mockk.impl.annotations.MockK
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import net.kyori.adventure.text.Component
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage
|
||||
import net.trueog.diamondbankog.Constants.playerUuid
|
||||
import net.trueog.diamondbankog.InventoryExtensions.countDiamondBlocks
|
||||
import net.trueog.diamondbankog.InventoryExtensions.countDiamonds
|
||||
import net.trueog.diamondbankog.InventoryExtensions.countShards
|
||||
import net.trueog.diamondbankog.InventoryExtensions.isLocked
|
||||
import net.trueog.diamondbankog.Utils.mockPlayerInventory
|
||||
import net.trueog.diamondbankog.Utils.waitForCoroutines
|
||||
import net.trueog.diamondbankog.commands.Deposit
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.Server
|
||||
import org.bukkit.command.Command
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import org.junit.jupiter.api.*
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.Arguments
|
||||
import org.junit.jupiter.params.provider.CsvSource
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
|
||||
class DepositTest {
|
||||
@MockK lateinit var config: Config
|
||||
|
||||
@MockK lateinit var balanceManager: BalanceManager
|
||||
|
||||
@MockK lateinit var mm: MiniMessage
|
||||
|
||||
@MockK lateinit var player: Player
|
||||
|
||||
@MockK lateinit var command: Command
|
||||
|
||||
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
MockKAnnotations.init(this)
|
||||
|
||||
every { config.prefix } returns "DiamondBank-OG"
|
||||
|
||||
val inputSlot = slot<String>()
|
||||
every { mm.deserialize(capture(inputSlot)) } answers { Component.text(inputSlot.captured) }
|
||||
|
||||
every { player.uniqueId } returns playerUuid
|
||||
every { player.world.name } returns "world"
|
||||
every { player.hasPermission("diamondbank-og.deposit") } returns true
|
||||
|
||||
every { Bukkit.getPlayer("player") } returns player
|
||||
every { server.getPlayer("player") } returns player
|
||||
every { Bukkit.getPlayer(playerUuid) } returns player
|
||||
every { server.getPlayer(playerUuid) } returns player
|
||||
|
||||
coEvery { balanceManager.addToBankShards(any(), any()) } returns Result.success(Unit)
|
||||
coEvery { balanceManager.setPlayerShards(any(), any(), PostgreSQL.ShardType.INVENTORY) } returns
|
||||
Result.success(Unit)
|
||||
coEvery { balanceManager.insertTransactionLog(any(), any(), any(), any(), any()) } returns Result.success(Unit)
|
||||
every { player.sendMessage(any<Component>()) } just Runs
|
||||
}
|
||||
|
||||
companion object {
|
||||
lateinit var server: Server
|
||||
|
||||
@JvmStatic
|
||||
@BeforeAll
|
||||
fun setupBukkit() {
|
||||
server = BukkitMock.mockBukkit()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun depositData() =
|
||||
listOf(
|
||||
Arguments.of(
|
||||
"1 Diamond",
|
||||
"1.0 <aqua>Diamond",
|
||||
arrayOf(ItemStack(Material.DIAMOND, 1)),
|
||||
"1",
|
||||
arrayOf<Long>(9),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
Arguments.of(
|
||||
"2 Diamonds",
|
||||
"2.0 <aqua>Diamonds",
|
||||
arrayOf(ItemStack(Material.DIAMOND, 3)),
|
||||
"2",
|
||||
arrayOf<Long>(2 * 9),
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
),
|
||||
Arguments.of(
|
||||
"2.2 Diamonds only diamonds",
|
||||
"2.2 <aqua>Diamonds",
|
||||
arrayOf(ItemStack(Material.DIAMOND, 3)),
|
||||
"2.2",
|
||||
arrayOf<Long>(20),
|
||||
7,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
Arguments.of(
|
||||
"2.2 Diamonds",
|
||||
"2.2 <aqua>Diamonds",
|
||||
arrayOf(Shard.createItemStack(2), ItemStack(Material.DIAMOND, 3)),
|
||||
"2.2",
|
||||
arrayOf<Long>(20),
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
),
|
||||
Arguments.of(
|
||||
"2.2 Diamonds with blocks",
|
||||
"2.2 <aqua>Diamonds",
|
||||
arrayOf(ItemStack(Material.DIAMOND_BLOCK, 1)),
|
||||
"2.2",
|
||||
arrayOf<Long>(20),
|
||||
7,
|
||||
6,
|
||||
0,
|
||||
),
|
||||
Arguments.of(
|
||||
"2.9 Diamonds",
|
||||
"3.0 <aqua>Diamonds",
|
||||
arrayOf(ItemStack(Material.DIAMOND, 4)),
|
||||
"2.9",
|
||||
arrayOf<Long>(27),
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
),
|
||||
Arguments.of(
|
||||
"all with 5 Diamonds",
|
||||
"5.0 <aqua>Diamonds",
|
||||
arrayOf(ItemStack(Material.DIAMOND, 5)),
|
||||
"all",
|
||||
arrayOf<Long>(45),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
Arguments.of(
|
||||
"all with 5 Diamonds and 8 shards",
|
||||
"5.8 <aqua>Diamonds",
|
||||
arrayOf(Shard.createItemStack(8), ItemStack(Material.DIAMOND, 5)),
|
||||
"all",
|
||||
arrayOf<Long>(53),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
Arguments.of(
|
||||
"1.8 Diamonds with overflow",
|
||||
"1.8 <aqua>Diamonds",
|
||||
Array(35) { ItemStack(Material.DIRT, 1) } + arrayOf(ItemStack(Material.DIAMOND, 3)),
|
||||
"1.8",
|
||||
arrayOf<Long>(17, 1),
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
),
|
||||
Arguments.of(
|
||||
"1.1 Diamonds with diamond block with overflow",
|
||||
"1.1 <aqua>Diamonds",
|
||||
Array(35) { ItemStack(Material.DIRT, 1) } + arrayOf(ItemStack(Material.DIAMOND_BLOCK, 2)),
|
||||
"1.1",
|
||||
arrayOf<Long>(10, 63, 8),
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@DisplayName("Successful Deposit")
|
||||
@MethodSource("depositData")
|
||||
fun successfulDeposit(
|
||||
@Suppress("UNUSED_PARAMETER") name: String,
|
||||
successDiamondText: String,
|
||||
inventoryContents: Array<ItemStack?>,
|
||||
commandArg: String,
|
||||
addedShards: Array<Long>,
|
||||
invShardCount: Long,
|
||||
invDiamondCount: Long,
|
||||
invDiamondBlockCount: Long,
|
||||
) = runTest {
|
||||
val inventory = mockPlayerInventory(player, playerUuid, inventoryContents)
|
||||
|
||||
val deposit = Deposit(config, balanceManager, mm, scope)
|
||||
deposit.onCommand(player, command, "deposit", arrayOf(commandArg))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
buildList {
|
||||
add {
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text(
|
||||
"DiamondBank-OG<reset>: <green>Successfully deposited <yellow>$successDiamondText <green>into your bank account."
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
addedShards.forEach { add { coVerify { balanceManager.addToBankShards(playerUuid, it) } } }
|
||||
add { assertEquals(invShardCount, inventory.countShards(), "Shard count") }
|
||||
add { assertEquals(invDiamondCount, inventory.countDiamonds(), "Diamond count") }
|
||||
add { assertEquals(invDiamondBlockCount, inventory.countDiamondBlocks(), "Diamond block count") }
|
||||
add { assertFalse(inventory.isLocked(), "Inventory locked") }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Deposit 2 Diamonds while only having 1")
|
||||
fun depositWholeDiamondsWhileShort() = runTest {
|
||||
val inventory = mockPlayerInventory(player, playerUuid, arrayOf(ItemStack(Material.DIAMOND, 1)))
|
||||
|
||||
val deposit = Deposit(config, balanceManager, mm, scope)
|
||||
deposit.onCommand(player, command, "deposit", arrayOf("2"))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text(
|
||||
"DiamondBank-OG<reset>: <red>You do not have <yellow>2.0 <aqua>Diamonds <red>to deposit."
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
{ coVerify(exactly = 0) { balanceManager.addToBankShards(any(), any()) } },
|
||||
{ assertEquals(1, inventory.countDiamonds(), "Diamond count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Deposit while in minigame should fail")
|
||||
fun depositWhileInMinigame() = runTest {
|
||||
every { player.world.name } returns "minigame_world"
|
||||
val inventory = mockPlayerInventory(player, playerUuid, arrayOf(ItemStack(Material.DIAMOND, 5)))
|
||||
|
||||
val deposit = Deposit(config, balanceManager, mm, scope)
|
||||
deposit.onCommand(player, command, "deposit", arrayOf("all"))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text("DiamondBank-OG<reset>: <red>You cannot use /deposit when in a minigame.")
|
||||
)
|
||||
}
|
||||
},
|
||||
{ coVerify(exactly = 0) { balanceManager.addToBankShards(any(), any()) } },
|
||||
{ assertEquals(5, inventory.countDiamonds(), "Diamond count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Deposit while locked should fail")
|
||||
fun depositWhileLocked() = runTest {
|
||||
val inventory = mockPlayerInventory(player, playerUuid, arrayOf(ItemStack(Material.DIAMOND, 5)))
|
||||
|
||||
val transactionLock = mockk<TransactionLock>()
|
||||
coEvery { transactionLock.tryWithLockSuspend<Unit>(any(), any()) } returns TransactionLock.LockResult.Failed
|
||||
|
||||
val deposit = Deposit(config, balanceManager, mm, scope, transactionLock)
|
||||
deposit.onCommand(player, command, "deposit", arrayOf("all"))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text("DiamondBank-OG<reset>: <red>You are currently blocked from using /deposit.")
|
||||
)
|
||||
}
|
||||
},
|
||||
{ coVerify(exactly = 0) { balanceManager.addToBankShards(any(), any()) } },
|
||||
{ assertEquals(5, inventory.countDiamonds(), "Diamond count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Deposit no arguments should fail")
|
||||
fun depositNoArgs() = runTest {
|
||||
val inventory = mockPlayerInventory(player, playerUuid, arrayOf(ItemStack(Material.DIAMOND, 5)))
|
||||
|
||||
val deposit = Deposit(config, balanceManager, mm, scope)
|
||||
deposit.onCommand(player, command, "deposit", arrayOf())
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text(
|
||||
"DiamondBank-OG<reset>: <red>You did not provide the amount of <aqua>Diamonds <red>that you want to deposit."
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
{ coVerify(exactly = 0) { balanceManager.addToBankShards(any(), any()) } },
|
||||
{ assertEquals(5, inventory.countDiamonds(), "Diamond count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Deposit too many arguments should fail")
|
||||
fun depositTooManyArgs() = runTest {
|
||||
val inventory = mockPlayerInventory(player, playerUuid, arrayOf(ItemStack(Material.DIAMOND, 5)))
|
||||
|
||||
val deposit = Deposit(config, balanceManager, mm, scope)
|
||||
deposit.onCommand(player, command, "deposit", arrayOf("1", "2"))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text(
|
||||
"DiamondBank-OG<reset>: <red>Please (only) provide the amount of <aqua>Diamonds <red>you want to deposit. Either a number or \"all\"."
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
{ coVerify(exactly = 0) { balanceManager.addToBankShards(any(), any()) } },
|
||||
{ assertEquals(5, inventory.countDiamonds(), "Diamond count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@DisplayName("Deposit with invalid argument should fail")
|
||||
@CsvSource(
|
||||
"Not a number, abc, Invalid argument",
|
||||
"Negative, -1, You cannot deposit a negative or zero amount",
|
||||
"Zero, 0, You cannot deposit a negative or zero amount",
|
||||
"More than one decimal digit, 1.11, <aqua>Diamonds<red> can only have one decimal digit. Issue /diamondbankhelp for more information",
|
||||
)
|
||||
fun depositInvalidArg(@Suppress("UNUSED_PARAMETER") name: String, argument: String, errorMessage: String) =
|
||||
runTest {
|
||||
val inventory = mockPlayerInventory(player, playerUuid, arrayOf(ItemStack(Material.DIAMOND, 5)))
|
||||
|
||||
val deposit = Deposit(config, balanceManager, mm, scope)
|
||||
deposit.onCommand(player, command, "deposit", arrayOf(argument))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{ verify { player.sendMessage(Component.text("DiamondBank-OG<reset>: <red>$errorMessage.")) } },
|
||||
{ coVerify(exactly = 0) { balanceManager.addToBankShards(any(), any()) } },
|
||||
{ assertEquals(5, inventory.countDiamonds(), "Diamond count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Deposit with no permission should fail")
|
||||
fun depositNoPermission() = runTest {
|
||||
every { player.hasPermission("diamondbank-og.deposit") } returns false
|
||||
val inventory = mockPlayerInventory(player, playerUuid, arrayOf(ItemStack(Material.DIAMOND, 5)))
|
||||
|
||||
val deposit = Deposit(config, balanceManager, mm, scope)
|
||||
deposit.onCommand(player, command, "deposit", arrayOf("1"))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text("DiamondBank-OG<reset>: <red>You do not have permission to use this command.")
|
||||
)
|
||||
}
|
||||
},
|
||||
{ coVerify(exactly = 0) { balanceManager.addToBankShards(any(), any()) } },
|
||||
{ assertEquals(5, inventory.countDiamonds(), "Diamond count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
package net.trueog.diamondbankog
|
||||
|
||||
import io.mockk.*
|
||||
import io.mockk.impl.annotations.MockK
|
||||
import java.util.*
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import net.kyori.adventure.text.Component
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage
|
||||
import net.luckperms.api.LuckPerms
|
||||
import net.luckperms.api.model.user.User
|
||||
import net.luckperms.api.node.Node
|
||||
import net.trueog.diamondbankog.Constants.otherPlayerUuid
|
||||
import net.trueog.diamondbankog.Constants.playerUuid
|
||||
import net.trueog.diamondbankog.InventoryExtensions.countDiamondBlocks
|
||||
import net.trueog.diamondbankog.InventoryExtensions.countDiamonds
|
||||
import net.trueog.diamondbankog.InventoryExtensions.countShards
|
||||
import net.trueog.diamondbankog.InventoryExtensions.countTotal
|
||||
import net.trueog.diamondbankog.InventoryExtensions.isLocked
|
||||
import net.trueog.diamondbankog.Utils.mockPlayerInventory
|
||||
import net.trueog.diamondbankog.Utils.waitForCoroutines
|
||||
import net.trueog.diamondbankog.commands.Pay
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.Server
|
||||
import org.bukkit.command.Command
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import org.junit.jupiter.api.*
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.Arguments
|
||||
import org.junit.jupiter.params.provider.CsvSource
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
|
||||
class PayTest {
|
||||
@MockK lateinit var config: Config
|
||||
|
||||
@MockK lateinit var balanceManager: BalanceManager
|
||||
|
||||
@MockK lateinit var mm: MiniMessage
|
||||
|
||||
@MockK lateinit var player: Player
|
||||
|
||||
@MockK lateinit var otherPlayer: Player
|
||||
|
||||
@MockK lateinit var command: Command
|
||||
|
||||
@MockK lateinit var luckPerms: LuckPerms
|
||||
|
||||
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
MockKAnnotations.init(this)
|
||||
|
||||
every { config.prefix } returns "DiamondBank-OG"
|
||||
|
||||
val inputSlot = slot<String>()
|
||||
every { mm.deserialize(capture(inputSlot)) } answers { Component.text(inputSlot.captured) }
|
||||
|
||||
every { player.uniqueId } returns playerUuid
|
||||
every { player.name } returns "Player"
|
||||
every { player.world.name } returns "world"
|
||||
every { player.hasPermission("diamondbank-og.pay") } returns true
|
||||
every { otherPlayer.uniqueId } returns otherPlayerUuid
|
||||
every { otherPlayer.name } returns "OtherPlayer"
|
||||
every { otherPlayer.isOnline } returns true
|
||||
every { otherPlayer.player } returns otherPlayer
|
||||
|
||||
every { Bukkit.getPlayer("player") } returns player
|
||||
every { server.getPlayer("player") } returns player
|
||||
every { Bukkit.getPlayer(playerUuid) } returns player
|
||||
every { server.getPlayer(playerUuid) } returns player
|
||||
|
||||
every { Bukkit.getPlayer("otherplayer") } returns otherPlayer
|
||||
every { server.getPlayer("otherplayer") } returns otherPlayer
|
||||
every { Bukkit.getPlayer(otherPlayerUuid) } returns otherPlayer
|
||||
every { server.getPlayer(otherPlayerUuid) } returns otherPlayer
|
||||
|
||||
every { otherPlayer.hasPlayedBefore() } returns true
|
||||
coEvery { balanceManager.addToBankShards(any(), any()) } returns Result.success(Unit)
|
||||
coEvery { balanceManager.setPlayerShards(any(), any(), PostgreSQL.ShardType.INVENTORY) } returns
|
||||
Result.success(Unit)
|
||||
coEvery { balanceManager.subtractFromBankShards(any(), any()) } returns Result.success(Unit)
|
||||
coEvery { balanceManager.insertTransactionLog(any(), any(), any(), any(), any()) } returns Result.success(Unit)
|
||||
|
||||
every { player.sendMessage(any<Component>()) } just Runs
|
||||
every { otherPlayer.sendMessage(any<Component>()) } just Runs
|
||||
}
|
||||
|
||||
companion object {
|
||||
lateinit var server: Server
|
||||
|
||||
@JvmStatic
|
||||
@BeforeAll
|
||||
fun setupBukkit() {
|
||||
server = BukkitMock.mockBukkit()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun payData() =
|
||||
listOf(
|
||||
Arguments.of(
|
||||
"1 Diamond from inventory",
|
||||
"1.0 <aqua>Diamond",
|
||||
arrayOf(ItemStack(Material.DIAMOND, 1)),
|
||||
0,
|
||||
-1,
|
||||
"1",
|
||||
arrayOf<Long>(),
|
||||
arrayOf<Long>(9),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
Arguments.of(
|
||||
"2 Diamonds from inventory",
|
||||
"2.0 <aqua>Diamonds",
|
||||
arrayOf(ItemStack(Material.DIAMOND, 3)),
|
||||
0,
|
||||
-1,
|
||||
"2",
|
||||
arrayOf<Long>(),
|
||||
arrayOf<Long>(2 * 9),
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
),
|
||||
Arguments.of(
|
||||
"2.2 Diamonds from inventory only diamonds",
|
||||
"2.2 <aqua>Diamonds",
|
||||
arrayOf(ItemStack(Material.DIAMOND, 3)),
|
||||
0,
|
||||
-1,
|
||||
"2.2",
|
||||
arrayOf<Long>(),
|
||||
arrayOf<Long>(20),
|
||||
7,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
Arguments.of(
|
||||
"2.2 Diamonds from inventory",
|
||||
"2.2 <aqua>Diamonds",
|
||||
arrayOf(Shard.createItemStack(2), ItemStack(Material.DIAMOND, 3)),
|
||||
0,
|
||||
-1,
|
||||
"2.2",
|
||||
arrayOf<Long>(),
|
||||
arrayOf<Long>(20),
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
),
|
||||
Arguments.of(
|
||||
"2.2 Diamonds from inventory with blocks",
|
||||
"2.2 <aqua>Diamonds",
|
||||
arrayOf(ItemStack(Material.DIAMOND_BLOCK, 1)),
|
||||
0,
|
||||
-1,
|
||||
"2.2",
|
||||
arrayOf<Long>(),
|
||||
arrayOf<Long>(20),
|
||||
7,
|
||||
6,
|
||||
0,
|
||||
),
|
||||
Arguments.of(
|
||||
"2.9 Diamonds from inventory",
|
||||
"3.0 <aqua>Diamonds",
|
||||
arrayOf(ItemStack(Material.DIAMOND, 4)),
|
||||
0,
|
||||
-1,
|
||||
"2.9",
|
||||
arrayOf<Long>(),
|
||||
arrayOf<Long>(27),
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
),
|
||||
Arguments.of(
|
||||
"1.8 Diamonds from inventory with overflow",
|
||||
"1.8 <aqua>Diamonds",
|
||||
Array(35) { ItemStack(Material.DIRT, 1) } + arrayOf(ItemStack(Material.DIAMOND, 3)),
|
||||
0,
|
||||
-1,
|
||||
"1.8",
|
||||
arrayOf<Long>(1),
|
||||
arrayOf<Long>(17),
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
),
|
||||
Arguments.of(
|
||||
"1.1 Diamonds from inventory with diamond block with overflow",
|
||||
"1.1 <aqua>Diamonds",
|
||||
Array(35) { ItemStack(Material.DIRT, 1) } + arrayOf(ItemStack(Material.DIAMOND_BLOCK, 2)),
|
||||
0,
|
||||
-1,
|
||||
"1.1",
|
||||
arrayOf<Long>(63, 8),
|
||||
arrayOf<Long>(10),
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
),
|
||||
Arguments.of(
|
||||
"1 Diamond: 0.5 Diamonds from bank and 1 Diamond in inventory",
|
||||
"1.0 <aqua>Diamond",
|
||||
arrayOf(ItemStack(Material.DIAMOND, 1)),
|
||||
5,
|
||||
5,
|
||||
"1",
|
||||
arrayOf<Long>(),
|
||||
arrayOf<Long>(9),
|
||||
5,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
Arguments.of(
|
||||
"5 Diamonds from bank",
|
||||
"5.0 <aqua>Diamonds",
|
||||
arrayOf<ItemStack>(),
|
||||
45,
|
||||
45,
|
||||
"5",
|
||||
arrayOf<Long>(),
|
||||
arrayOf<Long>(45),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@DisplayName("Successful Pay")
|
||||
@MethodSource("payData")
|
||||
fun successfulPay(
|
||||
@Suppress("UNUSED_PARAMETER") name: String,
|
||||
successDiamondText: String,
|
||||
inventoryContents: Array<ItemStack?>,
|
||||
bankShards: Long,
|
||||
bankShardsRemoved: Long,
|
||||
commandArg: String,
|
||||
addedShardsPlayer: Array<Long>,
|
||||
addedShardsOtherPlayer: Array<Long>,
|
||||
invShardCount: Long,
|
||||
invDiamondCount: Long,
|
||||
invDiamondBlockCount: Long,
|
||||
) = runTest {
|
||||
val inventory = mockPlayerInventory(player, playerUuid, inventoryContents)
|
||||
|
||||
coEvery { balanceManager.getBankShards(playerUuid) } returns Result.success(bankShards)
|
||||
|
||||
val user = mockk<User>()
|
||||
every { user.nodes } returns mutableListOf<Node>()
|
||||
every { luckPerms.userManager.getUser(any<UUID>()) } returns user
|
||||
|
||||
val pay = Pay(config, balanceManager, mm, scope, luckPerms)
|
||||
pay.onCommand(player, command, "pay", arrayOf("otherplayer", commandArg))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
buildList {
|
||||
add {
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text(
|
||||
"DiamondBank-OG<reset>: <green>Successfully paid <yellow>$successDiamondText <green>to OtherPlayer<reset><green>."
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
add {
|
||||
verify {
|
||||
otherPlayer.sendMessage(
|
||||
Component.text(
|
||||
"DiamondBank-OG<reset>: <green> Player<reset> <green>has paid you <yellow>$successDiamondText<green>."
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
add {
|
||||
if (bankShardsRemoved != -1L)
|
||||
coVerify { balanceManager.subtractFromBankShards(playerUuid, bankShardsRemoved) }
|
||||
}
|
||||
addedShardsPlayer.forEach { add { coVerify { balanceManager.addToBankShards(playerUuid, it) } } }
|
||||
addedShardsOtherPlayer.forEach {
|
||||
add { coVerify { balanceManager.addToBankShards(otherPlayerUuid, it) } }
|
||||
}
|
||||
add { assertEquals(invShardCount, inventory.countShards(), "Shard count") }
|
||||
add { assertEquals(invDiamondCount, inventory.countDiamonds(), "Diamond count") }
|
||||
add { assertEquals(invDiamondBlockCount, inventory.countDiamondBlocks(), "Diamond block count") }
|
||||
add { assertFalse(inventory.isLocked(), "Inventory locked") }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Pay while in minigame should fail")
|
||||
fun payWhileInMinigame() = runTest {
|
||||
every { player.world.name } returns "minigame_world"
|
||||
val inventory = mockPlayerInventory(player, playerUuid, emptyArray())
|
||||
|
||||
val pay = Pay(config, balanceManager, mm, scope, luckPerms)
|
||||
pay.onCommand(player, command, "pay", arrayOf("otherplayer", "1"))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text("DiamondBank-OG<reset>: <red>You cannot use /pay when in a minigame.")
|
||||
)
|
||||
}
|
||||
},
|
||||
{ coVerify(exactly = 0) { balanceManager.subtractFromBankShards(any(), any()) } },
|
||||
{ assertEquals(0, inventory.countTotal(), "Total count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Pay while locked should fail")
|
||||
fun payWhileLocked() = runTest {
|
||||
val inventory = mockPlayerInventory(player, playerUuid, emptyArray())
|
||||
|
||||
val transactionLock = mockk<TransactionLock>()
|
||||
coEvery { transactionLock.tryWithLockSuspend<Unit>(any(), any()) } returns TransactionLock.LockResult.Failed
|
||||
|
||||
val pay = Pay(config, balanceManager, mm, scope, luckPerms, transactionLock)
|
||||
pay.onCommand(player, command, "pay", arrayOf("otherplayer", "1"))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text("DiamondBank-OG<reset>: <red>You are currently blocked from using /pay.")
|
||||
)
|
||||
}
|
||||
},
|
||||
{ coVerify(exactly = 0) { balanceManager.subtractFromBankShards(any(), any()) } },
|
||||
{ assertEquals(0, inventory.countTotal(), "Total count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Pay no arguments should fail")
|
||||
fun payNoArgs() = runTest {
|
||||
val inventory = mockPlayerInventory(player, playerUuid, emptyArray())
|
||||
|
||||
val pay = Pay(config, balanceManager, mm, scope, luckPerms)
|
||||
pay.onCommand(player, command, "pay", arrayOf())
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text(
|
||||
"DiamondBank-OG<reset>: <red>You did not provide the name or the UUID of a player and the amount of <aqua>Diamonds<red>."
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
{ coVerify(exactly = 0) { balanceManager.subtractFromBankShards(any(), any()) } },
|
||||
{ assertEquals(0, inventory.countTotal(), "Total count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Pay too few arguments should fail")
|
||||
fun payTooFewArgs() = runTest {
|
||||
val inventory = mockPlayerInventory(player, playerUuid, emptyArray())
|
||||
|
||||
val pay = Pay(config, balanceManager, mm, scope, luckPerms)
|
||||
pay.onCommand(player, command, "pay", arrayOf("1"))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text(
|
||||
"DiamondBank-OG<reset>: <red>Please (only) provide the name or the UUID of a player and the amount of <aqua>Diamonds<red>."
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
{ coVerify(exactly = 0) { balanceManager.subtractFromBankShards(any(), any()) } },
|
||||
{ assertEquals(0, inventory.countTotal(), "Total count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Pay too many arguments should fail")
|
||||
fun payTooManyArgs() = runTest {
|
||||
val inventory = mockPlayerInventory(player, playerUuid, emptyArray())
|
||||
|
||||
val pay = Pay(config, balanceManager, mm, scope, luckPerms)
|
||||
pay.onCommand(player, command, "pay", arrayOf("1", "2", "3"))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text(
|
||||
"DiamondBank-OG<reset>: <red>Please (only) provide the name or the UUID of a player and the amount of <aqua>Diamonds<red>."
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
{ coVerify(exactly = 0) { balanceManager.subtractFromBankShards(any(), any()) } },
|
||||
{ assertEquals(0, inventory.countTotal(), "Total count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@DisplayName("Pay with invalid argument should fail")
|
||||
@CsvSource(
|
||||
"Not a number, abc, Invalid argument",
|
||||
"Negative, -1, You cannot pay a negative or zero amount",
|
||||
"Zero, 0, You cannot pay a negative or zero amount",
|
||||
"More than one decimal digit, 1.11, <aqua>Diamonds<red> can only have one decimal digit. Issue /diamondbankhelp for more information",
|
||||
)
|
||||
fun payInvalidArg(@Suppress("UNUSED_PARAMETER") name: String, argument: String, errorMessage: String) = runTest {
|
||||
val inventory = mockPlayerInventory(player, playerUuid, emptyArray())
|
||||
|
||||
val pay = Pay(config, balanceManager, mm, scope, luckPerms)
|
||||
pay.onCommand(player, command, "pay", arrayOf("otherplayer", argument))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{ verify { player.sendMessage(Component.text("DiamondBank-OG<reset>: <red>$errorMessage.")) } },
|
||||
{ coVerify(exactly = 0) { balanceManager.subtractFromBankShards(any(), any()) } },
|
||||
{ assertEquals(0, inventory.countTotal(), "Total count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Pay with no permission should fail")
|
||||
fun payNoPermission() = runTest {
|
||||
every { player.hasPermission("diamondbank-og.pay") } returns false
|
||||
val inventory = mockPlayerInventory(player, playerUuid, emptyArray())
|
||||
|
||||
val pay = Pay(config, balanceManager, mm, scope, luckPerms)
|
||||
pay.onCommand(player, command, "pay", arrayOf("1"))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text("DiamondBank-OG<reset>: <red>You do not have permission to use this command.")
|
||||
)
|
||||
}
|
||||
},
|
||||
{ coVerify(exactly = 0) { balanceManager.subtractFromBankShards(any(), any()) } },
|
||||
{ assertEquals(0, inventory.countTotal(), "Total count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package net.trueog.diamondbankog
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import java.util.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import org.bukkit.inventory.PlayerInventory
|
||||
|
||||
object Utils {
|
||||
suspend fun waitForCoroutines(scope: CoroutineScope) {
|
||||
scope.coroutineContext[Job]?.children?.forEach { it.join() }
|
||||
}
|
||||
|
||||
fun allImpl(contents: Array<ItemStack?>, material: Material): HashMap<Int, out ItemStack> =
|
||||
HashMap(
|
||||
contents.mapIndexedNotNull { index, item -> if (item?.type == material) index to item else null }.toMap()
|
||||
)
|
||||
|
||||
fun mockPlayerInventory(player: Player, uuid: UUID, contents: Array<ItemStack?>): PlayerInventory {
|
||||
val playerInventory = mockk<PlayerInventory>()
|
||||
var contents = contents.copyOf(36)
|
||||
every { playerInventory.contents } answers { contents }
|
||||
every { playerInventory.storageContents = any() } answers { contents = firstArg() }
|
||||
every { playerInventory.all(any<Material>()) } answers
|
||||
{
|
||||
val material = firstArg<Material>()
|
||||
allImpl(contents, material)
|
||||
}
|
||||
every { playerInventory.heldItemSlot } returns 0
|
||||
every { playerInventory.holder!!.uniqueId } returns uuid
|
||||
every { player.inventory } returns playerInventory
|
||||
return playerInventory
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
package net.trueog.diamondbankog
|
||||
|
||||
import io.mockk.*
|
||||
import io.mockk.impl.annotations.MockK
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import net.kyori.adventure.text.Component
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage
|
||||
import net.trueog.diamondbankog.Constants.playerUuid
|
||||
import net.trueog.diamondbankog.InventoryExtensions.countDiamonds
|
||||
import net.trueog.diamondbankog.InventoryExtensions.countShards
|
||||
import net.trueog.diamondbankog.InventoryExtensions.countTotal
|
||||
import net.trueog.diamondbankog.InventoryExtensions.isLocked
|
||||
import net.trueog.diamondbankog.Utils.mockPlayerInventory
|
||||
import net.trueog.diamondbankog.Utils.waitForCoroutines
|
||||
import net.trueog.diamondbankog.commands.Withdraw
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.Server
|
||||
import org.bukkit.command.Command
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import org.junit.jupiter.api.*
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.CsvSource
|
||||
|
||||
class WithdrawTest {
|
||||
@MockK lateinit var config: Config
|
||||
|
||||
@MockK lateinit var balanceManager: BalanceManager
|
||||
|
||||
@MockK lateinit var mm: MiniMessage
|
||||
|
||||
@MockK lateinit var player: Player
|
||||
|
||||
@MockK lateinit var command: Command
|
||||
|
||||
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
MockKAnnotations.init(this)
|
||||
|
||||
every { config.prefix } returns "DiamondBank-OG"
|
||||
|
||||
val inputSlot = slot<String>()
|
||||
every { mm.deserialize(capture(inputSlot)) } answers { Component.text(inputSlot.captured) }
|
||||
|
||||
every { player.uniqueId } returns playerUuid
|
||||
every { player.world.name } returns "world"
|
||||
every { player.hasPermission("diamondbank-og.withdraw") } returns true
|
||||
|
||||
every { Bukkit.getPlayer("player") } returns player
|
||||
every { server.getPlayer("player") } returns player
|
||||
|
||||
coEvery { balanceManager.addToBankShards(any(), any()) } returns Result.success(Unit)
|
||||
coEvery { balanceManager.setPlayerShards(any(), any(), PostgreSQL.ShardType.INVENTORY) } returns
|
||||
Result.success(Unit)
|
||||
coEvery { balanceManager.subtractFromBankShards(any(), any()) } returns Result.success(Unit)
|
||||
coEvery { balanceManager.insertTransactionLog(any(), any(), any(), any(), any()) } returns Result.success(Unit)
|
||||
every { player.sendMessage(any<Component>()) } just Runs
|
||||
}
|
||||
|
||||
companion object {
|
||||
lateinit var server: Server
|
||||
|
||||
@JvmStatic
|
||||
@BeforeAll
|
||||
fun setupBukkit() {
|
||||
server = BukkitMock.mockBukkit()
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@DisplayName("Successful withdraw")
|
||||
@CsvSource(
|
||||
"2 Diamonds, 2.0 <aqua>Diamonds, 2, 0, 2, 18",
|
||||
"2.2 Diamonds, 2.2 <aqua>Diamonds, 2.2, 2, 2, 20",
|
||||
"2.9 Diamonds, 3.0 <aqua>Diamonds, 2.9, 0, 3, 27",
|
||||
"All, 11.1 <aqua>Diamonds, all, 1, 11, 100",
|
||||
)
|
||||
fun successfulWithdraw(
|
||||
@Suppress("UNUSED_PARAMETER") name: String,
|
||||
successDiamondText: String,
|
||||
commandArg: String,
|
||||
invShardsCount: Long,
|
||||
invDiamondsCount: Long,
|
||||
removedShardCount: Long,
|
||||
) = runTest {
|
||||
coEvery { balanceManager.getBankShards(playerUuid) } returns Result.success(100)
|
||||
val inventory = mockPlayerInventory(player, playerUuid, emptyArray())
|
||||
|
||||
val withdraw = Withdraw(config, balanceManager, mm, scope)
|
||||
withdraw.onCommand(player, command, "withdraw", arrayOf(commandArg))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text(
|
||||
"DiamondBank-OG<reset>: <green>Successfully withdrew <yellow>$successDiamondText <green>from your bank account."
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
{ coVerify { balanceManager.subtractFromBankShards(playerUuid, removedShardCount) } },
|
||||
{ assertEquals(invShardsCount, inventory.countShards(), "Shard count") },
|
||||
{ assertEquals(invDiamondsCount, inventory.countDiamonds(), "Diamond count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Withdraw while in minigame should fail")
|
||||
fun withdrawWhileInMinigame() = runTest {
|
||||
every { player.world.name } returns "minigame_world"
|
||||
val inventory = mockPlayerInventory(player, playerUuid, emptyArray())
|
||||
|
||||
val withdraw = Withdraw(config, balanceManager, mm, scope)
|
||||
withdraw.onCommand(player, command, "withdraw", arrayOf("all"))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text("DiamondBank-OG<reset>: <red>You cannot use /withdraw when in a minigame.")
|
||||
)
|
||||
}
|
||||
},
|
||||
{ coVerify(exactly = 0) { balanceManager.subtractFromBankShards(any(), any()) } },
|
||||
{ assertEquals(0, inventory.countTotal(), "Total count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Withdraw while locked should fail")
|
||||
fun withdrawWhileLocked() = runTest {
|
||||
val inventory = mockPlayerInventory(player, playerUuid, emptyArray())
|
||||
|
||||
val transactionLock = mockk<TransactionLock>()
|
||||
coEvery { transactionLock.tryWithLockSuspend<Unit>(any(), any()) } returns TransactionLock.LockResult.Failed
|
||||
|
||||
val withdraw = Withdraw(config, balanceManager, mm, scope, transactionLock)
|
||||
withdraw.onCommand(player, command, "withdraw", arrayOf("all"))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text("DiamondBank-OG<reset>: <red>You are currently blocked from using /withdraw.")
|
||||
)
|
||||
}
|
||||
},
|
||||
{ coVerify(exactly = 0) { balanceManager.subtractFromBankShards(any(), any()) } },
|
||||
{ assertEquals(0, inventory.countTotal(), "Total count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Withdraw no arguments should fail")
|
||||
fun withdrawNoArgs() = runTest {
|
||||
val inventory = mockPlayerInventory(player, playerUuid, emptyArray())
|
||||
|
||||
val withdraw = Withdraw(config, balanceManager, mm, scope)
|
||||
withdraw.onCommand(player, command, "withdraw", arrayOf())
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text(
|
||||
"DiamondBank-OG<reset>: <red>You did not provide the amount of <aqua>Diamonds <red>that you want to withdraw."
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
{ coVerify(exactly = 0) { balanceManager.subtractFromBankShards(any(), any()) } },
|
||||
{ assertEquals(0, inventory.countTotal(), "Total count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Withdraw too many arguments should fail")
|
||||
fun withdrawTooManyArgs() = runTest {
|
||||
val inventory = mockPlayerInventory(player, playerUuid, emptyArray())
|
||||
|
||||
val withdraw = Withdraw(config, balanceManager, mm, scope)
|
||||
withdraw.onCommand(player, command, "withdraw", arrayOf("1", "2"))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text(
|
||||
"DiamondBank-OG<reset>: <red>Please (only) provide the amount of <aqua>Diamonds <red>you want to withdraw. Either a number or \"all\"."
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
{ coVerify(exactly = 0) { balanceManager.subtractFromBankShards(any(), any()) } },
|
||||
{ assertEquals(0, inventory.countTotal(), "Total count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@DisplayName("Withdraw with invalid argument should fail")
|
||||
@CsvSource(
|
||||
"Not a number, abc, Invalid argument",
|
||||
"Negative, -1, You cannot withdraw a negative or zero amount",
|
||||
"Zero, 0, You cannot withdraw a negative or zero amount",
|
||||
"More than one decimal digit, 1.11, <aqua>Diamonds<red> can only have one decimal digit. Issue /diamondbankhelp for more information",
|
||||
)
|
||||
fun withdrawInvalidArg(@Suppress("UNUSED_PARAMETER") name: String, argument: String, errorMessage: String) =
|
||||
runTest {
|
||||
val inventory = mockPlayerInventory(player, playerUuid, emptyArray())
|
||||
|
||||
val withdraw = Withdraw(config, balanceManager, mm, scope)
|
||||
withdraw.onCommand(player, command, "withdraw", arrayOf(argument))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{ verify { player.sendMessage(Component.text("DiamondBank-OG<reset>: <red>$errorMessage.")) } },
|
||||
{ coVerify(exactly = 0) { balanceManager.subtractFromBankShards(any(), any()) } },
|
||||
{ assertEquals(0, inventory.countTotal(), "Total count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Withdraw with no permission should fail")
|
||||
fun withdrawNoPermission() = runTest {
|
||||
every { player.hasPermission("diamondbank-og.withdraw") } returns false
|
||||
val inventory = mockPlayerInventory(player, playerUuid, emptyArray())
|
||||
|
||||
val withdraw = Withdraw(config, balanceManager, mm, scope)
|
||||
withdraw.onCommand(player, command, "withdraw", arrayOf("1"))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text("DiamondBank-OG<reset>: <red>You do not have permission to use this command.")
|
||||
)
|
||||
}
|
||||
},
|
||||
{ coVerify(exactly = 0) { balanceManager.subtractFromBankShards(any(), any()) } },
|
||||
{ assertEquals(0, inventory.countTotal(), "Total count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Withdraw when not enough Diamonds in bank should fail")
|
||||
fun withdrawNotEnoughDiamondsInBank() = runTest {
|
||||
coEvery { balanceManager.getBankShards(playerUuid) } returns Result.success(18)
|
||||
val inventory = mockPlayerInventory(player, playerUuid, emptyArray())
|
||||
|
||||
val withdraw = Withdraw(config, balanceManager, mm, scope)
|
||||
withdraw.onCommand(player, command, "withdraw", arrayOf("5"))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text(
|
||||
"DiamondBank-OG<reset>: <red>Cannot withdraw <yellow>5.0 <aqua>Diamonds <red>because your bank only contains <yellow><yellow>2.0 <aqua>Diamonds<red>."
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
{ coVerify(exactly = 0) { balanceManager.subtractFromBankShards(any(), any()) } },
|
||||
{ assertEquals(0, inventory.countTotal(), "Total count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Withdraw when not enough inventory space should fail")
|
||||
fun withdrawNotEnoughInventorySpace() = runTest {
|
||||
coEvery { balanceManager.getBankShards(playerUuid) } returns Result.success(18)
|
||||
val inventory = mockPlayerInventory(player, playerUuid, Array(36) { ItemStack(Material.DIRT, 1) })
|
||||
|
||||
val withdraw = Withdraw(config, balanceManager, mm, scope)
|
||||
withdraw.onCommand(player, command, "withdraw", arrayOf("2"))
|
||||
waitForCoroutines(scope)
|
||||
|
||||
assertAll(
|
||||
{
|
||||
verify {
|
||||
player.sendMessage(
|
||||
Component.text(
|
||||
"DiamondBank-OG<reset>: <red>You don't have enough inventory space to withdraw <yellow>2.0 <aqua>Diamonds<red>."
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
{ coVerify(exactly = 0) { balanceManager.subtractFromBankShards(any(), any()) } },
|
||||
{ assertEquals(0, inventory.countTotal(), "Total count") },
|
||||
{ assertFalse(inventory.isLocked(), "Inventory locked") },
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user