Archived
0

Work on commands. Implemented helper functions, among others to handle asynchronous commands

This commit is contained in:
Dico200
2018-07-24 01:14:23 +01:00
parent 42026191ec
commit d15d1b767b
24 changed files with 426 additions and 104 deletions

View File

@@ -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

View File

@@ -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() }
}

View File

@@ -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()

View File

@@ -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)
}
}
}

View File

@@ -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)

View 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)
}
}

View File

@@ -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
}

View File

@@ -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() {

View File

@@ -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")
}
}

View 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 }
}

View File

@@ -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
}

View File

@@ -1,7 +1,7 @@
package io.dico.parcels2.math
data class Vec2i(
val x: Int,
val z: Int
val x: Int,
val z: Int
)

View File

@@ -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?)

View File

@@ -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()
}

View File

@@ -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()) {

View File

@@ -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")

View File

@@ -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 {

View File

@@ -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) }
}

View File

@@ -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) {

View File

@@ -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)
}

View File

@@ -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) {

View File

@@ -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)