summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDico200 <dico.karssiens@gmail.com>2018-07-24 01:14:23 +0100
committerDico200 <dico.karssiens@gmail.com>2018-07-24 01:14:23 +0100
commitd15d1b767bc89d087fd46450cb5e62fe0c4e9e61 (patch)
treea1904036d8d46ecca2ac5f3901d89367878b2cc5
parent42026191ec3a1f6468d8a46304d6ce5cd2d0689c (diff)
Work on commands. Implemented helper functions, among others to handle asynchronous commands
-rw-r--r--.editorconfig19
-rw-r--r--build.gradle.kts9
-rw-r--r--src/main/kotlin/io/dico/parcels2/Options.kt1
-rw-r--r--src/main/kotlin/io/dico/parcels2/Parcel.kt23
-rw-r--r--src/main/kotlin/io/dico/parcels2/ParcelWorld.kt8
-rw-r--r--src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt15
-rw-r--r--src/main/kotlin/io/dico/parcels2/WorldGenerator.kt2
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/CommandAsync.kt83
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/CommandRequirement.kt63
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/DebugCommands.kt (renamed from src/main/kotlin/io/dico/parcels2/PlotCommands.kt)7
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt68
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/ParcelCommands.kt51
-rw-r--r--src/main/kotlin/io/dico/parcels2/math/NumberExtensions.kt2
-rw-r--r--src/main/kotlin/io/dico/parcels2/math/Vec2i.kt4
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/Backing.kt4
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt72
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/ExposedExtensions.kt14
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/Hikari.kt2
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/Jackson.kt5
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/Storage.kt40
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt5
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/MiscExtensions.kt6
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt21
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt4
24 files changed, 425 insertions, 103 deletions
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..32f5e31
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,19 @@
+[*]
+charset=utf-8
+end_of_line=lf
+insert_final_newline=false
+indent_style=space
+indent_size=4
+
+[{.babelrc,.stylelintrc,jest.config,.eslintrc,*.bowerrc,*.jsb3,*.jsb2,*.json}]
+indent_style=space
+indent_size=2
+
+[{*.ddl,*.sql}]
+indent_style=space
+indent_size=2
+
+[{*.yml,*.yaml}]
+indent_style=space
+indent_size=2
+
diff --git a/build.gradle.kts b/build.gradle.kts
index 8b65cd9..421da58 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -28,7 +28,7 @@ dependencies {
compile("org.jetbrains.exposed:exposed:0.10.3")
compile("org.jetbrains.kotlinx:kotlinx-coroutines-core:0.23.4")
compile("com.zaxxer:HikariCP:3.2.0")
- compile(files("../h2/bin/h2-client-1.4.197.jar"))
+ compile(files("../h2/bin/h2-1.4.197.jar"))
val jacksonVersion = "2.9.6"
compile("com.fasterxml.jackson.core:jackson-core:$jacksonVersion")
@@ -44,9 +44,12 @@ dependencies {
}
tasks {
- val jar by getting(Jar::class)
+ val jar by getting(Jar::class) {
+ group = "artifacts"
+ }
val fatJar by creating(Jar::class) {
+ group = "artifacts"
destinationDir = file("$rootDir/debug/plugins")
baseName = "parcels2-all"
from(*configurations.compile.map(::zipTree).toTypedArray())
@@ -54,6 +57,7 @@ tasks {
}
val shadowJar by getting(ShadowJar::class) {
+ group = "artifacts"
destinationDir = file("$rootDir/debug/plugins")
baseName = "parcels2-shaded"
@@ -72,6 +76,7 @@ tasks {
}
val relocateSnakeyamlJar by creating(ShadowJar::class) {
+ group = "artifacts"
destinationDir = file("$rootDir/debug/plugins")
baseName = "parcels2-shaded"
relocate("org.yaml", "shadow.org.yaml")
diff --git a/src/main/kotlin/io/dico/parcels2/Options.kt b/src/main/kotlin/io/dico/parcels2/Options.kt
index 5dcb318..d8cc9d6 100644
--- a/src/main/kotlin/io/dico/parcels2/Options.kt
+++ b/src/main/kotlin/io/dico/parcels2/Options.kt
@@ -5,7 +5,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore
import io.dico.parcels2.storage.Storage
import io.dico.parcels2.storage.StorageFactory
import io.dico.parcels2.storage.yamlObjectMapper
-import org.bukkit.Bukkit
import org.bukkit.Bukkit.createBlockData
import org.bukkit.GameMode
import org.bukkit.Material
diff --git a/src/main/kotlin/io/dico/parcels2/Parcel.kt b/src/main/kotlin/io/dico/parcels2/Parcel.kt
index 046d6f5..ef45e08 100644
--- a/src/main/kotlin/io/dico/parcels2/Parcel.kt
+++ b/src/main/kotlin/io/dico/parcels2/Parcel.kt
@@ -19,6 +19,15 @@ interface ParcelData {
var allowInteractInputs: Boolean
var allowInteractInventory: Boolean
+
+ fun isOwner(uuid: UUID): Boolean {
+ return owner?.uuid == uuid
+ }
+
+ val infoString: String
+ get() {
+ TODO()
+ }
}
/**
@@ -88,14 +97,14 @@ class ParcelDataHolder : ParcelData {
override fun getAddedStatus(uuid: UUID): AddedStatus = added.getOrDefault(uuid, AddedStatus.DEFAULT)
override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean = status.takeIf { it != AddedStatus.DEFAULT }
- ?.let { added.put(uuid, it) != it }
- ?: added.remove(uuid) != null
+ ?.let { added.put(uuid, it) != it }
+ ?: added.remove(uuid) != null
override fun isBanned(uuid: UUID) = getAddedStatus(uuid) == AddedStatus.BANNED
override fun isAllowed(uuid: UUID) = getAddedStatus(uuid) == AddedStatus.ALLOWED
override fun canBuild(player: Player) = isAllowed(player.uniqueId)
- || owner?.matches(player, allowNameMatch = false) ?: false
- || player.hasBuildAnywhere
+ || owner?.matches(player, allowNameMatch = false) ?: false
+ || player.hasBuildAnywhere
override var allowInteractInputs = true
override var allowInteractInventory = true
@@ -121,7 +130,7 @@ class ParcelOwner(val uuid: UUID? = null,
companion object {
fun create(uuid: UUID?, name: String?): ParcelOwner? {
return uuid?.let { ParcelOwner(uuid, name) }
- ?: name?.let { ParcelOwner(uuid, name) }
+ ?: name?.let { ParcelOwner(uuid, name) }
}
}
@@ -141,7 +150,7 @@ class ParcelOwner(val uuid: UUID? = null,
fun matches(player: Player, allowNameMatch: Boolean = false): Boolean {
return uuid?.let { it == player.uniqueId } ?: false
- || (allowNameMatch && name?.let { it == player.name } ?: false)
+ || (allowNameMatch && name?.let { it == player.name } ?: false)
}
val onlinePlayer: Player? get() = uuid?.let { Bukkit.getPlayer(uuid) }
@@ -150,5 +159,5 @@ class ParcelOwner(val uuid: UUID? = null,
@Suppress("DEPRECATION")
val offlinePlayer
get() = (uuid?.let { Bukkit.getOfflinePlayer(it) } ?: Bukkit.getOfflinePlayer(name))
- ?.takeIf { it.isOnline() || it.hasPlayedBefore() }
+ ?.takeIf { it.isOnline() || it.hasPlayedBefore() }
}
diff --git a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt
index 376b2ab..1003075 100644
--- a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt
+++ b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt
@@ -53,10 +53,10 @@ class Worlds(private val plugin: ParcelsPlugin) {
try {
world = ParcelWorld(
- worldName,
- worldOptions,
- worldOptions.generator.getGenerator(this, worldName),
- plugin.storage)
+ worldName,
+ worldOptions,
+ worldOptions.generator.getGenerator(this, worldName),
+ plugin.storage)
} catch (ex: Exception) {
ex.printStackTrace()
diff --git a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt
index 9e9fc10..da8577c 100644
--- a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt
+++ b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt
@@ -1,8 +1,8 @@
package io.dico.parcels2
-import io.dico.dicore.command.CommandBuilder
import io.dico.dicore.command.EOverridePolicy
import io.dico.dicore.command.ICommandDispatcher
+import io.dico.parcels2.command.getParcelCommands
import io.dico.parcels2.storage.Storage
import io.dico.parcels2.storage.yamlObjectMapper
import io.dico.parcels2.util.tryCreate
@@ -14,6 +14,7 @@ import java.io.File
val logger = LoggerFactory.getLogger("ParcelsPlugin")
private inline val plogger get() = logger
+const val debugging = true
class ParcelsPlugin : JavaPlugin() {
lateinit var optionsFile: File
@@ -73,15 +74,9 @@ class ParcelsPlugin : JavaPlugin() {
}
private fun registerCommands() {
- //@formatting:off
- cmdDispatcher = CommandBuilder()
- .group("parcel", "plot", "plots", "p")
- .registerCommands(PlotCommands(this))
- .parent()
- .getDispatcher()
- //@formatting:on
-
- cmdDispatcher!!.registerToCommandMap("parcels:", EOverridePolicy.FALLBACK_ONLY)
+ cmdDispatcher = getParcelCommands(this).apply {
+ registerToCommandMap("parcels:", EOverridePolicy.FALLBACK_ONLY)
+ }
}
} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/WorldGenerator.kt b/src/main/kotlin/io/dico/parcels2/WorldGenerator.kt
index 1123955..e9fdcc1 100644
--- a/src/main/kotlin/io/dico/parcels2/WorldGenerator.kt
+++ b/src/main/kotlin/io/dico/parcels2/WorldGenerator.kt
@@ -179,7 +179,7 @@ class DefaultParcelGenerator(val worlds: Worlds, val name: String, private val o
}
override fun getBottomCoord(parcel: Parcel): Vec2i = Vec2i(sectionSize * parcel.pos.x + pathOffset + o.offsetX,
- sectionSize * parcel.pos.z + pathOffset + o.offsetZ)
+ sectionSize * parcel.pos.z + pathOffset + o.offsetZ)
override fun getHomeLocation(parcel: Parcel): Location {
val bottom = getBottomCoord(parcel)
diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandAsync.kt b/src/main/kotlin/io/dico/parcels2/command/CommandAsync.kt
new file mode 100644
index 0000000..bf38e04
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/command/CommandAsync.kt
@@ -0,0 +1,83 @@
+package io.dico.parcels2.command
+
+import io.dico.dicore.command.CommandException
+import io.dico.dicore.command.CommandResult
+import io.dico.dicore.command.EMessageType
+import io.dico.dicore.command.ExecutionContext
+import io.dico.dicore.command.chat.IChatController
+import io.dico.parcels2.ParcelsPlugin
+import io.dico.parcels2.logger
+import kotlinx.coroutines.experimental.*
+import org.bukkit.command.CommandSender
+import kotlin.coroutines.experimental.Continuation
+import kotlin.coroutines.experimental.suspendCoroutine
+
+/*
+ * Interface to implicitly access plugin by creating extension functions for it
+ */
+interface HasPlugin {
+ val plugin: ParcelsPlugin
+}
+
+class CommandAsyncScope {
+
+ suspend fun <T> HasPlugin.awaitSynchronousTask(delay: Int = 0, task: () -> T): T {
+ return suspendCoroutine { cont: Continuation<T> ->
+ plugin.server.scheduler.runTaskLater(plugin, l@ {
+ val result = try {
+ task()
+ } catch (ex: CommandException) {
+ cont.resumeWithException(ex)
+ return@l
+ } catch (ex: Throwable) {
+ cont.context.cancel(ex)
+ return@l
+ }
+ cont.resume(result)
+ }, delay.toLong())
+ }
+ }
+
+ fun <T> HasPlugin.synchronousTask(delay: Int = 0, task: () -> T): Deferred<T> {
+ return async { awaitSynchronousTask(delay, task) }
+ }
+
+}
+
+fun <T : Any?> HasPlugin.delegateCommandAsync(context: ExecutionContext,
+ block: suspend CommandAsyncScope.() -> T) {
+
+ val job: Deferred<Any?> = async(/*context = plugin.storage.asyncDispatcher, */start = CoroutineStart.ATOMIC) {
+ CommandAsyncScope().block()
+ }
+
+ fun Job.invokeOnCompletionSynchronously(block: (Throwable?) -> Unit) = invokeOnCompletion {
+ plugin.server.scheduler.runTask(plugin) { block(it) }
+ }
+
+ job.invokeOnCompletionSynchronously l@{ exception: Throwable? ->
+ exception?.let {
+ context.address.chatController.handleCoroutineException(context.sender, context, it)
+ return@l
+ }
+
+ val result = job.getCompleted()
+ val message = when (result) {
+ is String -> result
+ is CommandResult -> result.message
+ else -> null
+ }
+
+ context.address.chatController.sendMessage(context.sender, EMessageType.RESULT, message)
+ }
+
+}
+
+fun IChatController.handleCoroutineException(sender: CommandSender, context: ExecutionContext, exception: Throwable) {
+ if (exception is CancellationException) {
+ sendMessage(sender, EMessageType.EXCEPTION, "The command was cancelled unexpectedly (see console)")
+ logger.warn("An asynchronously dispatched command was cancelled unexpectedly", exception)
+ } else {
+ handleException(sender, context, exception)
+ }
+}
diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandRequirement.kt b/src/main/kotlin/io/dico/parcels2/command/CommandRequirement.kt
new file mode 100644
index 0000000..b4eea83
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/command/CommandRequirement.kt
@@ -0,0 +1,63 @@
+@file:Suppress("NOTHING_TO_INLINE")
+
+package io.dico.parcels2.command
+
+import io.dico.dicore.command.CommandException
+import io.dico.dicore.command.Validate
+import io.dico.parcels2.Parcel
+import io.dico.parcels2.ParcelWorld
+import io.dico.parcels2.Worlds
+import io.dico.parcels2.util.hasAdminManage
+import io.dico.parcels2.util.uuid
+import org.bukkit.entity.Player
+
+/*
+ * Scope types for extension lambdas
+ */
+sealed class BaseScope
+
+class WorldOnlyScope(val world: ParcelWorld) : BaseScope()
+class ParcelScope(val world: ParcelWorld, val parcel: Parcel) : BaseScope()
+
+/*
+ * Interface to implicitly access worlds object by creating extension functions for it
+ */
+interface HasWorlds {
+ val worlds: Worlds
+}
+
+/*
+ * Functions to be used by command implementations
+ */
+inline fun <T> HasWorlds.requireInWorld(player: Player,
+ admin: Boolean = false,
+ block: WorldOnlyScope.() -> T): T {
+ return WorldOnlyScope(worlds.getWorldRequired(player, admin = admin)).block()
+}
+
+inline fun <T> HasWorlds.requireInParcel(player: Player,
+ admin: Boolean = false,
+ own: Boolean = false,
+ block: ParcelScope.() -> T): T {
+ val parcel = worlds.getParcelRequired(player, admin = admin, own = own)
+ return ParcelScope(parcel.world, parcel).block()
+}
+
+/*
+ * Functions for checking
+ */
+fun Worlds.getWorldRequired(player: Player, admin: Boolean = false): ParcelWorld {
+ if (admin) Validate.isTrue(player.hasAdminManage, "You must have admin rights to use that command")
+ return getWorld(player.world)
+ ?: throw CommandException("You must be in a parcel world to use that command")
+}
+
+fun Worlds.getParcelRequired(player: Player, admin: Boolean = false, own: Boolean = false): Parcel {
+ val parcel = getWorldRequired(player, admin = admin).parcelAt(player)
+ ?: throw CommandException("You must be in a parcel to use that command")
+ if (own) Validate.isTrue(parcel.isOwner(player.uuid) || player.hasAdminManage,
+ "You must own this parcel to use that command")
+ return parcel
+}
+
+
diff --git a/src/main/kotlin/io/dico/parcels2/PlotCommands.kt b/src/main/kotlin/io/dico/parcels2/command/DebugCommands.kt
index 0001067..24a61c5 100644
--- a/src/main/kotlin/io/dico/parcels2/PlotCommands.kt
+++ b/src/main/kotlin/io/dico/parcels2/command/DebugCommands.kt
@@ -1,11 +1,12 @@
-package io.dico.parcels2
+package io.dico.parcels2.command
+
import io.dico.dicore.command.CommandException
import io.dico.dicore.command.annotation.Cmd
+import io.dico.parcels2.ParcelsPlugin
import org.bukkit.Bukkit
import org.bukkit.entity.Player
-
-class PlotCommands(val plugin: ParcelsPlugin) {
+class DebugCommands(val plugin: ParcelsPlugin) {
@Cmd("reloadoptions")
fun reloadOptions() {
diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt
new file mode 100644
index 0000000..1cca5be
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt
@@ -0,0 +1,68 @@
+package io.dico.parcels2.command
+
+import io.dico.dicore.command.CommandBuilder
+import io.dico.dicore.command.CommandException
+import io.dico.dicore.command.ICommandDispatcher
+import io.dico.dicore.command.parameter.ArgumentBuffer
+import io.dico.dicore.command.parameter.IParameter
+import io.dico.dicore.command.parameter.type.ParameterType
+import io.dico.parcels2.Parcel
+import io.dico.parcels2.ParcelsPlugin
+import io.dico.parcels2.Worlds
+import io.dico.parcels2.debugging
+import org.bukkit.command.CommandSender
+import org.bukkit.entity.Player
+
+@Suppress("UsePropertyAccessSyntax")
+fun getParcelCommands(plugin: ParcelsPlugin): ICommandDispatcher {
+ //@formatter:off
+ return CommandBuilder()
+ .addParameterType(false, ParcelParameterType(plugin.worlds))
+ .group("parcel", "plot", "plots", "p")
+ .registerCommands(ParcelCommands(plugin))
+ .putDebugCommands(plugin)
+ .parent()
+ .getDispatcher()
+ //@formatter:on
+}
+
+private fun CommandBuilder.putDebugCommands(plugin: ParcelsPlugin): CommandBuilder {
+ if (!debugging) return this
+ //@formatter:off
+ return group("debug", "d")
+ .registerCommands(DebugCommands(plugin))
+ .parent()
+ //@formatter:on
+}
+
+private val regex = Regex.fromLiteral("((.+)->)?([0-9]+):([0-9]+)")
+
+private class ParcelParameterType(val worlds: Worlds) : ParameterType<Parcel, Unit>(Parcel::class.java) {
+
+ private fun invalidInput(parameter: IParameter<*>, message: String): Nothing {
+ throw CommandException("invalid input for ${parameter.name}: $message")
+ }
+
+ override fun parse(parameter: IParameter<Parcel>, sender: CommandSender, buffer: ArgumentBuffer): Parcel {
+ val matchResult = regex.matchEntire(buffer.next())
+ ?: invalidInput(parameter, "must match (w->)?a:b (/${regex.pattern}/)")
+
+ val worldName = matchResult.groupValues[2]
+ .takeUnless { it.isEmpty() }
+ ?: (sender as? Player)?.world?.name
+ ?: invalidInput(parameter, "console cannot omit the world name")
+
+ val world = worlds.getWorld(worldName)
+ ?: invalidInput(parameter, "$worldName is not a parcel world")
+
+ val x = matchResult.groupValues[3].toIntOrNull()
+ ?: invalidInput(parameter, "couldn't parse int")
+
+ val z = matchResult.groupValues[4].toIntOrNull()
+ ?: invalidInput(parameter, "couldn't parse int")
+
+ return world.parcelByID(x, z)
+ ?: invalidInput(parameter, "parcel id is out of range")
+ }
+
+}
diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelCommands.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelCommands.kt
new file mode 100644
index 0000000..016ad22
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/command/ParcelCommands.kt
@@ -0,0 +1,51 @@
+package io.dico.parcels2.command
+
+import io.dico.dicore.command.CommandException
+import io.dico.dicore.command.ExecutionContext
+import io.dico.dicore.command.annotation.Cmd
+import io.dico.dicore.command.annotation.Desc
+import io.dico.parcels2.ParcelOwner
+import io.dico.parcels2.ParcelsPlugin
+import io.dico.parcels2.util.parcelLimit
+import io.dico.parcels2.util.uuid
+import org.bukkit.entity.Player
+
+class ParcelCommands(override val plugin: ParcelsPlugin) : HasWorlds, HasPlugin {
+ override val worlds = plugin.worlds
+
+ private fun error(message: String): Nothing {
+ throw CommandException(message)
+ }
+
+ @Cmd("auto")
+ @Desc("Finds the unclaimed parcel nearest to origin,",
+ "and gives it to you",
+ shortVersion = "sets you up with a fresh, unclaimed parcel")
+ fun cmdAuto(player: Player, context: ExecutionContext) = requireInWorld(player) {
+ delegateCommandAsync(context) {
+ val numOwnedParcels = plugin.storage.getNumParcels(ParcelOwner(uuid = player.uuid)).await()
+
+ awaitSynchronousTask {
+ val limit = player.parcelLimit
+
+ if (numOwnedParcels >= limit) {
+ error("You have enough plots for now")
+ }
+
+ val parcel = world.nextEmptyParcel()
+ ?: error("This world is full, please ask an admin to upsize it")
+ parcel.owner = ParcelOwner(uuid = player.uuid)
+ player.teleport(world.generator.getHomeLocation(parcel))
+ "Enjoy your new parcel!"
+ }
+ }
+ }
+
+ @Cmd("info", aliases = ["i"])
+ @Desc("Displays general information",
+ "about the parcel you're on",
+ shortVersion = "displays information about this parcel")
+ fun cmdInfo(player: Player) = requireInParcel(player) { parcel.infoString }
+
+
+} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/math/NumberExtensions.kt b/src/main/kotlin/io/dico/parcels2/math/NumberExtensions.kt
index faf2939..985fd2e 100644
--- a/src/main/kotlin/io/dico/parcels2/math/NumberExtensions.kt
+++ b/src/main/kotlin/io/dico/parcels2/math/NumberExtensions.kt
@@ -3,7 +3,7 @@ package io.dico.parcels2.math
fun Double.floor(): Int {
val down = toInt()
if (down.toDouble() != this && (java.lang.Double.doubleToRawLongBits(this).ushr(63).toInt()) == 1) {
- return down-1
+ return down - 1
}
return down
}
diff --git a/src/main/kotlin/io/dico/parcels2/math/Vec2i.kt b/src/main/kotlin/io/dico/parcels2/math/Vec2i.kt
index 70ca246..bff0326 100644
--- a/src/main/kotlin/io/dico/parcels2/math/Vec2i.kt
+++ b/src/main/kotlin/io/dico/parcels2/math/Vec2i.kt
@@ -1,7 +1,7 @@
package io.dico.parcels2.math
data class Vec2i(
- val x: Int,
- val z: Int
+ val x: Int,
+ val z: Int
)
diff --git a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt
index 0f8829d..252c602 100644
--- a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt
+++ b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt
@@ -10,6 +10,8 @@ interface Backing {
val name: String
+ val isConnected: Boolean
+
suspend fun init()
suspend fun shutdown()
@@ -25,6 +27,8 @@ interface Backing {
suspend fun getOwnedParcels(user: ParcelOwner): List<SerializableParcel>
+ suspend fun getNumParcels(user: ParcelOwner): Int = getOwnedParcels(user).size
+
suspend fun setParcelData(parcelFor: Parcel, data: ParcelData?)
diff --git a/src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt b/src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt
index e79c7e0..47efc87 100644
--- a/src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt
+++ b/src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt
@@ -3,6 +3,7 @@ package io.dico.parcels2.storage
import com.zaxxer.hikari.HikariDataSource
import io.dico.parcels2.*
import io.dico.parcels2.math.Vec2i
+import io.dico.parcels2.util.synchronized
import io.dico.parcels2.util.toByteArray
import io.dico.parcels2.util.toUUID
import kotlinx.coroutines.experimental.channels.ProducerScope
@@ -16,7 +17,7 @@ object WorldsT : Table("worlds") {
val id = integer("id").autoIncrement().primaryKey()
val name = varchar("name", 50)
val uid = binary("uid", 16)
- .also { uniqueIndex("index_uid", it) }
+ .also { uniqueIndex("index_uid", it) }
}
object ParcelsT : Table("parcels") {
@@ -24,31 +25,31 @@ object ParcelsT : Table("parcels") {
val px = integer("px")
val pz = integer("pz")
val world_id = integer("id")
- .also { uniqueIndex("index_location", it, px, pz) }
- .references(WorldsT.id)
+ .also { uniqueIndex("index_location", it, px, pz) }
+ .references(WorldsT.id)
val owner_uuid = binary("owner_uuid", 16).nullable()
val owner_name = varchar("owner_name", 16).nullable()
}
object AddedLocalT : Table("parcels_added_local") {
val parcel_id = integer("parcel_id")
- .references(ParcelsT.id, ReferenceOption.CASCADE)
+ .references(ParcelsT.id, ReferenceOption.CASCADE)
val player_uuid = binary("player_uuid", 16)
- .also { uniqueIndex("index_pair", parcel_id, it) }
+ .also { uniqueIndex("index_pair", parcel_id, it) }
val allowed_flag = bool("allowed_flag")
}
object AddedGlobalT : Table("parcels_added_global") {
val owner_uuid = binary("owner_uuid", 16)
val player_uuid = binary("player_uuid", 16)
- .also { uniqueIndex("index_pair", owner_uuid, it) }
+ .also { uniqueIndex("index_pair", owner_uuid, it) }
val allowed_flag = bool("allowed_flag")
}
object ParcelOptionsT : Table("parcel_options") {
val parcel_id = integer("parcel_id")
- .also { uniqueIndex("index_parcel_id", it) }
- .references(ParcelsT.id, ReferenceOption.CASCADE)
+ .also { uniqueIndex("index_parcel_id", it) }
+ .references(ParcelsT.id, ReferenceOption.CASCADE)
val interact_inventory = bool("interact_inventory").default(false)
val interact_inputs = bool("interact_inputs").default(false)
}
@@ -58,18 +59,29 @@ private class ExposedDatabaseException(message: String? = null) : Exception(mess
@Suppress("NOTHING_TO_INLINE")
class ExposedBacking(val dataSource: DataSource) : Backing {
override val name get() = "Exposed"
- lateinit var database: Database
+ private var database: Database? = null
+ private var isShutdown: Boolean = false
+
+ override val isConnected get() = database != null
override suspend fun init() {
- database = Database.connect(dataSource)
- transaction(database) {
- create(ParcelsT, AddedLocalT)
+ synchronized {
+ if (isShutdown) throw IllegalStateException()
+ database = Database.connect(dataSource)
+ transaction(database) {
+ create(WorldsT, ParcelsT, AddedLocalT, ParcelOptionsT)
+ }
}
}
override suspend fun shutdown() {
- if (dataSource is HikariDataSource) {
- dataSource.close()
+ synchronized {
+ if (isShutdown) throw IllegalStateException()
+ if (dataSource is HikariDataSource) {
+ dataSource.close()
+ }
+ database = null
+ isShutdown = true
}
}
@@ -86,13 +98,13 @@ class ExposedBacking(val dataSource: DataSource) : Backing {
private inline fun Transaction.getOrInitWorldId(worldUid: UUID, worldName: String): Int {
val binaryUid = worldUid.toByteArray()!!
return getWorldId(binaryUid)
- ?: WorldsT.insertIgnore { it[uid] = binaryUid; it[name] = worldName }.get(WorldsT.id)
- ?: throw ExposedDatabaseException("This should not happen - failed to insert world named $worldName and get its id")
+ ?: WorldsT.insertIgnore { it[uid] = binaryUid; it[name] = worldName }.get(WorldsT.id)
+ ?: throw ExposedDatabaseException("This should not happen - failed to insert world named $worldName and get its id")
}
private inline fun Transaction.getParcelId(worldId: Int, parcelX: Int, parcelZ: Int): Int? {
return ParcelsT.select { (ParcelsT.world_id eq worldId) and (ParcelsT.px eq parcelX) and (ParcelsT.pz eq parcelZ) }
- .firstOrNull()?.let { it[ParcelsT.id] }
+ .firstOrNull()?.let { it[ParcelsT.id] }
}
private inline fun Transaction.getParcelId(worldUid: UUID, parcelX: Int, parcelZ: Int): Int? {
@@ -102,8 +114,8 @@ class ExposedBacking(val dataSource: DataSource) : Backing {
private inline fun Transaction.getOrInitParcelId(worldUid: UUID, worldName: String, parcelX: Int, parcelZ: Int): Int {
val worldId = getOrInitWorldId(worldUid, worldName)
return getParcelId(worldId, parcelX, parcelZ)
- ?: ParcelsT.insertIgnore { it[world_id] = worldId; it[px] = parcelX; it[pz] = parcelZ }.get(ParcelsT.id)
- ?: throw ExposedDatabaseException("This should not happen - failed to insert parcel at $worldName($parcelX, $parcelZ)")
+ ?: ParcelsT.insertIgnore { it[world_id] = worldId; it[px] = parcelX; it[pz] = parcelZ }.get(ParcelsT.id)
+ ?: throw ExposedDatabaseException("This should not happen - failed to insert parcel at $worldName($parcelX, $parcelZ)")
}
private inline fun Transaction.getParcelRow(id: Int): ResultRow? {
@@ -144,8 +156,8 @@ class ExposedBacking(val dataSource: DataSource) : Backing {
ParcelDataHolder().apply {
owner = ParcelOwner.create(
- uuid = row[ParcelsT.owner_uuid]?.toUUID(),
- name = row[ParcelsT.owner_name]
+ uuid = row[ParcelsT.owner_uuid]?.toUUID(),
+ name = row[ParcelsT.owner_name]
)
val parcelId = row[ParcelsT.id]
@@ -176,16 +188,16 @@ class ExposedBacking(val dataSource: DataSource) : Backing {
}
ParcelsT.select(where)
- .map { parcelRow ->
- val worldId = parcelRow[ParcelsT.world_id]
- val worldRow = WorldsT.select({ WorldsT.id eq worldId }).firstOrNull()
- ?: return@map null
+ .map { parcelRow ->
+ val worldId = parcelRow[ParcelsT.world_id]
+ val worldRow = WorldsT.select({ WorldsT.id eq worldId }).firstOrNull()
+ ?: return@map null
- val world = SerializableWorld(worldRow[WorldsT.name], worldRow[WorldsT.uid].toUUID())
- SerializableParcel(world, Vec2i(parcelRow[ParcelsT.px], parcelRow[ParcelsT.pz]))
- }
- .filterNotNull()
- .toList()
+ val world = SerializableWorld(worldRow[WorldsT.name], worldRow[WorldsT.uid].toUUID())
+ SerializableParcel(world, Vec2i(parcelRow[ParcelsT.px], parcelRow[ParcelsT.pz]))
+ }
+ .filterNotNull()
+ .toList()
}
diff --git a/src/main/kotlin/io/dico/parcels2/storage/ExposedExtensions.kt b/src/main/kotlin/io/dico/parcels2/storage/ExposedExtensions.kt
index f429d7e..d4aee11 100644
--- a/src/main/kotlin/io/dico/parcels2/storage/ExposedExtensions.kt
+++ b/src/main/kotlin/io/dico/parcels2/storage/ExposedExtensions.kt
@@ -10,15 +10,15 @@ import org.jetbrains.exposed.sql.transactions.TransactionManager
* insertOrUpdate from https://github.com/JetBrains/Exposed/issues/167#issuecomment-403837917
*/
inline fun <T : Table> T.insertOrUpdate(vararg onDuplicateUpdateKeys: Column<*>, body: T.(InsertStatement<Number>) -> Unit) =
- InsertOrUpdate<Number>(onDuplicateUpdateKeys, this).apply {
- body(this)
- execute(TransactionManager.current())
- }
+ InsertOrUpdate<Number>(onDuplicateUpdateKeys, this).apply {
+ body(this)
+ execute(TransactionManager.current())
+ }
class InsertOrUpdate<Key : Any>(
- private val onDuplicateUpdateKeys: Array<out Column<*>>,
- table: Table,
- isIgnore: Boolean = false
+ private val onDuplicateUpdateKeys: Array<out Column<*>>,
+ table: Table,
+ isIgnore: Boolean = false
) : InsertStatement<Key>(table, isIgnore) {
override fun prepareSQL(transaction: Transaction): String {
val onUpdateSQL = if (onDuplicateUpdateKeys.isNotEmpty()) {
diff --git a/src/main/kotlin/io/dico/parcels2/storage/Hikari.kt b/src/main/kotlin/io/dico/parcels2/storage/Hikari.kt
index 108ad92..ae16c83 100644
--- a/src/main/kotlin/io/dico/parcels2/storage/Hikari.kt
+++ b/src/main/kotlin/io/dico/parcels2/storage/Hikari.kt
@@ -31,7 +31,7 @@ fun getHikariDataSource(dialectName: String,
dataSourceProperties.remove("serverName")
dataSourceProperties.remove("port")
dataSourceProperties.remove("databaseName")
- addDataSourceProperty("url", "jdbc:h2:tcp://$address/~/${dco.database}")
+ addDataSourceProperty("url", "jdbc:h2:${if (address.isBlank()) "" else "tcp://$address/"}~/${dco.database}")
} else {
// doesn't exist on the MariaDB driver
addDataSourceProperty("cachePrepStmts", "true")
diff --git a/src/main/kotlin/io/dico/parcels2/storage/Jackson.kt b/src/main/kotlin/io/dico/parcels2/storage/Jackson.kt
index 08ca810..97225b8 100644
--- a/src/main/kotlin/io/dico/parcels2/storage/Jackson.kt
+++ b/src/main/kotlin/io/dico/parcels2/storage/Jackson.kt
@@ -8,10 +8,11 @@ import com.fasterxml.jackson.databind.ser.BeanSerializerModifier
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.KotlinModule
-import io.dico.parcels2.*
+import io.dico.parcels2.GeneratorFactory
+import io.dico.parcels2.GeneratorOptions
+import io.dico.parcels2.StorageOptions
import org.bukkit.Bukkit
import org.bukkit.block.data.BlockData
-import kotlin.reflect.KClass
import kotlin.reflect.full.isSuperclassOf
val yamlObjectMapper = ObjectMapper(YAMLFactory()).apply {
diff --git a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt
index 67c4b05..7af6bc5 100644
--- a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt
+++ b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt
@@ -14,10 +14,11 @@ interface Storage {
val name: String
val syncDispatcher: CoroutineDispatcher
val asyncDispatcher: CoroutineDispatcher
+ val isConnected: Boolean
- fun init(): Deferred<Unit>
+ fun init(): Job
- fun shutdown(): Deferred<Unit>
+ fun shutdown(): Job
fun readParcelData(parcelFor: Parcel): Deferred<ParcelData?>
@@ -26,16 +27,18 @@ interface Storage {
fun getOwnedParcels(user: ParcelOwner): Deferred<List<SerializableParcel>>
+ fun getNumParcels(user: ParcelOwner): Deferred<Int>
- fun setParcelData(parcelFor: Parcel, data: ParcelData?): Deferred<Unit>
- fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?): Deferred<Unit>
+ fun setParcelData(parcelFor: Parcel, data: ParcelData?): Job
- fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?): Deferred<Unit>
+ fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?): Job
- fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean): Deferred<Unit>
+ fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?): Job
- fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean): Deferred<Unit>
+ fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean): Job
+
+ fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean): Job
}
@@ -44,15 +47,21 @@ class StorageWithCoroutineBacking internal constructor(val backing: Backing) : S
override val syncDispatcher = Executor { it.run() }.asCoroutineDispatcher()
val poolSize: Int get() = 4
override val asyncDispatcher = Executors.newFixedThreadPool(poolSize) { Thread(it, "Parcels2_StorageThread") }.asCoroutineDispatcher()
+ override val isConnected get() = backing.isConnected
@Suppress("NOTHING_TO_INLINE")
private inline fun <T> defer(noinline block: suspend CoroutineScope.() -> T): Deferred<T> {
return async(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block)
}
- override fun init() = defer { backing.init() }
+ @Suppress("NOTHING_TO_INLINE")
+ private inline fun job(noinline block: suspend CoroutineScope.() -> Unit): Job {
+ return launch(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block)
+ }
+
+ override fun init() = job { backing.init() }
- override fun shutdown() = defer { backing.shutdown() }
+ override fun shutdown() = job { backing.shutdown() }
override fun readParcelData(parcelFor: Parcel) = defer { backing.readParcelData(parcelFor) }
@@ -61,16 +70,17 @@ class StorageWithCoroutineBacking internal constructor(val backing: Backing) : S
with(backing) { produceParcelData(parcelsFor) }
}
+ override fun getOwnedParcels(user: ParcelOwner) = defer { backing.getOwnedParcels(user) }
- override fun setParcelData(parcelFor: Parcel, data: ParcelData?) = defer { backing.setParcelData(parcelFor, data) }
+ override fun getNumParcels(user: ParcelOwner) = defer { backing.getNumParcels(user) }
- override fun getOwnedParcels(user: ParcelOwner) = defer { backing.getOwnedParcels(user) }
+ override fun setParcelData(parcelFor: Parcel, data: ParcelData?) = job { backing.setParcelData(parcelFor, data) }
- override fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) = defer { backing.setParcelOwner(parcelFor, owner) }
+ override fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) = job { backing.setParcelOwner(parcelFor, owner) }
- override fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?) = defer { backing.setParcelPlayerState(parcelFor, player, state) }
+ override fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?) = job { backing.setParcelPlayerState(parcelFor, player, state) }
- override fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean) = defer { backing.setParcelAllowsInteractInventory(parcel, value) }
+ override fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean) = job { backing.setParcelAllowsInteractInventory(parcel, value) }
- override fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean) = defer { backing.setParcelAllowsInteractInputs(parcel, value) }
+ override fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean) = job { backing.setParcelAllowsInteractInputs(parcel, value) }
}
diff --git a/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt b/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt
index c1e158a..b0140f1 100644
--- a/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt
+++ b/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt
@@ -1,7 +1,6 @@
package io.dico.parcels2.storage
import io.dico.parcels2.DataConnectionOptions
-import net.minecraft.server.v1_13_R1.WorldType.types
import kotlin.reflect.KClass
interface StorageFactory {
@@ -29,8 +28,8 @@ class ConnectionStorageFactory : StorageFactory {
override val optionsClass = DataConnectionOptions::class
private val types: Map<String, String> = mutableMapOf(
- "mysql" to "com.mysql.jdbc.jdbc2.optional.MysqlDataSource",
- "h2" to "org.h2.jdbcx.JdbcDataSource"
+ "mysql" to "com.mysql.jdbc.jdbc2.optional.MysqlDataSource",
+ "h2" to "org.h2.jdbcx.JdbcDataSource"
)
fun register(companion: StorageFactory.StorageFactories) {
diff --git a/src/main/kotlin/io/dico/parcels2/util/MiscExtensions.kt b/src/main/kotlin/io/dico/parcels2/util/MiscExtensions.kt
index f70014d..952595b 100644
--- a/src/main/kotlin/io/dico/parcels2/util/MiscExtensions.kt
+++ b/src/main/kotlin/io/dico/parcels2/util/MiscExtensions.kt
@@ -1,9 +1,7 @@
package io.dico.parcels2.util
import io.dico.parcels2.logger
-import org.slf4j.Logger
import java.io.File
-import java.io.PrintWriter
fun File.tryCreate(): Boolean {
val parent = parentFile
@@ -13,3 +11,7 @@ fun File.tryCreate(): Boolean {
}
return true
}
+
+inline fun <R> Any.synchronized(block: () -> R): R {
+ return synchronized(this, block)
+}
diff --git a/src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt b/src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt
index 3ef3c89..3424655 100644
--- a/src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt
+++ b/src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt
@@ -6,18 +6,19 @@ import io.dico.parcels2.logger
import org.bukkit.entity.Player
import org.bukkit.plugin.java.JavaPlugin
-inline val Player.hasBanBypass get() = hasPermission("plots.admin.bypass.ban")
-inline val Player.hasBuildAnywhere get() = hasPermission("plots.admin.bypass.build")
-inline val Player.hasGamemodeBypass get() = hasPermission("plots.admin.bypass.gamemode")
-inline val Player.hasAdminManage get() = hasPermission("plots.admin.manage")
-inline val Player.hasPlotHomeOthers get() = hasPermission("plots.command.home.others")
-inline val Player.hasRandomSpecific get() = hasPermission("plots.command.random.specific")
-val Player.plotLimit: Int
+inline val Player.uuid get() = uniqueId
+inline val Player.hasBanBypass get() = hasPermission("parcels.admin.bypass.ban")
+inline val Player.hasBuildAnywhere get() = hasPermission("parcels.admin.bypass.build")
+inline val Player.hasGamemodeBypass get() = hasPermission("parcels.admin.bypass.gamemode")
+inline val Player.hasAdminManage get() = hasPermission("parcels.admin.manage")
+inline val Player.hasParcelHomeOthers get() = hasPermission("parcels.command.home.others")
+inline val Player.hasRandomSpecific get() = hasPermission("parcels.command.random.specific")
+val Player.parcelLimit: Int
get() {
for (info in effectivePermissions) {
val perm = info.permission
- if (perm.startsWith("plots.limit.")) {
- val limitString = perm.substring("plots.limit.".length)
+ if (perm.startsWith("parcels.limit.")) {
+ val limitString = perm.substring("parcels.limit.".length)
if (limitString == "*") {
return Int.MAX_VALUE
}
@@ -32,7 +33,7 @@ val Player.plotLimit: Int
private const val DEFAULT_LIMIT = 1
private val prefix = Formatting.translateChars('&', "&4[&c${JavaPlugin.getPlugin(ParcelsPlugin::class.java).name}&4] &a")
-fun Player.sendPlotMessage(except: Boolean = false, nopermit: Boolean = false, message: String) {
+fun Player.sendParcelMessage(except: Boolean = false, nopermit: Boolean = false, message: String) {
if (except) {
sendMessage(prefix + Formatting.YELLOW + Formatting.translateChars('&', message))
} else if (nopermit) {
diff --git a/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt b/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt
index 7cd298f..6cdbe3a 100644
--- a/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt
+++ b/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt
@@ -8,8 +8,8 @@ import java.util.*
@Suppress("UsePropertyAccessSyntax")
fun getPlayerName(uuid: UUID?, ifUnknown: String? = null): String {
return uuid?.let { Bukkit.getOfflinePlayer(uuid)?.takeIf { it.isOnline() || it.hasPlayedBefore() }?.name }
- ?: ifUnknown
- ?: ":unknown_name:"
+ ?: ifUnknown
+ ?: ":unknown_name:"
}
@Contract("null -> null; !null -> !null", pure = true)