Work on commands. Implemented helper functions, among others to handle asynchronous commands
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
83
src/main/kotlin/io/dico/parcels2/command/CommandAsync.kt
Normal file
83
src/main/kotlin/io/dico/parcels2/command/CommandAsync.kt
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
51
src/main/kotlin/io/dico/parcels2/command/ParcelCommands.kt
Normal file
51
src/main/kotlin/io/dico/parcels2/command/ParcelCommands.kt
Normal file
@@ -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 }
|
||||
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.dico.parcels2.math
|
||||
|
||||
data class Vec2i(
|
||||
val x: Int,
|
||||
val z: Int
|
||||
val x: Int,
|
||||
val z: Int
|
||||
)
|
||||
|
||||
|
||||
@@ -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?)
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 shutdown() = defer { backing.shutdown() }
|
||||
override fun init() = job { backing.init() }
|
||||
|
||||
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 setParcelData(parcelFor: Parcel, data: ParcelData?) = defer { backing.setParcelData(parcelFor, data) }
|
||||
|
||||
override fun getOwnedParcels(user: ParcelOwner) = defer { backing.getOwnedParcels(user) }
|
||||
|
||||
override fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) = defer { backing.setParcelOwner(parcelFor, owner) }
|
||||
override fun getNumParcels(user: ParcelOwner) = defer { backing.getNumParcels(user) }
|
||||
|
||||
override fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?) = defer { backing.setParcelPlayerState(parcelFor, player, state) }
|
||||
override fun setParcelData(parcelFor: Parcel, data: ParcelData?) = job { backing.setParcelData(parcelFor, data) }
|
||||
|
||||
override fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean) = defer { backing.setParcelAllowsInteractInventory(parcel, value) }
|
||||
override fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) = job { backing.setParcelOwner(parcelFor, owner) }
|
||||
|
||||
override fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean) = defer { backing.setParcelAllowsInteractInputs(parcel, value) }
|
||||
override fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?) = job { backing.setParcelPlayerState(parcelFor, player, state) }
|
||||
|
||||
override fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean) = job { backing.setParcelAllowsInteractInventory(parcel, value) }
|
||||
|
||||
override fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean) = job { backing.setParcelAllowsInteractInputs(parcel, value) }
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user