From 4b34c708d2fac7c4ed1cceb8e86b4f86faf0dfb2 Mon Sep 17 00:00:00 2001 From: Dico Karssiens Date: Tue, 31 Jul 2018 00:56:59 +0100 Subject: Move to local PC cuz teamviewer connection is sucky rn --- src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt | 19 +++++++++++++++++++ .../io/dico/parcels2/command/ParcelsChatController.kt | 3 +++ 2 files changed, 22 insertions(+) create mode 100644 src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt diff --git a/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt b/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt new file mode 100644 index 0000000..f94bdd3 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt @@ -0,0 +1,19 @@ +package io.dico.parcels2 + +import org.bukkit.OfflinePlayer +import java.util.* + +class GlobalAddedDataManager(val plugin: ParcelsPlugin) { + + + operator fun get(player: OfflinePlayer): AddedData { + + } + + operator fun get(uuid: UUID): AddedData { + + } + + +} + diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelsChatController.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelsChatController.kt index 456893b..fe9ca0d 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelsChatController.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelsChatController.kt @@ -4,5 +4,8 @@ import io.dico.dicore.command.chat.AbstractChatController class ParcelsChatController : AbstractChatController() { + override fun filterMessage(message: String?): String { + return "[Parcels] $message" + } } \ No newline at end of file -- cgit v1.2.3 From 1ec6dd136b678a312d5865ef1fdfd994d58796d3 Mon Sep 17 00:00:00 2001 From: Dico200 Date: Tue, 31 Jul 2018 16:00:07 +0100 Subject: work on global added data --- .../kotlin/io/dico/parcels2/GlobalAddedData.kt | 48 +++++++++++++++++++++- src/main/kotlin/io/dico/parcels2/Parcel.kt | 4 +- src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt | 9 +++- .../kotlin/io/dico/parcels2/storage/Backing.kt | 6 +-- .../io/dico/parcels2/storage/ExposedBacking.kt | 4 +- .../kotlin/io/dico/parcels2/storage/Storage.kt | 10 ++--- 6 files changed, 66 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt b/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt index f94bdd3..eb00c52 100644 --- a/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt +++ b/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt @@ -1,19 +1,63 @@ package io.dico.parcels2 +import io.dico.parcels2.util.uuid +import kotlinx.coroutines.experimental.CompletableDeferred +import kotlinx.coroutines.experimental.Deferred import org.bukkit.OfflinePlayer import java.util.* +interface GlobalAddedData : AddedData { + val uuid: UUID +} + class GlobalAddedDataManager(val plugin: ParcelsPlugin) { + private val map = mutableMapOf() + operator fun get(player: OfflinePlayer) = get(player.uuid) - operator fun get(player: OfflinePlayer): AddedData { + operator fun get(uuid: UUID): GlobalAddedData? { + + } + + fun getDeferred(uuid: UUID): Deferred { + get(uuid)?.let { return CompletableDeferred(it) } + + } + private suspend fun getAsync(uuid: UUID): GlobalAddedData { + val data = plugin.storage.readGlobalAddedData(ParcelOwner(uuid = uuid)).await() + ?: return GlobalAddedDataImpl(uuid) + val result = GlobalAddedDataImpl(uuid, data) + map[uuid] = result + return result } - operator fun get(uuid: UUID): AddedData { + private inner class GlobalAddedDataImpl(override val uuid: UUID, + data: MutableMap = emptyData) + : AddedDataHolder(data), GlobalAddedData { + + private inline var data get() = added; set(value) = run { added = value } + private inline val isEmpty get() = data === emptyData + + override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean { + if (isEmpty) { + if (status == AddedStatus.DEFAULT) return false + data = mutableMapOf() + } + return super.setAddedStatus(uuid, status).also { + if (it) plugin.storage.setGlobalAddedStatus(ParcelOwner(uuid = this.uuid), uuid, status) + } + } } + private companion object { + val emptyData = mapOf() as MutableMap + } } + + + + diff --git a/src/main/kotlin/io/dico/parcels2/Parcel.kt b/src/main/kotlin/io/dico/parcels2/Parcel.kt index e86667b..1d323dd 100644 --- a/src/main/kotlin/io/dico/parcels2/Parcel.kt +++ b/src/main/kotlin/io/dico/parcels2/Parcel.kt @@ -117,8 +117,8 @@ class Parcel(val world: ParcelWorld, val pos: Vec2i) : ParcelData { var hasBlockVisitors: Boolean = false; private set } -open class AddedDataHolder : AddedData { - override var added = mutableMapOf() +open class AddedDataHolder(override var added: MutableMap + = mutableMapOf()) : AddedData { 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 } diff --git a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt index cc06f85..53a2d15 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt @@ -11,7 +11,6 @@ import io.dico.parcels2.listener.ParcelListeners import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.yamlObjectMapper import io.dico.parcels2.util.tryCreate -import kotlinx.coroutines.experimental.asCoroutineDispatcher import org.bukkit.Bukkit import org.bukkit.plugin.java.JavaPlugin import org.slf4j.LoggerFactory @@ -33,6 +32,14 @@ class ParcelsPlugin : JavaPlugin() { private var cmdDispatcher: ICommandDispatcher? = null val worktimeLimiter: WorktimeLimiter by lazy { TickWorktimeLimiter(this, options.tickWorktime) } + val mainThreadDispatcher = object : Executor { + private val mainThread = Thread.currentThread() + override fun execute(command: Runnable) { + if (Thread.currentThread() === mainThread) command.run() + else server.scheduler.runTask(this@ParcelsPlugin, command) + } + } + override fun onEnable() { plogger.info("Debug enabled: ${plogger.isDebugEnabled}") if (!init()) { diff --git a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt index 31b6574..7f00976 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt @@ -1,6 +1,6 @@ package io.dico.parcels2.storage -import io.dico.parcels2.AddedData +import io.dico.parcels2.AddedStatus import io.dico.parcels2.Parcel import io.dico.parcels2.ParcelData import io.dico.parcels2.ParcelOwner @@ -44,8 +44,8 @@ interface Backing { suspend fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean) - suspend fun readGlobalPlayerStateData(owner: ParcelOwner): AddedData? + suspend fun readGlobalAddedData(owner: ParcelOwner): MutableMap - suspend fun setGlobalPlayerState(owner: ParcelOwner, player: UUID, state: Boolean?) + suspend fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) } \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt b/src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt index cbcb6f4..1a8a252 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt @@ -275,11 +275,11 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource) : Backing } } - override suspend fun readGlobalPlayerStateData(owner: ParcelOwner): AddedData? { + override suspend fun readGlobalAddedData(owner: ParcelOwner): AddedData? { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } - override suspend fun setGlobalPlayerState(owner: ParcelOwner, player: UUID, state: Boolean?) { + override suspend fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: Boolean?) { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } diff --git a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt index cb3c3d0..2323571 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt @@ -1,6 +1,6 @@ package io.dico.parcels2.storage -import io.dico.parcels2.AddedData +import io.dico.parcels2.AddedStatus import io.dico.parcels2.Parcel import io.dico.parcels2.ParcelData import io.dico.parcels2.ParcelOwner @@ -44,9 +44,9 @@ interface Storage { fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean): Job - fun readGlobalPlayerStateData(owner: ParcelOwner): Deferred + fun readGlobalAddedData(owner: ParcelOwner): Deferred?> - fun setGlobalPlayerState(owner: ParcelOwner, player: UUID, state: Boolean?): Job + fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus): Job } class StorageWithCoroutineBacking internal constructor(val backing: Backing) : Storage { @@ -94,7 +94,7 @@ class StorageWithCoroutineBacking internal constructor(val backing: Backing) : S override fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean) = job { backing.setParcelAllowsInteractInputs(parcel, value) } - override fun readGlobalPlayerStateData(owner: ParcelOwner): Deferred = defer { backing.readGlobalPlayerStateData(owner) } + override fun readGlobalAddedData(owner: ParcelOwner): Deferred?> = defer { backing.readGlobalAddedData(owner) } - override fun setGlobalPlayerState(owner: ParcelOwner, player: UUID, state: Boolean?) = job { backing.setGlobalPlayerState(owner, player, state) } + override fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) = job { backing.setGlobalAddedStatus(owner, player, status) } } -- cgit v1.2.3 From 472e700e0422d1829aa26e04b74e2077807e75f0 Mon Sep 17 00:00:00 2001 From: Dico Karssiens Date: Wed, 1 Aug 2018 16:45:27 +0100 Subject: Improve database abstractions, add GlobalAddedData, some other things --- build.gradle.kts | 4 +- src/main/kotlin/io/dico/parcels2/AddedData.kt | 47 +++ .../kotlin/io/dico/parcels2/GlobalAddedData.kt | 39 +-- src/main/kotlin/io/dico/parcels2/Parcel.kt | 93 +----- src/main/kotlin/io/dico/parcels2/ParcelOwner.kt | 53 ++++ src/main/kotlin/io/dico/parcels2/ParcelWorld.kt | 2 +- src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt | 14 +- src/main/kotlin/io/dico/parcels2/WorldGenerator.kt | 2 +- .../io/dico/parcels2/blockvisitor/Attachables.kt | 2 +- .../io/dico/parcels2/blockvisitor/Schematic.kt | 1 + .../dico/parcels2/blockvisitor/WorktimeLimiter.kt | 27 +- .../io/dico/parcels2/command/CommandsGeneral.kt | 13 +- .../io/dico/parcels2/command/ParcelMatcher.kt | 23 ++ .../dico/parcels2/command/ParcelParameterTypes.kt | 1 + .../io/dico/parcels2/listener/ListenerHelper.kt | 23 ++ .../kotlin/io/dico/parcels2/storage/Backing.kt | 6 +- .../io/dico/parcels2/storage/ExposedBacking.kt | 322 --------------------- .../kotlin/io/dico/parcels2/storage/Storage.kt | 20 +- .../io/dico/parcels2/storage/StorageFactory.kt | 3 +- .../parcels2/storage/exposed/ExposedBacking.kt | 186 ++++++++++++ .../io/dico/parcels2/storage/exposed/IdTables.kt | 121 ++++++++ .../io/dico/parcels2/storage/exposed/ListTables.kt | 104 +++++++ .../kotlin/io/dico/parcels2/util/BukkitAwait.kt | 2 - .../kotlin/io/dico/parcels2/util/FunctionHelper.kt | 53 ++++ src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt | 18 +- src/main/kotlin/io/dico/parcels2/util/Vec3i.kt | 34 ++- 26 files changed, 726 insertions(+), 487 deletions(-) create mode 100644 src/main/kotlin/io/dico/parcels2/AddedData.kt create mode 100644 src/main/kotlin/io/dico/parcels2/ParcelOwner.kt create mode 100644 src/main/kotlin/io/dico/parcels2/command/ParcelMatcher.kt create mode 100644 src/main/kotlin/io/dico/parcels2/listener/ListenerHelper.kt delete mode 100644 src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt create mode 100644 src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt create mode 100644 src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt create mode 100644 src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt create mode 100644 src/main/kotlin/io/dico/parcels2/util/FunctionHelper.kt diff --git a/build.gradle.kts b/build.gradle.kts index 878e17c..11ead8b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -51,7 +51,7 @@ project(":dicore3:dicore3-command") { dependencies { c.kotlinStd(kotlin("stdlib-jdk8")) c.kotlinStd(kotlin("reflect")) - c.kotlinStd(kotlinx("coroutines-core:0.23.4")) + c.kotlinStd(kotlinx("coroutines-core:0.24.0")) compile(project(":dicore3:dicore3-core")) compile("com.thoughtworks.paranamer:paranamer:2.8") @@ -86,6 +86,8 @@ tasks { val compileKotlin by getting(KotlinCompile::class) { kotlinOptions { javaParameters = true + suppressWarnings = true + //freeCompilerArgs = listOf("-XXLanguage:+InlineClasses", "-Xuse-experimental=kotlin.Experimental") } } diff --git a/src/main/kotlin/io/dico/parcels2/AddedData.kt b/src/main/kotlin/io/dico/parcels2/AddedData.kt new file mode 100644 index 0000000..7bcf8f1 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/AddedData.kt @@ -0,0 +1,47 @@ +package io.dico.parcels2 + +import io.dico.parcels2.util.uuid +import org.bukkit.OfflinePlayer +import java.util.* + +interface AddedData { + val added: Map + + fun getAddedStatus(uuid: UUID): AddedStatus + fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean + + fun compareAndSetAddedStatus(uuid: UUID, expect: AddedStatus, status: AddedStatus): Boolean = + (getAddedStatus(uuid) == expect).also { if (it) setAddedStatus(uuid, status) } + + fun isAllowed(uuid: UUID) = getAddedStatus(uuid) == AddedStatus.ALLOWED + fun allow(uuid: UUID) = setAddedStatus(uuid, AddedStatus.ALLOWED) + fun disallow(uuid: UUID) = compareAndSetAddedStatus(uuid, AddedStatus.ALLOWED, AddedStatus.DEFAULT) + fun isBanned(uuid: UUID) = getAddedStatus(uuid) == AddedStatus.BANNED + fun ban(uuid: UUID) = setAddedStatus(uuid, AddedStatus.BANNED) + fun unban(uuid: UUID) = compareAndSetAddedStatus(uuid, AddedStatus.BANNED, AddedStatus.DEFAULT) + + fun isAllowed(player: OfflinePlayer) = isAllowed(player.uuid) + fun allow(player: OfflinePlayer) = allow(player.uuid) + fun disallow(player: OfflinePlayer) = disallow(player.uuid) + fun isBanned(player: OfflinePlayer) = isBanned(player.uuid) + fun ban(player: OfflinePlayer) = ban(player.uuid) + fun unban(player: OfflinePlayer) = unban(player.uuid) +} + +open class AddedDataHolder(override var added: MutableMap + = mutableMapOf()) : AddedData { + 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 +} + +enum class AddedStatus { + DEFAULT, + ALLOWED, + BANNED; + + val isDefault get() = this == DEFAULT + val isAllowed get() = this == ALLOWED + val isBanned get() = this == BANNED +} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt b/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt index eb00c52..055e681 100644 --- a/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt +++ b/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt @@ -1,38 +1,25 @@ +@file:Suppress("UNCHECKED_CAST") + package io.dico.parcels2 -import io.dico.parcels2.util.uuid -import kotlinx.coroutines.experimental.CompletableDeferred -import kotlinx.coroutines.experimental.Deferred -import org.bukkit.OfflinePlayer import java.util.* interface GlobalAddedData : AddedData { - val uuid: UUID + val owner: ParcelOwner } -class GlobalAddedDataManager(val plugin: ParcelsPlugin) { - private val map = mutableMapOf() - - operator fun get(player: OfflinePlayer) = get(player.uuid) - - operator fun get(uuid: UUID): GlobalAddedData? { - - } - - fun getDeferred(uuid: UUID): Deferred { - get(uuid)?.let { return CompletableDeferred(it) } +interface GlobalAddedDataManager { + operator fun get(owner: ParcelOwner): GlobalAddedData +} - } +class GlobalAddedDataManagerImpl(val plugin: ParcelsPlugin) : GlobalAddedDataManager { + private val map = mutableMapOf() - private suspend fun getAsync(uuid: UUID): GlobalAddedData { - val data = plugin.storage.readGlobalAddedData(ParcelOwner(uuid = uuid)).await() - ?: return GlobalAddedDataImpl(uuid) - val result = GlobalAddedDataImpl(uuid, data) - map[uuid] = result - return result + override fun get(owner: ParcelOwner): GlobalAddedData { + return map[owner] ?: GlobalAddedDataImpl(owner).also { map[owner] = it } } - private inner class GlobalAddedDataImpl(override val uuid: UUID, + private inner class GlobalAddedDataImpl(override val owner: ParcelOwner, data: MutableMap = emptyData) : AddedDataHolder(data), GlobalAddedData { @@ -45,7 +32,7 @@ class GlobalAddedDataManager(val plugin: ParcelsPlugin) { data = mutableMapOf() } return super.setAddedStatus(uuid, status).also { - if (it) plugin.storage.setGlobalAddedStatus(ParcelOwner(uuid = this.uuid), uuid, status) + if (it) plugin.storage.setGlobalAddedStatus(owner, uuid, status) } } @@ -59,5 +46,3 @@ class GlobalAddedDataManager(val plugin: ParcelsPlugin) { - - diff --git a/src/main/kotlin/io/dico/parcels2/Parcel.kt b/src/main/kotlin/io/dico/parcels2/Parcel.kt index 1d323dd..c8e7713 100644 --- a/src/main/kotlin/io/dico/parcels2/Parcel.kt +++ b/src/main/kotlin/io/dico/parcels2/Parcel.kt @@ -1,42 +1,16 @@ package io.dico.parcels2 import io.dico.parcels2.util.Vec2i -import io.dico.parcels2.util.getPlayerName import io.dico.parcels2.util.hasBuildAnywhere -import io.dico.parcels2.util.isValid -import io.dico.parcels2.util.uuid import org.bukkit.Bukkit import org.bukkit.OfflinePlayer import org.bukkit.entity.Player import org.joda.time.DateTime import java.util.* -interface AddedData { - val added: Map - - fun getAddedStatus(uuid: UUID): AddedStatus - fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean - - fun compareAndSetAddedStatus(uuid: UUID, expect: AddedStatus, status: AddedStatus): Boolean = - (getAddedStatus(uuid) == expect).also { if (it) setAddedStatus(uuid, status) } - - fun isAllowed(uuid: UUID) = getAddedStatus(uuid) == AddedStatus.ALLOWED - fun allow(uuid: UUID) = setAddedStatus(uuid, AddedStatus.ALLOWED) - fun disallow(uuid: UUID) = compareAndSetAddedStatus(uuid, AddedStatus.ALLOWED, AddedStatus.DEFAULT) - fun isBanned(uuid: UUID) = getAddedStatus(uuid) == AddedStatus.BANNED - fun ban(uuid: UUID) = setAddedStatus(uuid, AddedStatus.BANNED) - fun unban(uuid: UUID) = compareAndSetAddedStatus(uuid, AddedStatus.BANNED, AddedStatus.DEFAULT) - - fun isAllowed(player: OfflinePlayer) = isAllowed(player.uuid) - fun allow(player: OfflinePlayer) = allow(player.uuid) - fun disallow(player: OfflinePlayer) = disallow(player.uuid) - fun isBanned(player: OfflinePlayer) = isBanned(player.uuid) - fun ban(player: OfflinePlayer) = ban(player.uuid) - fun unban(player: OfflinePlayer) = unban(player.uuid) -} - interface ParcelData : AddedData { var owner: ParcelOwner? + val since: DateTime? fun canBuild(player: OfflinePlayer, checkAdmin: Boolean = true, checkGlobal: Boolean = true): Boolean @@ -83,6 +57,8 @@ class Parcel(val world: ParcelWorld, val pos: Vec2i) : ParcelData { override fun isAllowed(uuid: UUID) = data.isAllowed(uuid) override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean) = data.canBuild(player) + override val since: DateTime? get() = data.since + override var owner: ParcelOwner? get() = data.owner set(value) { @@ -94,7 +70,7 @@ class Parcel(val world: ParcelWorld, val pos: Vec2i) : ParcelData { override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean { return data.setAddedStatus(uuid, status).also { - if (it) world.storage.setParcelPlayerState(this, uuid, status.asBoolean) + if (it) world.storage.setParcelPlayerStatus(this, uuid, status) } } @@ -117,16 +93,9 @@ class Parcel(val world: ParcelWorld, val pos: Vec2i) : ParcelData { var hasBlockVisitors: Boolean = false; private set } -open class AddedDataHolder(override var added: MutableMap - = mutableMapOf()) : AddedData { - 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 -} - class ParcelDataHolder : AddedDataHolder(), ParcelData { override var owner: ParcelOwner? = null + override var since: DateTime? = null override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean) = isAllowed(player.uniqueId) || owner.let { it != null && it.matches(player, allowNameMatch = false) } || (checkAdmin && player is Player && player.hasBuildAnywhere) @@ -135,55 +104,3 @@ class ParcelDataHolder : AddedDataHolder(), ParcelData { override var allowInteractInventory = true } -enum class AddedStatus { - DEFAULT, - ALLOWED, - BANNED; - - val asBoolean - get() = when (this) { - DEFAULT -> null - ALLOWED -> true - BANNED -> false - } -} - -@Suppress("UsePropertyAccessSyntax") -class ParcelOwner(val uuid: UUID? = null, - name: String? = null, - val since: DateTime? = null) { - - companion object { - fun create(uuid: UUID?, name: String?, time: DateTime? = null): ParcelOwner? { - return uuid?.let { ParcelOwner(uuid, name, time) } - ?: name?.let { ParcelOwner(uuid, name, time) } - } - } - - val name: String? - - init { - uuid ?: name ?: throw IllegalArgumentException("uuid and/or name must be present") - - if (name != null) this.name = name - else { - val offlinePlayer = Bukkit.getOfflinePlayer(uuid).takeIf { it.isValid } - this.name = offlinePlayer?.name - } - } - - val playerName get() = getPlayerName(uuid, name) - - fun matches(player: OfflinePlayer, allowNameMatch: Boolean = false): Boolean { - return uuid?.let { it == player.uniqueId } ?: false - || (allowNameMatch && name?.let { it == player.name } ?: false) - } - - val onlinePlayer: Player? get() = uuid?.let { Bukkit.getPlayer(uuid) } - val onlinePlayerAllowingNameMatch: Player? get() = onlinePlayer ?: name?.let { Bukkit.getPlayer(name) } - - @Suppress("DEPRECATION") - val offlinePlayer - get() = (uuid?.let { Bukkit.getOfflinePlayer(it) } ?: Bukkit.getOfflinePlayer(name)) - ?.takeIf { it.isValid } -} diff --git a/src/main/kotlin/io/dico/parcels2/ParcelOwner.kt b/src/main/kotlin/io/dico/parcels2/ParcelOwner.kt new file mode 100644 index 0000000..c602ff3 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/ParcelOwner.kt @@ -0,0 +1,53 @@ +package io.dico.parcels2 + +import io.dico.parcels2.util.getPlayerNameOrDefault +import io.dico.parcels2.util.isValid +import io.dico.parcels2.util.uuid +import org.bukkit.Bukkit +import org.bukkit.OfflinePlayer +import org.bukkit.entity.Player +import java.util.* + +@Suppress("UsePropertyAccessSyntax") +class ParcelOwner private constructor(val uuid: UUID?, + name: String?) { + var name: String? = name + get() = field ?: getPlayerNameOrDefault(uuid!!).also { field = it } + private set + + constructor(name: String) : this(null, name) + constructor(uuid: UUID) : this(uuid, null) + constructor(player: OfflinePlayer) : this(player.uuid, player.name) + + companion object { + fun nameless(player: OfflinePlayer) = ParcelOwner(player.uuid, null) + } + + inline val hasUUID: Boolean get() = uuid != null + val onlinePlayer: Player? get() = uuid?.let { Bukkit.getPlayer(uuid) } + val onlinePlayerAllowingNameMatch: Player? get() = onlinePlayer ?: name?.let { Bukkit.getPlayer(name) } + + @Suppress("DEPRECATION") + val offlinePlayer + get() = (uuid?.let { Bukkit.getOfflinePlayer(it) } ?: Bukkit.getOfflinePlayer(name)) + ?.takeIf { it.isValid } + + fun matches(player: OfflinePlayer, allowNameMatch: Boolean = false): Boolean { + return uuid?.let { it == player.uniqueId } ?: false + || (allowNameMatch && name?.let { it == player.name } ?: false) + } + + fun equals(other: ParcelOwner): Boolean { + return if (hasUUID) other.hasUUID && uuid == other.uuid + else !other.hasUUID && name == other.name + } + + override fun equals(other: Any?): Boolean { + return other is ParcelOwner && equals(other) + } + + override fun hashCode(): Int { + return if (hasUUID) uuid!!.hashCode() else name!!.hashCode() + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt index 9a50b31..7bb827c 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt @@ -219,7 +219,7 @@ class DefaultParcelContainer(private val world: ParcelWorld, } fun loadAllData() { - val channel = storage.readParcelData(allParcels(), 100) + val channel = storage.readParcelData(allParcels()) launch(storage.asyncDispatcher) { for ((parcel, data) in channel) { data?.let { parcel.copyDataIgnoringDatabase(it) } diff --git a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt index 53a2d15..d55320e 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt @@ -10,7 +10,9 @@ import io.dico.parcels2.listener.ParcelEntityTracker import io.dico.parcels2.listener.ParcelListeners import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.yamlObjectMapper +import io.dico.parcels2.util.FunctionHelper import io.dico.parcels2.util.tryCreate +import kotlinx.coroutines.experimental.asCoroutineDispatcher import org.bukkit.Bukkit import org.bukkit.plugin.java.JavaPlugin import org.slf4j.LoggerFactory @@ -25,20 +27,15 @@ class ParcelsPlugin : JavaPlugin() { lateinit var options: Options; private set lateinit var worlds: Worlds; private set lateinit var storage: Storage; private set + lateinit var globalAddedData: GlobalAddedDataManager; private set val registrator = Registrator(this) lateinit var entityTracker: ParcelEntityTracker; private set private var listeners: ParcelListeners? = null private var cmdDispatcher: ICommandDispatcher? = null - val worktimeLimiter: WorktimeLimiter by lazy { TickWorktimeLimiter(this, options.tickWorktime) } - val mainThreadDispatcher = object : Executor { - private val mainThread = Thread.currentThread() - override fun execute(command: Runnable) { - if (Thread.currentThread() === mainThread) command.run() - else server.scheduler.runTask(this@ParcelsPlugin, command) - } - } + val functionHelper: FunctionHelper = FunctionHelper(this) + val worktimeLimiter: WorktimeLimiter by lazy { TickWorktimeLimiter(this, options.tickWorktime) } override fun onEnable() { plogger.info("Debug enabled: ${plogger.isDebugEnabled}") @@ -73,6 +70,7 @@ class ParcelsPlugin : JavaPlugin() { return false } + globalAddedData = GlobalAddedDataManagerImpl(this) entityTracker = ParcelEntityTracker(worlds) registerListeners() registerCommands() diff --git a/src/main/kotlin/io/dico/parcels2/WorldGenerator.kt b/src/main/kotlin/io/dico/parcels2/WorldGenerator.kt index 72ca3bd..04e9a26 100644 --- a/src/main/kotlin/io/dico/parcels2/WorldGenerator.kt +++ b/src/main/kotlin/io/dico/parcels2/WorldGenerator.kt @@ -218,7 +218,7 @@ class DefaultParcelGenerator(val worlds: Worlds, val name: String, private val o val sign = signBlock.state as org.bukkit.block.Sign sign.setLine(0, parcel.id) - sign.setLine(2, owner.playerName) + sign.setLine(2, owner.name) sign.update() skullBlock.type = Material.PLAYER_HEAD diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt index c046940..e753295 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt @@ -64,4 +64,4 @@ val attachables: Set = EnumSet.of( WALL_SIGN, LILY_PAD, DANDELION -); \ No newline at end of file +) \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt index 41df083..c375e5a 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt @@ -6,6 +6,7 @@ import io.dico.parcels2.util.get import org.bukkit.World import org.bukkit.block.data.BlockData +// TODO order paste such that attachables are placed after the block they depend on class Schematic { val size: Vec3i get() = _size!! private var _size: Vec3i? = null diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt index 45196f2..7c5f2c5 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt @@ -1,11 +1,12 @@ package io.dico.parcels2.blockvisitor -import kotlinx.coroutines.experimental.* -import org.bukkit.plugin.Plugin +import io.dico.parcels2.ParcelsPlugin +import io.dico.parcels2.util.FunctionHelper +import kotlinx.coroutines.experimental.CancellationException +import kotlinx.coroutines.experimental.Job import org.bukkit.scheduler.BukkitTask import java.lang.System.currentTimeMillis import java.util.* -import java.util.concurrent.Executor import java.util.logging.Level import kotlin.coroutines.experimental.Continuation import kotlin.coroutines.experimental.intrinsics.COROUTINE_SUSPENDED @@ -101,9 +102,7 @@ private interface WorkerContinuation : Worker, WorkerScope { * There is a configurable maxiumum amount of milliseconds that can be allocated to all workers together in each server tick * This object attempts to split that maximum amount of milliseconds equally between all jobs */ -class TickWorktimeLimiter(private val plugin: Plugin, var options: TickWorktimeOptions) : WorktimeLimiter() { - // Coroutine dispatcher for jobs - private val dispatcher = Executor(Runnable::run).asCoroutineDispatcher() +class TickWorktimeLimiter(private val plugin: ParcelsPlugin, var options: TickWorktimeOptions) : WorktimeLimiter() { // The currently registered bukkit scheduler task private var bukkitTask: BukkitTask? = null // The workers. @@ -111,9 +110,9 @@ class TickWorktimeLimiter(private val plugin: Plugin, var options: TickWorktimeO override val workers: List = _workers override fun submit(task: TimeLimitedTask): Worker { - val worker: WorkerContinuation = WorkerImpl(plugin, dispatcher, task) + val worker: WorkerContinuation = WorkerImpl(plugin.functionHelper, task) _workers.addFirst(worker) - if (bukkitTask == null) bukkitTask = plugin.server.scheduler.runTaskTimer(plugin, ::tickJobs, 0, options.tickInterval.toLong()) + if (bukkitTask == null) bukkitTask = plugin.functionHelper.scheduleRepeating(0, options.tickInterval) { tickJobs() } return worker } @@ -146,8 +145,7 @@ class TickWorktimeLimiter(private val plugin: Plugin, var options: TickWorktimeO } -private class WorkerImpl(val plugin: Plugin, - val dispatcher: CoroutineDispatcher, +private class WorkerImpl(val functionHelper: FunctionHelper, val task: TimeLimitedTask) : WorkerContinuation { override var job: Job? = null; private set @@ -179,7 +177,7 @@ private class WorkerImpl(val plugin: Plugin, // report any error that occurred completionException = exception?.also { if (it !is CancellationException) - plugin.logger.log(Level.SEVERE, "TimeLimitedTask for plugin ${plugin.name} generated an exception", it) + functionHelper.plugin.logger.log(Level.SEVERE, "TimeLimitedTask for plugin ${functionHelper.plugin.name} generated an exception", it) } // convert to elapsed time here @@ -236,10 +234,9 @@ private class WorkerImpl(val plugin: Plugin, } try { - launch(context = dispatcher, start = CoroutineStart.UNDISPATCHED) { - initJob(job = kotlin.coroutines.experimental.coroutineContext[Job]!!) - task() - } + val job = functionHelper.launchLazilyOnMainThread { task() } + initJob(job = job) + job.start() } catch (t: Throwable) { // do nothing: handled by job.invokeOnCompletion() } diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt index 78a989e..fa9a696 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt @@ -4,6 +4,7 @@ import io.dico.dicore.command.EMessageType import io.dico.dicore.command.ExecutionContext import io.dico.dicore.command.annotation.Cmd import io.dico.dicore.command.annotation.Desc +import io.dico.dicore.command.annotation.Flag import io.dico.dicore.command.annotation.RequireParameters import io.dico.parcels2.ParcelOwner import io.dico.parcels2.ParcelsPlugin @@ -84,12 +85,22 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { @Cmd("clear") @ParcelRequire(owner = true) - fun ParcelScope.cmdClear(context: ExecutionContext) { + fun ParcelScope.cmdClear(context: ExecutionContext, @Flag sure: Boolean): Any? { + if (!sure) return "Are you sure? You cannot undo this action!\n" + + "Type ${context.rawInput} -sure if you want to go through with this." + world.generator.clearParcel(parcel) .onProgressUpdate(1000, 1000) { progress, elapsedTime -> context.sendMessage(EMessageType.INFORMATIVE, "Clear progress: %.02f%%, %.2fs elapsed" .format(progress * 100, elapsedTime / 1000.0)) } + + return null + } + + @Cmd("swap") + fun ParcelScope.cmdSwap(context: ExecutionContext, @Flag sure: Boolean): Any? { + } @Cmd("make_mess") diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelMatcher.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelMatcher.kt new file mode 100644 index 0000000..e9472bc --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelMatcher.kt @@ -0,0 +1,23 @@ +package io.dico.parcels2.command + +import io.dico.parcels2.Parcel +import io.dico.parcels2.ParcelWorld +import io.dico.parcels2.ParcelsPlugin +import kotlinx.coroutines.experimental.Deferred + +interface ParcelTarget { + val world: ParcelWorld + val isByID: Boolean + val isByOwner: Boolean get() = !isByID + suspend fun ParcelsPlugin.await(): Parcel? + fun ParcelsPlugin.get(): Deferred = +} + +class ParcelTargetByOwner : ParcelTarget { + override val isByID get() = false +} + +class ParcelTargetByID : ParcelTarget { + override val isByID get() = true + +} diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt index ab97023..dcc3f8c 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt @@ -7,6 +7,7 @@ import io.dico.dicore.command.parameter.type.ParameterConfig import io.dico.dicore.command.parameter.type.ParameterType import io.dico.parcels2.Parcel import io.dico.parcels2.ParcelWorld +import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.Worlds import io.dico.parcels2.util.isValid import org.bukkit.Bukkit diff --git a/src/main/kotlin/io/dico/parcels2/listener/ListenerHelper.kt b/src/main/kotlin/io/dico/parcels2/listener/ListenerHelper.kt new file mode 100644 index 0000000..97d045f --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/listener/ListenerHelper.kt @@ -0,0 +1,23 @@ +package io.dico.parcels2.listener + +import io.dico.dicore.RegistratorListener +import io.dico.parcels2.ParcelsPlugin +import org.bukkit.event.Event + +interface HasPlugin { + val plugin: ParcelsPlugin +} + +inline fun HasPlugin.listener(crossinline block: suspend (T) -> Unit) = RegistratorListener { event -> + + + + + + + + + +} + + diff --git a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt index 7f00976..7224dd1 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt @@ -37,15 +37,17 @@ interface Backing { suspend fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) - suspend fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?) + suspend fun setLocalPlayerStatus(parcelFor: Parcel, player: UUID, status: AddedStatus) suspend fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean) suspend fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean) + suspend fun ProducerScope>>.produceAllGlobalAddedData() + suspend fun readGlobalAddedData(owner: ParcelOwner): MutableMap - suspend fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) + suspend fun setGlobalPlayerStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) } \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt b/src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt deleted file mode 100644 index 1a8a252..0000000 --- a/src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt +++ /dev/null @@ -1,322 +0,0 @@ -package io.dico.parcels2.storage - -import com.zaxxer.hikari.HikariDataSource -import io.dico.parcels2.* -import io.dico.parcels2.util.Vec2i -import io.dico.parcels2.util.toByteArray -import io.dico.parcels2.util.toUUID -import kotlinx.coroutines.experimental.channels.ProducerScope -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SchemaUtils.create -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.vendors.DatabaseDialect -import org.joda.time.DateTime -import java.util.* -import javax.sql.DataSource - -object WorldsT : Table("worlds") { - val id = integer("world_id").autoIncrement().primaryKey() - val name = varchar("name", 50) - val uid = binary("uid", 16) - val index_uid = uniqueIndexR("index_uid", uid) -} - -object ParcelsT : Table("parcels") { - val id = integer("parcel_id").autoIncrement().primaryKey() - val px = integer("px") - val pz = integer("pz") - val world_id = integer("world_id").references(WorldsT.id) - val owner_uuid = binary("owner_uuid", 16).nullable() - val owner_name = varchar("owner_name", 16).nullable() - val claim_time = datetime("claim_time").nullable() - val index_location = uniqueIndexR("index_location", world_id, px, pz) -} - -object AddedLocalT : Table("parcels_added_local") { - val parcel_id = integer("parcel_id").references(ParcelsT.id, ReferenceOption.CASCADE) - val player_uuid = binary("player_uuid", 16) - val allowed_flag = bool("allowed_flag") - val index_pair = uniqueIndexR("index_pair", parcel_id, player_uuid) -} - -object AddedGlobalT : Table("parcels_added_global") { - val owner_uuid = binary("owner_uuid", 16) - val player_uuid = binary("player_uuid", 16) - val allowed_flag = bool("allowed_flag") - val index_pair = uniqueIndexR("index_pair", owner_uuid, player_uuid) -} - -object ParcelOptionsT : Table("parcel_options") { - val parcel_id = integer("parcel_id").primaryKey().references(ParcelsT.id, ReferenceOption.CASCADE) - val interact_inventory = bool("interact_inventory").default(false) - val interact_inputs = bool("interact_inputs").default(false) -} - -private class ExposedDatabaseException(message: String? = null) : Exception(message) - -@Suppress("NOTHING_TO_INLINE") -class ExposedBacking(private val dataSourceFactory: () -> DataSource) : Backing { - override val name get() = "Exposed" - private var dataSource: DataSource? = null - private var database: Database? = null - private var isShutdown: Boolean = false - - override val isConnected get() = database != null - - companion object { - init { - Database.registerDialect("mariadb") { - Class.forName("org.jetbrains.exposed.sql.vendors.MysqlDialect").newInstance() as DatabaseDialect - } - } - } - - override suspend fun init() { - if (isShutdown) throw IllegalStateException() - dataSource = dataSourceFactory() - database = Database.connect(dataSource!!) - transaction(database) { - create(WorldsT, ParcelsT, AddedLocalT, ParcelOptionsT) - } - } - - override suspend fun shutdown() { - if (isShutdown) throw IllegalStateException() - dataSource?.let { - if (it is HikariDataSource) it.close() - } - database = null - isShutdown = true - } - - private fun transaction(statement: Transaction.() -> T) = transaction(database, statement) - - private inline fun Transaction.getWorldId(binaryUid: ByteArray): Int? { - return WorldsT.select { WorldsT.uid eq binaryUid }.firstOrNull()?.let { it[WorldsT.id] } - } - - private inline fun Transaction.getWorldId(worldUid: UUID): Int? { - return getWorldId(worldUid.toByteArray()!!) - } - - private inline fun Transaction.getOrInitWorldId(worldUid: UUID, worldName: String): Int { - val binaryUid = worldUid.toByteArray()!! - return getWorldId(binaryUid) - ?: WorldsT.insert /*Ignore*/ { 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] } - } - - private inline fun Transaction.getParcelId(worldUid: UUID, parcelX: Int, parcelZ: Int): Int? { - return getWorldId(worldUid)?.let { getParcelId(it, parcelX, parcelZ) } - } - - 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.insert /*Ignore*/ { 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? { - return ParcelsT.select { ParcelsT.id eq id }.firstOrNull() - } - - fun Transaction.getWorldId(world: ParcelWorld): Int? { - return getWorldId(world.world.uid) - } - - fun Transaction.getOrInitWorldId(world: ParcelWorld): Int { - return world.world.let { getOrInitWorldId(it.uid, it.name) } - } - - fun Transaction.getParcelId(parcel: Parcel): Int? { - return getParcelId(parcel.world.world.uid, parcel.pos.x, parcel.pos.z) - } - - fun Transaction.getOrInitParcelId(parcel: Parcel): Int { - return parcel.world.world.let { getOrInitParcelId(it.uid, it.name, parcel.pos.x, parcel.pos.z) } - } - - fun Transaction.getParcelRow(parcel: Parcel): ResultRow? { - return getParcelId(parcel)?.let { getParcelRow(it) } - } - - override suspend fun ProducerScope>.produceParcelData(parcels: Sequence) { - for (parcel in parcels) { - val data = readParcelData(parcel) - channel.send(parcel to data) - } - channel.close() - } - - override suspend fun ProducerScope>.produceAllParcelData() { - ParcelsT.selectAll().forEach { row -> - val parcel = rowToSerializableParcel(row) ?: return@forEach - val data = rowToParcelData(row) - channel.send(parcel to data) - } - channel.close() - } - - override suspend fun readParcelData(parcelFor: Parcel): ParcelData? = transaction { - val row = getParcelRow(parcelFor) ?: return@transaction null - rowToParcelData(row) - } - - override suspend fun getOwnedParcels(user: ParcelOwner): List = transaction { - val where: SqlExpressionBuilder.() -> Op - - if (user.uuid != null) { - val binaryUuid = user.uuid.toByteArray() - where = { ParcelsT.owner_uuid eq binaryUuid } - } else { - val name = user.name - where = { ParcelsT.owner_name eq name } - } - - ParcelsT.select(where) - .orderBy(ParcelsT.claim_time, isAsc = true) - .mapNotNull(::rowToSerializableParcel) - .toList() - } - - - override suspend fun setParcelData(parcelFor: Parcel, data: ParcelData?) { - if (data == null) { - transaction { - getParcelId(parcelFor)?.let { id -> - ParcelsT.deleteIgnoreWhere() { ParcelsT.id eq id } - - // Below should cascade automatically - /* - AddedLocalT.deleteIgnoreWhere { AddedLocalT.parcel_id eq id } - ParcelOptionsT.deleteIgnoreWhere(limit = 1) { ParcelOptionsT.parcel_id eq id } - */ - } - - } - return - } - - val id = transaction { - val id = getOrInitParcelId(parcelFor) - AddedLocalT.deleteIgnoreWhere { AddedLocalT.parcel_id eq id } - id - } - - setParcelOwner(parcelFor, data.owner) - - for ((uuid, status) in data.added) { - val state = status.asBoolean - setParcelPlayerState(parcelFor, uuid, state) - } - - setParcelAllowsInteractInputs(parcelFor, data.allowInteractInputs) - setParcelAllowsInteractInventory(parcelFor, data.allowInteractInventory) - } - - override suspend fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) = transaction { - val binaryUuid = owner?.uuid?.toByteArray() - val name = owner?.name - val time = owner?.let { DateTime.now() } - - val id = if (owner == null) - getParcelId(parcelFor) ?: return@transaction - else - getOrInitParcelId(parcelFor) - - ParcelsT.update({ ParcelsT.id eq id }) { - it[ParcelsT.owner_uuid] = binaryUuid - it[ParcelsT.owner_name] = name - it[ParcelsT.claim_time] = time - } - } - - override suspend fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?) = transaction { - val binaryUuid = player.toByteArray()!! - - if (state == null) { - getParcelId(parcelFor)?.let { id -> - AddedLocalT.deleteWhere { (AddedLocalT.parcel_id eq id) and (AddedLocalT.player_uuid eq binaryUuid) } - } - return@transaction - } - - val id = getOrInitParcelId(parcelFor) - AddedLocalT.upsert(AddedLocalT.parcel_id) { - it[AddedLocalT.parcel_id] = id - it[AddedLocalT.player_uuid] = binaryUuid - } - } - - override suspend fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean): Unit = transaction { - val id = getOrInitParcelId(parcel) - /*ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { - it[ParcelOptionsT.parcel_id] = id - it[ParcelOptionsT.interact_inventory] = value - }*/ - - ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { - it[ParcelOptionsT.parcel_id] = id - it[ParcelOptionsT.interact_inventory] = value - } - } - - override suspend fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean): Unit = transaction { - val id = getOrInitParcelId(parcel) - ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { - it[ParcelOptionsT.parcel_id] = id - it[ParcelOptionsT.interact_inputs] = value - } - } - - override suspend fun readGlobalAddedData(owner: ParcelOwner): AddedData? { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - override suspend fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: Boolean?) { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - private fun rowToSerializableParcel(row: ResultRow): SerializableParcel? { - val worldId = row[ParcelsT.world_id] - val worldRow = WorldsT.select { WorldsT.id eq worldId }.firstOrNull() - ?: return null - - val world = SerializableWorld(worldRow[WorldsT.name], worldRow[WorldsT.uid].toUUID()) - return SerializableParcel(world, Vec2i(row[ParcelsT.px], row[ParcelsT.pz])) - } - - private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply { - owner = ParcelOwner.create( - uuid = row[ParcelsT.owner_uuid]?.toUUID(), - name = row[ParcelsT.owner_name], - time = row[ParcelsT.claim_time] - ) - - val parcelId = row[ParcelsT.id] - AddedLocalT.select { AddedLocalT.parcel_id eq parcelId }.forEach { - val uuid = it[AddedLocalT.player_uuid].toUUID()!! - val status = if (it[AddedLocalT.allowed_flag]) AddedStatus.ALLOWED else AddedStatus.BANNED - setAddedStatus(uuid, status) - } - - ParcelOptionsT.select { ParcelOptionsT.parcel_id eq parcelId }.firstOrNull()?.let { - allowInteractInputs = it[ParcelOptionsT.interact_inputs] - allowInteractInventory = it[ParcelOptionsT.interact_inventory] - } - } - -} - - - - - - - diff --git a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt index 2323571..36f241e 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt @@ -24,9 +24,9 @@ interface Storage { fun readParcelData(parcelFor: Parcel): Deferred - fun readParcelData(parcelsFor: Sequence, channelCapacity: Int): ReceiveChannel> + fun readParcelData(parcelsFor: Sequence): ReceiveChannel> - fun readAllParcelData(channelCapacity: Int): ReceiveChannel> + fun readAllParcelData(): ReceiveChannel> fun getOwnedParcels(user: ParcelOwner): Deferred> @@ -37,13 +37,15 @@ interface Storage { fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?): Job - fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?): Job + fun setParcelPlayerStatus(parcelFor: Parcel, player: UUID, status: AddedStatus): Job fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean): Job fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean): Job + fun readAllGlobalAddedData(): ReceiveChannel>> + fun readGlobalAddedData(owner: ParcelOwner): Deferred?> fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus): Job @@ -55,6 +57,7 @@ class StorageWithCoroutineBacking internal constructor(val backing: Backing) : S val poolSize: Int get() = 4 override val asyncDispatcher = Executors.newFixedThreadPool(poolSize) { Thread(it, "Parcels2_StorageThread") }.asCoroutineDispatcher() override val isConnected get() = backing.isConnected + val channelCapacity = 16 @Suppress("NOTHING_TO_INLINE") private inline fun defer(noinline block: suspend CoroutineScope.() -> T): Deferred { @@ -73,10 +76,10 @@ class StorageWithCoroutineBacking internal constructor(val backing: Backing) : S override fun readParcelData(parcelFor: Parcel) = defer { backing.readParcelData(parcelFor) } - override fun readParcelData(parcelsFor: Sequence, channelCapacity: Int) = + override fun readParcelData(parcelsFor: Sequence) = produce(asyncDispatcher, capacity = channelCapacity) { with(backing) { produceParcelData(parcelsFor) } } - override fun readAllParcelData(channelCapacity: Int): ReceiveChannel> = + override fun readAllParcelData(): ReceiveChannel> = produce(asyncDispatcher, capacity = channelCapacity) { with(backing) { produceAllParcelData() } } override fun getOwnedParcels(user: ParcelOwner) = defer { backing.getOwnedParcels(user) } @@ -87,14 +90,17 @@ class StorageWithCoroutineBacking internal constructor(val backing: Backing) : S override fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) = job { backing.setParcelOwner(parcelFor, owner) } - override fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?) = job { backing.setParcelPlayerState(parcelFor, player, state) } + override fun setParcelPlayerStatus(parcelFor: Parcel, player: UUID, status: AddedStatus) = job { backing.setLocalPlayerStatus(parcelFor, player, status) } override fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean) = job { backing.setParcelAllowsInteractInventory(parcel, value) } override fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean) = job { backing.setParcelAllowsInteractInputs(parcel, value) } + override fun readAllGlobalAddedData(): ReceiveChannel>> = + produce(asyncDispatcher, capacity = channelCapacity) { with(backing) { produceAllGlobalAddedData() } } + override fun readGlobalAddedData(owner: ParcelOwner): Deferred?> = defer { backing.readGlobalAddedData(owner) } - override fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) = job { backing.setGlobalAddedStatus(owner, player, status) } + override fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) = job { backing.setGlobalPlayerStatus(owner, player, status) } } diff --git a/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt b/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt index bb5013a..90992c6 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt @@ -2,6 +2,7 @@ package io.dico.parcels2.storage import com.zaxxer.hikari.HikariDataSource import io.dico.parcels2.DataConnectionOptions +import io.dico.parcels2.storage.exposed.ExposedBacking import kotlin.reflect.KClass interface StorageFactory { @@ -35,7 +36,7 @@ class ConnectionStorageFactory : StorageFactory { override fun newStorageInstance(dialect: String, options: Any): Storage { val hikariConfig = getHikariConfig(dialect, options as DataConnectionOptions) - val dataSourceFactory = { HikariDataSource(hikariConfig) } + val dataSourceFactory = suspend { HikariDataSource(hikariConfig) } return StorageWithCoroutineBacking(ExposedBacking(dataSourceFactory)) } diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt new file mode 100644 index 0000000..affa14e --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt @@ -0,0 +1,186 @@ +@file:Suppress("NOTHING_TO_INLINE", "PARAMETER_NAME_CHANGED_ON_OVERRIDE", "LocalVariableName") + +package io.dico.parcels2.storage.exposed + +import com.zaxxer.hikari.HikariDataSource +import io.dico.parcels2.* +import io.dico.parcels2.storage.* +import io.dico.parcels2.util.toUUID +import kotlinx.coroutines.experimental.channels.ProducerScope +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SchemaUtils.create +import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.sql.vendors.DatabaseDialect +import org.joda.time.DateTime +import java.util.* +import javax.sql.DataSource + +class ExposedDatabaseException(message: String? = null) : Exception(message) + +class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) : Backing { + override val name get() = "Exposed" + private var dataSource: DataSource? = null + private var database: Database? = null + private var isShutdown: Boolean = false + + override val isConnected get() = database != null + + companion object { + init { + Database.registerDialect("mariadb") { + Class.forName("org.jetbrains.exposed.sql.vendors.MysqlDialect").newInstance() as DatabaseDialect + } + } + } + + private fun transaction(statement: Transaction.() -> T) = transaction(database!!, statement) + + override suspend fun init() { + if (isShutdown) throw IllegalStateException() + dataSource = dataSourceFactory() + database = Database.connect(dataSource!!) + transaction(database) { + create(WorldsT, OwnersT, ParcelsT, ParcelOptionsT, AddedLocalT, AddedGlobalT) + } + } + + override suspend fun shutdown() { + if (isShutdown) throw IllegalStateException() + dataSource?.let { + (it as? HikariDataSource)?.close() + } + database = null + isShutdown = true + } + + override suspend fun ProducerScope>.produceParcelData(parcels: Sequence) { + for (parcel in parcels) { + val data = readParcelData(parcel) + channel.send(parcel to data) + } + channel.close() + } + + override suspend fun ProducerScope>.produceAllParcelData() { + ParcelsT.selectAll().forEach { row -> + val parcel = ParcelsT.getSerializable(row) ?: return@forEach + val data = rowToParcelData(row) + channel.send(parcel to data) + } + channel.close() + } + + override suspend fun readParcelData(parcelFor: Parcel): ParcelData? = transaction { + val row = ParcelsT.getRow(parcelFor) ?: return@transaction null + rowToParcelData(row) + } + + override suspend fun getOwnedParcels(user: ParcelOwner): List = transaction { + val user_id = OwnersT.getId(user) ?: return@transaction emptyList() + ParcelsT.select { ParcelsT.owner_id eq user_id } + .orderBy(ParcelsT.claim_time, isAsc = true) + .mapNotNull(ParcelsT::getSerializable) + .toList() + } + + override suspend fun setParcelData(parcelFor: Parcel, data: ParcelData?) { + if (data == null) { + transaction { + ParcelsT.getId(parcelFor)?.let { id -> + ParcelsT.deleteIgnoreWhere { ParcelsT.id eq id } + + // Below should cascade automatically + /* + AddedLocalT.deleteIgnoreWhere { AddedLocalT.parcel_id eq id } + ParcelOptionsT.deleteIgnoreWhere(limit = 1) { ParcelOptionsT.parcel_id eq id } + */ + } + + } + return + } + + transaction { + val id = ParcelsT.getOrInitId(parcelFor) + AddedLocalT.deleteIgnoreWhere { AddedLocalT.attach_id eq id } + } + + setParcelOwner(parcelFor, data.owner) + + for ((uuid, status) in data.added) { + setLocalPlayerStatus(parcelFor, uuid, status) + } + + setParcelAllowsInteractInputs(parcelFor, data.allowInteractInputs) + setParcelAllowsInteractInventory(parcelFor, data.allowInteractInventory) + } + + override suspend fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) = transaction { + val id = if (owner == null) + ParcelsT.getId(parcelFor) ?: return@transaction + else + ParcelsT.getOrInitId(parcelFor) + + val owner_id = owner?.let { OwnersT.getOrInitId(it) } + val time = owner?.let { DateTime.now() } + + ParcelsT.update({ ParcelsT.id eq id }) { + it[ParcelsT.owner_id] = owner_id + it[claim_time] = time + } + } + + override suspend fun setLocalPlayerStatus(parcelFor: Parcel, player: UUID, status: AddedStatus) = transaction { + AddedLocalT.setPlayerStatus(parcelFor, player, status) + } + + override suspend fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean): Unit = transaction { + val id = ParcelsT.getOrInitId(parcel) + ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { + it[ParcelOptionsT.parcel_id] = id + it[ParcelOptionsT.interact_inventory] = value + } + } + + override suspend fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean): Unit = transaction { + val id = ParcelsT.getOrInitId(parcel) + ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { + it[ParcelOptionsT.parcel_id] = id + it[ParcelOptionsT.interact_inputs] = value + } + } + + override suspend fun ProducerScope>>.produceAllGlobalAddedData() { + AddedGlobalT.sendAllAddedData(channel) + channel.close() + } + + override suspend fun readGlobalAddedData(owner: ParcelOwner): MutableMap { + return AddedGlobalT.readAddedData(OwnersT.getId(owner) ?: return hashMapOf()) + } + + override suspend fun setGlobalPlayerStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) = transaction { + AddedGlobalT.setPlayerStatus(owner, player, status) + } + + private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply { + owner = row[ParcelsT.owner_id]?.let { OwnersT.getSerializable(it) } + since = row[ParcelsT.claim_time] + + val parcelId = row[ParcelsT.id] + added = AddedLocalT.readAddedData(parcelId) + + AddedLocalT.select { AddedLocalT.attach_id eq parcelId }.forEach { + val uuid = it[AddedLocalT.player_uuid].toUUID() + val status = if (it[AddedLocalT.allowed_flag]) AddedStatus.ALLOWED else AddedStatus.BANNED + setAddedStatus(uuid, status) + } + + ParcelOptionsT.select { ParcelOptionsT.parcel_id eq parcelId }.firstOrNull()?.let { + allowInteractInputs = it[ParcelOptionsT.interact_inputs] + allowInteractInventory = it[ParcelOptionsT.interact_inventory] + } + } + +} + diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt new file mode 100644 index 0000000..c75ea9f --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt @@ -0,0 +1,121 @@ +@file:Suppress("NOTHING_TO_INLINE", "PARAMETER_NAME_CHANGED_ON_OVERRIDE", "unused", "MemberVisibilityCanBePrivate") + +package io.dico.parcels2.storage.exposed + +import io.dico.parcels2.Parcel +import io.dico.parcels2.ParcelOwner +import io.dico.parcels2.ParcelWorld +import io.dico.parcels2.storage.SerializableParcel +import io.dico.parcels2.storage.SerializableWorld +import io.dico.parcels2.storage.uniqueIndexR +import io.dico.parcels2.util.Vec2i +import io.dico.parcels2.util.toByteArray +import io.dico.parcels2.util.toUUID +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.statements.InsertStatement +import java.util.* + +sealed class IdTransactionsTable, + QueryObj, SerializableObj>(tableName: String, columnName: String) + : Table(tableName) { + val id = integer(columnName).autoIncrement().primaryKey() + + @Suppress("UNCHECKED_CAST") + inline val table: TableT + get() = this as TableT + + internal inline fun getId(where: SqlExpressionBuilder.(TableT) -> Op): Int? { + return select { where(table) }.firstOrNull()?.let { it[id] } + } + + internal inline fun insertAndGetId(objName: String, noinline body: TableT.(InsertStatement) -> Unit): Int { + return table.insert(body)[id] ?: insertError(objName) + } + + private inline fun insertError(obj: String): Nothing = throw ExposedDatabaseException("This should not happen - failed to insert $obj and get its id") + + abstract fun getId(obj: QueryObj): Int? + abstract fun getOrInitId(obj: QueryObj): Int + fun getSerializable(id: Int): SerializableObj? = select { this@IdTransactionsTable.id eq id }.firstOrNull()?.let { getSerializable(it) } + abstract fun getSerializable(row: ResultRow): SerializableObj? +} + +object WorldsT : IdTransactionsTable("parcel_worlds", "world_id") { + val name = varchar("name", 50) + val uid = binary("uid", 2) + val index_uid = uniqueIndexR("index_uid", uid) + + internal inline fun getId(binaryUid: ByteArray): Int? = getId { uid eq binaryUid } + internal inline fun getId(uid: UUID): Int? = getId(uid.toByteArray()) + internal inline fun getOrInitId(worldUid: UUID, worldName: String): Int = worldUid.toByteArray().let { binaryUid -> + getId(binaryUid) + ?: insertAndGetId("world named $worldName") { it[uid] = binaryUid; it[name] = worldName } + } + + override fun getId(world: ParcelWorld): Int? = getId(world.world.uid) + override fun getOrInitId(world: ParcelWorld): Int = world.world.let { getOrInitId(it.uid, it.name) } + + override fun getSerializable(row: ResultRow): SerializableWorld { + return SerializableWorld(row[name], row[uid].toUUID()) + } +} + +object ParcelsT : IdTransactionsTable("parcels", "parcel_id") { + val world_id = integer("world_id").references(WorldsT.id) + val px = integer("px") + val pz = integer("pz") + val owner_id = integer("owner_id").references(OwnersT.id).nullable() + val claim_time = datetime("claim_time").nullable() + val index_location = uniqueIndexR("index_location", world_id, px, pz) + + private inline fun getId(worldId: Int, parcelX: Int, parcelZ: Int): Int? = getId { world_id.eq(worldId) and px.eq(parcelX) and pz.eq(parcelZ) } + private inline fun getId(worldUid: UUID, parcelX: Int, parcelZ: Int): Int? = WorldsT.getId(worldUid)?.let { getId(it, parcelX, parcelZ) } + private inline fun getOrInitId(worldUid: UUID, worldName: String, parcelX: Int, parcelZ: Int): Int { + val worldId = WorldsT.getOrInitId(worldUid, worldName) + return getId(worldId, parcelX, parcelZ) + ?: insertAndGetId("parcel at $worldName($parcelX, $parcelZ)") { it[world_id] = worldId; it[px] = parcelX; it[pz] = parcelZ } + } + + override fun getId(parcel: Parcel): Int? = getId(parcel.world.world.uid, parcel.pos.x, parcel.pos.z) + override fun getOrInitId(parcel: Parcel): Int = parcel.world.world.let { getOrInitId(it.uid, it.name, parcel.pos.x, parcel.pos.z) } + + private inline fun getRow(id: Int): ResultRow? = select { ParcelsT.id eq id }.firstOrNull() + fun getRow(parcel: Parcel): ResultRow? = getId(parcel)?.let { getRow(it) } + + override fun getSerializable(row: ResultRow): SerializableParcel? { + val worldId = row[world_id] + val world = WorldsT.getSerializable(worldId) ?: return null + return SerializableParcel(world, Vec2i(row[px], row[pz])) + } +} + +object OwnersT : IdTransactionsTable("parcel_owners", "owner_id") { + val uuid = binary("uuid", 2).nullable() + val name = varchar("name", 32).nullable() + val index_pair = uniqueIndexR("index_pair", uuid, name) + + private inline fun getId(binaryUuid: ByteArray) = getId { uuid eq binaryUuid } + private inline fun getId(uuid: UUID) = getId(uuid.toByteArray()) + private inline fun getId(name: String) = getId { OwnersT.name eq name } + + private inline fun getOrInitId(uuid: UUID) = uuid.toByteArray().let { binaryUuid -> + getId(binaryUuid) + ?: insertAndGetId("owner(uuid = $uuid)") { it[OwnersT.uuid] = binaryUuid } + } + + private inline fun getOrInitId(name: String) = + getId(name) + ?: insertAndGetId("owner(name = $name)") { it[OwnersT.name] = name } + + override fun getId(owner: ParcelOwner): Int? = + if (owner.hasUUID) getId(owner.uuid!!) + else getId(owner.name!!) + + override fun getOrInitId(owner: ParcelOwner): Int = + if (owner.hasUUID) getOrInitId(owner.uuid!!) + else getOrInitId(owner.name!!) + + override fun getSerializable(row: ResultRow): ParcelOwner { + return row[uuid]?.toUUID()?.let { ParcelOwner(it) } ?: ParcelOwner(row[name]!!) + } +} diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt new file mode 100644 index 0000000..5c6ce25 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt @@ -0,0 +1,104 @@ +@file:Suppress("PropertyName", "LocalVariableName", "NOTHING_TO_INLINE") + +package io.dico.parcels2.storage.exposed + +import io.dico.parcels2.AddedStatus +import io.dico.parcels2.Parcel +import io.dico.parcels2.ParcelOwner +import io.dico.parcels2.storage.SerializableParcel +import io.dico.parcels2.storage.uniqueIndexR +import io.dico.parcels2.storage.upsert +import io.dico.parcels2.util.toByteArray +import io.dico.parcels2.util.toUUID +import kotlinx.coroutines.experimental.channels.SendChannel +import org.jetbrains.exposed.sql.* +import java.util.* + +object AddedLocalT : AddedTable("parcels_added_local", ParcelsT) +object AddedGlobalT : AddedTable("parcels_added_global", OwnersT) + +object ParcelOptionsT : Table("parcel_options") { + val parcel_id = integer("parcel_id").primaryKey().references(ParcelsT.id, ReferenceOption.CASCADE) + val interact_inventory = bool("interact_inventory").default(false) + val interact_inputs = bool("interact_inputs").default(false) +} + +typealias AddedStatusSendChannel = SendChannel>> + +sealed class AddedTable(name: String, val idTable: IdTransactionsTable<*, AttachT, SerializableT>) : Table(name) { + val attach_id = integer("attach_id").references(idTable.id, ReferenceOption.CASCADE) + val player_uuid = binary("player_uuid", 16) + val allowed_flag = bool("allowed_flag") + val index_pair = uniqueIndexR("index_pair", attach_id, player_uuid) + + fun setPlayerStatus(attachedOn: AttachT, player: UUID, status: AddedStatus) { + val binaryUuid = player.toByteArray() + + if (status.isDefault) { + idTable.getId(attachedOn)?.let { id -> + deleteWhere { (attach_id eq id) and (player_uuid eq binaryUuid) } + } + return + } + + val id = idTable.getOrInitId(attachedOn) + upsert(conflictIndex = index_pair) { + it[attach_id] = id + it[player_uuid] = binaryUuid + it[allowed_flag] = status.isAllowed + } + } + + fun readAddedData(id: Int): MutableMap { + return slice(player_uuid, allowed_flag).select { attach_id eq id } + .associateByTo(hashMapOf(), { it[player_uuid].toUUID() }, { it[allowed_flag].asAddedStatus() }) + } + + suspend fun sendAllAddedData(channel: AddedStatusSendChannel) { + val iterator = selectAll().orderBy(attach_id).iterator() + + if (iterator.hasNext()) { + val firstRow = iterator.next() + var id: Int = firstRow[attach_id] + var attach: SerializableT? = null + var map: MutableMap? = null + + fun initAttachAndMap() { + attach = idTable.getSerializable(id) + map = attach?.let { mutableMapOf() } + } + + suspend fun sendIfPresent() { + if (attach != null && map != null && map!!.isNotEmpty()) { + channel.send(attach!! to map!!) + } + attach = null + map = null + } + + initAttachAndMap() + + for (row in iterator) { + val rowId = row[attach_id] + if (rowId != id) { + sendIfPresent() + id = rowId + initAttachAndMap() + } + + if (attach == null) { + continue // owner not found for this owner id + } + + val player_uuid = row[player_uuid].toUUID() + val status = row[allowed_flag].asAddedStatus() + map!![player_uuid] = status + } + + sendIfPresent() + } + } + + private inline fun Boolean?.asAddedStatus() = if (this == null) AddedStatus.DEFAULT else if (this) AddedStatus.ALLOWED else AddedStatus.BANNED + +} diff --git a/src/main/kotlin/io/dico/parcels2/util/BukkitAwait.kt b/src/main/kotlin/io/dico/parcels2/util/BukkitAwait.kt index 4ca549f..0df14e7 100644 --- a/src/main/kotlin/io/dico/parcels2/util/BukkitAwait.kt +++ b/src/main/kotlin/io/dico/parcels2/util/BukkitAwait.kt @@ -49,8 +49,6 @@ class AwaitTask : Runnable { onSuccess!!.invoke() } - elapsedChecks++ - if (maxChecks in 1 until elapsedChecks) { cancel() onFailure?.invoke() diff --git a/src/main/kotlin/io/dico/parcels2/util/FunctionHelper.kt b/src/main/kotlin/io/dico/parcels2/util/FunctionHelper.kt new file mode 100644 index 0000000..ea16652 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/util/FunctionHelper.kt @@ -0,0 +1,53 @@ +package io.dico.parcels2.util + +import io.dico.parcels2.ParcelsPlugin +import kotlinx.coroutines.experimental.* +import org.bukkit.scheduler.BukkitTask +import kotlin.coroutines.experimental.CoroutineContext + +@Suppress("NOTHING_TO_INLINE") +class FunctionHelper(val plugin: ParcelsPlugin) { + val mainThreadDispatcher: MainThreadDispatcher = MainThreadDispatcherImpl() + + fun deferLazilyOnMainThread(block: suspend CoroutineScope.() -> T): Deferred { + return async(context = mainThreadDispatcher, start = CoroutineStart.LAZY, block = block) + } + + fun deferUndispatchedOnMainThread(block: suspend CoroutineScope.() -> T): Deferred { + return async(context = mainThreadDispatcher, start = CoroutineStart.UNDISPATCHED, block = block) + } + + fun launchLazilyOnMainThread(block: suspend CoroutineScope.() -> Unit): Job { + return launch(context = mainThreadDispatcher, start = CoroutineStart.LAZY, block = block) + } + + inline fun schedule(noinline task: () -> Unit) = schedule(0, task) + + fun schedule(delay: Int, task: () -> Unit): BukkitTask { + return plugin.server.scheduler.runTaskLater(plugin, task, delay.toLong()) + } + + fun scheduleRepeating(delay: Int, interval: Int, task: () -> Unit): BukkitTask { + return plugin.server.scheduler.runTaskTimer(plugin, task, delay.toLong(), interval.toLong()) + } + + abstract class MainThreadDispatcher : CoroutineDispatcher() { + abstract val mainThread: Thread + abstract fun runOnMainThread(task: Runnable) + } + + private inner class MainThreadDispatcherImpl : MainThreadDispatcher() { + override val mainThread: Thread = Thread.currentThread() + + override fun dispatch(context: CoroutineContext, block: Runnable) { + runOnMainThread(block) + } + + @Suppress("OVERRIDE_BY_INLINE") + override inline fun runOnMainThread(task: Runnable) { + if (Thread.currentThread() === mainThread) task.run() + else plugin.server.scheduler.runTaskLater(plugin, task, 0) + } + } + +} diff --git a/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt b/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt index 10fbbbb..b93dec2 100644 --- a/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt +++ b/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt @@ -6,21 +6,21 @@ import java.nio.ByteBuffer import java.util.* @Suppress("UsePropertyAccessSyntax") -fun getPlayerName(uuid: UUID?, ifUnknown: String? = null): String { - return uuid?.let { Bukkit.getOfflinePlayer(uuid)?.takeIf { it.isValid }?.name } +fun getPlayerNameOrDefault(uuid: UUID?, ifUnknown: String? = null): String { + return uuid + ?.let { getPlayerName(it) } ?: ifUnknown ?: ":unknown_name:" } -@Contract("null -> null; !null -> !null", pure = true) -fun UUID?.toByteArray(): ByteArray? = this?.let { +fun getPlayerName(uuid: UUID): String? { + return Bukkit.getOfflinePlayer(uuid)?.takeIf { it.isValid }?.name +} + +fun UUID.toByteArray(): ByteArray = ByteBuffer.allocate(16).apply { putLong(mostSignificantBits) putLong(leastSignificantBits) }.array() -} -@Contract("null -> null; !null -> !null", pure = true) -fun ByteArray?.toUUID(): UUID? = this?.let { - ByteBuffer.wrap(it).run { UUID(long, long) } -} \ No newline at end of file +fun ByteArray.toUUID(): UUID = ByteBuffer.wrap(this).run { UUID(long, long) } diff --git a/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt b/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt index a4655d0..694f2aa 100644 --- a/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt +++ b/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt @@ -16,4 +16,36 @@ data class Vec3i( } @Suppress("NOTHING_TO_INLINE") -inline operator fun World.get(vec: Vec3i): Block = getBlockAt(vec.x, vec.y, vec.z) \ No newline at end of file +inline operator fun World.get(vec: Vec3i): Block = getBlockAt(vec.x, vec.y, vec.z) + +/* +inline class IVec3i(private val data: Long) { + + private companion object { + const val mask = 0x001F_FFFF + const val max: Int = 0x000F_FFFF // +1048575 + const val min: Int = -max - 1 // -1048575 // 0xFFF0_0000 + + @Suppress("NOTHING_TO_INLINE") + inline fun Int.compressIntoLong(offset: Int): Long { + if (this !in min..max) throw IllegalArgumentException() + return and(mask).toLong().shl(offset) + } + + @Suppress("NOTHING_TO_INLINE") + inline fun Long.extractInt(offset: Int): Int { + return ushr(offset).toInt().and(mask) + } + } + + constructor(x: Int, y: Int, z: Int) : this( + x.compressIntoLong(42) + or y.compressIntoLong(21) + or z.compressIntoLong(0)) + + val x: Int get() = data.extractInt(42) + val y: Int get() = data.extractInt(21) + val z: Int get() = data.extractInt(0) + +} +*/ -- cgit v1.2.3 From 3917855a72c60d1c78632949b4fea21471873347 Mon Sep 17 00:00:00 2001 From: Dico Karssiens Date: Thu, 2 Aug 2018 01:16:38 +0100 Subject: Improve (ParcelTarget)ing for commands, ParcelOwner things, various little bits --- src/main/kotlin/io/dico/parcels2/AddedData.kt | 10 +- .../kotlin/io/dico/parcels2/GlobalAddedData.kt | 2 +- src/main/kotlin/io/dico/parcels2/Parcel.kt | 110 ++++++++++---- src/main/kotlin/io/dico/parcels2/ParcelOwner.kt | 18 +-- src/main/kotlin/io/dico/parcels2/ParcelWorld.kt | 6 +- src/main/kotlin/io/dico/parcels2/WorldGenerator.kt | 18 ++- .../io/dico/parcels2/command/CommandsGeneral.kt | 16 +- .../io/dico/parcels2/command/ParcelMatcher.kt | 23 --- .../dico/parcels2/command/ParcelParameterTypes.kt | 72 --------- .../io/dico/parcels2/command/ParcelTarget.kt | 163 +++++++++++++++++++++ .../parcels2/storage/exposed/ExposedBacking.kt | 4 +- .../io/dico/parcels2/storage/exposed/IdTables.kt | 21 +-- .../io/dico/parcels2/storage/exposed/ListTables.kt | 4 +- src/main/kotlin/io/dico/parcels2/util/Vec3i.kt | 5 +- 14 files changed, 307 insertions(+), 165 deletions(-) delete mode 100644 src/main/kotlin/io/dico/parcels2/command/ParcelMatcher.kt create mode 100644 src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt diff --git a/src/main/kotlin/io/dico/parcels2/AddedData.kt b/src/main/kotlin/io/dico/parcels2/AddedData.kt index 7bcf8f1..5d2a68d 100644 --- a/src/main/kotlin/io/dico/parcels2/AddedData.kt +++ b/src/main/kotlin/io/dico/parcels2/AddedData.kt @@ -5,7 +5,7 @@ import org.bukkit.OfflinePlayer import java.util.* interface AddedData { - val added: Map + val addedMap: Map fun getAddedStatus(uuid: UUID): AddedStatus fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean @@ -28,12 +28,12 @@ interface AddedData { fun unban(player: OfflinePlayer) = unban(player.uuid) } -open class AddedDataHolder(override var added: MutableMap +open class AddedDataHolder(override var addedMap: MutableMap = mutableMapOf()) : AddedData { - override fun getAddedStatus(uuid: UUID): AddedStatus = added.getOrDefault(uuid, AddedStatus.DEFAULT) + override fun getAddedStatus(uuid: UUID): AddedStatus = addedMap.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 { addedMap.put(uuid, it) != it } + ?: addedMap.remove(uuid) != null } enum class AddedStatus { diff --git a/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt b/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt index 055e681..69bc07f 100644 --- a/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt +++ b/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt @@ -23,7 +23,7 @@ class GlobalAddedDataManagerImpl(val plugin: ParcelsPlugin) : GlobalAddedDataMan data: MutableMap = emptyData) : AddedDataHolder(data), GlobalAddedData { - private inline var data get() = added; set(value) = run { added = value } + private inline var data get() = addedMap; set(value) = run { addedMap = value } private inline val isEmpty get() = data === emptyData override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean { diff --git a/src/main/kotlin/io/dico/parcels2/Parcel.kt b/src/main/kotlin/io/dico/parcels2/Parcel.kt index c8e7713..712c32f 100644 --- a/src/main/kotlin/io/dico/parcels2/Parcel.kt +++ b/src/main/kotlin/io/dico/parcels2/Parcel.kt @@ -1,26 +1,14 @@ package io.dico.parcels2 +import io.dico.dicore.Formatting import io.dico.parcels2.util.Vec2i +import io.dico.parcels2.util.getPlayerName import io.dico.parcels2.util.hasBuildAnywhere -import org.bukkit.Bukkit import org.bukkit.OfflinePlayer import org.bukkit.entity.Player import org.joda.time.DateTime import java.util.* - -interface ParcelData : AddedData { - var owner: ParcelOwner? - val since: DateTime? - - fun canBuild(player: OfflinePlayer, checkAdmin: Boolean = true, checkGlobal: Boolean = true): Boolean - - var allowInteractInputs: Boolean - var allowInteractInventory: Boolean - - fun isOwner(uuid: UUID): Boolean { - return owner?.uuid == uuid - } -} +import kotlin.reflect.KProperty /** * Parcel implementation of ParcelData will update the database when changes are made. @@ -31,16 +19,12 @@ interface ParcelData : AddedData { * Therefore, database query callbacks should schedule their updates using the bukkit scheduler. */ class Parcel(val world: ParcelWorld, val pos: Vec2i) : ParcelData { + var data: ParcelData = ParcelDataHolder(); private set + val id get() = "${pos.x}:${pos.z}" val homeLocation get() = world.generator.getHomeLocation(this) - private var blockVisitors = 0 - val infoString: String - get() { - return "$id; owned by ${owner?.let { it.name ?: Bukkit.getOfflinePlayer(it.uuid).name }}" - } - - var data: ParcelData = ParcelDataHolder(); private set + val infoString by ParcelInfoStringComputer fun copyDataIgnoringDatabase(data: ParcelData) { this.data = data @@ -48,14 +32,19 @@ class Parcel(val world: ParcelWorld, val pos: Vec2i) : ParcelData { fun copyData(data: ParcelData) { world.storage.setParcelData(this, data) - this.data = data + copyDataIgnoringDatabase(data) } - override val added: Map get() = data.added + override val addedMap: Map get() = data.addedMap override fun getAddedStatus(uuid: UUID) = data.getAddedStatus(uuid) override fun isBanned(uuid: UUID) = data.isBanned(uuid) override fun isAllowed(uuid: UUID) = data.isAllowed(uuid) - override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean) = data.canBuild(player) + override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean): Boolean { + return (data.canBuild(player, checkAdmin, false)) + || checkGlobal && world.globalAddedData[owner ?: return false].isAllowed(player) + } + + val globalAddedMap: Map? get() = owner?.let { world.globalAddedData[it].addedMap } override val since: DateTime? get() = data.since @@ -93,7 +82,22 @@ class Parcel(val world: ParcelWorld, val pos: Vec2i) : ParcelData { var hasBlockVisitors: Boolean = false; private set } +interface ParcelData : AddedData { + var owner: ParcelOwner? + val since: DateTime? + + fun canBuild(player: OfflinePlayer, checkAdmin: Boolean = true, checkGlobal: Boolean = true): Boolean + + var allowInteractInputs: Boolean + var allowInteractInventory: Boolean + + fun isOwner(uuid: UUID): Boolean { + return owner?.uuid == uuid + } +} + class ParcelDataHolder : AddedDataHolder(), ParcelData { + override var owner: ParcelOwner? = null override var since: DateTime? = null override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean) = isAllowed(player.uniqueId) @@ -104,3 +108,59 @@ class ParcelDataHolder : AddedDataHolder(), ParcelData { override var allowInteractInventory = true } +private object ParcelInfoStringComputer { + val infoStringColor1 = Formatting.GREEN + val infoStringColor2 = Formatting.AQUA + + private inline fun StringBuilder.appendField(name: String, value: StringBuilder.() -> Unit) { + append(infoStringColor1) + append(name) + append(": ") + append(infoStringColor2) + value() + append(' ') + } + + operator fun getValue(parcel: Parcel, property: KProperty<*>): String = buildString { + appendField("ID") { + append(parcel.pos.x) + append(':') + append(parcel.pos.z) + } + + appendField("Owner") { + val owner = parcel.owner + if (owner == null) { + append(infoStringColor1) + append("none") + } else { + append(owner.notNullName) + } + } + + // plotme appends biome here + + append('\n') + + val allowedMap = parcel.addedMap.filterValues { it.isAllowed } + if (allowedMap.isNotEmpty()) appendField("Allowed") { + allowedMap.keys.map(::getPlayerName).joinTo(this) + } + + val bannedMap = parcel.addedMap.filterValues { it.isBanned } + if (bannedMap.isNotEmpty()) appendField("Banned") { + bannedMap.keys.map(::getPlayerName).joinTo(this) + } + + if (!parcel.allowInteractInputs || !parcel.allowInteractInventory) { + appendField("Options") { + append("(") + appendField("inputs") { append(parcel.allowInteractInputs)} + append(", ") + appendField("inventory") { append(parcel.allowInteractInventory) } + append(")") + } + } + + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/ParcelOwner.kt b/src/main/kotlin/io/dico/parcels2/ParcelOwner.kt index c602ff3..07e7c09 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelOwner.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelOwner.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package io.dico.parcels2 import io.dico.parcels2.util.getPlayerNameOrDefault @@ -10,10 +12,8 @@ import java.util.* @Suppress("UsePropertyAccessSyntax") class ParcelOwner private constructor(val uuid: UUID?, - name: String?) { - var name: String? = name - get() = field ?: getPlayerNameOrDefault(uuid!!).also { field = it } - private set + val name: String?) { + val notNullName: String by lazy { name ?: getPlayerNameOrDefault(uuid!!) } constructor(name: String) : this(null, name) constructor(uuid: UUID) : this(uuid, null) @@ -24,13 +24,13 @@ class ParcelOwner private constructor(val uuid: UUID?, } inline val hasUUID: Boolean get() = uuid != null - val onlinePlayer: Player? get() = uuid?.let { Bukkit.getPlayer(uuid) } - val onlinePlayerAllowingNameMatch: Player? get() = onlinePlayer ?: name?.let { Bukkit.getPlayer(name) } + val onlinePlayer: Player? get() = uuid?.let { Bukkit.getPlayer(uuid) } + @Suppress("DEPRECATION") + val onlinePlayerAllowingNameMatch: Player? get() = onlinePlayer ?: name?.let { Bukkit.getPlayerExact(name) } + val offlinePlayer: OfflinePlayer? get() = uuid?.let { Bukkit.getOfflinePlayer(it).takeIf { it.isValid } } @Suppress("DEPRECATION") - val offlinePlayer - get() = (uuid?.let { Bukkit.getOfflinePlayer(it) } ?: Bukkit.getOfflinePlayer(name)) - ?.takeIf { it.isValid } + val offlinePlayerAllowingNameMatch: OfflinePlayer? get() = offlinePlayer ?: Bukkit.getOfflinePlayer(name).takeIf { it.isValid } fun matches(player: OfflinePlayer, allowNameMatch: Boolean = false): Boolean { return uuid?.let { it == player.uniqueId } ?: false diff --git a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt index 7bb827c..c94d273 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt @@ -64,7 +64,8 @@ class Worlds(val plugin: ParcelsPlugin) { worldName, worldOptions, worldOptions.generator.getGenerator(this, worldName), - plugin.storage) + plugin.storage, + plugin.globalAddedData) } catch (ex: Exception) { ex.printStackTrace() @@ -117,7 +118,8 @@ interface ParcelProvider { class ParcelWorld constructor(val name: String, val options: WorldOptions, val generator: ParcelGenerator, - val storage: Storage) : ParcelProvider by generator, ParcelContainer { + val storage: Storage, + val globalAddedData: GlobalAddedDataManager) : ParcelProvider by generator, ParcelContainer { val world: World by lazy { Bukkit.getWorld(name) ?: throw NullPointerException("World $name does not appear to be loaded") } diff --git a/src/main/kotlin/io/dico/parcels2/WorldGenerator.kt b/src/main/kotlin/io/dico/parcels2/WorldGenerator.kt index 04e9a26..ccbc7e9 100644 --- a/src/main/kotlin/io/dico/parcels2/WorldGenerator.kt +++ b/src/main/kotlin/io/dico/parcels2/WorldGenerator.kt @@ -24,6 +24,8 @@ abstract class ParcelGenerator : ChunkGenerator(), ParcelProvider { abstract val factory: GeneratorFactory + abstract fun parcelIDAt(x: Int, z: Int): Vec2i? + abstract override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData abstract fun populate(world: World?, random: Random?, chunk: Chunk?) @@ -171,19 +173,29 @@ class DefaultParcelGenerator(val worlds: Worlds, val name: String, private val o return Location(world, o.offsetX + fix, o.floorHeight + 1.0, o.offsetZ + fix) } - override fun parcelAt(x: Int, z: Int): Parcel? { + private inline fun convertBlockLocationToID(x: Int, z: Int, mapper: (Int, Int) -> T): T? { val sectionSize = sectionSize val parcelSize = o.parcelSize val absX = x - o.offsetX - pathOffset val absZ = z - o.offsetZ - pathOffset val modX = absX umod sectionSize val modZ = absZ umod sectionSize - if (0 <= modX && modX < parcelSize && 0 <= modZ && modZ < parcelSize) { - return world.parcelByID((absX - modX) / sectionSize, (absZ - modZ) / sectionSize) + if (modX in 0 until parcelSize && modZ in 0 until parcelSize) { + return mapper((absX - modX) / sectionSize, (absZ - modZ) / sectionSize) } return null } + override fun parcelIDAt(x: Int, z: Int): Vec2i? { + return convertBlockLocationToID(x, z) { idx, idz -> Vec2i(idx, idz) } + } + + override fun parcelAt(x: Int, z: Int): Parcel? { + return convertBlockLocationToID(x, z) { idx, idz -> + world.parcelByID(idx, idz) + } + } + override fun getBottomCoord(parcel: Parcel): Vec2i = Vec2i(sectionSize * parcel.pos.x + pathOffset + o.offsetX, sectionSize * parcel.pos.z + pathOffset + o.offsetZ) diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt index fa9a696..929b0c7 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt @@ -9,7 +9,6 @@ import io.dico.dicore.command.annotation.RequireParameters import io.dico.parcels2.ParcelOwner import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.blockvisitor.RegionTraversal -import io.dico.parcels2.command.NamedParcelDefaultValue.FIRST_OWNED import io.dico.parcels2.storage.getParcelBySerializedValue import io.dico.parcels2.util.hasAdminManage import io.dico.parcels2.util.hasParcelHomeOthers @@ -49,18 +48,17 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { "more than one parcel", shortVersion = "teleports you to parcels") @RequireParameters(0) - suspend fun cmdHome(player: Player, - @NamedParcelDefault(FIRST_OWNED) target: NamedParcelTarget): Any? { - if (player !== target.player && !player.hasParcelHomeOthers) { + suspend fun cmdHome(player: Player, @ParcelTarget.Kind(ParcelTarget.OWNER_REAL) target: ParcelTarget): Any? { + val ownerTarget = target as ParcelTarget.ByOwner + if (!ownerTarget.owner.matches(player) && !player.hasParcelHomeOthers) { error("You do not have permission to teleport to other people's parcels") } - val ownedParcelsResult = plugin.storage.getOwnedParcels(ParcelOwner(uuid = target.player.uuid)).await() + val ownedParcelsResult = plugin.storage.getOwnedParcels(ownerTarget.owner).await() - val uuid = target.player.uuid val ownedParcels = ownedParcelsResult .map { worlds.getParcelBySerializedValue(it) } - .filter { it != null && it.world == target.world && it.owner?.uuid == uuid } + .filter { it != null && ownerTarget.world == it.world && ownerTarget.owner == it.owner } val targetMatch = ownedParcels.getOrNull(target.index) ?: error("The specified parcel could not be matched") @@ -79,7 +77,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { } checkParcelLimit(player) - parcel.owner = ParcelOwner(uuid = player.uuid, name = player.name) + parcel.owner = ParcelOwner(player) return "Enjoy your new parcel!" } @@ -100,7 +98,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { @Cmd("swap") fun ParcelScope.cmdSwap(context: ExecutionContext, @Flag sure: Boolean): Any? { - + TODO() } @Cmd("make_mess") diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelMatcher.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelMatcher.kt deleted file mode 100644 index e9472bc..0000000 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelMatcher.kt +++ /dev/null @@ -1,23 +0,0 @@ -package io.dico.parcels2.command - -import io.dico.parcels2.Parcel -import io.dico.parcels2.ParcelWorld -import io.dico.parcels2.ParcelsPlugin -import kotlinx.coroutines.experimental.Deferred - -interface ParcelTarget { - val world: ParcelWorld - val isByID: Boolean - val isByOwner: Boolean get() = !isByID - suspend fun ParcelsPlugin.await(): Parcel? - fun ParcelsPlugin.get(): Deferred = -} - -class ParcelTargetByOwner : ParcelTarget { - override val isByID get() = false -} - -class ParcelTargetByID : ParcelTarget { - override val isByID get() = true - -} diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt index dcc3f8c..b5a1abf 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt @@ -49,75 +49,3 @@ class ParcelParameterType(val worlds: Worlds) : ParameterType(Parc } } - - -class NamedParcelTarget(val world: ParcelWorld, val player: OfflinePlayer, val index: Int) - -@Target(AnnotationTarget.VALUE_PARAMETER) -@Retention(AnnotationRetention.RUNTIME) -annotation class NamedParcelDefault(val value: NamedParcelDefaultValue) - -enum class NamedParcelDefaultValue { - FIRST_OWNED, - NULL -} - -class NamedParcelTargetConfig : ParameterConfig(NamedParcelDefault::class.java) { - - override fun toParameterInfo(annotation: NamedParcelDefault): NamedParcelDefaultValue { - return annotation.value - } -} - -class ParcelHomeParameterType(val worlds: Worlds) : ParameterType(NamedParcelTarget::class.java, NamedParcelTargetConfig()) { - - val regex = Regex.fromLiteral("((.+)->)?(.+)|((.+):([0-9]+))") - - private fun requirePlayer(sender: CommandSender, parameter: Parameter<*, *>): Player { - if (sender !is Player) invalidInput(parameter, "console cannot omit the player name") - return sender - } - - @Suppress("UsePropertyAccessSyntax") - private fun getOfflinePlayer(input: String, parameter: Parameter<*, *>) = Bukkit.getOfflinePlayer(input) - ?.takeIf { it.isValid } - ?: invalidInput(parameter, "do not know who $input is") - - override fun parse(parameter: Parameter, - sender: CommandSender, buffer: ArgumentBuffer): NamedParcelTarget { - val matchResult = regex.matchEntire(buffer.next()) - ?: invalidInput(parameter, "must be a player, index, or player:index (/${regex.pattern}/)") - - val world = worlds.getTargetWorld(matchResult.groupValues[2], sender, parameter) - - matchResult.groupValues[3].takeUnless { it.isEmpty() }?.let { - // first group was matched, it's a player or an int - it.toIntOrNull()?.let { - requirePlayer(sender, parameter) - return NamedParcelTarget(world, sender as Player, it) - } - - return NamedParcelTarget(world, getOfflinePlayer(it, parameter), 0) - } - - val player = getOfflinePlayer(matchResult.groupValues[5], parameter) - val index = matchResult.groupValues[6].toIntOrNull() - ?: invalidInput(parameter, "couldn't parse int") - - return NamedParcelTarget(world, player, index) - } - - override fun getDefaultValue(parameter: Parameter, - sender: CommandSender, buffer: ArgumentBuffer): NamedParcelTarget? { - if (parameter.paramInfo == NamedParcelDefaultValue.NULL) { - return null - } - - val world = worlds.getTargetWorld(null, sender, parameter) - val player = requirePlayer(sender, parameter) - return NamedParcelTarget(world, player, 0) - } - -} diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt new file mode 100644 index 0000000..4dd2825 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt @@ -0,0 +1,163 @@ +package io.dico.parcels2.command + +import io.dico.dicore.command.parameter.ArgumentBuffer +import io.dico.dicore.command.parameter.Parameter +import io.dico.dicore.command.parameter.type.ParameterConfig +import io.dico.dicore.command.parameter.type.ParameterType +import io.dico.parcels2.* +import io.dico.parcels2.storage.getParcelBySerializedValue +import io.dico.parcels2.util.Vec2i +import io.dico.parcels2.util.floor +import io.dico.parcels2.util.isValid +import kotlinx.coroutines.experimental.Deferred +import org.bukkit.Bukkit +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player + +sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) { + abstract suspend fun ParcelsPlugin.getParcelSuspend(): Parcel? + fun ParcelsPlugin.getParcelDeferred(): Deferred = functionHelper.deferUndispatchedOnMainThread { getParcelSuspend() } + + class ByID(world: ParcelWorld, val id: Vec2i?, isDefault: Boolean) : ParcelTarget(world, isDefault) { + override suspend fun ParcelsPlugin.getParcelSuspend(): Parcel? = getParcel() + fun getParcel() = id?.let { world.parcelByID(it) } + val isPath: Boolean get() = id == null + } + + class ByOwner(world: ParcelWorld, val owner: ParcelOwner, val index: Int, isDefault: Boolean) : ParcelTarget(world, isDefault) { + init { + if (index < 0) throw IllegalArgumentException("Invalid parcel home index: $index") + } + + override suspend fun ParcelsPlugin.getParcelSuspend(): Parcel? { + val ownedParcelsSerialized = storage.getOwnedParcels(owner).await() + val ownedParcels = ownedParcelsSerialized + .map { worlds.getParcelBySerializedValue(it) } + .filter { it != null && world == it.world && owner == it.owner } + return ownedParcels.getOrNull(index) + } + } + + annotation class Kind(val kind: Int) + + companion object Config : ParameterConfig(Kind::class.java) { + override fun toParameterInfo(annotation: Kind): Int { + return annotation.kind + } + + const val ID = 1 // ID + const val OWNER_REAL = 2 // an owner backed by a UUID + const val OWNER_FAKE = 3 // an owner not backed by a UUID + + const val OWNER = OWNER_REAL or OWNER_FAKE // any owner + const val ANY = ID or OWNER_REAL or OWNER_FAKE // any + const val REAL = ID or OWNER_REAL // no owner not backed by a UUID + + const val DEFAULT_KIND = REAL + + const val PREFER_OWNED_FOR_DEFAULT = 4 // if the kind can be ID and OWNER_REAL, prefer OWNER_REAL for default + // instead of parcel that the player is in + } + + class PType(val worlds: Worlds) : ParameterType(ParcelTarget::class.java, ParcelTarget.Config) { + + override fun parse(parameter: Parameter, sender: CommandSender, buffer: ArgumentBuffer): ParcelTarget { + var input = buffer.next() + val worldString = input.substringBefore("->", missingDelimiterValue = "") + input = input.substringAfter("->") + + val world = if (worldString.isEmpty()) { + val player = requirePlayer(sender, parameter, "the world") + worlds.getWorld(player.world) + ?: invalidInput(parameter, "You cannot omit the world if you're not in a parcel world") + } else { + worlds.getWorld(worldString) ?: invalidInput(parameter, "$worldString is not a parcel world") + } + + val kind = parameter.paramInfo ?: DEFAULT_KIND + if (input.contains(',')) { + if (kind and ID == 0) invalidInput(parameter, "You must specify a parcel by ID, that is, the x and z component separated by a comma") + return ByID(world, getId(parameter, input), false) + } + + if (kind and OWNER == 0) invalidInput(parameter, "You must specify a parcel by OWNER, that is, an owner and index") + val (owner, index) = getHomeIndex(parameter, sender, input) + return ByOwner(world, owner, index, false) + } + + private fun getId(parameter: Parameter<*, *>, input: String): Vec2i { + val x = input.substringBefore(',').run { + toIntOrNull() ?: invalidInput(parameter, "ID(x) must be an integer, $this is not an integer") + } + val z = input.substringAfter(',').run { + toIntOrNull() ?: invalidInput(parameter, "ID(z) must be an integer, $this is not an integer") + } + return Vec2i(x, z) + } + + private fun getHomeIndex(parameter: Parameter<*, Int>, sender: CommandSender, input: String): Pair { + val splitIdx = input.indexOf(':') + val ownerString: String + val indexString: String + + if (splitIdx == -1) { + // just the index. + ownerString = "" + indexString = input + } else { + ownerString = input.substring(0, splitIdx) + indexString = input.substring(0, splitIdx + 1) + } + + val owner = if (ownerString.isEmpty()) + ParcelOwner(requirePlayer(sender, parameter, "the player")) + else + inputAsOwner(parameter, ownerString) + + val index = if (indexString.isEmpty()) 0 else indexString.toIntOrNull() + ?: invalidInput(parameter, "The home index must be an integer, $indexString is not an integer") + + return owner to index + } + + private fun requirePlayer(sender: CommandSender, parameter: Parameter<*, *>, objName: String): Player { + if (sender !is Player) invalidInput(parameter, "console cannot omit the $objName") + return sender + } + + @Suppress("DEPRECATION") + private fun inputAsOwner(parameter: Parameter<*, Int>, input: String): ParcelOwner { + val kind = parameter.paramInfo ?: DEFAULT_KIND + if (kind and OWNER_REAL == 0) { + return ParcelOwner(input) + } + + val player = Bukkit.getOfflinePlayer(input).takeIf { it.isValid } + if (player == null) { + if (kind and OWNER_FAKE == 0) invalidInput(parameter, "The player $input does not exist") + return ParcelOwner(input) + } + + return ParcelOwner(player) + } + + override fun getDefaultValue(parameter: Parameter, sender: CommandSender, buffer: ArgumentBuffer): ParcelTarget? { + val kind = parameter.paramInfo ?: DEFAULT_KIND + val useLocation = when { + kind and REAL == REAL -> kind and PREFER_OWNED_FOR_DEFAULT == 0 + kind and ID != 0 -> true + kind and OWNER_REAL != 0 -> false + else -> return null + } + + val player = requirePlayer(sender, parameter, "the parcel") + val world = worlds.getWorld(player.world) ?: invalidInput(parameter, "You must be in a parcel world to omit the parcel") + if (useLocation) { + val id = player.location.let { world.generator.parcelIDAt(it.x.floor(), it.z.floor()) } + return ByID(world, id, true) + } + + return ByOwner(world, ParcelOwner(player), 0, true) + } + } +} diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt index affa14e..729bbff 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt @@ -107,7 +107,7 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) : setParcelOwner(parcelFor, data.owner) - for ((uuid, status) in data.added) { + for ((uuid, status) in data.addedMap) { setLocalPlayerStatus(parcelFor, uuid, status) } @@ -168,7 +168,7 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) : since = row[ParcelsT.claim_time] val parcelId = row[ParcelsT.id] - added = AddedLocalT.readAddedData(parcelId) + addedMap = AddedLocalT.readAddedData(parcelId) AddedLocalT.select { AddedLocalT.attach_id eq parcelId }.forEach { val uuid = it[AddedLocalT.player_uuid].toUUID() diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt index c75ea9f..e19fd3f 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt @@ -32,7 +32,7 @@ sealed class IdTransactionsTable("par object OwnersT : IdTransactionsTable("parcel_owners", "owner_id") { val uuid = binary("uuid", 2).nullable() - val name = varchar("name", 32).nullable() + val name = varchar("name", 32) val index_pair = uniqueIndexR("index_pair", uuid, name) private inline fun getId(binaryUuid: ByteArray) = getId { uuid eq binaryUuid } private inline fun getId(uuid: UUID) = getId(uuid.toByteArray()) - private inline fun getId(name: String) = getId { OwnersT.name eq name } + private inline fun getId(nameIn: String) = getId { uuid.isNull() and (name eq nameIn) } - private inline fun getOrInitId(uuid: UUID) = uuid.toByteArray().let { binaryUuid -> - getId(binaryUuid) - ?: insertAndGetId("owner(uuid = $uuid)") { it[OwnersT.uuid] = binaryUuid } + private inline fun getOrInitId(uuid: UUID, name: String) = uuid.toByteArray().let { binaryUuid -> + getId(binaryUuid) ?: insertAndGetId("owner(uuid = $uuid)") { + it[this@OwnersT.uuid] = binaryUuid + it[this@OwnersT.name] = name + } } private inline fun getOrInitId(name: String) = - getId(name) - ?: insertAndGetId("owner(name = $name)") { it[OwnersT.name] = name } + getId(name) ?: insertAndGetId("owner(name = $name)") { it[OwnersT.name] = name } override fun getId(owner: ParcelOwner): Int? = if (owner.hasUUID) getId(owner.uuid!!) else getId(owner.name!!) override fun getOrInitId(owner: ParcelOwner): Int = - if (owner.hasUUID) getOrInitId(owner.uuid!!) + if (owner.hasUUID) getOrInitId(owner.uuid!!, owner.notNullName) else getOrInitId(owner.name!!) override fun getSerializable(row: ResultRow): ParcelOwner { - return row[uuid]?.toUUID()?.let { ParcelOwner(it) } ?: ParcelOwner(row[name]!!) + return row[uuid]?.toUUID()?.let { ParcelOwner(it) } ?: ParcelOwner(row[name]) } } diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt index 5c6ce25..ca2943d 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt @@ -19,8 +19,8 @@ object AddedGlobalT : AddedTable("parcels_added_global object ParcelOptionsT : Table("parcel_options") { val parcel_id = integer("parcel_id").primaryKey().references(ParcelsT.id, ReferenceOption.CASCADE) - val interact_inventory = bool("interact_inventory").default(false) - val interact_inputs = bool("interact_inputs").default(false) + val interact_inventory = bool("interact_inventory").default(true) + val interact_inputs = bool("interact_inputs").default(true) } typealias AddedStatusSendChannel = SendChannel>> diff --git a/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt b/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt index 694f2aa..6db98af 100644 --- a/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt +++ b/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt @@ -19,7 +19,7 @@ data class Vec3i( inline operator fun World.get(vec: Vec3i): Block = getBlockAt(vec.x, vec.y, vec.z) /* -inline class IVec3i(private val data: Long) { +private /*inline */class IVec3i(private val data: Long) { private companion object { const val mask = 0x001F_FFFF @@ -34,7 +34,8 @@ inline class IVec3i(private val data: Long) { @Suppress("NOTHING_TO_INLINE") inline fun Long.extractInt(offset: Int): Int { - return ushr(offset).toInt().and(mask) + val result = ushr(offset).toInt().and(mask) + return if (result > max) result or mask.inv() else result } } -- cgit v1.2.3 From 6513ad9237dbda0244a52608ae639fee5822b3ee Mon Sep 17 00:00:00 2001 From: Dico Date: Thu, 2 Aug 2018 03:42:48 +0100 Subject: Fix bugs --- .../kotlin/io/dico/parcels2/GlobalAddedData.kt | 2 +- src/main/kotlin/io/dico/parcels2/Parcel.kt | 7 ++- src/main/kotlin/io/dico/parcels2/ParcelWorld.kt | 53 ++++++++---------- src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt | 41 +++++++------- .../dico/parcels2/blockvisitor/RegionTraversal.kt | 2 +- .../parcels2/command/AbstractParcelCommands.kt | 7 +-- .../dico/parcels2/command/CommandsAddedStatus.kt | 4 +- .../io/dico/parcels2/command/CommandsDebug.kt | 26 ++++++++- .../io/dico/parcels2/command/CommandsGeneral.kt | 37 ++++--------- .../dico/parcels2/command/ParcelCommandBuilder.kt | 2 +- .../io/dico/parcels2/storage/ExposedExtensions.kt | 63 ---------------------- .../io/dico/parcels2/storage/SerializableTypes.kt | 5 +- .../parcels2/storage/exposed/ExposedBacking.kt | 20 +++++-- .../parcels2/storage/exposed/ExposedExtensions.kt | 63 ++++++++++++++++++++++ .../io/dico/parcels2/storage/exposed/IdTables.kt | 5 +- .../io/dico/parcels2/storage/exposed/ListTables.kt | 5 +- 16 files changed, 180 insertions(+), 162 deletions(-) delete mode 100644 src/main/kotlin/io/dico/parcels2/storage/ExposedExtensions.kt create mode 100644 src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedExtensions.kt diff --git a/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt b/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt index 69bc07f..1528d21 100644 --- a/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt +++ b/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt @@ -39,7 +39,7 @@ class GlobalAddedDataManagerImpl(val plugin: ParcelsPlugin) : GlobalAddedDataMan } private companion object { - val emptyData = mapOf() as MutableMap + val emptyData = Collections.emptyMap() as MutableMap } } diff --git a/src/main/kotlin/io/dico/parcels2/Parcel.kt b/src/main/kotlin/io/dico/parcels2/Parcel.kt index 712c32f..335b415 100644 --- a/src/main/kotlin/io/dico/parcels2/Parcel.kt +++ b/src/main/kotlin/io/dico/parcels2/Parcel.kt @@ -31,8 +31,13 @@ class Parcel(val world: ParcelWorld, val pos: Vec2i) : ParcelData { } fun copyData(data: ParcelData) { - world.storage.setParcelData(this, data) copyDataIgnoringDatabase(data) + world.storage.setParcelData(this, data) + } + + fun dispose() { + copyDataIgnoringDatabase(ParcelDataHolder()) + world.storage.setParcelData(this, null) } override val addedMap: Map get() = data.addedMap diff --git a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt index c94d273..5e7187b 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt @@ -3,8 +3,8 @@ package io.dico.parcels2 import io.dico.parcels2.storage.SerializableParcel import io.dico.parcels2.storage.SerializableWorld import io.dico.parcels2.storage.Storage +import io.dico.parcels2.storage.getParcelBySerializedValue import io.dico.parcels2.util.Vec2i -import io.dico.parcels2.util.doAwait import io.dico.parcels2.util.floor import kotlinx.coroutines.experimental.launch import org.bukkit.Bukkit @@ -17,8 +17,6 @@ import org.bukkit.entity.Player import java.util.* import kotlin.coroutines.experimental.buildIterator import kotlin.coroutines.experimental.buildSequence -import kotlin.reflect.jvm.javaMethod -import kotlin.reflect.jvm.kotlinFunction class Worlds(val plugin: ParcelsPlugin) { val worlds: Map get() = _worlds @@ -42,11 +40,6 @@ class Worlds(val plugin: ParcelsPlugin) { } } - init { - val function = ::loadWorlds - function.javaMethod!!.kotlinFunction - } - operator fun SerializableParcel.invoke(): Parcel? { return world()?.parcelByID(pos) } @@ -72,31 +65,28 @@ class Worlds(val plugin: ParcelsPlugin) { continue } - _worlds.put(worldName, world) - - if (Bukkit.getWorld(worldName) == null) { - plugin.doAwait { - cond = { - try { - // server.getDefaultGameMode() throws an error before any worlds are initialized. - // createWorld() below calls that method. - // Plugin needs to load on STARTUP for generators to be registered correctly. - // Means we need to await the initial worlds getting loaded. - - plugin.server.defaultGameMode; true - } catch (ex: Throwable) { - false - } - } - - onSuccess = { - val bworld = WorldCreator(worldName).generator(world.generator).createWorld() - val spawn = world.generator.getFixedSpawnLocation(bworld, null) - bworld.setSpawnLocation(spawn.x.floor(), spawn.y.floor(), spawn.z.floor()) - } + _worlds[worldName] = world + } + + plugin.functionHelper.schedule(10) { + println("Parcels generating worlds now") + for ((name, world) in _worlds) { + if (Bukkit.getWorld(name) == null) { + val bworld = WorldCreator(name).generator(world.generator).createWorld() + val spawn = world.generator.getFixedSpawnLocation(bworld, null) + bworld.setSpawnLocation(spawn.x.floor(), spawn.y.floor(), spawn.z.floor()) } } + val channel = plugin.storage.readAllParcelData() + val job = plugin.functionHelper.launchLazilyOnMainThread { + do { + val pair = channel.receiveOrNull() ?: break + val parcel = getParcelBySerializedValue(pair.first) ?: continue + pair.second?.let { parcel.copyDataIgnoringDatabase(it) } + } while (true) + } + job.start() } } @@ -205,7 +195,8 @@ class DefaultParcelContainer(private val world: ParcelWorld, buildIterator { val center = world.options.axisLimit for (radius in 0..center) { - var x = center - radius; var z = center - radius + var x = center - radius; + var z = center - radius repeat(radius * 2) { yield(parcels[x++][z]) } repeat(radius * 2) { yield(parcels[x][z++]) } repeat(radius * 2) { yield(parcels[x--][z]) } diff --git a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt index d55320e..2533277 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt @@ -12,14 +12,13 @@ import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.yamlObjectMapper import io.dico.parcels2.util.FunctionHelper import io.dico.parcels2.util.tryCreate -import kotlinx.coroutines.experimental.asCoroutineDispatcher import org.bukkit.Bukkit import org.bukkit.plugin.java.JavaPlugin +import org.slf4j.Logger import org.slf4j.LoggerFactory import java.io.File -import java.util.concurrent.Executor -val logger = LoggerFactory.getLogger("ParcelsPlugin") +val logger: Logger = LoggerFactory.getLogger("ParcelsPlugin") private inline val plogger get() = logger class ParcelsPlugin : JavaPlugin() { @@ -64,14 +63,14 @@ class ParcelsPlugin : JavaPlugin() { return false } + globalAddedData = GlobalAddedDataManagerImpl(this) worlds.loadWorlds(options) + entityTracker = ParcelEntityTracker(worlds) } catch (ex: Exception) { plogger.error("Error loading options", ex) return false } - globalAddedData = GlobalAddedDataManagerImpl(this) - entityTracker = ParcelEntityTracker(worlds) registerListeners() registerCommands() @@ -79,21 +78,23 @@ class ParcelsPlugin : JavaPlugin() { } fun loadOptions(): Boolean { - if (optionsFile.exists()) { - yamlObjectMapper.readerForUpdating(options).readValue(optionsFile) - } else if (optionsFile.tryCreate()) { - options.addWorld("parcels", WorldOptions()) - try { - yamlObjectMapper.writeValue(optionsFile, options) - } catch (ex: Throwable) { - optionsFile.delete() - throw ex + when { + optionsFile.exists() -> yamlObjectMapper.readerForUpdating(options).readValue(optionsFile) + optionsFile.tryCreate() -> { + options.addWorld("parcels", WorldOptions()) + try { + yamlObjectMapper.writeValue(optionsFile, options) + } catch (ex: Throwable) { + optionsFile.delete() + throw ex + } + plogger.warn("Created options file with a world template. Please review it before next start.") + return false + } + else -> { + plogger.error("Failed to save options file ${optionsFile.canonicalPath}") + return false } - plogger.warn("Created options file with a world template. Please review it before next start.") - return false - } else { - plogger.error("Failed to save options file ${optionsFile.canonicalPath}") - return false } return true } @@ -105,7 +106,7 @@ class ParcelsPlugin : JavaPlugin() { } private fun registerListeners() { - if (listeners != null) { + if (listeners == null) { listeners = ParcelListeners(worlds, entityTracker) registrator.registerListeners(listeners!!) } diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraversal.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraversal.kt index 19eb7ee..85fe946 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraversal.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraversal.kt @@ -20,7 +20,7 @@ enum class RegionTraversal(private val builder: suspend SequenceBuilder.( }), - UPDARD({ region -> + UPWARD({ region -> val origin = region.origin val size = region.size diff --git a/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt b/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt index 2cee99a..f712d00 100644 --- a/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt +++ b/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt @@ -4,10 +4,10 @@ import io.dico.dicore.command.CommandException import io.dico.dicore.command.ExecutionContext import io.dico.dicore.command.ICommandReceiver import io.dico.parcels2.ParcelOwner +import io.dico.parcels2.ParcelWorld import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.util.hasAdminManage import io.dico.parcels2.util.parcelLimit -import io.dico.parcels2.util.uuid import org.bukkit.entity.Player import org.bukkit.plugin.Plugin import java.lang.reflect.Method @@ -29,9 +29,10 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei if (!plugin.storage.isConnected) error("Parcels cannot $action right now because of a database error") } - protected suspend fun checkParcelLimit(player: Player) { + protected suspend fun checkParcelLimit(player: Player, world: ParcelWorld) { if (player.hasAdminManage) return - val numOwnedParcels = plugin.storage.getNumParcels(ParcelOwner(uuid = player.uuid)).await() + val numOwnedParcels = plugin.storage.getOwnedParcels(ParcelOwner(player)).await() + .filter { it.world.world == world.world }.size val limit = player.parcelLimit if (numOwnedParcels >= limit) { diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatus.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatus.kt index adc8e60..997044a 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatus.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatus.kt @@ -15,7 +15,7 @@ class CommandsAddedStatus(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin shortVersion = "allows a player to build on this parcel") @ParcelRequire(owner = true) fun ParcelScope.cmdAllow(sender: Player, player: OfflinePlayer): Any? { - Validate.isTrue(parcel.owner != null && !sender.hasAdminManage, "This parcel is unowned") + Validate.isTrue(parcel.owner != null || sender.hasAdminManage, "This parcel is unowned") Validate.isTrue(!parcel.owner!!.matches(player), "The target already owns the parcel") Validate.isTrue(parcel.allow(player), "${player.name} is already allowed to build on this parcel") return "${player.name} is now allowed to build on this parcel" @@ -37,7 +37,7 @@ class CommandsAddedStatus(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin shortVersion = "bans a player from this parcel") @ParcelRequire(owner = true) fun ParcelScope.cmdBan(sender: Player, player: OfflinePlayer): Any? { - Validate.isTrue(parcel.owner != null && !sender.hasAdminManage, "This parcel is unowned") + Validate.isTrue(parcel.owner != null || sender.hasAdminManage, "This parcel is unowned") Validate.isTrue(!parcel.owner!!.matches(player), "The owner cannot be banned from the parcel") Validate.isTrue(parcel.ban(player), "${player.name} is already banned from this parcel") return "${player.name} is now banned from this parcel" diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt index 8f7f6ba..9493d26 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt @@ -1,12 +1,17 @@ package io.dico.parcels2.command import io.dico.dicore.command.CommandException +import io.dico.dicore.command.EMessageType +import io.dico.dicore.command.ExecutionContext import io.dico.dicore.command.annotation.Cmd import io.dico.parcels2.ParcelsPlugin +import io.dico.parcels2.blockvisitor.RegionTraversal import org.bukkit.Bukkit +import org.bukkit.Material import org.bukkit.entity.Player +import java.util.* -class CommandsDebug(val plugin: ParcelsPlugin) { +class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { @Cmd("reloadoptions") fun reloadOptions() { @@ -23,4 +28,23 @@ class CommandsDebug(val plugin: ParcelsPlugin) { return "Teleported you to $worldName spawn" } + @Cmd("make_mess") + @ParcelRequire(owner = true) + fun ParcelScope.cmdMakeMess(context: ExecutionContext) { + val server = plugin.server + val blockDatas = arrayOf( + server.createBlockData(Material.STICKY_PISTON), + server.createBlockData(Material.GLASS), + server.createBlockData(Material.STONE_SLAB), + server.createBlockData(Material.QUARTZ_BLOCK) + ) + val random = Random() + world.generator.doBlockOperation(parcel, direction = RegionTraversal.UPWARD) { block -> + block.blockData = blockDatas[random.nextInt(4)] + }.onProgressUpdate(1000, 1000) { progress, elapsedTime -> + context.sendMessage(EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed" + .format(progress * 100, elapsedTime / 1000.0)) + } + } + } \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt index 929b0c7..bfa1f6c 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt @@ -8,16 +8,12 @@ import io.dico.dicore.command.annotation.Flag import io.dico.dicore.command.annotation.RequireParameters import io.dico.parcels2.ParcelOwner import io.dico.parcels2.ParcelsPlugin -import io.dico.parcels2.blockvisitor.RegionTraversal import io.dico.parcels2.storage.getParcelBySerializedValue import io.dico.parcels2.util.hasAdminManage import io.dico.parcels2.util.hasParcelHomeOthers import io.dico.parcels2.util.uuid -import org.bukkit.Material import org.bukkit.entity.Player -import java.util.* -//@Suppress("unused") class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { @Cmd("auto") @@ -26,7 +22,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { shortVersion = "sets you up with a fresh, unclaimed parcel") suspend fun WorldScope.cmdAuto(player: Player): Any? { checkConnected("be claimed") - checkParcelLimit(player) + checkParcelLimit(player, world) val parcel = world.nextEmptyParcel() ?: error("This world is full, please ask an admin to upsize it") @@ -58,7 +54,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { val ownedParcels = ownedParcelsResult .map { worlds.getParcelBySerializedValue(it) } - .filter { it != null && ownerTarget.world == it.world && ownerTarget.owner == it.owner } + .filter { it != null && ownerTarget.world == it.world } val targetMatch = ownedParcels.getOrNull(target.index) ?: error("The specified parcel could not be matched") @@ -76,11 +72,19 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { error(if (it.matches(player)) "You already own this parcel" else "This parcel is not available") } - checkParcelLimit(player) + checkParcelLimit(player, world) parcel.owner = ParcelOwner(player) return "Enjoy your new parcel!" } + @Cmd("unclaim") + @Desc("Unclaims this parcel") + @ParcelRequire(owner = true) + fun ParcelScope.cmdUnclaim(player: Player): Any? { + parcel.dispose() + return "Your parcel has been disposed" + } + @Cmd("clear") @ParcelRequire(owner = true) fun ParcelScope.cmdClear(context: ExecutionContext, @Flag sure: Boolean): Any? { @@ -101,23 +105,4 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { TODO() } - @Cmd("make_mess") - @ParcelRequire(owner = true) - fun ParcelScope.cmdMakeMess(context: ExecutionContext) { - val server = plugin.server - val blockDatas = arrayOf( - server.createBlockData(Material.STICKY_PISTON), - server.createBlockData(Material.GLASS), - server.createBlockData(Material.STONE_SLAB), - server.createBlockData(Material.QUARTZ_BLOCK) - ) - val random = Random() - world.generator.doBlockOperation(parcel, direction = RegionTraversal.UPDARD) { block -> - block.blockData = blockDatas[random.nextInt(4)] - }.onProgressUpdate(1000, 1000) { progress, elapsedTime -> - context.sendMessage(EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed" - .format(progress * 100, elapsedTime / 1000.0)) - } - } - } \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt index 2ba16e2..f3bd63c 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt @@ -15,7 +15,7 @@ fun getParcelCommands(plugin: ParcelsPlugin): ICommandDispatcher { return CommandBuilder() .setChatController(ParcelsChatController()) .addParameterType(false, ParcelParameterType(plugin.worlds)) - .addParameterType(true, ParcelHomeParameterType(plugin.worlds)) + .addParameterType(true, ParcelTarget.PType(plugin.worlds)) .group("parcel", "plot", "plots", "p") .registerCommands(CommandsGeneral(plugin)) diff --git a/src/main/kotlin/io/dico/parcels2/storage/ExposedExtensions.kt b/src/main/kotlin/io/dico/parcels2/storage/ExposedExtensions.kt deleted file mode 100644 index 816ff97..0000000 --- a/src/main/kotlin/io/dico/parcels2/storage/ExposedExtensions.kt +++ /dev/null @@ -1,63 +0,0 @@ -package io.dico.parcels2.storage - -import org.jetbrains.exposed.sql.Column -import org.jetbrains.exposed.sql.Index -import org.jetbrains.exposed.sql.Table -import org.jetbrains.exposed.sql.Transaction -import org.jetbrains.exposed.sql.statements.InsertStatement -import org.jetbrains.exposed.sql.transactions.TransactionManager - -class UpsertStatement(table: Table, conflictColumn: Column<*>? = null, conflictIndex: Index? = null) - : InsertStatement(table, false) { - val indexName: String - val indexColumns: List> - - init { - when { - conflictIndex != null -> { - indexName = conflictIndex.indexName - indexColumns = conflictIndex.columns - } - conflictColumn != null -> { - indexName = conflictColumn.name - indexColumns = listOf(conflictColumn) - } - else -> throw IllegalArgumentException() - } - } - - override fun prepareSQL(transaction: Transaction) = buildString { - append(super.prepareSQL(transaction)) - - val dialect = transaction.db.vendor - if (dialect == "postgresql") { - - append(" ON CONFLICT(") - append(indexName) - append(") DO UPDATE SET ") - - values.keys.filter { it !in indexColumns }.joinTo(this) { "${transaction.identity(it)}=EXCLUDED.${transaction.identity(it)}" } - - } else { - - append (" ON DUPLICATE KEY UPDATE ") - values.keys.filter { it !in indexColumns }.joinTo(this) { "${transaction.identity(it)}=VALUES(${transaction.identity(it)})" } - - } - } - -} - -inline fun T.upsert(conflictColumn: Column<*>? = null, conflictIndex: Index? = null, body: T.(UpsertStatement) -> Unit) = - UpsertStatement(this, conflictColumn, conflictIndex).apply { - body(this) - execute(TransactionManager.current()) - } - -fun Table.indexR(customIndexName: String? = null, isUnique: Boolean = false, vararg columns: Column<*>): Index { - val index = Index(columns.toList(), isUnique, customIndexName) - indices.add(index) - return index -} - -fun Table.uniqueIndexR(customIndexName: String? = null, vararg columns: Column<*>): Index = indexR(customIndexName, true, *columns) diff --git a/src/main/kotlin/io/dico/parcels2/storage/SerializableTypes.kt b/src/main/kotlin/io/dico/parcels2/storage/SerializableTypes.kt index 8d8d938..1f659fb 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/SerializableTypes.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/SerializableTypes.kt @@ -17,6 +17,8 @@ data class SerializableWorld(val name: String? = null, val world: World? by lazy { uid?.let { Bukkit.getWorld(it) } ?: name?.let { Bukkit.getWorld(it) } } //val parcelWorld: ParcelWorld? by lazy { TODO() } + + constructor(world: World) : this(world.name, world.uid) } /** @@ -33,5 +35,6 @@ fun Worlds.getWorldBySerializedValue(input: SerializableWorld): ParcelWorld? { } fun Worlds.getParcelBySerializedValue(input: SerializableParcel): Parcel? { - return getWorldBySerializedValue(input.world)?.parcelByID(input.pos) + return getWorldBySerializedValue(input.world) + ?.parcelByID(input.pos) } \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt index 729bbff..483bb16 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt @@ -4,9 +4,13 @@ package io.dico.parcels2.storage.exposed import com.zaxxer.hikari.HikariDataSource import io.dico.parcels2.* -import io.dico.parcels2.storage.* +import io.dico.parcels2.storage.Backing +import io.dico.parcels2.storage.SerializableParcel import io.dico.parcels2.util.toUUID +import kotlinx.coroutines.experimental.CoroutineStart +import kotlinx.coroutines.experimental.Unconfined import kotlinx.coroutines.experimental.channels.ProducerScope +import kotlinx.coroutines.experimental.launch import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SchemaUtils.create import org.jetbrains.exposed.sql.transactions.transaction @@ -35,6 +39,12 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) : private fun transaction(statement: Transaction.() -> T) = transaction(database!!, statement) + private suspend fun transactionLaunch(statement: suspend Transaction.() -> Unit): Unit = transaction(database!!) { + launch(context = Unconfined, start = CoroutineStart.UNDISPATCHED) { + statement(this@transaction) + } + } + override suspend fun init() { if (isShutdown) throw IllegalStateException() dataSource = dataSourceFactory() @@ -61,7 +71,7 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) : channel.close() } - override suspend fun ProducerScope>.produceAllParcelData() { + override suspend fun ProducerScope>.produceAllParcelData() = transactionLaunch { ParcelsT.selectAll().forEach { row -> val parcel = ParcelsT.getSerializable(row) ?: return@forEach val data = rowToParcelData(row) @@ -150,13 +160,13 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) : } } - override suspend fun ProducerScope>>.produceAllGlobalAddedData() { + override suspend fun ProducerScope>>.produceAllGlobalAddedData() = transactionLaunch { AddedGlobalT.sendAllAddedData(channel) channel.close() } - override suspend fun readGlobalAddedData(owner: ParcelOwner): MutableMap { - return AddedGlobalT.readAddedData(OwnersT.getId(owner) ?: return hashMapOf()) + override suspend fun readGlobalAddedData(owner: ParcelOwner): MutableMap = transaction { + return@transaction AddedGlobalT.readAddedData(OwnersT.getId(owner) ?: return@transaction hashMapOf()) } override suspend fun setGlobalPlayerStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) = transaction { diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedExtensions.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedExtensions.kt new file mode 100644 index 0000000..e20e11b --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedExtensions.kt @@ -0,0 +1,63 @@ +package io.dico.parcels2.storage.exposed + +import org.jetbrains.exposed.sql.Column +import org.jetbrains.exposed.sql.Index +import org.jetbrains.exposed.sql.Table +import org.jetbrains.exposed.sql.Transaction +import org.jetbrains.exposed.sql.statements.InsertStatement +import org.jetbrains.exposed.sql.transactions.TransactionManager + +class UpsertStatement(table: Table, conflictColumn: Column<*>? = null, conflictIndex: Index? = null) + : InsertStatement(table, false) { + val indexName: String + val indexColumns: List> + + init { + when { + conflictIndex != null -> { + indexName = conflictIndex.indexName + indexColumns = conflictIndex.columns + } + conflictColumn != null -> { + indexName = conflictColumn.name + indexColumns = listOf(conflictColumn) + } + else -> throw IllegalArgumentException() + } + } + + override fun prepareSQL(transaction: Transaction) = buildString { + append(super.prepareSQL(transaction)) + + val dialect = transaction.db.vendor + if (dialect == "postgresql") { + + append(" ON CONFLICT(") + append(indexName) + append(") DO UPDATE SET ") + + values.keys.filter { it !in indexColumns }.joinTo(this) { "${transaction.identity(it)}=EXCLUDED.${transaction.identity(it)}" } + + } else { + + append (" ON DUPLICATE KEY UPDATE ") + values.keys.filter { it !in indexColumns }.joinTo(this) { "${transaction.identity(it)}=VALUES(${transaction.identity(it)})" } + + } + } + +} + +inline fun T.upsert(conflictColumn: Column<*>? = null, conflictIndex: Index? = null, body: T.(UpsertStatement) -> Unit) = + UpsertStatement(this, conflictColumn, conflictIndex).apply { + body(this) + execute(TransactionManager.current()) + } + +fun Table.indexR(customIndexName: String? = null, isUnique: Boolean = false, vararg columns: Column<*>): Index { + val index = Index(columns.toList(), isUnique, customIndexName) + indices.add(index) + return index +} + +fun Table.uniqueIndexR(customIndexName: String? = null, vararg columns: Column<*>): Index = indexR(customIndexName, true, *columns) diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt index e19fd3f..60e9bc0 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt @@ -7,7 +7,6 @@ import io.dico.parcels2.ParcelOwner import io.dico.parcels2.ParcelWorld import io.dico.parcels2.storage.SerializableParcel import io.dico.parcels2.storage.SerializableWorld -import io.dico.parcels2.storage.uniqueIndexR import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.toByteArray import io.dico.parcels2.util.toUUID @@ -42,7 +41,7 @@ sealed class IdTransactionsTable("parcel_worlds", "world_id") { val name = varchar("name", 50) - val uid = binary("uid", 2) + val uid = binary("uid", 16) val index_uid = uniqueIndexR("index_uid", uid) internal inline fun getId(binaryUid: ByteArray): Int? = getId { uid eq binaryUid } @@ -90,7 +89,7 @@ object ParcelsT : IdTransactionsTable("par } object OwnersT : IdTransactionsTable("parcel_owners", "owner_id") { - val uuid = binary("uuid", 2).nullable() + val uuid = binary("uuid", 16).nullable() val name = varchar("name", 32) val index_pair = uniqueIndexR("index_pair", uuid, name) diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt index ca2943d..3e1438a 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt @@ -6,8 +6,6 @@ import io.dico.parcels2.AddedStatus import io.dico.parcels2.Parcel import io.dico.parcels2.ParcelOwner import io.dico.parcels2.storage.SerializableParcel -import io.dico.parcels2.storage.uniqueIndexR -import io.dico.parcels2.storage.upsert import io.dico.parcels2.util.toByteArray import io.dico.parcels2.util.toUUID import kotlinx.coroutines.experimental.channels.SendChannel @@ -55,6 +53,7 @@ sealed class AddedTable(name: String, val idTable: IdTra } suspend fun sendAllAddedData(channel: AddedStatusSendChannel) { + /* val iterator = selectAll().orderBy(attach_id).iterator() if (iterator.hasNext()) { @@ -96,7 +95,7 @@ sealed class AddedTable(name: String, val idTable: IdTra } sendIfPresent() - } + }*/ } private inline fun Boolean?.asAddedStatus() = if (this == null) AddedStatus.DEFAULT else if (this) AddedStatus.ALLOWED else AddedStatus.BANNED -- cgit v1.2.3 From 0af2e615d3fa1d8509be46e14f99d40dc9cdb342 Mon Sep 17 00:00:00 2001 From: Dico Date: Thu, 2 Aug 2018 18:22:36 +0100 Subject: Refactor and improve a lot of the API. Move default implementations into a package. Reformatting. --- build.gradle.kts | 1 + src/main/kotlin/io/dico/parcels2/AddedData.kt | 20 +- .../kotlin/io/dico/parcels2/GlobalAddedData.kt | 48 ---- src/main/kotlin/io/dico/parcels2/Options.kt | 51 ++-- src/main/kotlin/io/dico/parcels2/Parcel.kt | 137 +-------- .../kotlin/io/dico/parcels2/ParcelGenerator.kt | 83 ++++++ src/main/kotlin/io/dico/parcels2/ParcelId.kt | 49 ++++ src/main/kotlin/io/dico/parcels2/ParcelOwner.kt | 15 +- src/main/kotlin/io/dico/parcels2/ParcelWorld.kt | 235 ++++----------- src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt | 19 +- src/main/kotlin/io/dico/parcels2/WorldGenerator.kt | 319 --------------------- .../io/dico/parcels2/blockvisitor/Attachables.kt | 2 +- .../dico/parcels2/blockvisitor/WorktimeLimiter.kt | 2 +- .../parcels2/command/AbstractParcelCommands.kt | 6 +- .../dico/parcels2/command/CommandsAddedStatus.kt | 56 ---- .../parcels2/command/CommandsAddedStatusGlobal.kt | 65 +++++ .../parcels2/command/CommandsAddedStatusLocal.kt | 56 ++++ .../io/dico/parcels2/command/CommandsDebug.kt | 5 +- .../io/dico/parcels2/command/CommandsGeneral.kt | 9 +- .../dico/parcels2/command/ParcelCommandBuilder.kt | 17 +- .../parcels2/command/ParcelCommandReceivers.kt | 14 +- .../dico/parcels2/command/ParcelParameterTypes.kt | 15 +- .../io/dico/parcels2/command/ParcelTarget.kt | 21 +- .../parcels2/defaultimpl/DefaultParcelContainer.kt | 65 +++++ .../parcels2/defaultimpl/DefaultParcelGenerator.kt | 267 +++++++++++++++++ .../defaultimpl/GlobalAddedDataManagerImpl.kt | 39 +++ .../io/dico/parcels2/defaultimpl/ParcelImpl.kt | 138 +++++++++ .../parcels2/defaultimpl/ParcelProviderImpl.kt | 119 ++++++++ .../dico/parcels2/defaultimpl/ParcelWorldImpl.kt | 94 ++++++ .../io/dico/parcels2/listener/ListenerHelper.kt | 9 +- .../dico/parcels2/listener/ParcelEntityTracker.kt | 6 +- .../io/dico/parcels2/listener/ParcelListeners.kt | 90 +++--- .../kotlin/io/dico/parcels2/storage/Backing.kt | 31 +- .../kotlin/io/dico/parcels2/storage/Jackson.kt | 4 +- .../io/dico/parcels2/storage/SerializableTypes.kt | 40 --- .../kotlin/io/dico/parcels2/storage/Storage.kt | 64 +++-- .../io/dico/parcels2/storage/StorageFactory.kt | 2 +- .../parcels2/storage/exposed/ExposedBacking.kt | 52 ++-- .../parcels2/storage/exposed/ExposedExtensions.kt | 2 +- .../io/dico/parcels2/storage/exposed/IdTables.kt | 63 ++-- .../io/dico/parcels2/storage/exposed/ListTables.kt | 15 +- .../dico/parcels2/storage/migration/Migration.kt | 8 + .../parcels2/storage/migration/MigrationFactory.kt | 5 + .../storage/migration/plotme/PlotmeMigration.kt | 118 ++++++++ .../storage/migration/plotme/PlotmeTables.kt | 26 ++ .../kotlin/io/dico/parcels2/util/BukkitAwait.kt | 72 ----- .../io/dico/parcels2/util/MaterialExtensions.kt | 115 ++++---- .../io/dico/parcels2/util/PlayerExtensions.kt | 3 +- src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt | 3 +- 49 files changed, 1512 insertions(+), 1183 deletions(-) delete mode 100644 src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt create mode 100644 src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt create mode 100644 src/main/kotlin/io/dico/parcels2/ParcelId.kt delete mode 100644 src/main/kotlin/io/dico/parcels2/WorldGenerator.kt delete mode 100644 src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatus.kt create mode 100644 src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusGlobal.kt create mode 100644 src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusLocal.kt create mode 100644 src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt create mode 100644 src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt create mode 100644 src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalAddedDataManagerImpl.kt create mode 100644 src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt create mode 100644 src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt create mode 100644 src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt delete mode 100644 src/main/kotlin/io/dico/parcels2/storage/SerializableTypes.kt create mode 100644 src/main/kotlin/io/dico/parcels2/storage/migration/Migration.kt create mode 100644 src/main/kotlin/io/dico/parcels2/storage/migration/MigrationFactory.kt create mode 100644 src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt create mode 100644 src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeTables.kt delete mode 100644 src/main/kotlin/io/dico/parcels2/util/BukkitAwait.kt diff --git a/build.gradle.kts b/build.gradle.kts index 11ead8b..8941601 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -72,6 +72,7 @@ dependencies { compile("org.jetbrains.exposed:exposed:0.10.3") { isTransitive = false } compile("joda-time:joda-time:2.10") compile("com.zaxxer:HikariCP:3.2.0") + compile("ch.qos.logback:logback-classic:1.2.3") val jacksonVersion = "2.9.6" compile("com.fasterxml.jackson.core:jackson-core:$jacksonVersion") diff --git a/src/main/kotlin/io/dico/parcels2/AddedData.kt b/src/main/kotlin/io/dico/parcels2/AddedData.kt index 5d2a68d..633fe72 100644 --- a/src/main/kotlin/io/dico/parcels2/AddedData.kt +++ b/src/main/kotlin/io/dico/parcels2/AddedData.kt @@ -2,10 +2,13 @@ package io.dico.parcels2 import io.dico.parcels2.util.uuid import org.bukkit.OfflinePlayer -import java.util.* +import java.util.UUID + +typealias MutableAddedDataMap = MutableMap +typealias AddedDataMap = Map interface AddedData { - val addedMap: Map + val addedMap: AddedDataMap fun getAddedStatus(uuid: UUID): AddedStatus fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean @@ -28,8 +31,7 @@ interface AddedData { fun unban(player: OfflinePlayer) = unban(player.uuid) } -open class AddedDataHolder(override var addedMap: MutableMap - = mutableMapOf()) : AddedData { +open class AddedDataHolder(override var addedMap: MutableAddedDataMap = mutableMapOf()) : AddedData { override fun getAddedStatus(uuid: UUID): AddedStatus = addedMap.getOrDefault(uuid, AddedStatus.DEFAULT) override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean = status.takeIf { it != AddedStatus.DEFAULT } ?.let { addedMap.put(uuid, it) != it } @@ -44,4 +46,12 @@ enum class AddedStatus { val isDefault get() = this == DEFAULT val isAllowed get() = this == ALLOWED val isBanned get() = this == BANNED -} \ No newline at end of file +} + +interface GlobalAddedData : AddedData { + val owner: ParcelOwner +} + +interface GlobalAddedDataManager { + operator fun get(owner: ParcelOwner): GlobalAddedData +} diff --git a/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt b/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt deleted file mode 100644 index 1528d21..0000000 --- a/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt +++ /dev/null @@ -1,48 +0,0 @@ -@file:Suppress("UNCHECKED_CAST") - -package io.dico.parcels2 - -import java.util.* - -interface GlobalAddedData : AddedData { - val owner: ParcelOwner -} - -interface GlobalAddedDataManager { - operator fun get(owner: ParcelOwner): GlobalAddedData -} - -class GlobalAddedDataManagerImpl(val plugin: ParcelsPlugin) : GlobalAddedDataManager { - private val map = mutableMapOf() - - override fun get(owner: ParcelOwner): GlobalAddedData { - return map[owner] ?: GlobalAddedDataImpl(owner).also { map[owner] = it } - } - - private inner class GlobalAddedDataImpl(override val owner: ParcelOwner, - data: MutableMap = emptyData) - : AddedDataHolder(data), GlobalAddedData { - - private inline var data get() = addedMap; set(value) = run { addedMap = value } - private inline val isEmpty get() = data === emptyData - - override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean { - if (isEmpty) { - if (status == AddedStatus.DEFAULT) return false - data = mutableMapOf() - } - return super.setAddedStatus(uuid, status).also { - if (it) plugin.storage.setGlobalAddedStatus(owner, uuid, status) - } - } - - } - - private companion object { - val emptyData = Collections.emptyMap() as MutableMap - } - -} - - - diff --git a/src/main/kotlin/io/dico/parcels2/Options.kt b/src/main/kotlin/io/dico/parcels2/Options.kt index c349d8b..aefa9e1 100644 --- a/src/main/kotlin/io/dico/parcels2/Options.kt +++ b/src/main/kotlin/io/dico/parcels2/Options.kt @@ -1,27 +1,33 @@ package io.dico.parcels2 - import com.fasterxml.jackson.annotation.JsonIgnore import io.dico.parcels2.blockvisitor.TickWorktimeOptions +import io.dico.parcels2.defaultimpl.DefaultGeneratorOptions import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.StorageFactory import io.dico.parcels2.storage.yamlObjectMapper -import org.bukkit.Bukkit.createBlockData import org.bukkit.GameMode import org.bukkit.Material -import org.bukkit.block.Biome -import org.bukkit.block.data.BlockData import java.io.Reader import java.io.Writer -import java.util.* +import java.util.EnumSet class Options { - var worlds: Map = HashMap() + var worlds: Map = hashMapOf() private set var storage: StorageOptions = StorageOptions("postgresql", DataConnectionOptions()) var tickWorktime: TickWorktimeOptions = TickWorktimeOptions(20, 1) - fun addWorld(name: String, options: WorldOptions) = (worlds as MutableMap).put(name, options) + fun addWorld(name: String, + generatorOptions: GeneratorOptions? = null, + worldOptions: WorldOptions? = null) { + val optionsHolder = WorldOptionsHolder( + generatorOptions ?: DefaultGeneratorOptions(), + worldOptions ?: WorldOptions() + ) + + (worlds as MutableMap).put(name, optionsHolder) + } fun writeTo(writer: Writer) = yamlObjectMapper.writeValue(writer, this) @@ -31,6 +37,9 @@ class Options { } +class WorldOptionsHolder(var generator: GeneratorOptions = DefaultGeneratorOptions(), + var runtime: WorldOptions = WorldOptions()) + data class WorldOptions(var gameMode: GameMode? = GameMode.CREATIVE, var dayTime: Boolean = true, var noWeather: Boolean = true, @@ -42,8 +51,7 @@ data class WorldOptions(var gameMode: GameMode? = GameMode.CREATIVE, var blockPortalCreation: Boolean = true, var blockMobSpawning: Boolean = true, var blockedItems: Set = EnumSet.of(Material.FLINT_AND_STEEL, Material.SNOWBALL), - var axisLimit: Int = 10, - var generator: GeneratorOptions = DefaultGeneratorOptions()) { + var axisLimit: Int = 10) { } @@ -51,23 +59,7 @@ abstract class GeneratorOptions { abstract fun generatorFactory(): GeneratorFactory - fun getGenerator(worlds: Worlds, worldName: String) = generatorFactory().newParcelGenerator(worlds, worldName, this) - -} - -data class DefaultGeneratorOptions(var defaultBiome: Biome = Biome.JUNGLE, - var wallType: BlockData = createBlockData(Material.STONE_SLAB), - var floorType: BlockData = createBlockData(Material.QUARTZ_BLOCK), - var fillType: BlockData = createBlockData(Material.QUARTZ_BLOCK), - var pathMainType: BlockData = createBlockData(Material.SANDSTONE), - var pathAltType: BlockData = createBlockData(Material.REDSTONE_BLOCK), - var parcelSize: Int = 101, - var pathSize: Int = 9, - var floorHeight: Int = 64, - var offsetX: Int = 0, - var offsetZ: Int = 0) : GeneratorOptions() { - - override fun generatorFactory(): GeneratorFactory = DefaultParcelGenerator.Factory + fun newGenerator(worldName: String) = generatorFactory().newParcelGenerator(worldName, this) } @@ -104,4 +96,9 @@ data class DataConnectionOptions(val address: String = "localhost", } -data class DataFileOptions(val location: String = "/flatfile-storage/") \ No newline at end of file +data class DataFileOptions(val location: String = "/flatfile-storage/") + +class MigrationOptions() { + + +} diff --git a/src/main/kotlin/io/dico/parcels2/Parcel.kt b/src/main/kotlin/io/dico/parcels2/Parcel.kt index 335b415..a69116a 100644 --- a/src/main/kotlin/io/dico/parcels2/Parcel.kt +++ b/src/main/kotlin/io/dico/parcels2/Parcel.kt @@ -1,14 +1,11 @@ package io.dico.parcels2 -import io.dico.dicore.Formatting import io.dico.parcels2.util.Vec2i -import io.dico.parcels2.util.getPlayerName import io.dico.parcels2.util.hasBuildAnywhere import org.bukkit.OfflinePlayer import org.bukkit.entity.Player import org.joda.time.DateTime -import java.util.* -import kotlin.reflect.KProperty +import java.util.UUID /** * Parcel implementation of ParcelData will update the database when changes are made. @@ -18,73 +15,21 @@ import kotlin.reflect.KProperty * However, this implementation is intentionally not thread-safe. * Therefore, database query callbacks should schedule their updates using the bukkit scheduler. */ -class Parcel(val world: ParcelWorld, val pos: Vec2i) : ParcelData { - var data: ParcelData = ParcelDataHolder(); private set +interface Parcel : ParcelData { + val id: ParcelId + val world: ParcelWorld + val pos: Vec2i + val x: Int + val z: Int + val data: ParcelData + val infoString: String + val hasBlockVisitors: Boolean - val id get() = "${pos.x}:${pos.z}" - val homeLocation get() = world.generator.getHomeLocation(this) + fun copyDataIgnoringDatabase(data: ParcelData) - val infoString by ParcelInfoStringComputer + fun copyData(data: ParcelData) - fun copyDataIgnoringDatabase(data: ParcelData) { - this.data = data - } - - fun copyData(data: ParcelData) { - copyDataIgnoringDatabase(data) - world.storage.setParcelData(this, data) - } - - fun dispose() { - copyDataIgnoringDatabase(ParcelDataHolder()) - world.storage.setParcelData(this, null) - } - - override val addedMap: Map get() = data.addedMap - override fun getAddedStatus(uuid: UUID) = data.getAddedStatus(uuid) - override fun isBanned(uuid: UUID) = data.isBanned(uuid) - override fun isAllowed(uuid: UUID) = data.isAllowed(uuid) - override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean): Boolean { - return (data.canBuild(player, checkAdmin, false)) - || checkGlobal && world.globalAddedData[owner ?: return false].isAllowed(player) - } - - val globalAddedMap: Map? get() = owner?.let { world.globalAddedData[it].addedMap } - - override val since: DateTime? get() = data.since - - override var owner: ParcelOwner? - get() = data.owner - set(value) { - if (data.owner != value) { - world.storage.setParcelOwner(this, value) - data.owner = value - } - } - - override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean { - return data.setAddedStatus(uuid, status).also { - if (it) world.storage.setParcelPlayerStatus(this, uuid, status) - } - } - - override var allowInteractInputs: Boolean - get() = data.allowInteractInputs - set(value) { - if (data.allowInteractInputs == value) return - world.storage.setParcelAllowsInteractInputs(this, value) - data.allowInteractInputs = value - } - - override var allowInteractInventory: Boolean - get() = data.allowInteractInventory - set(value) { - if (data.allowInteractInventory == value) return - world.storage.setParcelAllowsInteractInventory(this, value) - data.allowInteractInventory = value - } - - var hasBlockVisitors: Boolean = false; private set + fun dispose() } interface ParcelData : AddedData { @@ -113,59 +58,3 @@ class ParcelDataHolder : AddedDataHolder(), ParcelData { override var allowInteractInventory = true } -private object ParcelInfoStringComputer { - val infoStringColor1 = Formatting.GREEN - val infoStringColor2 = Formatting.AQUA - - private inline fun StringBuilder.appendField(name: String, value: StringBuilder.() -> Unit) { - append(infoStringColor1) - append(name) - append(": ") - append(infoStringColor2) - value() - append(' ') - } - - operator fun getValue(parcel: Parcel, property: KProperty<*>): String = buildString { - appendField("ID") { - append(parcel.pos.x) - append(':') - append(parcel.pos.z) - } - - appendField("Owner") { - val owner = parcel.owner - if (owner == null) { - append(infoStringColor1) - append("none") - } else { - append(owner.notNullName) - } - } - - // plotme appends biome here - - append('\n') - - val allowedMap = parcel.addedMap.filterValues { it.isAllowed } - if (allowedMap.isNotEmpty()) appendField("Allowed") { - allowedMap.keys.map(::getPlayerName).joinTo(this) - } - - val bannedMap = parcel.addedMap.filterValues { it.isBanned } - if (bannedMap.isNotEmpty()) appendField("Banned") { - bannedMap.keys.map(::getPlayerName).joinTo(this) - } - - if (!parcel.allowInteractInputs || !parcel.allowInteractInventory) { - appendField("Options") { - append("(") - appendField("inputs") { append(parcel.allowInteractInputs)} - append(", ") - appendField("inventory") { append(parcel.allowInteractInventory) } - append(")") - } - } - - } -} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt b/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt new file mode 100644 index 0000000..ff28537 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt @@ -0,0 +1,83 @@ +package io.dico.parcels2 + +import io.dico.parcels2.blockvisitor.RegionTraversal +import io.dico.parcels2.blockvisitor.Worker +import io.dico.parcels2.blockvisitor.WorktimeLimiter +import io.dico.parcels2.defaultimpl.DefaultParcelGenerator +import io.dico.parcels2.util.Vec2i +import org.bukkit.Chunk +import org.bukkit.Location +import org.bukkit.World +import org.bukkit.block.Biome +import org.bukkit.block.Block +import org.bukkit.entity.Entity +import org.bukkit.generator.BlockPopulator +import org.bukkit.generator.ChunkGenerator +import java.util.HashMap +import java.util.Random +import kotlin.reflect.KClass + +object GeneratorFactories { + private val map: MutableMap = HashMap() + + fun registerFactory(generator: GeneratorFactory): Boolean = map.putIfAbsent(generator.name, generator) == null + + fun getFactory(name: String): GeneratorFactory? = map.get(name) + + init { + registerFactory(DefaultParcelGenerator.Factory) + } +} + +interface GeneratorFactory { + val name: String + + val optionsClass: KClass + + fun newParcelGenerator(worldName: String, options: GeneratorOptions): ParcelGenerator +} + +abstract class ParcelGenerator : ChunkGenerator() { + abstract val world: World + + abstract override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData + + abstract fun populate(world: World?, random: Random?, chunk: Chunk?) + + abstract override fun getFixedSpawnLocation(world: World?, random: Random?): Location + + override fun getDefaultPopulators(world: World?): MutableList { + return mutableListOf(object : BlockPopulator() { + override fun populate(world: World?, random: Random?, chunk: Chunk?) { + this@ParcelGenerator.populate(world, random, chunk) + } + }) + } + + abstract fun makeParcelBlockManager(worktimeLimiter: WorktimeLimiter): ParcelBlockManager + + abstract fun makeParcelLocator(container: ParcelContainer): ParcelLocator +} + +interface ParcelBlockManager { + val world: World + val worktimeLimiter: WorktimeLimiter + + fun getBottomBlock(parcel: ParcelId): Vec2i + + fun getHomeLocation(parcel: ParcelId): Location + + fun setOwnerBlock(parcel: ParcelId, owner: ParcelOwner?) + + @Deprecated("") + fun getEntities(parcel: ParcelId): Collection = TODO() + + @Deprecated("") + fun getBlocks(parcel: ParcelId, yRange: IntRange = 0..255): Iterator = TODO() + + fun setBiome(parcel: ParcelId, biome: Biome): Worker + + fun clearParcel(parcel: ParcelId): Worker + + fun doBlockOperation(parcel: ParcelId, direction: RegionTraversal = RegionTraversal.DOWNWARD, operation: (Block) -> Unit): Worker +} diff --git a/src/main/kotlin/io/dico/parcels2/ParcelId.kt b/src/main/kotlin/io/dico/parcels2/ParcelId.kt new file mode 100644 index 0000000..951a172 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/ParcelId.kt @@ -0,0 +1,49 @@ +package io.dico.parcels2 + +import io.dico.parcels2.util.Vec2i +import org.bukkit.Bukkit +import org.bukkit.World +import java.util.UUID + +/** + * Used by storage backing options to encompass the identity of a world + * Does NOT support equality operator. + */ +interface ParcelWorldId { + val name: String + val uid: UUID? + fun equals(id: ParcelWorldId): Boolean = name == name || (uid != null && uid == id.uid) + + val bukkitWorld: World? get() = Bukkit.getWorld(name) ?: uid?.let { Bukkit.getWorld(it) } + + companion object { + operator fun invoke(worldName: String, worldUid: UUID?): ParcelWorldId = ParcelWorldIdImpl(worldName, worldUid) + operator fun invoke(worldName: String): ParcelWorldId = ParcelWorldIdImpl(worldName, null) + } +} + +/** + * Used by storage backing options to encompass the location of a parcel + * Does NOT support equality operator. + */ +interface ParcelId { + val worldId: ParcelWorldId + val x: Int + val z: Int + val pos: Vec2i get() = Vec2i(x, z) + fun equals(id: ParcelId): Boolean = x == id.x && z == id.z && worldId.equals(id.worldId) + + companion object { + operator fun invoke(worldId: ParcelWorldId, pos: Vec2i) = invoke(worldId, pos.x, pos.z) + operator fun invoke(worldName: String, worldUid: UUID?, pos: Vec2i) = invoke(worldName, worldUid, pos.x, pos.z) + operator fun invoke(worldName: String, worldUid: UUID?, x: Int, z: Int) = invoke(ParcelWorldId(worldName, worldUid), x, z) + operator fun invoke(worldId: ParcelWorldId, x: Int, z: Int): ParcelId = ParcelIdImpl(worldId, x, z) + } +} + +private class ParcelWorldIdImpl(override val name: String, + override val uid: UUID?) : ParcelWorldId + +private class ParcelIdImpl(override val worldId: ParcelWorldId, + override val x: Int, + override val z: Int) : ParcelId diff --git a/src/main/kotlin/io/dico/parcels2/ParcelOwner.kt b/src/main/kotlin/io/dico/parcels2/ParcelOwner.kt index 07e7c09..5a36cac 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelOwner.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelOwner.kt @@ -1,4 +1,4 @@ -@file:Suppress("unused") +@file:Suppress("unused", "UsePropertyAccessSyntax", "DEPRECATION") package io.dico.parcels2 @@ -8,11 +8,10 @@ import io.dico.parcels2.util.uuid import org.bukkit.Bukkit import org.bukkit.OfflinePlayer import org.bukkit.entity.Player -import java.util.* +import java.util.UUID -@Suppress("UsePropertyAccessSyntax") -class ParcelOwner private constructor(val uuid: UUID?, - val name: String?) { +class ParcelOwner(val uuid: UUID?, + val name: String?) { val notNullName: String by lazy { name ?: getPlayerNameOrDefault(uuid!!) } constructor(name: String) : this(null, name) @@ -26,11 +25,11 @@ class ParcelOwner private constructor(val uuid: UUID?, inline val hasUUID: Boolean get() = uuid != null val onlinePlayer: Player? get() = uuid?.let { Bukkit.getPlayer(uuid) } - @Suppress("DEPRECATION") + val onlinePlayerAllowingNameMatch: Player? get() = onlinePlayer ?: name?.let { Bukkit.getPlayerExact(name) } val offlinePlayer: OfflinePlayer? get() = uuid?.let { Bukkit.getOfflinePlayer(it).takeIf { it.isValid } } - @Suppress("DEPRECATION") - val offlinePlayerAllowingNameMatch: OfflinePlayer? get() = offlinePlayer ?: Bukkit.getOfflinePlayer(name).takeIf { it.isValid } + val offlinePlayerAllowingNameMatch: OfflinePlayer? + get() = offlinePlayer ?: Bukkit.getOfflinePlayer(name).takeIf { it.isValid } fun matches(player: OfflinePlayer, allowNameMatch: Boolean = false): Boolean { return uuid?.let { it == player.uniqueId } ?: false diff --git a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt index 5e7187b..16f108f 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt @@ -1,223 +1,86 @@ package io.dico.parcels2 -import io.dico.parcels2.storage.SerializableParcel -import io.dico.parcels2.storage.SerializableWorld import io.dico.parcels2.storage.Storage -import io.dico.parcels2.storage.getParcelBySerializedValue import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.floor -import kotlinx.coroutines.experimental.launch -import org.bukkit.Bukkit import org.bukkit.Location import org.bukkit.World -import org.bukkit.WorldCreator import org.bukkit.block.Block import org.bukkit.entity.Entity -import org.bukkit.entity.Player -import java.util.* -import kotlin.coroutines.experimental.buildIterator -import kotlin.coroutines.experimental.buildSequence +import java.util.UUID -class Worlds(val plugin: ParcelsPlugin) { - val worlds: Map get() = _worlds - private val _worlds: MutableMap = HashMap() +interface ParcelProvider { + val worlds: Map + + fun getWorldById(id: ParcelWorldId): ParcelWorld? + + fun getParcelById(id: ParcelId): Parcel? - fun getWorld(name: String): ParcelWorld? = _worlds[name] + fun getWorld(name: String): ParcelWorld? fun getWorld(world: World): ParcelWorld? = getWorld(world.name) - fun getParcelAt(block: Block): Parcel? = getParcelAt(block.world, block.x, block.z) + fun getWorld(block: Block): ParcelWorld? = getWorld(block.world) + + fun getWorld(loc: Location): ParcelWorld? = getWorld(loc.world) - fun getParcelAt(player: Player): Parcel? = getParcelAt(player.location) + fun getWorld(entity: Entity): ParcelWorld? = getWorld(entity.location) - fun getParcelAt(location: Location): Parcel? = getParcelAt(location.world, location.x.floor(), location.z.floor()) + fun getParcelAt(worldName: String, x: Int, z: Int): Parcel? = getWorld(worldName)?.locator?.getParcelAt(x, z) fun getParcelAt(world: World, x: Int, z: Int): Parcel? = getParcelAt(world.name, x, z) - fun getParcelAt(world: String, x: Int, z: Int): Parcel? { - with(getWorld(world) ?: return null) { - return generator.parcelAt(x, z) - } - } - - operator fun SerializableParcel.invoke(): Parcel? { - return world()?.parcelByID(pos) - } - - operator fun SerializableWorld.invoke(): ParcelWorld? { - return world?.let { getWorld(it) } - } - - fun loadWorlds(options: Options) { - for ((worldName, worldOptions) in options.worlds.entries) { - val world: ParcelWorld - try { - - world = ParcelWorld( - worldName, - worldOptions, - worldOptions.generator.getGenerator(this, worldName), - plugin.storage, - plugin.globalAddedData) - - } catch (ex: Exception) { - ex.printStackTrace() - continue - } - - _worlds[worldName] = world - } - - plugin.functionHelper.schedule(10) { - println("Parcels generating worlds now") - for ((name, world) in _worlds) { - if (Bukkit.getWorld(name) == null) { - val bworld = WorldCreator(name).generator(world.generator).createWorld() - val spawn = world.generator.getFixedSpawnLocation(bworld, null) - bworld.setSpawnLocation(spawn.x.floor(), spawn.y.floor(), spawn.z.floor()) - } - } - - val channel = plugin.storage.readAllParcelData() - val job = plugin.functionHelper.launchLazilyOnMainThread { - do { - val pair = channel.receiveOrNull() ?: break - val parcel = getParcelBySerializedValue(pair.first) ?: continue - pair.second?.let { parcel.copyDataIgnoringDatabase(it) } - } while (true) - } - job.start() - } - - } + fun getParcelAt(world: World, vec: Vec2i): Parcel? = getParcelAt(world, vec.x, vec.z) + + fun getParcelAt(loc: Location): Parcel? = getParcelAt(loc.world, loc.x.floor(), loc.z.floor()) + + fun getParcelAt(entity: Entity): Parcel? = getParcelAt(entity.location) + + fun getParcelAt(block: Block): Parcel? = getParcelAt(block.world, block.x, block.z) + + fun getWorldGenerator(worldName: String): ParcelGenerator? + + fun loadWorlds() } -interface ParcelProvider { +interface ParcelLocator { + val world: World - fun parcelAt(x: Int, z: Int): Parcel? + fun getParcelIdAt(x: Int, z: Int): ParcelId? - fun parcelAt(vec: Vec2i): Parcel? = parcelAt(vec.x, vec.z) + fun getParcelAt(x: Int, z: Int): Parcel? - fun parcelAt(loc: Location): Parcel? = parcelAt(loc.x.floor(), loc.z.floor()) + fun getParcelAt(vec: Vec2i): Parcel? = getParcelAt(vec.x, vec.z) - fun parcelAt(entity: Entity): Parcel? = parcelAt(entity.location) + fun getParcelAt(loc: Location): Parcel? = getParcelAt(loc.x.floor(), loc.z.floor()).takeIf { loc.world == world } - fun parcelAt(block: Block): Parcel? = parcelAt(block.x, block.z) -} + fun getParcelAt(entity: Entity): Parcel? = getParcelAt(entity.location).takeIf { entity.world == world } -class ParcelWorld constructor(val name: String, - val options: WorldOptions, - val generator: ParcelGenerator, - val storage: Storage, - val globalAddedData: GlobalAddedDataManager) : ParcelProvider by generator, ParcelContainer { - val world: World by lazy { - Bukkit.getWorld(name) ?: throw NullPointerException("World $name does not appear to be loaded") - } - val container: ParcelContainer = DefaultParcelContainer(this, storage) - - override fun parcelByID(x: Int, z: Int): Parcel? { - return container.parcelByID(x, z) - } - - override fun nextEmptyParcel(): Parcel? { - return container.nextEmptyParcel() - } - - fun parcelByID(id: Vec2i): Parcel? = parcelByID(id.x, id.z) - - fun enforceOptionsIfApplicable() { - val world = world - val options = options - if (options.dayTime) { - world.setGameRuleValue("doDaylightCycle", "false") - world.setTime(6000) - } - - if (options.noWeather) { - world.setStorm(false) - world.setThundering(false) - world.weatherDuration = Integer.MAX_VALUE - } - - world.setGameRuleValue("doTileDrops", "${options.doTileDrops}") - } + fun getParcelAt(block: Block): Parcel? = getParcelAt(block.x, block.z).takeIf { block.world == world } } +typealias ParcelContainerFactory = (ParcelWorld) -> ParcelContainer + interface ParcelContainer { - fun parcelByID(x: Int, z: Int): Parcel? + fun getParcelById(x: Int, z: Int): Parcel? + + fun getParcelById(id: Vec2i): Parcel? = getParcelById(id.x, id.z) fun nextEmptyParcel(): Parcel? } -typealias ParcelContainerFactory = (ParcelWorld) -> ParcelContainer - -class DefaultParcelContainer(private val world: ParcelWorld, - private val storage: Storage) : ParcelContainer { - private var parcels: Array> - - init { - parcels = initArray(world.options.axisLimit, world) - } - - fun resizeIfSizeChanged() { - if (parcels.size / 2 != world.options.axisLimit) { - resize(world.options.axisLimit) - } - } - - fun resize(axisLimit: Int) { - parcels = initArray(axisLimit, world, this) - } - - fun initArray(axisLimit: Int, world: ParcelWorld, cur: DefaultParcelContainer? = null): Array> { - val arraySize = 2 * axisLimit + 1 - return Array(arraySize) { - val x = it - axisLimit - Array(arraySize) { - val z = it - axisLimit - cur?.parcelByID(x, z) ?: Parcel(world, Vec2i(x, z)) - } - } - } - - override fun parcelByID(x: Int, z: Int): Parcel? { - return parcels.getOrNull(x + world.options.axisLimit)?.getOrNull(z + world.options.axisLimit) - } - - override fun nextEmptyParcel(): Parcel? { - return walkInCircle().find { it.owner == null } - } - - private fun walkInCircle(): Iterable = Iterable { - buildIterator { - val center = world.options.axisLimit - for (radius in 0..center) { - var x = center - radius; - var z = center - radius - repeat(radius * 2) { yield(parcels[x++][z]) } - repeat(radius * 2) { yield(parcels[x][z++]) } - repeat(radius * 2) { yield(parcels[x--][z]) } - repeat(radius * 2) { yield(parcels[x][z--]) } - } - } - } - - fun allParcels(): Sequence = buildSequence { - for (array in parcels) { - yieldAll(array.iterator()) - } - } - - fun loadAllData() { - val channel = storage.readParcelData(allParcels()) - launch(storage.asyncDispatcher) { - for ((parcel, data) in channel) { - data?.let { parcel.copyDataIgnoringDatabase(it) } - } - } - } - -} \ No newline at end of file +interface ParcelWorld : ParcelLocator, ParcelContainer, ParcelBlockManager { + val id: ParcelWorldId + val name: String + val uid: UUID? + val options: WorldOptions + val generator: ParcelGenerator + val storage: Storage + val container: ParcelContainer + val locator: ParcelLocator + val blockManager: ParcelBlockManager + val globalAddedData: GlobalAddedDataManager +} diff --git a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt index 2533277..3a74626 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt @@ -6,6 +6,8 @@ import io.dico.dicore.command.ICommandDispatcher import io.dico.parcels2.blockvisitor.TickWorktimeLimiter import io.dico.parcels2.blockvisitor.WorktimeLimiter import io.dico.parcels2.command.getParcelCommands +import io.dico.parcels2.defaultimpl.GlobalAddedDataManagerImpl +import io.dico.parcels2.defaultimpl.ParcelProviderImpl import io.dico.parcels2.listener.ParcelEntityTracker import io.dico.parcels2.listener.ParcelListeners import io.dico.parcels2.storage.Storage @@ -13,6 +15,7 @@ import io.dico.parcels2.storage.yamlObjectMapper import io.dico.parcels2.util.FunctionHelper import io.dico.parcels2.util.tryCreate import org.bukkit.Bukkit +import org.bukkit.generator.ChunkGenerator import org.bukkit.plugin.java.JavaPlugin import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -24,7 +27,7 @@ private inline val plogger get() = logger class ParcelsPlugin : JavaPlugin() { lateinit var optionsFile: File; private set lateinit var options: Options; private set - lateinit var worlds: Worlds; private set + lateinit var parcelProvider: ParcelProvider; private set lateinit var storage: Storage; private set lateinit var globalAddedData: GlobalAddedDataManager; private set @@ -50,7 +53,7 @@ class ParcelsPlugin : JavaPlugin() { private fun init(): Boolean { optionsFile = File(dataFolder, "options.yml") options = Options() - worlds = Worlds(this) + parcelProvider = ParcelProviderImpl(this) try { if (!loadOptions()) return false @@ -64,8 +67,7 @@ class ParcelsPlugin : JavaPlugin() { } globalAddedData = GlobalAddedDataManagerImpl(this) - worlds.loadWorlds(options) - entityTracker = ParcelEntityTracker(worlds) + entityTracker = ParcelEntityTracker(parcelProvider) } catch (ex: Exception) { plogger.error("Error loading options", ex) return false @@ -74,6 +76,7 @@ class ParcelsPlugin : JavaPlugin() { registerListeners() registerCommands() + parcelProvider.loadWorlds() return true } @@ -81,7 +84,7 @@ class ParcelsPlugin : JavaPlugin() { when { optionsFile.exists() -> yamlObjectMapper.readerForUpdating(options).readValue(optionsFile) optionsFile.tryCreate() -> { - options.addWorld("parcels", WorldOptions()) + options.addWorld("parcels") try { yamlObjectMapper.writeValue(optionsFile, options) } catch (ex: Throwable) { @@ -99,6 +102,10 @@ class ParcelsPlugin : JavaPlugin() { return true } + override fun getDefaultWorldGenerator(worldName: String, generatorId: String?): ChunkGenerator? { + return parcelProvider.getWorldGenerator(worldName) + } + private fun registerCommands() { cmdDispatcher = getParcelCommands(this).apply { registerToCommandMap("parcels:", EOverridePolicy.FALLBACK_ONLY) @@ -107,7 +114,7 @@ class ParcelsPlugin : JavaPlugin() { private fun registerListeners() { if (listeners == null) { - listeners = ParcelListeners(worlds, entityTracker) + listeners = ParcelListeners(parcelProvider, entityTracker) registrator.registerListeners(listeners!!) } } diff --git a/src/main/kotlin/io/dico/parcels2/WorldGenerator.kt b/src/main/kotlin/io/dico/parcels2/WorldGenerator.kt deleted file mode 100644 index ccbc7e9..0000000 --- a/src/main/kotlin/io/dico/parcels2/WorldGenerator.kt +++ /dev/null @@ -1,319 +0,0 @@ -package io.dico.parcels2 - -import io.dico.parcels2.blockvisitor.Worker -import io.dico.parcels2.blockvisitor.RegionTraversal -import io.dico.parcels2.util.* -import org.bukkit.* -import org.bukkit.Bukkit.createBlockData -import org.bukkit.block.Biome -import org.bukkit.block.Block -import org.bukkit.block.BlockFace -import org.bukkit.block.Skull -import org.bukkit.block.data.BlockData -import org.bukkit.block.data.type.Sign -import org.bukkit.block.data.type.Slab -import org.bukkit.entity.Entity -import org.bukkit.generator.BlockPopulator -import org.bukkit.generator.ChunkGenerator -import java.util.* -import kotlin.coroutines.experimental.buildIterator -import kotlin.reflect.KClass - -abstract class ParcelGenerator : ChunkGenerator(), ParcelProvider { - abstract val world: ParcelWorld - - abstract val factory: GeneratorFactory - - abstract fun parcelIDAt(x: Int, z: Int): Vec2i? - - abstract override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData - - abstract fun populate(world: World?, random: Random?, chunk: Chunk?) - - abstract override fun getFixedSpawnLocation(world: World?, random: Random?): Location - - override fun getDefaultPopulators(world: World?): MutableList { - return Collections.singletonList(object : BlockPopulator() { - override fun populate(world: World?, random: Random?, chunk: Chunk?) { - this@ParcelGenerator.populate(world, random, chunk) - } - }) - } - - abstract fun updateOwner(parcel: Parcel) - - abstract fun getBottomCoord(parcel: Parcel): Vec2i - - abstract fun getHomeLocation(parcel: Parcel): Location - - abstract fun setBiome(parcel: Parcel, biome: Biome) - - abstract fun getEntities(parcel: Parcel): Collection - - abstract fun getBlocks(parcel: Parcel, yRange: IntRange = 0..255): Iterator - - abstract fun clearParcel(parcel: Parcel): Worker - - abstract fun doBlockOperation(parcel: Parcel, direction: RegionTraversal = RegionTraversal.DOWNWARD, operation: (Block) -> Unit): Worker - -} - -interface GeneratorFactory { - companion object GeneratorFactories { - private val map: MutableMap = HashMap() - - fun registerFactory(generator: GeneratorFactory): Boolean = map.putIfAbsent(generator.name, generator) == null - - fun getFactory(name: String): GeneratorFactory? = map.get(name) - - init { - registerFactory(DefaultParcelGenerator.Factory) - } - - } - - val name: String - - val optionsClass: KClass - - fun newParcelGenerator(worlds: Worlds, worldName: String, options: GeneratorOptions): ParcelGenerator - -} - -class DefaultParcelGenerator(val worlds: Worlds, val name: String, private val o: DefaultGeneratorOptions) : ParcelGenerator() { - override val world: ParcelWorld by lazy { worlds.getWorld(name)!! } - override val factory = Factory - val worktimeLimiter = worlds.plugin.worktimeLimiter - val maxHeight by lazy { world.world.maxHeight } - val airType = worlds.plugin.server.createBlockData(Material.AIR) - - companion object Factory : GeneratorFactory { - override val name get() = "default" - override val optionsClass get() = DefaultGeneratorOptions::class - override fun newParcelGenerator(worlds: Worlds, worldName: String, options: GeneratorOptions): ParcelGenerator { - return DefaultParcelGenerator(worlds, worldName, options as DefaultGeneratorOptions) - } - } - - val sectionSize = o.parcelSize + o.pathSize - val pathOffset = (if (o.pathSize % 2 == 0) o.pathSize + 2 else o.pathSize + 1) / 2 - val makePathMain = o.pathSize > 2 - val makePathAlt = o.pathSize > 4 - - private inline fun generate(chunkX: Int, - chunkZ: Int, - floor: T, wall: - T, pathMain: T, - pathAlt: T, - fill: T, - setter: (Int, Int, Int, T) -> Unit) { - - val floorHeight = o.floorHeight - val parcelSize = o.parcelSize - val sectionSize = sectionSize - val pathOffset = pathOffset - val makePathMain = makePathMain - val makePathAlt = makePathAlt - - // parcel bottom x and z - // umod is unsigned %: the result is always >= 0 - val pbx = ((chunkX shl 4) - o.offsetX) umod sectionSize - val pbz = ((chunkZ shl 4) - o.offsetZ) umod sectionSize - - var curHeight: Int - var x: Int - var z: Int - for (cx in 0..15) { - for (cz in 0..15) { - x = (pbx + cx) % sectionSize - pathOffset - z = (pbz + cz) % sectionSize - pathOffset - curHeight = floorHeight - - val type = when { - (x in 0 until parcelSize && z in 0 until parcelSize) -> floor - (x in -1..parcelSize && z in -1..parcelSize) -> { - curHeight++ - wall - } - (makePathAlt && x in -2 until parcelSize + 2 && z in -2 until parcelSize + 2) -> pathAlt - (makePathMain) -> pathMain - else -> { - curHeight++ - wall - } - } - - for (y in 0 until curHeight) { - setter(cx, y, cz, fill) - } - setter(cx, curHeight, cz, type) - } - } - } - - override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData { - val out = Bukkit.createChunkData(world) - generate(chunkX, chunkZ, o.floorType, o.wallType, o.pathMainType, o.pathAltType, o.fillType) { x, y, z, type -> - out.setBlock(x, y, z, type) - } - return out - } - - - override fun populate(world: World?, random: Random?, chunk: Chunk?) { - /* - generate(chunk!!.x, chunk.z, o.floorType.data, o.wallType.data, o.pathMainType.data, o.pathAltType.data, o.fillType.data) { x, y, z, type -> - if (type == 0.toByte()) chunk.getBlock(x, y, z).setData(type, false) - } - */ - } - - override fun getFixedSpawnLocation(world: World?, random: Random?): Location { - val fix = if (o.parcelSize.even) 0.5 else 0.0 - return Location(world, o.offsetX + fix, o.floorHeight + 1.0, o.offsetZ + fix) - } - - private inline fun convertBlockLocationToID(x: Int, z: Int, mapper: (Int, Int) -> T): T? { - val sectionSize = sectionSize - val parcelSize = o.parcelSize - val absX = x - o.offsetX - pathOffset - val absZ = z - o.offsetZ - pathOffset - val modX = absX umod sectionSize - val modZ = absZ umod sectionSize - if (modX in 0 until parcelSize && modZ in 0 until parcelSize) { - return mapper((absX - modX) / sectionSize, (absZ - modZ) / sectionSize) - } - return null - } - - override fun parcelIDAt(x: Int, z: Int): Vec2i? { - return convertBlockLocationToID(x, z) { idx, idz -> Vec2i(idx, idz) } - } - - override fun parcelAt(x: Int, z: Int): Parcel? { - return convertBlockLocationToID(x, z) { idx, idz -> - world.parcelByID(idx, idz) - } - } - - override fun getBottomCoord(parcel: Parcel): Vec2i = Vec2i(sectionSize * parcel.pos.x + pathOffset + o.offsetX, - sectionSize * parcel.pos.z + pathOffset + o.offsetZ) - - override fun getHomeLocation(parcel: Parcel): Location { - val bottom = getBottomCoord(parcel) - return Location(world.world, bottom.x.toDouble(), o.floorHeight + 1.0, bottom.z + (o.parcelSize - 1) / 2.0, -90F, 0F) - } - - override fun updateOwner(parcel: Parcel) { - val world = this.world.world - val b = getBottomCoord(parcel) - - val wallBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 1) - val signBlock = world.getBlockAt(b.x - 2, o.floorHeight + 1, b.z - 1) - val skullBlock = world.getBlockAt(b.x - 1, o.floorHeight + 2, b.z - 1) - - val owner = parcel.owner - if (owner == null) { - wallBlock.blockData = o.wallType - signBlock.type = Material.AIR - skullBlock.type = Material.AIR - } else { - - val wallBlockType: BlockData = if (o.wallType is Slab) - (o.wallType.clone() as Slab).apply { type = Slab.Type.DOUBLE } - else - o.wallType - - wallBlock.blockData = wallBlockType - - signBlock.blockData = (createBlockData(Material.WALL_SIGN) as Sign).apply { rotation = BlockFace.NORTH } - - val sign = signBlock.state as org.bukkit.block.Sign - sign.setLine(0, parcel.id) - sign.setLine(2, owner.name) - sign.update() - - skullBlock.type = Material.PLAYER_HEAD - val skull = skullBlock.state as Skull - if (owner.uuid != null) { - skull.owningPlayer = owner.offlinePlayer - } else { - skull.owner = owner.name - } - skull.rotation = BlockFace.WEST - skull.update() - } - } - - override fun setBiome(parcel: Parcel, biome: Biome) { - val world = this.world.world - val b = getBottomCoord(parcel) - val parcelSize = o.parcelSize - for (x in b.x until b.x + parcelSize) { - for (z in b.z until b.z + parcelSize) { - world.setBiome(x, z, biome) - } - } - } - - override fun getEntities(parcel: Parcel): Collection { - val world = this.world.world - val b = getBottomCoord(parcel) - val parcelSize = o.parcelSize - val center = Location(world, (b.x + parcelSize) / 2.0, 128.0, (b.z + parcelSize) / 2.0) - return world.getNearbyEntities(center, parcelSize / 2.0 + 0.2, 128.0, parcelSize / 2.0 + 0.2) - } - - override fun getBlocks(parcel: Parcel, yRange: IntRange): Iterator = buildIterator { - val range = yRange.clamp(0, 255) - val world = this@DefaultParcelGenerator.world.world - val b = getBottomCoord(parcel) - val parcelSize = o.parcelSize - for (x in b.x until b.x + parcelSize) { - for (z in b.z until b.z + parcelSize) { - for (y in range) { - yield(world.getBlockAt(x, y, z)) - } - } - } - } - - override fun clearParcel(parcel: Parcel) = worktimeLimiter.submit { - val bottom = getBottomCoord(parcel) - val region = Region(Vec3i(bottom.x, 0, bottom.z), Vec3i(o.parcelSize, maxHeight + 1, o.parcelSize)) - val blocks = RegionTraversal.DOWNWARD.regionTraverser(region) - val blockCount = region.blockCount.toDouble() - - val world = world.world - val floorHeight = o.floorHeight - val airType = airType; val floorType = o.floorType; val fillType = o.fillType - - for ((index, vec) in blocks.withIndex()) { - markSuspensionPoint() - val y = vec.y - val blockType = when { - y > floorHeight -> airType - y == floorHeight -> floorType - else -> fillType - } - world[vec].blockData = blockType - setProgress((index + 1) / blockCount) - } - - } - - override fun doBlockOperation(parcel: Parcel, direction: RegionTraversal, operation: (Block) -> Unit) = worktimeLimiter.submit { - val bottom = getBottomCoord(parcel) - val region = Region(Vec3i(bottom.x, 0, bottom.z), Vec3i(o.parcelSize, maxHeight + 1, o.parcelSize)) - val blocks = direction.regionTraverser(region) - val blockCount = region.blockCount.toDouble() - val world = world.world - - for ((index, vec) in blocks.withIndex()) { - markSuspensionPoint() - operation(world[vec]) - setProgress((index + 1) / blockCount) - } - } - -} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt index e753295..9403f34 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt @@ -2,7 +2,7 @@ package io.dico.parcels2.blockvisitor import org.bukkit.Material import org.bukkit.Material.* -import java.util.* +import java.util.EnumSet val attachables: Set = EnumSet.of( ACACIA_DOOR, diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt index 7c5f2c5..a18c63b 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt @@ -6,7 +6,7 @@ import kotlinx.coroutines.experimental.CancellationException import kotlinx.coroutines.experimental.Job import org.bukkit.scheduler.BukkitTask import java.lang.System.currentTimeMillis -import java.util.* +import java.util.LinkedList import java.util.logging.Level import kotlin.coroutines.experimental.Continuation import kotlin.coroutines.experimental.intrinsics.COROUTINE_SUSPENDED diff --git a/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt b/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt index f712d00..2fceb5b 100644 --- a/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt +++ b/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt @@ -16,10 +16,10 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei override fun getPlugin(): Plugin = plugin override fun getReceiver(context: ExecutionContext, target: Method, cmdName: String): ICommandReceiver { - return getParcelCommandReceiver(plugin.worlds, context, target, cmdName) + return getParcelCommandReceiver(plugin.parcelProvider, context, target, cmdName) } - protected inline val worlds get() = plugin.worlds + protected inline val worlds get() = plugin.parcelProvider protected fun error(message: String): Nothing { throw CommandException(message) @@ -32,7 +32,7 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei protected suspend fun checkParcelLimit(player: Player, world: ParcelWorld) { if (player.hasAdminManage) return val numOwnedParcels = plugin.storage.getOwnedParcels(ParcelOwner(player)).await() - .filter { it.world.world == world.world }.size + .filter { it.worldId.equals(world.id) }.size val limit = player.parcelLimit if (numOwnedParcels >= limit) { diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatus.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatus.kt deleted file mode 100644 index 997044a..0000000 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatus.kt +++ /dev/null @@ -1,56 +0,0 @@ -package io.dico.parcels2.command - -import io.dico.dicore.command.Validate -import io.dico.dicore.command.annotation.Cmd -import io.dico.dicore.command.annotation.Desc -import io.dico.parcels2.ParcelsPlugin -import io.dico.parcels2.util.hasAdminManage -import org.bukkit.OfflinePlayer -import org.bukkit.entity.Player - -class CommandsAddedStatus(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { - - @Cmd("allow", aliases = ["add", "permit"]) - @Desc("Allows a player to build on this parcel", - shortVersion = "allows a player to build on this parcel") - @ParcelRequire(owner = true) - fun ParcelScope.cmdAllow(sender: Player, player: OfflinePlayer): Any? { - Validate.isTrue(parcel.owner != null || sender.hasAdminManage, "This parcel is unowned") - Validate.isTrue(!parcel.owner!!.matches(player), "The target already owns the parcel") - Validate.isTrue(parcel.allow(player), "${player.name} is already allowed to build on this parcel") - return "${player.name} is now allowed to build on this parcel" - } - - @Cmd("disallow", aliases = ["remove", "forbid"]) - @Desc("Disallows a player to build on this parcel,", - "they won't be allowed to anymore", - shortVersion = "disallows a player to build on this parcel") - @ParcelRequire(owner = true) - fun ParcelScope.cmdDisallow(sender: Player, player: OfflinePlayer): Any? { - Validate.isTrue(parcel.disallow(player), "${player.name} is not currently allowed to build on this parcel") - return "${player.name} is not allowed to build on this parcel anymore" - } - - @Cmd("ban", aliases = ["deny"]) - @Desc("Bans a player from this parcel,", - "making them unable to enter", - shortVersion = "bans a player from this parcel") - @ParcelRequire(owner = true) - fun ParcelScope.cmdBan(sender: Player, player: OfflinePlayer): Any? { - Validate.isTrue(parcel.owner != null || sender.hasAdminManage, "This parcel is unowned") - Validate.isTrue(!parcel.owner!!.matches(player), "The owner cannot be banned from the parcel") - Validate.isTrue(parcel.ban(player), "${player.name} is already banned from this parcel") - return "${player.name} is now banned from this parcel" - } - - @Cmd("unban", aliases = ["undeny"]) - @Desc("Unbans a player from this parcel,", - "they will be able to enter it again", - shortVersion = "unbans a player from this parcel") - @ParcelRequire(owner = true) - fun ParcelScope.cmdUnban(sender: Player, player: OfflinePlayer): Any? { - Validate.isTrue(parcel.unban(player), "${player.name} is not currently banned from this parcel") - return "${player.name} is not banned from this parcel anymore" - } - -} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusGlobal.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusGlobal.kt new file mode 100644 index 0000000..d483126 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusGlobal.kt @@ -0,0 +1,65 @@ +package io.dico.parcels2.command + +import io.dico.dicore.command.Validate +import io.dico.dicore.command.annotation.Cmd +import io.dico.dicore.command.annotation.Desc +import io.dico.parcels2.GlobalAddedData +import io.dico.parcels2.GlobalAddedDataManager +import io.dico.parcels2.ParcelOwner +import io.dico.parcels2.ParcelsPlugin +import org.bukkit.OfflinePlayer +import org.bukkit.entity.Player + +class CommandsAddedStatusGlobal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { + private inline val data get() = plugin.globalAddedData + @Suppress("NOTHING_TO_INLINE") + private inline operator fun GlobalAddedDataManager.get(player: OfflinePlayer): GlobalAddedData = data[ParcelOwner(player)] + + @Cmd("allow", aliases = ["add", "permit"]) + @Desc("Globally allows a player to build on all", + "the parcels that you own.", + shortVersion = "globally allows a player to build on your parcels") + @ParcelRequire(owner = true) + fun cmdAllow(sender: Player, player: OfflinePlayer): Any? { + Validate.isTrue(player != sender, "The target cannot be yourself") + Validate.isTrue(data[sender].allow(player), "${player.name} is already allowed globally") + return "${player.name} is now allowed to build on all your parcels" + } + + @Cmd("disallow", aliases = ["remove", "forbid"]) + @Desc("Globally disallows a player to build on", + "the parcels that you own.", + "If the player is allowed to build on specific", + "parcels, they can still build there.", + shortVersion = "globally disallows a player to build on your parcels") + @ParcelRequire(owner = true) + fun cmdDisallow(sender: Player, player: OfflinePlayer): Any? { + Validate.isTrue(player != sender, "The target cannot be yourself") + Validate.isTrue(data[sender].disallow(player), "${player.name} is not currently allowed globally") + return "${player.name} is not allowed to build on all your parcels anymore" + } + + @Cmd("ban", aliases = ["deny"]) + @Desc("Globally bans a player from all the parcels", + "that you own, making them unable to enter.", + shortVersion = "globally bans a player from your parcels") + @ParcelRequire(owner = true) + fun cmdBan(sender: Player, player: OfflinePlayer): Any? { + Validate.isTrue(player != sender, "The target cannot be yourself") + Validate.isTrue(data[sender].ban(player), "${player.name} is already banned from all your parcels") + return "${player.name} is now banned from all your parcels" + } + + @Cmd("unban", aliases = ["undeny"]) + @Desc("Globally unbans a player from all the parcels", + "that you own, they can enter again.", + "If the player is banned from specific parcels,", + "they will still be banned there.", + shortVersion = "globally unbans a player from your parcels") + @ParcelRequire(owner = true) + fun cmdUnban(sender: Player, player: OfflinePlayer): Any? { + Validate.isTrue(data[sender].unban(player), "${player.name} is not currently banned from all your parcels") + return "${player.name} is not banned from all your parcels anymore" + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusLocal.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusLocal.kt new file mode 100644 index 0000000..69da341 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusLocal.kt @@ -0,0 +1,56 @@ +package io.dico.parcels2.command + +import io.dico.dicore.command.Validate +import io.dico.dicore.command.annotation.Cmd +import io.dico.dicore.command.annotation.Desc +import io.dico.parcels2.ParcelsPlugin +import io.dico.parcels2.util.hasAdminManage +import org.bukkit.OfflinePlayer +import org.bukkit.entity.Player + +class CommandsAddedStatusLocal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { + + @Cmd("allow", aliases = ["add", "permit"]) + @Desc("Allows a player to build on this parcel", + shortVersion = "allows a player to build on this parcel") + @ParcelRequire(owner = true) + fun ParcelScope.cmdAllow(sender: Player, player: OfflinePlayer): Any? { + Validate.isTrue(parcel.owner != null || sender.hasAdminManage, "This parcel is unowned") + Validate.isTrue(!parcel.owner!!.matches(player), "The target already owns the parcel") + Validate.isTrue(parcel.allow(player), "${player.name} is already allowed to build on this parcel") + return "${player.name} is now allowed to build on this parcel" + } + + @Cmd("disallow", aliases = ["remove", "forbid"]) + @Desc("Disallows a player to build on this parcel,", + "they won't be allowed to anymore", + shortVersion = "disallows a player to build on this parcel") + @ParcelRequire(owner = true) + fun ParcelScope.cmdDisallow(sender: Player, player: OfflinePlayer): Any? { + Validate.isTrue(parcel.disallow(player), "${player.name} is not currently allowed to build on this parcel") + return "${player.name} is not allowed to build on this parcel anymore" + } + + @Cmd("ban", aliases = ["deny"]) + @Desc("Bans a player from this parcel,", + "making them unable to enter", + shortVersion = "bans a player from this parcel") + @ParcelRequire(owner = true) + fun ParcelScope.cmdBan(sender: Player, player: OfflinePlayer): Any? { + Validate.isTrue(parcel.owner != null || sender.hasAdminManage, "This parcel is unowned") + Validate.isTrue(!parcel.owner!!.matches(player), "The owner cannot be banned from the parcel") + Validate.isTrue(parcel.ban(player), "${player.name} is already banned from this parcel") + return "${player.name} is now banned from this parcel" + } + + @Cmd("unban", aliases = ["undeny"]) + @Desc("Unbans a player from this parcel,", + "they will be able to enter it again", + shortVersion = "unbans a player from this parcel") + @ParcelRequire(owner = true) + fun ParcelScope.cmdUnban(sender: Player, player: OfflinePlayer): Any? { + Validate.isTrue(parcel.unban(player), "${player.name} is not currently banned from this parcel") + return "${player.name} is not banned from this parcel anymore" + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt index 9493d26..bf5a870 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt @@ -9,7 +9,7 @@ import io.dico.parcels2.blockvisitor.RegionTraversal import org.bukkit.Bukkit import org.bukkit.Material import org.bukkit.entity.Player -import java.util.* +import java.util.Random class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { @@ -39,7 +39,8 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { server.createBlockData(Material.QUARTZ_BLOCK) ) val random = Random() - world.generator.doBlockOperation(parcel, direction = RegionTraversal.UPWARD) { block -> + + world.doBlockOperation(parcel.id, direction = RegionTraversal.UPWARD) { block -> block.blockData = blockDatas[random.nextInt(4)] }.onProgressUpdate(1000, 1000) { progress, elapsedTime -> context.sendMessage(EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed" diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt index bfa1f6c..769cf5f 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt @@ -8,7 +8,6 @@ import io.dico.dicore.command.annotation.Flag import io.dico.dicore.command.annotation.RequireParameters import io.dico.parcels2.ParcelOwner import io.dico.parcels2.ParcelsPlugin -import io.dico.parcels2.storage.getParcelBySerializedValue import io.dico.parcels2.util.hasAdminManage import io.dico.parcels2.util.hasParcelHomeOthers import io.dico.parcels2.util.uuid @@ -27,7 +26,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { val parcel = world.nextEmptyParcel() ?: error("This world is full, please ask an admin to upsize it") parcel.owner = ParcelOwner(uuid = player.uuid) - player.teleport(parcel.homeLocation) + player.teleport(parcel.world.getHomeLocation(parcel.id)) return "Enjoy your new parcel!" } @@ -53,13 +52,13 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { val ownedParcelsResult = plugin.storage.getOwnedParcels(ownerTarget.owner).await() val ownedParcels = ownedParcelsResult - .map { worlds.getParcelBySerializedValue(it) } + .map { worlds.getParcelById(it) } .filter { it != null && ownerTarget.world == it.world } val targetMatch = ownedParcels.getOrNull(target.index) ?: error("The specified parcel could not be matched") - player.teleport(targetMatch.homeLocation) + player.teleport(targetMatch.world.getHomeLocation(targetMatch.id)) return "" } @@ -91,7 +90,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { if (!sure) return "Are you sure? You cannot undo this action!\n" + "Type ${context.rawInput} -sure if you want to go through with this." - world.generator.clearParcel(parcel) + world.clearParcel(parcel.id) .onProgressUpdate(1000, 1000) { progress, elapsedTime -> context.sendMessage(EMessageType.INFORMATIVE, "Clear progress: %.02f%%, %.2fs elapsed" .format(progress * 100, elapsedTime / 1000.0)) diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt index f3bd63c..b633c3e 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt @@ -3,29 +3,32 @@ package io.dico.parcels2.command import io.dico.dicore.command.CommandBuilder import io.dico.dicore.command.ICommandAddress import io.dico.dicore.command.ICommandDispatcher -import io.dico.dicore.command.predef.PredefinedCommand import io.dico.dicore.command.registration.reflect.ReflectiveRegistration import io.dico.parcels2.ParcelsPlugin -import io.dico.parcels2.logger -import java.util.* +import java.util.LinkedList +import java.util.Queue @Suppress("UsePropertyAccessSyntax") fun getParcelCommands(plugin: ParcelsPlugin): ICommandDispatcher { //@formatter:off return CommandBuilder() .setChatController(ParcelsChatController()) - .addParameterType(false, ParcelParameterType(plugin.worlds)) - .addParameterType(true, ParcelTarget.PType(plugin.worlds)) + .addParameterType(false, ParcelParameterType(plugin.parcelProvider)) + .addParameterType(true, ParcelTarget.PType(plugin.parcelProvider)) .group("parcel", "plot", "plots", "p") .registerCommands(CommandsGeneral(plugin)) - .registerCommands(CommandsAddedStatus(plugin)) + .registerCommands(CommandsAddedStatusLocal(plugin)) - .group("option") + .group("option", "opt", "o") //.apply { CommandsParcelOptions.setGroupDescription(this) } .registerCommands(CommandsParcelOptions(plugin)) .parent() + .group("global", "g") + .registerCommands(CommandsAddedStatusGlobal(plugin)) + .parent() + .group("admin", "a") .registerCommands(CommandsAdmin(plugin)) .parent() diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt index 5dd8270..eab02c4 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt @@ -5,8 +5,8 @@ import io.dico.dicore.command.ExecutionContext import io.dico.dicore.command.ICommandReceiver import io.dico.dicore.command.Validate import io.dico.parcels2.Parcel +import io.dico.parcels2.ParcelProvider 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 @@ -30,7 +30,7 @@ open class ParcelScope(val parcel: Parcel) : WorldScope(parcel.world) { "You must own this parcel to $action") } -fun getParcelCommandReceiver(worlds: Worlds, context: ExecutionContext, method: Method, cmdName: String): ICommandReceiver { +fun getParcelCommandReceiver(parcelProvider: ParcelProvider, context: ExecutionContext, method: Method, cmdName: String): ICommandReceiver { val player = context.sender as Player val function = method.kotlinFunction!! val receiverType = function.extensionReceiverParameter!!.type @@ -39,20 +39,20 @@ fun getParcelCommandReceiver(worlds: Worlds, context: ExecutionContext, method: val owner = require?.owner == true return when (receiverType.jvmErasure) { - ParcelScope::class -> ParcelScope(worlds.getParcelRequired(player, admin, owner)) - WorldScope::class -> WorldScope(worlds.getWorldRequired(player, admin)) + ParcelScope::class -> ParcelScope(parcelProvider.getParcelRequired(player, admin, owner)) + WorldScope::class -> WorldScope(parcelProvider.getWorldRequired(player, admin)) else -> throw InternalError("Invalid command receiver type") } } -fun Worlds.getWorldRequired(player: Player, admin: Boolean = false): ParcelWorld { +fun ParcelProvider.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) +fun ParcelProvider.getParcelRequired(player: Player, admin: Boolean = false, own: Boolean = false): Parcel { + val parcel = getWorldRequired(player, admin = admin).getParcelAt(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") diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt index b5a1abf..de3cf64 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt @@ -3,15 +3,10 @@ package io.dico.parcels2.command import io.dico.dicore.command.CommandException import io.dico.dicore.command.parameter.ArgumentBuffer import io.dico.dicore.command.parameter.Parameter -import io.dico.dicore.command.parameter.type.ParameterConfig import io.dico.dicore.command.parameter.type.ParameterType import io.dico.parcels2.Parcel +import io.dico.parcels2.ParcelProvider import io.dico.parcels2.ParcelWorld -import io.dico.parcels2.ParcelsPlugin -import io.dico.parcels2.Worlds -import io.dico.parcels2.util.isValid -import org.bukkit.Bukkit -import org.bukkit.OfflinePlayer import org.bukkit.command.CommandSender import org.bukkit.entity.Player @@ -19,7 +14,7 @@ fun invalidInput(parameter: Parameter<*, *>, message: String): Nothing { throw CommandException("invalid input for ${parameter.name}: $message") } -fun Worlds.getTargetWorld(input: String?, sender: CommandSender, parameter: Parameter<*, *>): ParcelWorld { +fun ParcelProvider.getTargetWorld(input: String?, sender: CommandSender, parameter: Parameter<*, *>): ParcelWorld { val worldName = input ?.takeUnless { it.isEmpty() } ?: (sender as? Player)?.world?.name @@ -29,14 +24,14 @@ fun Worlds.getTargetWorld(input: String?, sender: CommandSender, parameter: Para ?: invalidInput(parameter, "$worldName is not a parcel world") } -class ParcelParameterType(val worlds: Worlds) : ParameterType(Parcel::class.java) { +class ParcelParameterType(val parcelProvider: ParcelProvider) : ParameterType(Parcel::class.java) { val regex = Regex.fromLiteral("((.+)->)?([0-9]+):([0-9]+)") override fun parse(parameter: Parameter, sender: CommandSender, buffer: ArgumentBuffer): Parcel { val matchResult = regex.matchEntire(buffer.next()) ?: invalidInput(parameter, "must match (w->)?a:b (/${regex.pattern}/)") - val world = worlds.getTargetWorld(matchResult.groupValues[2], sender, parameter) + val world = parcelProvider.getTargetWorld(matchResult.groupValues[2], sender, parameter) val x = matchResult.groupValues[3].toIntOrNull() ?: invalidInput(parameter, "couldn't parse int") @@ -44,7 +39,7 @@ class ParcelParameterType(val worlds: Worlds) : ParameterType(Parc val z = matchResult.groupValues[4].toIntOrNull() ?: invalidInput(parameter, "couldn't parse int") - return world.parcelByID(x, z) + return world.getParcelById(x, z) ?: invalidInput(parameter, "parcel id is out of range") } diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt index 4dd2825..5504e6b 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt @@ -5,7 +5,6 @@ import io.dico.dicore.command.parameter.Parameter import io.dico.dicore.command.parameter.type.ParameterConfig import io.dico.dicore.command.parameter.type.ParameterType import io.dico.parcels2.* -import io.dico.parcels2.storage.getParcelBySerializedValue import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.floor import io.dico.parcels2.util.isValid @@ -20,7 +19,7 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) { class ByID(world: ParcelWorld, val id: Vec2i?, isDefault: Boolean) : ParcelTarget(world, isDefault) { override suspend fun ParcelsPlugin.getParcelSuspend(): Parcel? = getParcel() - fun getParcel() = id?.let { world.parcelByID(it) } + fun getParcel() = id?.let { world.getParcelById(it) } val isPath: Boolean get() = id == null } @@ -32,7 +31,7 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) { override suspend fun ParcelsPlugin.getParcelSuspend(): Parcel? { val ownedParcelsSerialized = storage.getOwnedParcels(owner).await() val ownedParcels = ownedParcelsSerialized - .map { worlds.getParcelBySerializedValue(it) } + .map { parcelProvider.getParcelById(it) } .filter { it != null && world == it.world && owner == it.owner } return ownedParcels.getOrNull(index) } @@ -59,7 +58,7 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) { // instead of parcel that the player is in } - class PType(val worlds: Worlds) : ParameterType(ParcelTarget::class.java, ParcelTarget.Config) { + class PType(val parcelProvider: ParcelProvider) : ParameterType(ParcelTarget::class.java, ParcelTarget.Config) { override fun parse(parameter: Parameter, sender: CommandSender, buffer: ArgumentBuffer): ParcelTarget { var input = buffer.next() @@ -68,19 +67,19 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) { val world = if (worldString.isEmpty()) { val player = requirePlayer(sender, parameter, "the world") - worlds.getWorld(player.world) + parcelProvider.getWorld(player.world) ?: invalidInput(parameter, "You cannot omit the world if you're not in a parcel world") } else { - worlds.getWorld(worldString) ?: invalidInput(parameter, "$worldString is not a parcel world") + parcelProvider.getWorld(worldString) ?: invalidInput(parameter, "$worldString is not a parcel world") } val kind = parameter.paramInfo ?: DEFAULT_KIND if (input.contains(',')) { - if (kind and ID == 0) invalidInput(parameter, "You must specify a parcel by ID, that is, the x and z component separated by a comma") + if (kind and ID == 0) invalidInput(parameter, "You must specify a parcel by OWNER, that is, an owner and index") return ByID(world, getId(parameter, input), false) } - if (kind and OWNER == 0) invalidInput(parameter, "You must specify a parcel by OWNER, that is, an owner and index") + if (kind and OWNER == 0) invalidInput(parameter, "You must specify a parcel by ID, that is, the x and z component separated by a comma") val (owner, index) = getHomeIndex(parameter, sender, input) return ByOwner(world, owner, index, false) } @@ -106,7 +105,7 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) { indexString = input } else { ownerString = input.substring(0, splitIdx) - indexString = input.substring(0, splitIdx + 1) + indexString = input.substring(splitIdx + 1) } val owner = if (ownerString.isEmpty()) @@ -151,9 +150,9 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) { } val player = requirePlayer(sender, parameter, "the parcel") - val world = worlds.getWorld(player.world) ?: invalidInput(parameter, "You must be in a parcel world to omit the parcel") + val world = parcelProvider.getWorld(player.world) ?: invalidInput(parameter, "You must be in a parcel world to omit the parcel") if (useLocation) { - val id = player.location.let { world.generator.parcelIDAt(it.x.floor(), it.z.floor()) } + val id = player.location.let { world.getParcelIdAt(it.x.floor(), it.z.floor())?.pos } return ByID(world, id, true) } diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt new file mode 100644 index 0000000..0597a9f --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt @@ -0,0 +1,65 @@ +package io.dico.parcels2.defaultimpl + +import io.dico.parcels2.Parcel +import io.dico.parcels2.ParcelContainer +import io.dico.parcels2.ParcelWorld +import kotlin.coroutines.experimental.buildIterator +import kotlin.coroutines.experimental.buildSequence + +class DefaultParcelContainer(val world: ParcelWorld) : ParcelContainer { + private var parcels: Array> + + init { + parcels = initArray(world.options.axisLimit, world) + } + + fun resizeIfSizeChanged() { + if (parcels.size != world.options.axisLimit * 2 + 1) { + resize(world.options.axisLimit) + } + } + + fun resize(axisLimit: Int) { + parcels = initArray(axisLimit, world, this) + } + + fun initArray(axisLimit: Int, world: ParcelWorld, cur: DefaultParcelContainer? = null): Array> { + val arraySize = 2 * axisLimit + 1 + return Array(arraySize) { + val x = it - axisLimit + Array(arraySize) { + val z = it - axisLimit + cur?.getParcelById(x, z) ?: ParcelImpl(world, x, z) + } + } + } + + override fun getParcelById(x: Int, z: Int): Parcel? { + return parcels.getOrNull(x + world.options.axisLimit)?.getOrNull(z + world.options.axisLimit) + } + + override fun nextEmptyParcel(): Parcel? { + return walkInCircle().find { it.owner == null } + } + + private fun walkInCircle(): Iterable = Iterable { + buildIterator { + val center = world.options.axisLimit + for (radius in 0..center) { + var x = center - radius; + var z = center - radius + repeat(radius * 2) { yield(parcels[x++][z]) } + repeat(radius * 2) { yield(parcels[x][z++]) } + repeat(radius * 2) { yield(parcels[x--][z]) } + repeat(radius * 2) { yield(parcels[x][z--]) } + } + } + } + + fun allParcels(): Sequence = buildSequence { + for (array in parcels) { + yieldAll(array.iterator()) + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt new file mode 100644 index 0000000..90eb631 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt @@ -0,0 +1,267 @@ +package io.dico.parcels2.defaultimpl + +import io.dico.parcels2.* +import io.dico.parcels2.blockvisitor.RegionTraversal +import io.dico.parcels2.blockvisitor.Worker +import io.dico.parcels2.blockvisitor.WorktimeLimiter +import io.dico.parcels2.util.* +import org.bukkit.* +import org.bukkit.block.Biome +import org.bukkit.block.Block +import org.bukkit.block.BlockFace +import org.bukkit.block.Skull +import org.bukkit.block.data.BlockData +import org.bukkit.block.data.type.Sign +import org.bukkit.block.data.type.Slab +import java.util.Random + +private val airType = Bukkit.createBlockData(Material.AIR) + +data class DefaultGeneratorOptions(var defaultBiome: Biome = Biome.JUNGLE, + var wallType: BlockData = Bukkit.createBlockData(Material.STONE_SLAB), + var floorType: BlockData = Bukkit.createBlockData(Material.QUARTZ_BLOCK), + var fillType: BlockData = Bukkit.createBlockData(Material.QUARTZ_BLOCK), + var pathMainType: BlockData = Bukkit.createBlockData(Material.SANDSTONE), + var pathAltType: BlockData = Bukkit.createBlockData(Material.REDSTONE_BLOCK), + var parcelSize: Int = 101, + var pathSize: Int = 9, + var floorHeight: Int = 64, + var offsetX: Int = 0, + var offsetZ: Int = 0) : GeneratorOptions() { + + override fun generatorFactory(): GeneratorFactory = DefaultParcelGenerator.Factory +} + +class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOptions) : ParcelGenerator() { + private var _world: World? = null + override val world: World + get() { + if (_world == null) _world = Bukkit.getWorld(name)!!.also { + maxHeight = it.maxHeight + return it + } + return _world!! + } + + private var maxHeight = 0 + + companion object Factory : GeneratorFactory { + override val name get() = "default" + override val optionsClass get() = DefaultGeneratorOptions::class + override fun newParcelGenerator(worldName: String, options: GeneratorOptions): ParcelGenerator { + return DefaultParcelGenerator(worldName, options as DefaultGeneratorOptions) + } + } + + val sectionSize = o.parcelSize + o.pathSize + val pathOffset = (if (o.pathSize % 2 == 0) o.pathSize + 2 else o.pathSize + 1) / 2 + val makePathMain = o.pathSize > 2 + val makePathAlt = o.pathSize > 4 + + private inline fun generate(chunkX: Int, + chunkZ: Int, + floor: T, wall: + T, pathMain: T, + pathAlt: T, + fill: T, + setter: (Int, Int, Int, T) -> Unit) { + + val floorHeight = o.floorHeight + val parcelSize = o.parcelSize + val sectionSize = sectionSize + val pathOffset = pathOffset + val makePathMain = makePathMain + val makePathAlt = makePathAlt + + // parcel bottom x and z + // umod is unsigned %: the result is always >= 0 + val pbx = ((chunkX shl 4) - o.offsetX) umod sectionSize + val pbz = ((chunkZ shl 4) - o.offsetZ) umod sectionSize + + var curHeight: Int + var x: Int + var z: Int + for (cx in 0..15) { + for (cz in 0..15) { + x = (pbx + cx) % sectionSize - pathOffset + z = (pbz + cz) % sectionSize - pathOffset + curHeight = floorHeight + + val type = when { + (x in 0 until parcelSize && z in 0 until parcelSize) -> floor + (x in -1..parcelSize && z in -1..parcelSize) -> { + curHeight++ + wall + } + (makePathAlt && x in -2 until parcelSize + 2 && z in -2 until parcelSize + 2) -> pathAlt + (makePathMain) -> pathMain + else -> { + curHeight++ + wall + } + } + + for (y in 0 until curHeight) { + setter(cx, y, cz, fill) + } + setter(cx, curHeight, cz, type) + } + } + } + + override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData { + val out = Bukkit.createChunkData(world) + generate(chunkX, chunkZ, o.floorType, o.wallType, o.pathMainType, o.pathAltType, o.fillType) { x, y, z, type -> + out.setBlock(x, y, z, type) + } + return out + } + + override fun populate(world: World?, random: Random?, chunk: Chunk?) { + // do nothing + } + + override fun getFixedSpawnLocation(world: World?, random: Random?): Location { + val fix = if (o.parcelSize.even) 0.5 else 0.0 + return Location(world, o.offsetX + fix, o.floorHeight + 1.0, o.offsetZ + fix) + } + + override fun makeParcelBlockManager(worktimeLimiter: WorktimeLimiter): ParcelBlockManager { + return ParcelBlockManagerImpl(worktimeLimiter) + } + + override fun makeParcelLocator(container: ParcelContainer): ParcelLocator { + return ParcelLocatorImpl(container) + } + + private inline fun convertBlockLocationToId(x: Int, z: Int, mapper: (Int, Int) -> T): T? { + val sectionSize = sectionSize + val parcelSize = o.parcelSize + val absX = x - o.offsetX - pathOffset + val absZ = z - o.offsetZ - pathOffset + val modX = absX umod sectionSize + val modZ = absZ umod sectionSize + if (modX in 0 until parcelSize && modZ in 0 until parcelSize) { + return mapper((absX - modX) / sectionSize, (absZ - modZ) / sectionSize) + } + return null + } + + private inner class ParcelLocatorImpl(val container: ParcelContainer) : ParcelLocator { + override val world: World = this@DefaultParcelGenerator.world + override fun getParcelAt(x: Int, z: Int): Parcel? { + return convertBlockLocationToId(x, z, container::getParcelById) + } + + override fun getParcelIdAt(x: Int, z: Int): ParcelId? { + return convertBlockLocationToId(x, z) { idx, idz -> ParcelId(world.name, world.uid, idx, idz) } + } + } + + @Suppress("DEPRECATION") + private inner class ParcelBlockManagerImpl(override val worktimeLimiter: WorktimeLimiter) : ParcelBlockManager { + override val world: World = this@DefaultParcelGenerator.world + + override fun getBottomBlock(parcel: ParcelId): Vec2i = Vec2i( + sectionSize * parcel.pos.x + pathOffset + o.offsetX, + sectionSize * parcel.pos.z + pathOffset + o.offsetZ + ) + + override fun getHomeLocation(parcel: ParcelId): Location { + val bottom = getBottomBlock(parcel) + return Location(world, bottom.x.toDouble(), o.floorHeight + 1.0, bottom.z + (o.parcelSize - 1) / 2.0, -90F, 0F) + } + + override fun setOwnerBlock(parcel: ParcelId, owner: ParcelOwner?) { + val b = getBottomBlock(parcel) + + val wallBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 1) + val signBlock = world.getBlockAt(b.x - 2, o.floorHeight + 1, b.z - 1) + val skullBlock = world.getBlockAt(b.x - 1, o.floorHeight + 2, b.z - 1) + + if (owner == null) { + wallBlock.blockData = o.wallType + signBlock.type = Material.AIR + skullBlock.type = Material.AIR + } else { + + val wallBlockType: BlockData = if (o.wallType is Slab) + (o.wallType.clone() as Slab).apply { type = Slab.Type.DOUBLE } + else + o.wallType + + wallBlock.blockData = wallBlockType + + signBlock.blockData = (Bukkit.createBlockData(Material.WALL_SIGN) as Sign).apply { rotation = BlockFace.NORTH } + + val sign = signBlock.state as org.bukkit.block.Sign + sign.setLine(0, "${parcel.x},${parcel.z}") + sign.setLine(2, owner.name) + sign.update() + + skullBlock.type = Material.PLAYER_HEAD + val skull = skullBlock.state as Skull + if (owner.uuid != null) { + skull.owningPlayer = owner.offlinePlayer + } else { + skull.owner = owner.name + } + skull.rotation = BlockFace.WEST + skull.update() + } + } + + override fun setBiome(parcel: ParcelId, biome: Biome): Worker = worktimeLimiter.submit { + val world = world + val b = getBottomBlock(parcel) + val parcelSize = o.parcelSize + for (x in b.x until b.x + parcelSize) { + for (z in b.z until b.z + parcelSize) { + markSuspensionPoint() + world.setBiome(x, z, biome) + } + } + } + + override fun clearParcel(parcel: ParcelId): Worker = worktimeLimiter.submit { + val bottom = getBottomBlock(parcel) + val region = Region(Vec3i(bottom.x, 0, bottom.z), Vec3i(o.parcelSize, maxHeight + 1, o.parcelSize)) + val blocks = RegionTraversal.DOWNWARD.regionTraverser(region) + val blockCount = region.blockCount.toDouble() + + val world = world + val floorHeight = o.floorHeight + val airType = airType + val floorType = o.floorType + val fillType = o.fillType + + for ((index, vec) in blocks.withIndex()) { + markSuspensionPoint() + val y = vec.y + val blockType = when { + y > floorHeight -> airType + y == floorHeight -> floorType + else -> fillType + } + world[vec].blockData = blockType + setProgress((index + 1) / blockCount) + } + } + + override fun doBlockOperation(parcel: ParcelId, direction: RegionTraversal, operation: (Block) -> Unit): Worker = worktimeLimiter.submit { + val bottom = getBottomBlock(parcel) + val region = Region(Vec3i(bottom.x, 0, bottom.z), Vec3i(o.parcelSize, maxHeight + 1, o.parcelSize)) + val blocks = direction.regionTraverser(region) + val blockCount = region.blockCount.toDouble() + val world = world + + for ((index, vec) in blocks.withIndex()) { + markSuspensionPoint() + operation(world[vec]) + setProgress((index + 1) / blockCount) + } + } + + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalAddedDataManagerImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalAddedDataManagerImpl.kt new file mode 100644 index 0000000..1ac053f --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalAddedDataManagerImpl.kt @@ -0,0 +1,39 @@ +@file:Suppress("UNCHECKED_CAST") + +package io.dico.parcels2.defaultimpl + +import io.dico.parcels2.* +import java.util.Collections +import java.util.UUID + +class GlobalAddedDataManagerImpl(val plugin: ParcelsPlugin) : GlobalAddedDataManager { + private val map = mutableMapOf() + + override fun get(owner: ParcelOwner): GlobalAddedData { + return map[owner] ?: GlobalAddedDataImpl(owner).also { map[owner] = it } + } + + private inner class GlobalAddedDataImpl(override val owner: ParcelOwner, + data: MutableAddedDataMap = emptyData) + : AddedDataHolder(data), GlobalAddedData { + + private inline var data get() = addedMap; set(value) = run { addedMap = value } + private inline val isEmpty get() = data === emptyData + + override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean { + if (isEmpty) { + if (status == AddedStatus.DEFAULT) return false + data = mutableMapOf() + } + return super.setAddedStatus(uuid, status).also { + if (it) plugin.storage.setGlobalAddedStatus(owner, uuid, status) + } + } + + } + + private companion object { + val emptyData = Collections.emptyMap() as MutableAddedDataMap + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt new file mode 100644 index 0000000..576fc48 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt @@ -0,0 +1,138 @@ +package io.dico.parcels2.defaultimpl + +import io.dico.dicore.Formatting +import io.dico.parcels2.* +import io.dico.parcels2.util.Vec2i +import io.dico.parcels2.util.getPlayerName +import org.bukkit.OfflinePlayer +import org.joda.time.DateTime +import java.util.UUID +import kotlin.reflect.KProperty + +class ParcelImpl(override val world: ParcelWorld, + override val x: Int, + override val z: Int) : Parcel, ParcelId { + override val id: ParcelId = this + override val pos get() = Vec2i(x, z) + override var data: ParcelDataHolder = ParcelDataHolder(); private set + override val infoString by ParcelInfoStringComputer + override var hasBlockVisitors: Boolean = false; private set + override val worldId: ParcelWorldId get() = world.id + + override fun copyDataIgnoringDatabase(data: ParcelData) { + this.data = ((data as? Parcel)?.data ?: data) as ParcelDataHolder + } + + override fun copyData(data: ParcelData) { + copyDataIgnoringDatabase(data) + world.storage.setParcelData(this, data) + } + + override fun dispose() { + copyDataIgnoringDatabase(ParcelDataHolder()) + world.storage.setParcelData(this, null) + } + + override val addedMap: Map get() = data.addedMap + override fun getAddedStatus(uuid: UUID) = data.getAddedStatus(uuid) + override fun isBanned(uuid: UUID) = data.isBanned(uuid) + override fun isAllowed(uuid: UUID) = data.isAllowed(uuid) + override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean): Boolean { + return (data.canBuild(player, checkAdmin, false)) + || checkGlobal && world.globalAddedData[owner ?: return false].isAllowed(player) + } + + val globalAddedMap: Map? get() = owner?.let { world.globalAddedData[it].addedMap } + + override val since: DateTime? get() = data.since + + override var owner: ParcelOwner? + get() = data.owner + set(value) { + if (data.owner != value) { + world.storage.setParcelOwner(this, value) + data.owner = value + } + } + + override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean { + return data.setAddedStatus(uuid, status).also { + if (it) world.storage.setParcelPlayerStatus(this, uuid, status) + } + } + + override var allowInteractInputs: Boolean + get() = data.allowInteractInputs + set(value) { + if (data.allowInteractInputs == value) return + world.storage.setParcelAllowsInteractInputs(this, value) + data.allowInteractInputs = value + } + + override var allowInteractInventory: Boolean + get() = data.allowInteractInventory + set(value) { + if (data.allowInteractInventory == value) return + world.storage.setParcelAllowsInteractInventory(this, value) + data.allowInteractInventory = value + } + + +} + +private object ParcelInfoStringComputer { + val infoStringColor1 = Formatting.GREEN + val infoStringColor2 = Formatting.AQUA + + private inline fun StringBuilder.appendField(name: String, value: StringBuilder.() -> Unit) { + append(infoStringColor1) + append(name) + append(": ") + append(infoStringColor2) + value() + append(' ') + } + + operator fun getValue(parcel: Parcel, property: KProperty<*>): String = buildString { + appendField("ID") { + append(parcel.x) + append(',') + append(parcel.z) + } + + appendField("Owner") { + val owner = parcel.owner + if (owner == null) { + append(infoStringColor1) + append("none") + } else { + append(owner.notNullName) + } + } + + // plotme appends biome here + + append('\n') + + val allowedMap = parcel.addedMap.filterValues { it.isAllowed } + if (allowedMap.isNotEmpty()) appendField("Allowed") { + allowedMap.keys.map(::getPlayerName).joinTo(this) + } + + val bannedMap = parcel.addedMap.filterValues { it.isBanned } + if (bannedMap.isNotEmpty()) appendField("Banned") { + bannedMap.keys.map(::getPlayerName).joinTo(this) + } + + if (!parcel.allowInteractInputs || !parcel.allowInteractInventory) { + appendField("Options") { + append("(") + appendField("inputs") { append(parcel.allowInteractInputs) } + append(", ") + appendField("inventory") { append(parcel.allowInteractInventory) } + append(")") + } + } + + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt new file mode 100644 index 0000000..52e675d --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt @@ -0,0 +1,119 @@ +package io.dico.parcels2.defaultimpl + +import io.dico.parcels2.* +import org.bukkit.Bukkit +import org.bukkit.WorldCreator + +class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider { + inline val options get() = plugin.options + override val worlds: Map get() = _worlds + private val _worlds: MutableMap = hashMapOf() + private val _generators: MutableMap = hashMapOf() + private var _worldsLoaded = false + private var _dataIsLoaded = false + + // disabled while !_dataIsLoaded. getParcelById() will work though for data loading. + override fun getWorld(name: String): ParcelWorld? = _worlds[name]?.takeIf { _dataIsLoaded } + + override fun getWorldById(id: ParcelWorldId): ParcelWorld? { + if (id is ParcelWorld) return id + return _worlds[id.name] ?: id.bukkitWorld?.let { getWorld(it) } + } + + override fun getParcelById(id: ParcelId): Parcel? { + if (id is Parcel) return id + return getWorldById(id.worldId)?.container?.getParcelById(id.x, id.z) + } + + override fun getWorldGenerator(worldName: String): ParcelGenerator? { + return _worlds[worldName]?.generator + ?: _generators[worldName] + ?: options.worlds[worldName]?.generator?.newGenerator(worldName)?.also { _generators[worldName] = it } + } + + override fun loadWorlds() { + if (_worldsLoaded) throw IllegalStateException() + _worldsLoaded = true + loadWorlds0() + } + + private fun loadWorlds0() { + if (Bukkit.getWorlds().isEmpty()) { + plugin.functionHelper.schedule(::loadWorlds0) + plugin.logger.warning("Scheduling to load worlds in the next tick, \nbecause no bukkit worlds are loaded yet") + return + } + + for ((worldName, worldOptions) in options.worlds.entries) { + var parcelWorld = _worlds[worldName] + if (parcelWorld != null) continue + + val generator: ParcelGenerator = getWorldGenerator(worldName)!! + val bukkitWorld = Bukkit.getWorld(worldName) ?: WorldCreator(worldName).generator(generator).createWorld() + parcelWorld = ParcelWorldImpl(bukkitWorld, generator, worldOptions.runtime, plugin.storage, + plugin.globalAddedData, ::DefaultParcelContainer, plugin.worktimeLimiter) + _worlds[worldName] = parcelWorld + } + + loadStoredData() + } + + private fun loadStoredData() { + plugin.functionHelper.launchLazilyOnMainThread { + val channel = plugin.storage.readAllParcelData() + do { + val pair = channel.receiveOrNull() ?: break + val parcel = getParcelById(pair.first) ?: continue + pair.second?.let { parcel.copyDataIgnoringDatabase(it) } + } while (true) + + _dataIsLoaded = true + }.start() + } + + /* + fun loadWorlds(options: Options) { + for ((worldName, worldOptions) in options.worlds.entries) { + val world: ParcelWorld + try { + + world = ParcelWorldImpl( + worldName, + worldOptions, + worldOptions.generator.newGenerator(this, worldName), + plugin.storage, + plugin.globalAddedData, + ::DefaultParcelContainer) + + } catch (ex: Exception) { + ex.printStackTrace() + continue + } + + _worlds[worldName] = world + } + + plugin.functionHelper.schedule(10) { + println("Parcels generating parcelProvider now") + for ((name, world) in _worlds) { + if (Bukkit.getWorld(name) == null) { + val bworld = WorldCreator(name).generator(world.generator).createWorld() + val spawn = world.generator.getFixedSpawnLocation(bworld, null) + bworld.setSpawnLocation(spawn.x.floor(), spawn.y.floor(), spawn.z.floor()) + } + } + + val channel = plugin.storage.readAllParcelData() + val job = plugin.functionHelper.launchLazilyOnMainThread { + do { + val pair = channel.receiveOrNull() ?: break + val parcel = getParcelById(pair.first) ?: continue + pair.second?.let { parcel.copyDataIgnoringDatabase(it) } + } while (true) + } + job.start() + } + + } + */ +} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt new file mode 100644 index 0000000..590794d --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt @@ -0,0 +1,94 @@ +@file:Suppress("CanBePrimaryConstructorProperty", "UsePropertyAccessSyntax") + +package io.dico.parcels2.defaultimpl + +import io.dico.parcels2.* +import io.dico.parcels2.blockvisitor.WorktimeLimiter +import io.dico.parcels2.storage.Storage +import org.bukkit.World +import java.util.UUID + +class ParcelWorldImpl private +constructor(override val world: World, + override val generator: ParcelGenerator, + override var options: WorldOptions, + override val storage: Storage, + override val globalAddedData: GlobalAddedDataManager, + containerFactory: ParcelContainerFactory, + blockManager: ParcelBlockManager) + : ParcelWorld, + ParcelWorldId, + ParcelContainer, // missing delegation + ParcelLocator, // missing delegation + ParcelBlockManager by blockManager { + override val id: ParcelWorldId get() = this + override val uid: UUID? get() = world.uid + + init { + if (generator.world != world) { + throw IllegalArgumentException() + } + } + + override val name: String = world.name!! + override val container: ParcelContainer = containerFactory(this) + override val locator: ParcelLocator = generator.makeParcelLocator(container) + override val blockManager: ParcelBlockManager = blockManager + + init { + enforceOptions() + } + + fun enforceOptions() { + if (options.dayTime) { + world.setGameRuleValue("doDaylightCycle", "false") + world.setTime(6000) + } + + if (options.noWeather) { + world.setStorm(false) + world.setThundering(false) + world.weatherDuration = Integer.MAX_VALUE + } + + world.setGameRuleValue("doTileDrops", "${options.doTileDrops}") + } + + /* + Interface delegation needs to be implemented manually because JetBrains has yet to fix it. + */ + + companion object { + // Use this to be able to delegate blockManager and assign it to a property too, at least. + operator fun invoke(world: World, + generator: ParcelGenerator, + options: WorldOptions, + storage: Storage, + globalAddedData: GlobalAddedDataManager, + containerFactory: ParcelContainerFactory, + worktimeLimiter: WorktimeLimiter): ParcelWorldImpl { + val blockManager = generator.makeParcelBlockManager(worktimeLimiter) + return ParcelWorldImpl(world, generator, options, storage, globalAddedData, containerFactory, blockManager) + } + } + + // ParcelLocator interface + override fun getParcelAt(x: Int, z: Int): Parcel? { + return locator.getParcelAt(x, z) + } + + override fun getParcelIdAt(x: Int, z: Int): ParcelId? { + return locator.getParcelIdAt(x, z) + } + + // ParcelContainer interface + override fun getParcelById(x: Int, z: Int): Parcel? { + return container.getParcelById(x, z) + } + + override fun nextEmptyParcel(): Parcel? { + return container.nextEmptyParcel() + } + + +} diff --git a/src/main/kotlin/io/dico/parcels2/listener/ListenerHelper.kt b/src/main/kotlin/io/dico/parcels2/listener/ListenerHelper.kt index 97d045f..e8617fd 100644 --- a/src/main/kotlin/io/dico/parcels2/listener/ListenerHelper.kt +++ b/src/main/kotlin/io/dico/parcels2/listener/ListenerHelper.kt @@ -8,14 +8,7 @@ interface HasPlugin { val plugin: ParcelsPlugin } -inline fun HasPlugin.listener(crossinline block: suspend (T) -> Unit) = RegistratorListener { event -> - - - - - - - +inline fun HasPlugin.listener(crossinline block: suspend (T) -> Unit) = RegistratorListener { event -> } diff --git a/src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt b/src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt index 2f64ab1..285fd3e 100644 --- a/src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt +++ b/src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt @@ -1,12 +1,12 @@ package io.dico.parcels2.listener import io.dico.parcels2.Parcel -import io.dico.parcels2.Worlds +import io.dico.parcels2.ParcelProvider import io.dico.parcels2.util.editLoop import io.dico.parcels2.util.isPresentAnd import org.bukkit.entity.Entity -class ParcelEntityTracker(val worlds: Worlds) { +class ParcelEntityTracker(val parcelProvider: ParcelProvider) { val map = mutableMapOf() fun untrack(entity: Entity) { @@ -32,7 +32,7 @@ class ParcelEntityTracker(val worlds: Worlds) { if (parcel.isPresentAnd { hasBlockVisitors }) { remove() } - val newParcel = worlds.getParcelAt(entity.location) + val newParcel = parcelProvider.getParcelAt(entity.location) if (newParcel !== parcel && !newParcel.isPresentAnd { hasBlockVisitors }) { remove() entity.remove() diff --git a/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt b/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt index f5eb5ca..d34f8bf 100644 --- a/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt +++ b/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt @@ -4,8 +4,8 @@ import gnu.trove.TLongCollection import io.dico.dicore.ListenerMarker import io.dico.dicore.RegistratorListener import io.dico.parcels2.Parcel +import io.dico.parcels2.ParcelProvider import io.dico.parcels2.ParcelWorld -import io.dico.parcels2.Worlds import io.dico.parcels2.util.* import org.bukkit.Material.* import org.bukkit.World @@ -26,11 +26,10 @@ import org.bukkit.event.player.* import org.bukkit.event.vehicle.VehicleMoveEvent import org.bukkit.event.weather.WeatherChangeEvent import org.bukkit.event.world.StructureGrowEvent -import org.bukkit.event.world.WorldLoadEvent import org.bukkit.inventory.InventoryHolder @Suppress("NOTHING_TO_INLINE") -class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker) { +class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: ParcelEntityTracker) { private inline fun Parcel?.canBuildN(user: Player) = isPresentAnd { canBuild(user) } || user.hasBuildAnywhere /** @@ -40,8 +39,8 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker * returns null if not in a registered parcel world */ private fun getWoAndPPa(block: Block): Pair? { - val world = worlds.getWorld(block.world) ?: return null - return world to world.parcelAt(block) + val world = parcelProvider.getWorld(block.world) ?: return null + return world to world.getParcelAt(block) } /* @@ -51,10 +50,10 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker val onPlayerMoveEvent = RegistratorListener l@{ event -> val user = event.player if (user.hasBanBypass) return@l - val parcel = worlds.getParcelAt(event.to) ?: return@l + val parcel = parcelProvider.getParcelAt(event.to) ?: return@l if (parcel.isBanned(user.uuid)) { - worlds.getParcelAt(event.from)?.also { - user.teleport(it.homeLocation) + parcelProvider.getParcelAt(event.from)?.also { + user.teleport(it.world.getHomeLocation(it.id)) user.sendParcelMessage(nopermit = true, message = "You are banned from this parcel") } ?: run { event.to = event.from } } @@ -113,7 +112,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker private inline fun TLongCollection.troveForEach(block: (Long) -> Unit) = iterator().let { while (it.hasNext()) block(it.next()) } //@formatter:on private fun checkPistonMovement(event: BlockPistonEvent, blocks: List) { - val world = worlds.getWorld(event.block.world) ?: return + val world = parcelProvider.getWorld(event.block.world) ?: return val direction = event.direction val columns = gnu.trove.set.hash.TLongHashSet(blocks.size * 2) @@ -123,7 +122,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker } columns.troveForEach { - val ppa = world.parcelAt(it.columnX, it.columnZ) + val ppa = world.getParcelAt(it.columnX, it.columnZ) if (ppa.isNullOr { hasBlockVisitors }) { event.isCancelled = true return @@ -150,8 +149,8 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker @field:ListenerMarker(priority = NORMAL) val onEntityExplodeEvent = RegistratorListener l@{ event -> entityTracker.untrack(event.entity) - val world = worlds.getWorld(event.entity.world) ?: return@l - if (world.options.disableExplosions || world.parcelAt(event.entity).isPresentAnd { hasBlockVisitors }) { + val world = parcelProvider.getWorld(event.entity.world) ?: return@l + if (world.options.disableExplosions || world.getParcelAt(event.entity).isPresentAnd { hasBlockVisitors }) { event.isCancelled = true } } @@ -175,9 +174,9 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker @field:ListenerMarker(priority = NORMAL) val onPlayerInteractEvent = RegistratorListener l@{ event -> val user = event.player - val world = worlds.getWorld(user.world) ?: return@l + val world = parcelProvider.getWorld(user.world) ?: return@l val clickedBlock = event.clickedBlock - val parcel = clickedBlock?.let { world.parcelAt(it) } + val parcel = clickedBlock?.let { world.getParcelAt(it) } if (!user.hasBuildAnywhere && parcel.isPresentAnd { isBanned(user.uuid) }) { user.sendParcelMessage(nopermit = true, message = "You cannot interact with parcels you're banned from") @@ -300,7 +299,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker */ @field:ListenerMarker(priority = NORMAL) val onEntityCreatePortalEvent = RegistratorListener l@{ event -> - val world = worlds.getWorld(event.entity.world) ?: return@l + val world = parcelProvider.getWorld(event.entity.world) ?: return@l if (world.options.blockPortalCreation) event.isCancelled = true } @@ -341,7 +340,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker */ @field:ListenerMarker(priority = NORMAL) val onWeatherChangeEvent = RegistratorListener l@{ event -> - val world = worlds.getWorld(event.world) ?: return@l + val world = parcelProvider.getWorld(event.world) ?: return@l if (world.options.noWeather && event.toWeatherState()) { event.isCancelled = true } @@ -353,29 +352,6 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker world.weatherDuration = Int.MAX_VALUE } - /* - * Sets time to day and doDayLightCycle gamerule if requested by the config for that world - * Sets the weather to sunny if requested by the config for that world. - */ - @field:ListenerMarker(priority = NORMAL) - val onWorldLoadEvent = RegistratorListener l@{ event -> - enforceWorldSettingsIfApplicable(event.world) - } - - fun enforceWorldSettingsIfApplicable(w: World) { - val world = worlds.getWorld(w) ?: return - if (world.options.dayTime) { - w.setGameRuleValue("doDaylightCycle", "false") - w.time = 6000 - } - - if (world.options.noWeather) { - resetWeather(w) - } - - w.setGameRuleValue("doTileDrops", world.options.doTileDrops.toString()) - } - // TODO: BlockFormEvent, BlockSpreadEvent, BlockFadeEvent /* @@ -396,10 +372,10 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker val cancel: Boolean = when (block.type) { - // prevent ice generation from Frost Walkers enchantment + // prevent ice generation from Frost Walkers enchantment ICE -> player != null && !ppa.canBuild(player) - // prevent snow generation from weather + // prevent snow generation from weather SNOW -> !hasEntity && wo.options.preventWeatherBlockChanges else -> false @@ -415,10 +391,10 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker */ @field:ListenerMarker(priority = NORMAL) val onEntitySpawnEvent = RegistratorListener l@{ event -> - val world = worlds.getWorld(event.entity.world) ?: return@l + val world = parcelProvider.getWorld(event.entity.world) ?: return@l if (event.entity is Creature && world.options.blockMobSpawning) { event.isCancelled = true - } else if (world.parcelAt(event.entity).isPresentAnd { hasBlockVisitors }) { + } else if (world.getParcelAt(event.entity).isPresentAnd { hasBlockVisitors }) { event.isCancelled = true } } @@ -448,7 +424,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker */ @field:ListenerMarker(priority = NORMAL) val onEntityDamageByEntityEvent = RegistratorListener l@{ event -> - val world = worlds.getWorld(event.entity.world) ?: return@l + val world = parcelProvider.getWorld(event.entity.world) ?: return@l if (world.options.disableExplosions && event.damager is ExplosiveMinecart || event.damager is Creeper) { event.isCancelled = true; return@l } @@ -457,19 +433,19 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker ?: (event.damager as? Projectile)?.let { it.shooter as? Player } ?: return@l - if (!world.parcelAt(event.entity).canBuildN(user)) { + if (!world.getParcelAt(event.entity).canBuildN(user)) { event.isCancelled = true } } @field:ListenerMarker(priority = NORMAL) val onHangingBreakEvent = RegistratorListener l@{ event -> - val world = worlds.getWorld(event.entity.world) ?: return@l + val world = parcelProvider.getWorld(event.entity.world) ?: return@l if (event.cause == HangingBreakEvent.RemoveCause.EXPLOSION && world.options.disableExplosions) { event.isCancelled = true; return@l } - if (world.parcelAt(event.entity).isPresentAnd { hasBlockVisitors }) { + if (world.getParcelAt(event.entity).isPresentAnd { hasBlockVisitors }) { event.isCancelled = true } } @@ -480,9 +456,9 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker */ @field:ListenerMarker(priority = NORMAL) val onHangingBreakByEntityEvent = RegistratorListener l@{ event -> - val world = worlds.getWorld(event.entity.world) ?: return@l + val world = parcelProvider.getWorld(event.entity.world) ?: return@l val user = event.remover as? Player ?: return@l - if (!world.parcelAt(event.entity).canBuildN(user)) { + if (!world.getParcelAt(event.entity).canBuildN(user)) { event.isCancelled = true } } @@ -492,9 +468,9 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker */ @field:ListenerMarker(priority = NORMAL) val onHangingPlaceEvent = RegistratorListener l@{ event -> - val world = worlds.getWorld(event.entity.world) ?: return@l + val world = parcelProvider.getWorld(event.entity.world) ?: return@l val block = event.block.getRelative(event.blockFace) - if (!world.parcelAt(block).canBuildN(event.player)) { + if (!world.getParcelAt(block).canBuildN(event.player)) { event.isCancelled = true } } @@ -513,7 +489,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker event.isCancelled = true; return@l } - event.blocks.removeIf { wo.parcelAt(it.block) !== ppa } + event.blocks.removeIf { wo.getParcelAt(it.block) !== ppa } } /* @@ -523,10 +499,10 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker val onBlockDispenseEvent = RegistratorListener l@{ event -> val block = event.block if (!block.type.let { it == DISPENSER || it == DROPPER }) return@l - val world = worlds.getWorld(block.world) ?: return@l + val world = parcelProvider.getWorld(block.world) ?: return@l val data = block.blockData as Directional val targetBlock = block.getRelative(data.facing) - if (world.parcelAt(targetBlock) == null) { + if (world.getParcelAt(targetBlock) == null) { event.isCancelled = true } } @@ -547,7 +523,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker @field:ListenerMarker(priority = NORMAL) val onEntityTeleportEvent = RegistratorListener l@{ event -> val (wo, ppa) = getWoAndPPa(event.from.block) ?: return@l - if (ppa !== wo.parcelAt(event.to)) { + if (ppa !== wo.getParcelAt(event.to)) { event.isCancelled = true } } @@ -572,7 +548,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker @field:ListenerMarker(priority = NORMAL) val onEntityDeathEvent = RegistratorListener l@{ event -> entityTracker.untrack(event.entity) - val world = worlds.getWorld(event.entity.world) ?: return@l + val world = parcelProvider.getWorld(event.entity.world) ?: return@l if (!world.options.dropEntityItems) { event.drops.clear() event.droppedExp = 0 @@ -584,7 +560,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker */ @field:ListenerMarker(priority = NORMAL) val onPlayerChangedWorldEvent = RegistratorListener l@{ event -> - val world = worlds.getWorld(event.player.world) ?: return@l + val world = parcelProvider.getWorld(event.player.world) ?: return@l if (world.options.gameMode != null && !event.player.hasGamemodeBypass) { event.player.gameMode = world.options.gameMode } diff --git a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt index 7224dd1..88ee5fd 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt @@ -1,11 +1,8 @@ package io.dico.parcels2.storage -import io.dico.parcels2.AddedStatus -import io.dico.parcels2.Parcel -import io.dico.parcels2.ParcelData -import io.dico.parcels2.ParcelOwner -import kotlinx.coroutines.experimental.channels.ProducerScope -import java.util.* +import io.dico.parcels2.* +import kotlinx.coroutines.experimental.channels.SendChannel +import java.util.UUID interface Backing { @@ -22,31 +19,31 @@ interface Backing { * This producer function is capable of constantly reading parcels from a potentially infinite sequence, * and provide parcel data for it as read from the database. */ - suspend fun ProducerScope>.produceParcelData(parcels: Sequence) + suspend fun produceParcelData(channel: SendChannel, parcels: Sequence) - suspend fun ProducerScope>.produceAllParcelData() + suspend fun produceAllParcelData(channel: SendChannel) - suspend fun readParcelData(parcelFor: Parcel): ParcelData? + suspend fun readParcelData(parcel: ParcelId): ParcelData? - suspend fun getOwnedParcels(user: ParcelOwner): List + suspend fun getOwnedParcels(user: ParcelOwner): List suspend fun getNumParcels(user: ParcelOwner): Int = getOwnedParcels(user).size - suspend fun setParcelData(parcelFor: Parcel, data: ParcelData?) + suspend fun setParcelData(parcel: ParcelId, data: ParcelData?) - suspend fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) + suspend fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?) - suspend fun setLocalPlayerStatus(parcelFor: Parcel, player: UUID, status: AddedStatus) + suspend fun setLocalPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus) - suspend fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean) + suspend fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) - suspend fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean) + suspend fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean) - suspend fun ProducerScope>>.produceAllGlobalAddedData() + suspend fun produceAllGlobalAddedData(channel: SendChannel>) - suspend fun readGlobalAddedData(owner: ParcelOwner): MutableMap + suspend fun readGlobalAddedData(owner: ParcelOwner): MutableAddedDataMap suspend fun setGlobalPlayerStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) diff --git a/src/main/kotlin/io/dico/parcels2/storage/Jackson.kt b/src/main/kotlin/io/dico/parcels2/storage/Jackson.kt index 97225b8..ede33ae 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Jackson.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Jackson.kt @@ -8,7 +8,7 @@ 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.GeneratorFactory +import io.dico.parcels2.GeneratorFactories import io.dico.parcels2.GeneratorOptions import io.dico.parcels2.StorageOptions import org.bukkit.Bukkit @@ -100,7 +100,7 @@ class GeneratorOptionsDeserializer : JsonDeserializer() { val node = parser!!.readValueAsTree() val name = node.get("name").asText() val optionsNode = node.get("options") - val factory = GeneratorFactory.getFactory(name) ?: throw IllegalStateException("Unknown generator: $name") + val factory = GeneratorFactories.getFactory(name) ?: throw IllegalStateException("Unknown generator: $name") return parser.codec.treeToValue(optionsNode, factory.optionsClass.java) } diff --git a/src/main/kotlin/io/dico/parcels2/storage/SerializableTypes.kt b/src/main/kotlin/io/dico/parcels2/storage/SerializableTypes.kt deleted file mode 100644 index 1f659fb..0000000 --- a/src/main/kotlin/io/dico/parcels2/storage/SerializableTypes.kt +++ /dev/null @@ -1,40 +0,0 @@ -package io.dico.parcels2.storage - -import io.dico.parcels2.Parcel -import io.dico.parcels2.ParcelWorld -import io.dico.parcels2.Worlds -import io.dico.parcels2.util.Vec2i -import org.bukkit.Bukkit -import org.bukkit.World -import java.util.* - -data class SerializableWorld(val name: String? = null, - val uid: UUID? = null) { - - init { - uid ?: name ?: throw IllegalArgumentException("uuid and/or name must be present") - } - - val world: World? by lazy { uid?.let { Bukkit.getWorld(it) } ?: name?.let { Bukkit.getWorld(it) } } - //val parcelWorld: ParcelWorld? by lazy { TODO() } - - constructor(world: World) : this(world.name, world.uid) -} - -/** - * Used by storage backing options to encompass the location of a parcel - */ -data class SerializableParcel(val world: SerializableWorld, - val pos: Vec2i) { - - //val parcel: Parcel? by lazy { TODO() } -} - -fun Worlds.getWorldBySerializedValue(input: SerializableWorld): ParcelWorld? { - return input.world?.let { getWorld(it) } -} - -fun Worlds.getParcelBySerializedValue(input: SerializableParcel): Parcel? { - return getWorldBySerializedValue(input.world) - ?.parcelByID(input.pos) -} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt index 36f241e..6c3d68f 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt @@ -1,16 +1,19 @@ +@file:Suppress("NOTHING_TO_INLINE") + package io.dico.parcels2.storage -import io.dico.parcels2.AddedStatus -import io.dico.parcels2.Parcel -import io.dico.parcels2.ParcelData -import io.dico.parcels2.ParcelOwner +import io.dico.parcels2.* import kotlinx.coroutines.experimental.* +import kotlinx.coroutines.experimental.channels.ProducerScope import kotlinx.coroutines.experimental.channels.ReceiveChannel import kotlinx.coroutines.experimental.channels.produce -import java.util.* +import java.util.UUID import java.util.concurrent.Executor import java.util.concurrent.Executors +typealias DataPair = Pair +typealias AddedDataPair = Pair + interface Storage { val name: String val syncDispatcher: CoroutineDispatcher @@ -22,31 +25,31 @@ interface Storage { fun shutdown(): Job - fun readParcelData(parcelFor: Parcel): Deferred + fun readParcelData(parcel: ParcelId): Deferred - fun readParcelData(parcelsFor: Sequence): ReceiveChannel> + fun readParcelData(parcels: Sequence): ReceiveChannel - fun readAllParcelData(): ReceiveChannel> + fun readAllParcelData(): ReceiveChannel - fun getOwnedParcels(user: ParcelOwner): Deferred> + fun getOwnedParcels(user: ParcelOwner): Deferred> fun getNumParcels(user: ParcelOwner): Deferred - fun setParcelData(parcelFor: Parcel, data: ParcelData?): Job + fun setParcelData(parcel: ParcelId, data: ParcelData?): Job - fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?): Job + fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?): Job - fun setParcelPlayerStatus(parcelFor: Parcel, player: UUID, status: AddedStatus): Job + fun setParcelPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus): Job - fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean): Job + fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean): Job - fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean): Job + fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean): Job - fun readAllGlobalAddedData(): ReceiveChannel>> + fun readAllGlobalAddedData(): ReceiveChannel> - fun readGlobalAddedData(owner: ParcelOwner): Deferred?> + fun readGlobalAddedData(owner: ParcelOwner): Deferred fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus): Job } @@ -59,48 +62,47 @@ class StorageWithCoroutineBacking internal constructor(val backing: Backing) : S override val isConnected get() = backing.isConnected val channelCapacity = 16 - @Suppress("NOTHING_TO_INLINE") private inline fun defer(noinline block: suspend CoroutineScope.() -> T): Deferred { return async(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block) } - @Suppress("NOTHING_TO_INLINE") private inline fun job(noinline block: suspend CoroutineScope.() -> Unit): Job { return launch(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block) } + private inline fun openChannel(noinline block: suspend ProducerScope.() -> Unit): ReceiveChannel { + return produce(asyncDispatcher, capacity = channelCapacity, block = block) + } + override fun init() = job { backing.init() } override fun shutdown() = job { backing.shutdown() } - override fun readParcelData(parcelFor: Parcel) = defer { backing.readParcelData(parcelFor) } + override fun readParcelData(parcel: ParcelId) = defer { backing.readParcelData(parcel) } - override fun readParcelData(parcelsFor: Sequence) = - produce(asyncDispatcher, capacity = channelCapacity) { with(backing) { produceParcelData(parcelsFor) } } + override fun readParcelData(parcels: Sequence) = openChannel { backing.produceParcelData(channel, parcels) } - override fun readAllParcelData(): ReceiveChannel> = - produce(asyncDispatcher, capacity = channelCapacity) { with(backing) { produceAllParcelData() } } + override fun readAllParcelData() = openChannel { backing.produceAllParcelData(channel) } override fun getOwnedParcels(user: ParcelOwner) = defer { backing.getOwnedParcels(user) } override fun getNumParcels(user: ParcelOwner) = defer { backing.getNumParcels(user) } - override fun setParcelData(parcelFor: Parcel, data: ParcelData?) = job { backing.setParcelData(parcelFor, data) } + override fun setParcelData(parcel: ParcelId, data: ParcelData?) = job { backing.setParcelData(parcel, data) } - override fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) = job { backing.setParcelOwner(parcelFor, owner) } + override fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?) = job { backing.setParcelOwner(parcel, owner) } - override fun setParcelPlayerStatus(parcelFor: Parcel, player: UUID, status: AddedStatus) = job { backing.setLocalPlayerStatus(parcelFor, player, status) } + override fun setParcelPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus) = job { backing.setLocalPlayerStatus(parcel, player, status) } - override fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean) = job { backing.setParcelAllowsInteractInventory(parcel, value) } + override fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) = job { backing.setParcelAllowsInteractInventory(parcel, value) } - override fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean) = job { backing.setParcelAllowsInteractInputs(parcel, value) } + override fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean) = job { backing.setParcelAllowsInteractInputs(parcel, value) } - override fun readAllGlobalAddedData(): ReceiveChannel>> = - produce(asyncDispatcher, capacity = channelCapacity) { with(backing) { produceAllGlobalAddedData() } } + override fun readAllGlobalAddedData(): ReceiveChannel> = openChannel { backing.produceAllGlobalAddedData(channel) } - override fun readGlobalAddedData(owner: ParcelOwner): Deferred?> = defer { backing.readGlobalAddedData(owner) } + override fun readGlobalAddedData(owner: ParcelOwner): Deferred = defer { backing.readGlobalAddedData(owner) } override fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) = job { backing.setGlobalPlayerStatus(owner, player, status) } } diff --git a/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt b/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt index 90992c6..e798df9 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt @@ -40,4 +40,4 @@ class ConnectionStorageFactory : StorageFactory { return StorageWithCoroutineBacking(ExposedBacking(dataSourceFactory)) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt index 483bb16..5685346 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt @@ -5,18 +5,18 @@ package io.dico.parcels2.storage.exposed import com.zaxxer.hikari.HikariDataSource import io.dico.parcels2.* import io.dico.parcels2.storage.Backing -import io.dico.parcels2.storage.SerializableParcel +import io.dico.parcels2.storage.DataPair import io.dico.parcels2.util.toUUID import kotlinx.coroutines.experimental.CoroutineStart import kotlinx.coroutines.experimental.Unconfined -import kotlinx.coroutines.experimental.channels.ProducerScope +import kotlinx.coroutines.experimental.channels.SendChannel import kotlinx.coroutines.experimental.launch import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SchemaUtils.create import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.vendors.DatabaseDialect import org.joda.time.DateTime -import java.util.* +import java.util.UUID import javax.sql.DataSource class ExposedDatabaseException(message: String? = null) : Exception(message) @@ -63,7 +63,7 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) : isShutdown = true } - override suspend fun ProducerScope>.produceParcelData(parcels: Sequence) { + override suspend fun produceParcelData(channel: SendChannel, parcels: Sequence) { for (parcel in parcels) { val data = readParcelData(parcel) channel.send(parcel to data) @@ -71,32 +71,32 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) : channel.close() } - override suspend fun ProducerScope>.produceAllParcelData() = transactionLaunch { + override suspend fun produceAllParcelData(channel: SendChannel>) = transactionLaunch { ParcelsT.selectAll().forEach { row -> - val parcel = ParcelsT.getSerializable(row) ?: return@forEach + val parcel = ParcelsT.getId(row) ?: return@forEach val data = rowToParcelData(row) channel.send(parcel to data) } channel.close() } - override suspend fun readParcelData(parcelFor: Parcel): ParcelData? = transaction { - val row = ParcelsT.getRow(parcelFor) ?: return@transaction null + override suspend fun readParcelData(parcel: ParcelId): ParcelData? = transaction { + val row = ParcelsT.getRow(parcel) ?: return@transaction null rowToParcelData(row) } - override suspend fun getOwnedParcels(user: ParcelOwner): List = transaction { + override suspend fun getOwnedParcels(user: ParcelOwner): List = transaction { val user_id = OwnersT.getId(user) ?: return@transaction emptyList() ParcelsT.select { ParcelsT.owner_id eq user_id } .orderBy(ParcelsT.claim_time, isAsc = true) - .mapNotNull(ParcelsT::getSerializable) + .mapNotNull(ParcelsT::getId) .toList() } - override suspend fun setParcelData(parcelFor: Parcel, data: ParcelData?) { + override suspend fun setParcelData(parcel: ParcelId, data: ParcelData?) { if (data == null) { transaction { - ParcelsT.getId(parcelFor)?.let { id -> + ParcelsT.getId(parcel)?.let { id -> ParcelsT.deleteIgnoreWhere { ParcelsT.id eq id } // Below should cascade automatically @@ -111,25 +111,25 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) : } transaction { - val id = ParcelsT.getOrInitId(parcelFor) + val id = ParcelsT.getOrInitId(parcel) AddedLocalT.deleteIgnoreWhere { AddedLocalT.attach_id eq id } } - setParcelOwner(parcelFor, data.owner) + setParcelOwner(parcel, data.owner) for ((uuid, status) in data.addedMap) { - setLocalPlayerStatus(parcelFor, uuid, status) + setLocalPlayerStatus(parcel, uuid, status) } - setParcelAllowsInteractInputs(parcelFor, data.allowInteractInputs) - setParcelAllowsInteractInventory(parcelFor, data.allowInteractInventory) + setParcelAllowsInteractInputs(parcel, data.allowInteractInputs) + setParcelAllowsInteractInventory(parcel, data.allowInteractInventory) } - override suspend fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) = transaction { + override suspend fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?) = transaction { val id = if (owner == null) - ParcelsT.getId(parcelFor) ?: return@transaction + ParcelsT.getId(parcel) ?: return@transaction else - ParcelsT.getOrInitId(parcelFor) + ParcelsT.getOrInitId(parcel) val owner_id = owner?.let { OwnersT.getOrInitId(it) } val time = owner?.let { DateTime.now() } @@ -140,11 +140,11 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) : } } - override suspend fun setLocalPlayerStatus(parcelFor: Parcel, player: UUID, status: AddedStatus) = transaction { - AddedLocalT.setPlayerStatus(parcelFor, player, status) + override suspend fun setLocalPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus) = transaction { + AddedLocalT.setPlayerStatus(parcel, player, status) } - override suspend fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean): Unit = transaction { + override suspend fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean): Unit = transaction { val id = ParcelsT.getOrInitId(parcel) ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { it[ParcelOptionsT.parcel_id] = id @@ -152,7 +152,7 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) : } } - override suspend fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean): Unit = transaction { + override suspend fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean): Unit = transaction { val id = ParcelsT.getOrInitId(parcel) ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { it[ParcelOptionsT.parcel_id] = id @@ -160,7 +160,7 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) : } } - override suspend fun ProducerScope>>.produceAllGlobalAddedData() = transactionLaunch { + override suspend fun produceAllGlobalAddedData(channel: SendChannel>>) = transactionLaunch { AddedGlobalT.sendAllAddedData(channel) channel.close() } @@ -174,7 +174,7 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) : } private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply { - owner = row[ParcelsT.owner_id]?.let { OwnersT.getSerializable(it) } + owner = row[ParcelsT.owner_id]?.let { OwnersT.getId(it) } since = row[ParcelsT.claim_time] val parcelId = row[ParcelsT.id] diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedExtensions.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedExtensions.kt index e20e11b..9f7f599 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedExtensions.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedExtensions.kt @@ -40,7 +40,7 @@ class UpsertStatement(table: Table, conflictColumn: Column<*>? = null } else { - append (" ON DUPLICATE KEY UPDATE ") + append(" ON DUPLICATE KEY UPDATE ") values.keys.filter { it !in indexColumns }.joinTo(this) { "${transaction.identity(it)}=VALUES(${transaction.identity(it)})" } } diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt index 60e9bc0..ac6e431 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt @@ -2,20 +2,16 @@ package io.dico.parcels2.storage.exposed -import io.dico.parcels2.Parcel +import io.dico.parcels2.ParcelId import io.dico.parcels2.ParcelOwner -import io.dico.parcels2.ParcelWorld -import io.dico.parcels2.storage.SerializableParcel -import io.dico.parcels2.storage.SerializableWorld -import io.dico.parcels2.util.Vec2i +import io.dico.parcels2.ParcelWorldId import io.dico.parcels2.util.toByteArray import io.dico.parcels2.util.toUUID import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.statements.InsertStatement -import java.util.* +import java.util.UUID -sealed class IdTransactionsTable, - QueryObj, SerializableObj>(tableName: String, columnName: String) +sealed class IdTransactionsTable, QueryObj>(tableName: String, columnName: String) : Table(tableName) { val id = integer(columnName).autoIncrement().primaryKey() @@ -35,31 +31,32 @@ sealed class IdTransactionsTable("parcel_worlds", "world_id") { +object WorldsT : IdTransactionsTable("parcel_worlds", "world_id") { val name = varchar("name", 50) - val uid = binary("uid", 16) + val uid = binary("uid", 16).nullable() + val index_name = uniqueIndexR("index_name", name) val index_uid = uniqueIndexR("index_uid", uid) - internal inline fun getId(binaryUid: ByteArray): Int? = getId { uid eq binaryUid } - internal inline fun getId(uid: UUID): Int? = getId(uid.toByteArray()) - internal inline fun getOrInitId(worldUid: UUID, worldName: String): Int = worldUid.toByteArray().let { binaryUid -> - getId(binaryUid) - ?: insertAndGetId("world named $worldName") { it[uid] = binaryUid; it[name] = worldName } + internal inline fun getId(worldName: String, binaryUid: ByteArray?): Int? = getId { (name eq worldName).let { if (binaryUid == null) it else it or (uid eq binaryUid) } } + internal inline fun getId(worldName: String, uid: UUID?): Int? = getId(worldName, uid?.toByteArray()) + internal inline fun getOrInitId(worldName: String, worldUid: UUID?): Int = worldUid?.toByteArray().let { binaryUid -> + getId(worldName, binaryUid) + ?: insertAndGetId("world named $worldName") { it[name] = worldName; binaryUid?.let { buid -> it[uid] = buid } } } - override fun getId(world: ParcelWorld): Int? = getId(world.world.uid) - override fun getOrInitId(world: ParcelWorld): Int = world.world.let { getOrInitId(it.uid, it.name) } + override fun getId(world: ParcelWorldId): Int? = getId(world.name, world.uid) + override fun getOrInitId(world: ParcelWorldId): Int = getOrInitId(world.name, world.uid) - override fun getSerializable(row: ResultRow): SerializableWorld { - return SerializableWorld(row[name], row[uid].toUUID()) + override fun getId(row: ResultRow): ParcelWorldId { + return ParcelWorldId(row[name], row[uid]?.toUUID()) } } -object ParcelsT : IdTransactionsTable("parcels", "parcel_id") { +object ParcelsT : IdTransactionsTable("parcels", "parcel_id") { val world_id = integer("world_id").references(WorldsT.id) val px = integer("px") val pz = integer("pz") @@ -68,27 +65,27 @@ object ParcelsT : IdTransactionsTable("par val index_location = uniqueIndexR("index_location", world_id, px, pz) private inline fun getId(worldId: Int, parcelX: Int, parcelZ: Int): Int? = getId { world_id.eq(worldId) and px.eq(parcelX) and pz.eq(parcelZ) } - private inline fun getId(worldUid: UUID, parcelX: Int, parcelZ: Int): Int? = WorldsT.getId(worldUid)?.let { getId(it, parcelX, parcelZ) } - private inline fun getOrInitId(worldUid: UUID, worldName: String, parcelX: Int, parcelZ: Int): Int { - val worldId = WorldsT.getOrInitId(worldUid, worldName) + private inline fun getId(worldName: String, worldUid: UUID?, parcelX: Int, parcelZ: Int): Int? = WorldsT.getId(worldName, worldUid)?.let { getId(it, parcelX, parcelZ) } + private inline fun getOrInitId(worldName: String, worldUid: UUID?, parcelX: Int, parcelZ: Int): Int { + val worldId = WorldsT.getOrInitId(worldName, worldUid) return getId(worldId, parcelX, parcelZ) ?: insertAndGetId("parcel at $worldName($parcelX, $parcelZ)") { it[world_id] = worldId; it[px] = parcelX; it[pz] = parcelZ } } - override fun getId(parcel: Parcel): Int? = getId(parcel.world.world.uid, parcel.pos.x, parcel.pos.z) - override fun getOrInitId(parcel: Parcel): Int = parcel.world.world.let { getOrInitId(it.uid, it.name, parcel.pos.x, parcel.pos.z) } + override fun getId(parcel: ParcelId): Int? = getId(parcel.worldId.name, parcel.worldId.uid, parcel.x, parcel.z) + override fun getOrInitId(parcel: ParcelId): Int = getOrInitId(parcel.worldId.name, parcel.worldId.uid, parcel.x, parcel.z) private inline fun getRow(id: Int): ResultRow? = select { ParcelsT.id eq id }.firstOrNull() - fun getRow(parcel: Parcel): ResultRow? = getId(parcel)?.let { getRow(it) } + fun getRow(parcel: ParcelId): ResultRow? = getId(parcel)?.let { getRow(it) } - override fun getSerializable(row: ResultRow): SerializableParcel? { + override fun getId(row: ResultRow): ParcelId? { val worldId = row[world_id] - val world = WorldsT.getSerializable(worldId) ?: return null - return SerializableParcel(world, Vec2i(row[px], row[pz])) + val world = WorldsT.getId(worldId) ?: return null + return ParcelId(world, row[px], row[pz]) } } -object OwnersT : IdTransactionsTable("parcel_owners", "owner_id") { +object OwnersT : IdTransactionsTable("parcel_owners", "owner_id") { val uuid = binary("uuid", 16).nullable() val name = varchar("name", 32) val index_pair = uniqueIndexR("index_pair", uuid, name) @@ -115,7 +112,7 @@ object OwnersT : IdTransactionsTable("parcel_ if (owner.hasUUID) getOrInitId(owner.uuid!!, owner.notNullName) else getOrInitId(owner.name!!) - override fun getSerializable(row: ResultRow): ParcelOwner { + override fun getId(row: ResultRow): ParcelOwner { return row[uuid]?.toUUID()?.let { ParcelOwner(it) } ?: ParcelOwner(row[name]) } } diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt index 3e1438a..20b36b1 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt @@ -3,17 +3,16 @@ package io.dico.parcels2.storage.exposed import io.dico.parcels2.AddedStatus -import io.dico.parcels2.Parcel +import io.dico.parcels2.ParcelId import io.dico.parcels2.ParcelOwner -import io.dico.parcels2.storage.SerializableParcel import io.dico.parcels2.util.toByteArray import io.dico.parcels2.util.toUUID import kotlinx.coroutines.experimental.channels.SendChannel import org.jetbrains.exposed.sql.* -import java.util.* +import java.util.UUID -object AddedLocalT : AddedTable("parcels_added_local", ParcelsT) -object AddedGlobalT : AddedTable("parcels_added_global", OwnersT) +object AddedLocalT : AddedTable("parcels_added_local", ParcelsT) +object AddedGlobalT : AddedTable("parcels_added_global", OwnersT) object ParcelOptionsT : Table("parcel_options") { val parcel_id = integer("parcel_id").primaryKey().references(ParcelsT.id, ReferenceOption.CASCADE) @@ -23,7 +22,7 @@ object ParcelOptionsT : Table("parcel_options") { typealias AddedStatusSendChannel = SendChannel>> -sealed class AddedTable(name: String, val idTable: IdTransactionsTable<*, AttachT, SerializableT>) : Table(name) { +sealed class AddedTable(name: String, val idTable: IdTransactionsTable<*, AttachT>) : Table(name) { val attach_id = integer("attach_id").references(idTable.id, ReferenceOption.CASCADE) val player_uuid = binary("player_uuid", 16) val allowed_flag = bool("allowed_flag") @@ -52,7 +51,7 @@ sealed class AddedTable(name: String, val idTable: IdTra .associateByTo(hashMapOf(), { it[player_uuid].toUUID() }, { it[allowed_flag].asAddedStatus() }) } - suspend fun sendAllAddedData(channel: AddedStatusSendChannel) { + suspend fun sendAllAddedData(channel: AddedStatusSendChannel) { /* val iterator = selectAll().orderBy(attach_id).iterator() @@ -63,7 +62,7 @@ sealed class AddedTable(name: String, val idTable: IdTra var map: MutableMap? = null fun initAttachAndMap() { - attach = idTable.getSerializable(id) + attach = idTable.getId(id) map = attach?.let { mutableMapOf() } } diff --git a/src/main/kotlin/io/dico/parcels2/storage/migration/Migration.kt b/src/main/kotlin/io/dico/parcels2/storage/migration/Migration.kt new file mode 100644 index 0000000..c8bc93c --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/storage/migration/Migration.kt @@ -0,0 +1,8 @@ +package io.dico.parcels2.storage.migration + +import io.dico.parcels2.storage.Storage + +interface Migration { + fun migrateTo(storage: Storage) +} + diff --git a/src/main/kotlin/io/dico/parcels2/storage/migration/MigrationFactory.kt b/src/main/kotlin/io/dico/parcels2/storage/migration/MigrationFactory.kt new file mode 100644 index 0000000..4fb3088 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/storage/migration/MigrationFactory.kt @@ -0,0 +1,5 @@ +package io.dico.parcels2.storage.migration + +interface MigrationFactory { + fun getMigration() +} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt new file mode 100644 index 0000000..e5b7d9d --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt @@ -0,0 +1,118 @@ +package io.dico.parcels2.storage.migration.plotme + +import com.zaxxer.hikari.HikariDataSource +import io.dico.parcels2.* +import io.dico.parcels2.storage.Storage +import io.dico.parcels2.storage.migration.Migration +import io.dico.parcels2.util.Vec2i +import io.dico.parcels2.util.isValid +import io.dico.parcels2.util.toUUID +import io.dico.parcels2.util.uuid +import kotlinx.coroutines.experimental.asCoroutineDispatcher +import kotlinx.coroutines.experimental.launch +import org.bukkit.Bukkit +import org.jetbrains.exposed.sql.* +import org.slf4j.LoggerFactory +import java.io.ByteArrayOutputStream +import java.sql.Blob +import java.util.UUID +import java.util.concurrent.Executors +import javax.sql.DataSource + +class PlotmeMigration(val parcelProvider: ParcelProvider, + val worldMapper: Map, + val dataSourceFactory: () -> DataSource) : Migration { + private var dataSource: DataSource? = null + private var database: Database? = null + private var isShutdown: Boolean = false + private val dispatcher = Executors.newSingleThreadExecutor { Thread(it, "PlotMe Migration Thread") }.asCoroutineDispatcher() + private val mlogger = LoggerFactory.getLogger("PlotMe Migrator") + + private fun transaction(statement: Transaction.() -> T) = org.jetbrains.exposed.sql.transactions.transaction(database!!, statement) + + override fun migrateTo(storage: Storage) { + launch(context = dispatcher) { + init() + doWork(storage) + shutdown() + } + } + + fun init() { + if (isShutdown) throw IllegalStateException() + dataSource = dataSourceFactory() + database = Database.connect(dataSource!!) + } + + fun shutdown() { + if (isShutdown) throw IllegalStateException() + dataSource?.let { + (it as? HikariDataSource)?.close() + } + database = null + isShutdown = true + } + + val parcelsCache = hashMapOf>() + + private fun getMap(worldName: String): MutableMap? { + val mapped = worldMapper[worldName] ?: return null + return parcelsCache.computeIfAbsent(mapped) { mutableMapOf() } + } + + private fun getData(worldName: String, position: Vec2i): ParcelData? { + return getMap(worldName)?.computeIfAbsent(position) { ParcelDataHolder() } + } + + fun doWork(target: Storage): Unit = transaction { + if (!PlotmePlotsT.exists()) { + mlogger.warn("Plotme tables don't appear to exist. Exiting.") + return@transaction + } + parcelsCache.clear() + + iterPlotmeTable(PlotmePlotsT) { data, row -> + // in practice, owner_uuid is not null for any plot currently. It will convert well. + data.owner = ParcelOwner(row[owner_uuid]?.toUUID(), row[owner_name]) + } + + iterPlotmeTable(PlotmeAllowedT) { data, row -> + val uuid = row[player_uuid]?.toUUID() + ?: Bukkit.getOfflinePlayer(row[player_name]).takeIf { it.isValid }?.uuid + ?: return@iterPlotmeTable + + data.setAddedStatus(uuid, AddedStatus.ALLOWED) + } + + iterPlotmeTable(PlotmeDeniedT) { data, row -> + val uuid = row[PlotmeAllowedT.player_uuid]?.toUUID() + ?: Bukkit.getOfflinePlayer(row[PlotmeAllowedT.player_name]).takeIf { it.isValid }?.uuid + ?: return@iterPlotmeTable + + data.setAddedStatus(uuid, AddedStatus.BANNED) + } + + for ((worldName, map) in parcelsCache) { + val world = ParcelWorldId(worldName) + for ((pos, data) in map) { + val parcel = ParcelId(world, pos) + target.setParcelData(parcel, data) + } + } + + } + + private fun Blob.toUUID(): UUID { + val out = ByteArrayOutputStream(16) + binaryStream.copyTo(out, bufferSize = 16) + return out.toByteArray().toUUID() + } + + private inline fun iterPlotmeTable(table: T, block: T.(ParcelData, ResultRow) -> Unit) { + table.selectAll().forEach { row -> + val data = getData(row[table.world_name], Vec2i(row[table.px], row[table.pz])) ?: return@forEach + table.block(data, row) + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeTables.kt b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeTables.kt new file mode 100644 index 0000000..3d07955 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeTables.kt @@ -0,0 +1,26 @@ +package io.dico.parcels2.storage.migration.plotme + +import org.jetbrains.exposed.sql.Table + +const val uppercase: Boolean = false +@Suppress("ConstantConditionIf") +fun String.toCorrectCase() = if (uppercase) this else toLowerCase() + +sealed class PlotmeTable(name: String) : Table(name) { + val px = PlotmePlotsT.integer("idX").primaryKey() + val pz = PlotmePlotsT.integer("idZ").primaryKey() + val world_name = PlotmePlotsT.varchar("world", 32).primaryKey() +} + +object PlotmePlotsT : PlotmeTable("plotmePlots".toCorrectCase()) { + val owner_name = varchar("owner", 32) + val owner_uuid = blob("ownerid").nullable() +} + +sealed class PlotmePlotPlayerMap(name: String) : PlotmeTable(name) { + val player_name = PlotmePlotsT.varchar("player", 32) + val player_uuid = PlotmePlotsT.blob("playerid").nullable() +} + +object PlotmeAllowedT : PlotmePlotPlayerMap("plotmeAllowed".toCorrectCase()) +object PlotmeDeniedT : PlotmePlotPlayerMap("plotmeDenied".toCorrectCase()) diff --git a/src/main/kotlin/io/dico/parcels2/util/BukkitAwait.kt b/src/main/kotlin/io/dico/parcels2/util/BukkitAwait.kt deleted file mode 100644 index 0df14e7..0000000 --- a/src/main/kotlin/io/dico/parcels2/util/BukkitAwait.kt +++ /dev/null @@ -1,72 +0,0 @@ -package io.dico.parcels2.util - -import org.bukkit.plugin.Plugin -import org.bukkit.scheduler.BukkitTask - -inline fun Plugin.doAwait(checkNow: Boolean = true, configure: AwaitTask.() -> Unit) { - with(AwaitTask()) { - configure() - start(checkNow = checkNow) - } -} - -private typealias Action = () -> T - -class AwaitTask : Runnable { - //@formatter:off - var cond: Action? = null ; set(value) { checkNotRunning(); field = value } - var onSuccess: Action? = null ; set(value) { checkNotRunning(); field = value } - var onFailure: Action? = null ; set(value) { checkNotRunning(); field = value } - var delay: Int = -1 ; set(value) { checkNotRunning(); field = value } - var interval: Int = 20 ; set(value) { checkNotRunning(); field = value } - var maxChecks: Int = 0 ; set(value) { checkNotRunning(); field = value } - - var task: BukkitTask? = null ; private set - var elapsedChecks = 0 ; private set - var cancelled = false ; private set - //@formatter:on - - fun Plugin.start(checkNow: Boolean = true) { - if (cancelled) throw IllegalStateException() - - requireNotNull(cond) - requireNotNull(onSuccess) - - if (checkNow && check()) { - cancel() - onSuccess!!.invoke() - return - } - - task = server.scheduler.runTaskTimer(this, this@AwaitTask, delay.toLong(), interval.toLong()) - } - - override fun run() { - if (task?.isCancelled != false) return - - if (check()) { - cancel() - onSuccess!!.invoke() - } - - if (maxChecks in 1 until elapsedChecks) { - cancel() - onFailure?.invoke() - } - } - - private fun check(): Boolean { - elapsedChecks++ - return cond!!.invoke() - } - - fun cancel() { - task?.cancel() - cancelled = true - } - - private fun checkNotRunning() { - if (cancelled || task != null) throw IllegalStateException() - } - -} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/util/MaterialExtensions.kt b/src/main/kotlin/io/dico/parcels2/util/MaterialExtensions.kt index f38f687..a2aefc8 100644 --- a/src/main/kotlin/io/dico/parcels2/util/MaterialExtensions.kt +++ b/src/main/kotlin/io/dico/parcels2/util/MaterialExtensions.kt @@ -10,62 +10,67 @@ wood: OAK_$, BIRCH_$, SPRUCE_$, JUNGLE_$, ACACIA_$, DARK_OAK_$, */ -val Material.isBed get() = when(this) { - WHITE_BED, - ORANGE_BED, - MAGENTA_BED, - LIGHT_BLUE_BED, - YELLOW_BED, - LIME_BED, - PINK_BED, - GRAY_BED, - LIGHT_GRAY_BED, - CYAN_BED, - PURPLE_BED, - BLUE_BED, - BROWN_BED, - GREEN_BED, - RED_BED, - BLACK_BED -> true - else -> false -} +val Material.isBed + get() = when (this) { + WHITE_BED, + ORANGE_BED, + MAGENTA_BED, + LIGHT_BLUE_BED, + YELLOW_BED, + LIME_BED, + PINK_BED, + GRAY_BED, + LIGHT_GRAY_BED, + CYAN_BED, + PURPLE_BED, + BLUE_BED, + BROWN_BED, + GREEN_BED, + RED_BED, + BLACK_BED -> true + else -> false + } -val Material.isWoodDoor get() = when(this) { - OAK_DOOR, - BIRCH_DOOR, - SPRUCE_DOOR, - JUNGLE_DOOR, - ACACIA_DOOR, - DARK_OAK_DOOR -> true - else -> false -} +val Material.isWoodDoor + get() = when (this) { + OAK_DOOR, + BIRCH_DOOR, + SPRUCE_DOOR, + JUNGLE_DOOR, + ACACIA_DOOR, + DARK_OAK_DOOR -> true + else -> false + } -val Material.isWoodTrapdoor get() = when(this) { - OAK_TRAPDOOR, - BIRCH_TRAPDOOR, - SPRUCE_TRAPDOOR, - JUNGLE_TRAPDOOR, - ACACIA_TRAPDOOR, - DARK_OAK_TRAPDOOR -> true - else -> false -} +val Material.isWoodTrapdoor + get() = when (this) { + OAK_TRAPDOOR, + BIRCH_TRAPDOOR, + SPRUCE_TRAPDOOR, + JUNGLE_TRAPDOOR, + ACACIA_TRAPDOOR, + DARK_OAK_TRAPDOOR -> true + else -> false + } -val Material.isWoodFenceGate get() = when(this) { - OAK_FENCE_GATE, - BIRCH_FENCE_GATE, - SPRUCE_FENCE_GATE, - JUNGLE_FENCE_GATE, - ACACIA_FENCE_GATE, - DARK_OAK_FENCE_GATE -> true - else -> false -} +val Material.isWoodFenceGate + get() = when (this) { + OAK_FENCE_GATE, + BIRCH_FENCE_GATE, + SPRUCE_FENCE_GATE, + JUNGLE_FENCE_GATE, + ACACIA_FENCE_GATE, + DARK_OAK_FENCE_GATE -> true + else -> false + } -val Material.isWoodButton get() = when(this) { - OAK_BUTTON, - BIRCH_BUTTON, - SPRUCE_BUTTON, - JUNGLE_BUTTON, - ACACIA_BUTTON, - DARK_OAK_BUTTON -> true - else -> false -} +val Material.isWoodButton + get() = when (this) { + OAK_BUTTON, + BIRCH_BUTTON, + SPRUCE_BUTTON, + JUNGLE_BUTTON, + ACACIA_BUTTON, + DARK_OAK_BUTTON -> true + else -> false + } diff --git a/src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt b/src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt index 6597441..8713da7 100644 --- a/src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt +++ b/src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt @@ -9,7 +9,8 @@ import org.bukkit.plugin.java.JavaPlugin inline val OfflinePlayer.uuid get() = uniqueId @Suppress("UsePropertyAccessSyntax") -inline val OfflinePlayer.isValid get() = isOnline() || hasPlayedBefore() +inline val OfflinePlayer.isValid + get() = isOnline() || hasPlayedBefore() inline val Player.hasBanBypass get() = hasPermission("parcels.admin.bypass.ban") inline val Player.hasGamemodeBypass get() = hasPermission("parcels.admin.bypass.gamemode") diff --git a/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt b/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt index b93dec2..bca2428 100644 --- a/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt +++ b/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt @@ -1,9 +1,8 @@ package io.dico.parcels2.util import org.bukkit.Bukkit -import org.jetbrains.annotations.Contract import java.nio.ByteBuffer -import java.util.* +import java.util.UUID @Suppress("UsePropertyAccessSyntax") fun getPlayerNameOrDefault(uuid: UUID?, ifUnknown: String? = null): String { -- cgit v1.2.3 From 5626ff565258f9a4f32c69afd9f29fb2d51a9d87 Mon Sep 17 00:00:00 2001 From: Dico Date: Thu, 2 Aug 2018 18:56:50 +0100 Subject: Force complete WorktimeLimiter tasks on shutdown, Improve parcel info string --- src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt | 1 + .../dico/parcels2/blockvisitor/WorktimeLimiter.kt | 35 ++++++++++++++--- .../io/dico/parcels2/defaultimpl/ParcelImpl.kt | 45 +++++++++++++++++----- 3 files changed, 66 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt index 3a74626..6d08d28 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt @@ -47,6 +47,7 @@ class ParcelsPlugin : JavaPlugin() { } override fun onDisable() { + worktimeLimiter.completeAllTasks() cmdDispatcher?.unregisterFromCommandMap() } diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt index a18c63b..30eaabd 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt @@ -28,6 +28,11 @@ sealed class WorktimeLimiter { * Get a list of all workers */ abstract val workers: List + + /** + * Attempts to complete any remaining tasks immediately, without suspension. + */ + abstract fun completeAllTasks() } interface Timed { @@ -91,8 +96,14 @@ interface WorkerScope : Timed { private interface WorkerContinuation : Worker, WorkerScope { /** - * Start or resume the execution of this worker - * returns true if the worker completed + * Start or resumes the execution of this worker + * and returns true if the worker completed + * + * [worktime] is the maximum amount of time, in milliseconds, + * that this job may run for until suspension. + * + * If [worktime] is not positive, the worker will complete + * without suspension and this method will always return true. */ fun resume(worktime: Long): Boolean } @@ -106,7 +117,7 @@ class TickWorktimeLimiter(private val plugin: ParcelsPlugin, var options: TickWo // The currently registered bukkit scheduler task private var bukkitTask: BukkitTask? = null // The workers. - private var _workers = LinkedList() + private val _workers = LinkedList() override val workers: List = _workers override fun submit(task: TimeLimitedTask): Worker { @@ -143,6 +154,15 @@ class TickWorktimeLimiter(private val plugin: ParcelsPlugin, var options: TickWo } } + override fun completeAllTasks() { + _workers.forEach { + it.resume(-1) + } + _workers.clear() + bukkitTask?.cancel() + bukkitTask = null + } + } private class WorkerImpl(val functionHelper: FunctionHelper, @@ -168,6 +188,7 @@ private class WorkerImpl(val functionHelper: FunctionHelper, private var onCompleted: WorkerUpdateLister? = null private var continuation: Continuation? = null private var nextSuspensionTime: Long = 0L + private var completeForcefully = false private fun initJob(job: Job) { this.job?.let { throw IllegalStateException() } @@ -202,7 +223,7 @@ private class WorkerImpl(val functionHelper: FunctionHelper, } override suspend fun markSuspensionPoint() { - if (System.currentTimeMillis() >= nextSuspensionTime) + if (System.currentTimeMillis() >= nextSuspensionTime && !completeForcefully) suspendCoroutineUninterceptedOrReturn { cont: Continuation -> continuation = cont COROUTINE_SUSPENDED @@ -220,7 +241,11 @@ private class WorkerImpl(val functionHelper: FunctionHelper, } override fun resume(worktime: Long): Boolean { - nextSuspensionTime = currentTimeMillis() + worktime + if (worktime > 0) { + nextSuspensionTime = currentTimeMillis() + worktime + } else { + completeForcefully = true + } continuation?.let { continuation = null diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt index 576fc48..c3d7c22 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt @@ -84,6 +84,15 @@ private object ParcelInfoStringComputer { val infoStringColor1 = Formatting.GREEN val infoStringColor2 = Formatting.AQUA + private inline fun StringBuilder.appendField(field: StringBuilder.() -> Unit, value: StringBuilder.() -> Unit) { + append(infoStringColor1) + field() + append(": ") + append(infoStringColor2) + value() + append(' ') + } + private inline fun StringBuilder.appendField(name: String, value: StringBuilder.() -> Unit) { append(infoStringColor1) append(name) @@ -93,6 +102,26 @@ private object ParcelInfoStringComputer { append(' ') } + private fun StringBuilder.appendAddedList(local: Map, global: Map, status: AddedStatus, fieldName: String) { + val globalSet = global.filterValues { it == status }.keys + val localList = local.filterValues { it == status }.keys.filter { it !in globalSet } + val stringList = globalSet.map(::getPlayerName).map { "(G)$it" } + localList.map(::getPlayerName) + if (stringList.isEmpty()) return + + appendField({ + append(fieldName) + append('(') + append(infoStringColor2) + append(stringList.size) + append(infoStringColor1) + append(')') + }) { + stringList.joinTo(this, + separator = infoStringColor1.toString() + ", " + infoStringColor2, + limit = 150) + } + } + operator fun getValue(parcel: Parcel, property: KProperty<*>): String = buildString { appendField("ID") { append(parcel.x) @@ -100,8 +129,8 @@ private object ParcelInfoStringComputer { append(parcel.z) } + val owner = parcel.owner appendField("Owner") { - val owner = parcel.owner if (owner == null) { append(infoStringColor1) append("none") @@ -114,15 +143,11 @@ private object ParcelInfoStringComputer { append('\n') - val allowedMap = parcel.addedMap.filterValues { it.isAllowed } - if (allowedMap.isNotEmpty()) appendField("Allowed") { - allowedMap.keys.map(::getPlayerName).joinTo(this) - } - - val bannedMap = parcel.addedMap.filterValues { it.isBanned } - if (bannedMap.isNotEmpty()) appendField("Banned") { - bannedMap.keys.map(::getPlayerName).joinTo(this) - } + val global = owner?.let { parcel.world.globalAddedData[owner].addedMap } ?: emptyMap() + val local = parcel.addedMap + appendAddedList(local, global, AddedStatus.ALLOWED, "Allowed") + append('\n') + appendAddedList(local, global, AddedStatus.BANNED, "Banned") if (!parcel.allowInteractInputs || !parcel.allowInteractInventory) { appendField("Options") { -- cgit v1.2.3 From 0f3934710bb7805d1f0af5398cbab35602943d8b Mon Sep 17 00:00:00 2001 From: Dico Date: Thu, 2 Aug 2018 19:10:18 +0100 Subject: Tweak some command output --- .../java/io/dico/dicore/command/chat/AbstractChatController.java | 2 +- src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/chat/AbstractChatController.java b/dicore3/command/src/main/java/io/dico/dicore/command/chat/AbstractChatController.java index 52c1e30..d88e852 100644 --- a/dicore3/command/src/main/java/io/dico/dicore/command/chat/AbstractChatController.java +++ b/dicore3/command/src/main/java/io/dico/dicore/command/chat/AbstractChatController.java @@ -36,7 +36,7 @@ public class AbstractChatController implements IChatController { @Override public void sendMessage(CommandSender sender, EMessageType type, String message) { if (message != null && !message.isEmpty()) { - sender.sendMessage(getMessagePrefixForType(type) + getChatFormatForType(type) + message); + sender.sendMessage(filterMessage(getMessagePrefixForType(type) + getChatFormatForType(type) + message)); } } diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt index 769cf5f..5395b1c 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt @@ -88,11 +88,13 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { @ParcelRequire(owner = true) fun ParcelScope.cmdClear(context: ExecutionContext, @Flag sure: Boolean): Any? { if (!sure) return "Are you sure? You cannot undo this action!\n" + - "Type ${context.rawInput} -sure if you want to go through with this." + "Run \"/${context.rawInput} -sure\" if you want to go through with this." world.clearParcel(parcel.id) .onProgressUpdate(1000, 1000) { progress, elapsedTime -> - context.sendMessage(EMessageType.INFORMATIVE, "Clear progress: %.02f%%, %.2fs elapsed" + val alt = context.getFormat(EMessageType.NUMBER) + val main = context.getFormat(EMessageType.INFORMATIVE) + context.sendMessage(EMessageType.INFORMATIVE, false, "Clear progress: $alt%.02f$main%%, $alt%.2f${main}s elapsed" .format(progress * 100, elapsedTime / 1000.0)) } -- cgit v1.2.3 From 7cd9844670896c5a67ca723de85a0ebe120dddfc Mon Sep 17 00:00:00 2001 From: Dico Date: Thu, 2 Aug 2018 19:10:35 +0100 Subject: bump version --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 8941601..11975a1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ import java.net.URL val stdout = PrintWriter("gradle-output.txt") group = "io.dico" -version = "0.1" +version = "0.2" plugins { java -- cgit v1.2.3