diff options
author | Dico Karssiens <dico.karssiens@gmail.com> | 2018-11-11 14:06:45 +0000 |
---|---|---|
committer | Dico Karssiens <dico.karssiens@gmail.com> | 2018-11-11 14:06:45 +0000 |
commit | 0f196f59c6a4cb76ab8409da62ff1f35505f94a8 (patch) | |
tree | 4b612eb874fc3b7aebe88430a28f1bfc7272c9a6 /src | |
parent | e55c595e54116961798cec03f6404d0c8d986e6d (diff) |
Changes I made before breaking my local repository. Hoping this works.dev
Diffstat (limited to 'src')
67 files changed, 6939 insertions, 6939 deletions
diff --git a/src/main/kotlin/io/dico/parcels2/Interactable.kt b/src/main/kotlin/io/dico/parcels2/Interactable.kt index c301340..f25b796 100644 --- a/src/main/kotlin/io/dico/parcels2/Interactable.kt +++ b/src/main/kotlin/io/dico/parcels2/Interactable.kt @@ -1,174 +1,174 @@ -package io.dico.parcels2 - -import io.dico.parcels2.util.math.ceilDiv -import io.dico.parcels2.util.ext.getMaterialsWithWoodTypePrefix -import org.bukkit.Material -import java.util.EnumMap - -class Interactables -private constructor( - val id: Int, - val name: String, - val interactableByDefault: Boolean, - vararg val materials: Material -) { - - companion object { - val classesById: List<Interactables> - val classesByName: Map<String, Interactables> - val listedMaterials: Map<Material, Int> - - init { - val array = getClassesArray() - classesById = array.asList() - classesByName = mapOf(*array.map { it.name to it }.toTypedArray()) - listedMaterials = EnumMap(mapOf(*array.flatMap { clazz -> clazz.materials.map { it to clazz.id } }.toTypedArray())) - } - - operator fun get(material: Material): Interactables? { - val id = listedMaterials[material] ?: return null - return classesById[id] - } - - operator fun get(name: String): Interactables { - return classesByName[name] ?: throw IllegalArgumentException("Interactables class does not exist: $name") - } - - operator fun get(id: Int): Interactables { - return classesById[id] - } - - private fun getClassesArray() = run { - var id = 0 - @Suppress("UNUSED_CHANGED_VALUE") - arrayOf( - Interactables( - id++, "buttons", true, - Material.STONE_BUTTON, - *getMaterialsWithWoodTypePrefix("BUTTON") - ), - - Interactables( - id++, "levers", true, - Material.LEVER - ), - - Interactables( - id++, "pressure_plates", true, - Material.STONE_PRESSURE_PLATE, - *getMaterialsWithWoodTypePrefix("PRESSURE_PLATE"), - Material.HEAVY_WEIGHTED_PRESSURE_PLATE, - Material.LIGHT_WEIGHTED_PRESSURE_PLATE - ), - - Interactables( - id++, "redstone", false, - Material.COMPARATOR, - Material.REPEATER - ), - - Interactables( - id++, "containers", false, - Material.CHEST, - Material.TRAPPED_CHEST, - Material.DISPENSER, - Material.DROPPER, - Material.HOPPER, - Material.FURNACE - ), - - Interactables( - id++, "gates", true, - *getMaterialsWithWoodTypePrefix("DOOR"), - *getMaterialsWithWoodTypePrefix("TRAPDOOR"), - *getMaterialsWithWoodTypePrefix("FENCE_GATE") - ) - ) - } - - } - -} - -val Parcel?.effectiveInteractableConfig: InteractableConfiguration - get() = this?.interactableConfig ?: pathInteractableConfig - -val pathInteractableConfig: InteractableConfiguration = run { - val data = BitmaskInteractableConfiguration().apply { - Interactables.classesById.forEach { - setInteractable(it, false) - } - } - object : InteractableConfiguration by data { - override fun setInteractable(clazz: Interactables, interactable: Boolean) = - throw IllegalStateException("pathInteractableConfig is immutable") - - override fun clear() = - throw IllegalStateException("pathInteractableConfig is immutable") - - override fun copyFrom(other: InteractableConfiguration) = - throw IllegalStateException("pathInteractableConfig is immutable") - } -} - -interface InteractableConfiguration { - val interactableClasses: List<Interactables> get() = Interactables.classesById.filter { isInteractable(it) } - - fun isInteractable(material: Material): Boolean - fun isInteractable(clazz: Interactables): Boolean - fun isDefault(): Boolean - - fun setInteractable(clazz: Interactables, interactable: Boolean): Boolean - fun clear(): Boolean - fun copyFrom(other: InteractableConfiguration) = - Interactables.classesById.fold(false) { cur, elem -> setInteractable(elem, other.isInteractable(elem) || cur) } - - operator fun invoke(material: Material) = isInteractable(material) - operator fun invoke(className: String) = isInteractable(Interactables[className]) -} - -fun InteractableConfiguration.isInteractable(clazz: Interactables?) = clazz != null && isInteractable(clazz) - -class BitmaskInteractableConfiguration : InteractableConfiguration { - val bitmaskArray = IntArray(Interactables.classesById.size ceilDiv Int.SIZE_BITS) - - private fun isBitSet(classId: Int): Boolean { - val idx = classId.ushr(5) - return idx < bitmaskArray.size && bitmaskArray[idx].and(0x1.shl(classId.and(0x1F))) != 0 - } - - override fun isInteractable(material: Material): Boolean { - val classId = Interactables.listedMaterials[material] ?: return false - return isBitSet(classId) != Interactables.classesById[classId].interactableByDefault - } - - override fun isInteractable(clazz: Interactables): Boolean { - return isBitSet(clazz.id) != clazz.interactableByDefault - } - - override fun isDefault(): Boolean { - for (x in bitmaskArray) { - if (x != 0) return false - } - return true - } - - override fun setInteractable(clazz: Interactables, interactable: Boolean): Boolean { - val idx = clazz.id.ushr(5) - if (idx >= bitmaskArray.size) return false - val bit = 0x1.shl(clazz.id.and(0x1F)) - val oldBitmask = bitmaskArray[idx] - bitmaskArray[idx] = if (interactable != clazz.interactableByDefault) oldBitmask.or(bit) else oldBitmask.and(bit.inv()) - return bitmaskArray[idx] != oldBitmask - } - - override fun clear(): Boolean { - var change = false - for (i in bitmaskArray.indices) { - if (!change && bitmaskArray[i] != 0) change = true - bitmaskArray[i] = 0 - } - return change - } - +package io.dico.parcels2
+
+import io.dico.parcels2.util.math.ceilDiv
+import io.dico.parcels2.util.ext.getMaterialsWithWoodTypePrefix
+import org.bukkit.Material
+import java.util.EnumMap
+
+class Interactables
+private constructor(
+ val id: Int,
+ val name: String,
+ val interactableByDefault: Boolean,
+ vararg val materials: Material
+) {
+
+ companion object {
+ val classesById: List<Interactables>
+ val classesByName: Map<String, Interactables>
+ val listedMaterials: Map<Material, Int>
+
+ init {
+ val array = getClassesArray()
+ classesById = array.asList()
+ classesByName = mapOf(*array.map { it.name to it }.toTypedArray())
+ listedMaterials = EnumMap(mapOf(*array.flatMap { clazz -> clazz.materials.map { it to clazz.id } }.toTypedArray()))
+ }
+
+ operator fun get(material: Material): Interactables? {
+ val id = listedMaterials[material] ?: return null
+ return classesById[id]
+ }
+
+ operator fun get(name: String): Interactables {
+ return classesByName[name] ?: throw IllegalArgumentException("Interactables class does not exist: $name")
+ }
+
+ operator fun get(id: Int): Interactables {
+ return classesById[id]
+ }
+
+ private fun getClassesArray() = run {
+ var id = 0
+ @Suppress("UNUSED_CHANGED_VALUE")
+ arrayOf(
+ Interactables(
+ id++, "buttons", true,
+ Material.STONE_BUTTON,
+ *getMaterialsWithWoodTypePrefix("BUTTON")
+ ),
+
+ Interactables(
+ id++, "levers", true,
+ Material.LEVER
+ ),
+
+ Interactables(
+ id++, "pressure_plates", true,
+ Material.STONE_PRESSURE_PLATE,
+ *getMaterialsWithWoodTypePrefix("PRESSURE_PLATE"),
+ Material.HEAVY_WEIGHTED_PRESSURE_PLATE,
+ Material.LIGHT_WEIGHTED_PRESSURE_PLATE
+ ),
+
+ Interactables(
+ id++, "redstone", false,
+ Material.COMPARATOR,
+ Material.REPEATER
+ ),
+
+ Interactables(
+ id++, "containers", false,
+ Material.CHEST,
+ Material.TRAPPED_CHEST,
+ Material.DISPENSER,
+ Material.DROPPER,
+ Material.HOPPER,
+ Material.FURNACE
+ ),
+
+ Interactables(
+ id++, "gates", true,
+ *getMaterialsWithWoodTypePrefix("DOOR"),
+ *getMaterialsWithWoodTypePrefix("TRAPDOOR"),
+ *getMaterialsWithWoodTypePrefix("FENCE_GATE")
+ )
+ )
+ }
+
+ }
+
+}
+
+val Parcel?.effectiveInteractableConfig: InteractableConfiguration
+ get() = this?.interactableConfig ?: pathInteractableConfig
+
+val pathInteractableConfig: InteractableConfiguration = run {
+ val data = BitmaskInteractableConfiguration().apply {
+ Interactables.classesById.forEach {
+ setInteractable(it, false)
+ }
+ }
+ object : InteractableConfiguration by data {
+ override fun setInteractable(clazz: Interactables, interactable: Boolean) =
+ throw IllegalStateException("pathInteractableConfig is immutable")
+
+ override fun clear() =
+ throw IllegalStateException("pathInteractableConfig is immutable")
+
+ override fun copyFrom(other: InteractableConfiguration) =
+ throw IllegalStateException("pathInteractableConfig is immutable")
+ }
+}
+
+interface InteractableConfiguration {
+ val interactableClasses: List<Interactables> get() = Interactables.classesById.filter { isInteractable(it) }
+
+ fun isInteractable(material: Material): Boolean
+ fun isInteractable(clazz: Interactables): Boolean
+ fun isDefault(): Boolean
+
+ fun setInteractable(clazz: Interactables, interactable: Boolean): Boolean
+ fun clear(): Boolean
+ fun copyFrom(other: InteractableConfiguration) =
+ Interactables.classesById.fold(false) { cur, elem -> setInteractable(elem, other.isInteractable(elem) || cur) }
+
+ operator fun invoke(material: Material) = isInteractable(material)
+ operator fun invoke(className: String) = isInteractable(Interactables[className])
+}
+
+fun InteractableConfiguration.isInteractable(clazz: Interactables?) = clazz != null && isInteractable(clazz)
+
+class BitmaskInteractableConfiguration : InteractableConfiguration {
+ val bitmaskArray = IntArray(Interactables.classesById.size ceilDiv Int.SIZE_BITS)
+
+ private fun isBitSet(classId: Int): Boolean {
+ val idx = classId.ushr(5)
+ return idx < bitmaskArray.size && bitmaskArray[idx].and(0x1.shl(classId.and(0x1F))) != 0
+ }
+
+ override fun isInteractable(material: Material): Boolean {
+ val classId = Interactables.listedMaterials[material] ?: return false
+ return isBitSet(classId) != Interactables.classesById[classId].interactableByDefault
+ }
+
+ override fun isInteractable(clazz: Interactables): Boolean {
+ return isBitSet(clazz.id) != clazz.interactableByDefault
+ }
+
+ override fun isDefault(): Boolean {
+ for (x in bitmaskArray) {
+ if (x != 0) return false
+ }
+ return true
+ }
+
+ override fun setInteractable(clazz: Interactables, interactable: Boolean): Boolean {
+ val idx = clazz.id.ushr(5)
+ if (idx >= bitmaskArray.size) return false
+ val bit = 0x1.shl(clazz.id.and(0x1F))
+ val oldBitmask = bitmaskArray[idx]
+ bitmaskArray[idx] = if (interactable != clazz.interactableByDefault) oldBitmask.or(bit) else oldBitmask.and(bit.inv())
+ return bitmaskArray[idx] != oldBitmask
+ }
+
+ override fun clear(): Boolean {
+ var change = false
+ for (i in bitmaskArray.indices) {
+ if (!change && bitmaskArray[i] != 0) change = true
+ bitmaskArray[i] = 0
+ }
+ return change
+ }
+
}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/JobDispatcher.kt b/src/main/kotlin/io/dico/parcels2/JobDispatcher.kt index 10da0da..12be89a 100644 --- a/src/main/kotlin/io/dico/parcels2/JobDispatcher.kt +++ b/src/main/kotlin/io/dico/parcels2/JobDispatcher.kt @@ -1,337 +1,337 @@ -package io.dico.parcels2 - -import io.dico.parcels2.util.math.clampMin -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.CoroutineStart.LAZY -import kotlinx.coroutines.Job as CoroutineJob -import kotlinx.coroutines.launch -import org.bukkit.scheduler.BukkitTask -import java.lang.System.currentTimeMillis -import java.util.LinkedList -import kotlin.coroutines.Continuation -import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED -import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn -import kotlin.coroutines.resume - -typealias JobFunction = suspend JobScope.() -> Unit -typealias JobUpdateLister = Job.(Double, Long) -> Unit - -data class TickJobtimeOptions(var jobTime: Int, var tickInterval: Int) - -interface JobDispatcher { - /** - * Submit a [function] that should be run synchronously, but limited such that it does not stall the server - */ - fun dispatch(function: JobFunction): Job - - /** - * Get a list of all jobs - */ - val jobs: List<Job> - - /** - * Attempts to complete any remaining tasks immediately, without suspension. - */ - fun completeAllTasks() -} - -interface JobAndScopeMembersUnion { - /** - * The time that elapsed since this job was dispatched, in milliseconds - */ - val elapsedTime: Long - - /** - * A value indicating the progress of this job, in the range 0.0 <= progress <= 1.0 - * with no guarantees to its accuracy. - */ - val progress: Double -} - -interface Job : JobAndScopeMembersUnion { - /** - * The coroutine associated with this job - */ - val coroutine: CoroutineJob - - /** - * true if this job has completed - */ - val isComplete: Boolean - - /** - * If an exception was thrown during the execution of this task, - * returns that exception. Returns null otherwise. - */ - val completionException: Throwable? - - /** - * Calls the given [block] whenever the progress of this job is updated, - * if [minInterval] milliseconds expired since the last call. - * The first call occurs after at least [minDelay] milliseconds in a likewise manner. - * Repeated invocations of this method result in an [IllegalStateException] - * - * if [asCompletionListener] is true, [onCompleted] is called with the same [block] - */ - fun onProgressUpdate(minDelay: Int, minInterval: Int, asCompletionListener: Boolean = true, block: JobUpdateLister): Job - - /** - * Calls the given [block] when this job completes, with the progress value 1.0. - * Multiple listeners may be registered to this function. - */ - fun onCompleted(block: JobUpdateLister): Job - - /** - * Await completion of this job - */ - suspend fun awaitCompletion() -} - -interface JobScope : JobAndScopeMembersUnion { - /** - * A task should call this frequently during its execution, such that the timer can suspend it when necessary. - */ - suspend fun markSuspensionPoint() - - /** - * A task should call this method to indicate its progress - */ - fun setProgress(progress: Double) - - /** - * Indicate that this job is complete - */ - fun markComplete() = setProgress(1.0) - - /** - * Get a [JobScope] that is responsible for [portion] part of the progress - * If [portion] is negative, the remaining progress is used - */ - fun delegateProgress(portion: Double = -1.0): JobScope -} - -inline fun <T> JobScope.delegateWork(portion: Double = -1.0, block: JobScope.() -> T): T { - delegateProgress(portion).apply { - val result = block() - markComplete() - return result - } -} - -interface JobInternal : Job, JobScope { - /** - * Start or resumes the execution of this job - * and returns true if the job completed - * - * [worktime] is the maximum amount of time, in milliseconds, - * that this job may run for until suspension. - * - * If [worktime] is not positive, the job will complete - * without suspension and this method will always return true. - */ - fun resume(worktime: Long): Boolean -} - -/** - * An object that controls one or more jobs, ensuring that they don't stall the server too much. - * There is a configurable maxiumum amount of milliseconds that can be allocated to all jobs together in each server tick - * This object attempts to split that maximum amount of milliseconds equally between all jobs - */ -class BukkitJobDispatcher(private val plugin: ParcelsPlugin, var options: TickJobtimeOptions) : JobDispatcher { - // The currently registered bukkit scheduler task - private var bukkitTask: BukkitTask? = null - // The jobs. - private val _jobs = LinkedList<JobInternal>() - override val jobs: List<Job> = _jobs - - override fun dispatch(function: JobFunction): Job { - val job: JobInternal = JobImpl(plugin, function) - - if (bukkitTask == null) { - val completed = job.resume(options.jobTime.toLong()) - if (completed) return job - bukkitTask = plugin.scheduleRepeating(0, options.tickInterval) { tickCoroutineJobs() } - } - _jobs.addFirst(job) - return job - } - - private fun tickCoroutineJobs() { - val jobs = _jobs - if (jobs.isEmpty()) return - val tickStartTime = System.currentTimeMillis() - - val iterator = jobs.listIterator(index = 0) - while (iterator.hasNext()) { - val time = System.currentTimeMillis() - val timeElapsed = time - tickStartTime - val timeLeft = options.jobTime - timeElapsed - if (timeLeft <= 0) return - - val count = jobs.size - iterator.nextIndex() - val timePerJob = (timeLeft + count - 1) / count - val job = iterator.next() - val completed = job.resume(timePerJob) - if (completed) { - iterator.remove() - } - } - - if (jobs.isEmpty()) { - bukkitTask?.cancel() - bukkitTask = null - } - } - - override fun completeAllTasks() { - _jobs.forEach { - it.resume(-1) - } - _jobs.clear() - bukkitTask?.cancel() - bukkitTask = null - } - -} - -private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal { - override val coroutine: CoroutineJob = scope.launch(start = LAZY) { task() } - - private var continuation: Continuation<Unit>? = null - private var nextSuspensionTime: Long = 0L - private var completeForcefully = false - private var isStarted = false - - override val elapsedTime - get() = - if (coroutine.isCompleted) startTimeOrElapsedTime - else currentTimeMillis() - startTimeOrElapsedTime - - override val isComplete get() = coroutine.isCompleted - - private var _progress = 0.0 - override val progress get() = _progress - override var completionException: Throwable? = null; private set - - private var startTimeOrElapsedTime: Long = 0L // startTime before completed, elapsed time otherwise - private var onProgressUpdate: JobUpdateLister? = null - private var progressUpdateInterval: Int = 0 - private var lastUpdateTime: Long = 0L - private var onCompleted: JobUpdateLister? = null - - init { - coroutine.invokeOnCompletion { exception -> - // report any error that occurred - completionException = exception?.also { - if (it !is CancellationException) - logger.error("JobFunction generated an exception", it) - } - - // convert to elapsed time here - startTimeOrElapsedTime = System.currentTimeMillis() - startTimeOrElapsedTime - onCompleted?.let { it(1.0, elapsedTime) } - - onCompleted = null - onProgressUpdate = { prog, el -> } - } - } - - override fun onProgressUpdate(minDelay: Int, minInterval: Int, asCompletionListener: Boolean, block: JobUpdateLister): Job { - onProgressUpdate?.let { throw IllegalStateException() } - if (asCompletionListener) onCompleted(block) - if (isComplete) return this - onProgressUpdate = block - progressUpdateInterval = minInterval - lastUpdateTime = System.currentTimeMillis() + minDelay - minInterval - - return this - } - - override fun onCompleted(block: JobUpdateLister): Job { - if (isComplete) { - block(1.0, startTimeOrElapsedTime) - return this - } - - val cur = onCompleted - onCompleted = if (cur == null) { - block - } else { - fun Job.(prog: Double, el: Long) { - cur(prog, el) - block(prog, el) - } - } - return this - } - - override suspend fun markSuspensionPoint() { - if (System.currentTimeMillis() >= nextSuspensionTime && !completeForcefully) - suspendCoroutineUninterceptedOrReturn { cont: Continuation<Unit> -> - continuation = cont - COROUTINE_SUSPENDED - } - } - - override fun setProgress(progress: Double) { - this._progress = progress - val onProgressUpdate = onProgressUpdate ?: return - val time = System.currentTimeMillis() - if (time > lastUpdateTime + progressUpdateInterval) { - onProgressUpdate(progress, elapsedTime) - lastUpdateTime = time - } - } - - override fun resume(worktime: Long): Boolean { - if (isComplete) return true - - if (worktime > 0) { - nextSuspensionTime = currentTimeMillis() + worktime - } else { - completeForcefully = true - } - - if (isStarted) { - continuation?.let { - continuation = null - it.resume(Unit) - return continuation == null - } - return true - } - - isStarted = true - startTimeOrElapsedTime = System.currentTimeMillis() - coroutine.start() - - return continuation == null - } - - override suspend fun awaitCompletion() { - coroutine.join() - } - - private fun delegateProgress(curPortion: Double, portion: Double): JobScope = - DelegateScope(progress, curPortion * (if (portion < 0) 1.0 - progress else portion).clampMin(0.0)) - - override fun delegateProgress(portion: Double): JobScope = delegateProgress(1.0, portion) - - private inner class DelegateScope(val progressStart: Double, val portion: Double) : JobScope { - override val elapsedTime: Long - get() = this@JobImpl.elapsedTime - - override suspend fun markSuspensionPoint() = - this@JobImpl.markSuspensionPoint() - - override val progress: Double - get() = (this@JobImpl.progress - progressStart) / portion - - override fun setProgress(progress: Double) = - this@JobImpl.setProgress(progressStart + progress * portion) - - override fun delegateProgress(portion: Double): JobScope = - this@JobImpl.delegateProgress(this.portion, portion) - } -} +package io.dico.parcels2
+
+import io.dico.parcels2.util.math.clampMin
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart.LAZY
+import kotlinx.coroutines.Job as CoroutineJob
+import kotlinx.coroutines.launch
+import org.bukkit.scheduler.BukkitTask
+import java.lang.System.currentTimeMillis
+import java.util.LinkedList
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
+import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
+import kotlin.coroutines.resume
+
+typealias JobFunction = suspend JobScope.() -> Unit
+typealias JobUpdateLister = Job.(Double, Long) -> Unit
+
+data class TickJobtimeOptions(var jobTime: Int, var tickInterval: Int)
+
+interface JobDispatcher {
+ /**
+ * Submit a [function] that should be run synchronously, but limited such that it does not stall the server
+ */
+ fun dispatch(function: JobFunction): Job
+
+ /**
+ * Get a list of all jobs
+ */
+ val jobs: List<Job>
+
+ /**
+ * Attempts to complete any remaining tasks immediately, without suspension.
+ */
+ fun completeAllTasks()
+}
+
+interface JobAndScopeMembersUnion {
+ /**
+ * The time that elapsed since this job was dispatched, in milliseconds
+ */
+ val elapsedTime: Long
+
+ /**
+ * A value indicating the progress of this job, in the range 0.0 <= progress <= 1.0
+ * with no guarantees to its accuracy.
+ */
+ val progress: Double
+}
+
+interface Job : JobAndScopeMembersUnion {
+ /**
+ * The coroutine associated with this job
+ */
+ val coroutine: CoroutineJob
+
+ /**
+ * true if this job has completed
+ */
+ val isComplete: Boolean
+
+ /**
+ * If an exception was thrown during the execution of this task,
+ * returns that exception. Returns null otherwise.
+ */
+ val completionException: Throwable?
+
+ /**
+ * Calls the given [block] whenever the progress of this job is updated,
+ * if [minInterval] milliseconds expired since the last call.
+ * The first call occurs after at least [minDelay] milliseconds in a likewise manner.
+ * Repeated invocations of this method result in an [IllegalStateException]
+ *
+ * if [asCompletionListener] is true, [onCompleted] is called with the same [block]
+ */
+ fun onProgressUpdate(minDelay: Int, minInterval: Int, asCompletionListener: Boolean = true, block: JobUpdateLister): Job
+
+ /**
+ * Calls the given [block] when this job completes, with the progress value 1.0.
+ * Multiple listeners may be registered to this function.
+ */
+ fun onCompleted(block: JobUpdateLister): Job
+
+ /**
+ * Await completion of this job
+ */
+ suspend fun awaitCompletion()
+}
+
+interface JobScope : JobAndScopeMembersUnion {
+ /**
+ * A task should call this frequently during its execution, such that the timer can suspend it when necessary.
+ */
+ suspend fun markSuspensionPoint()
+
+ /**
+ * A task should call this method to indicate its progress
+ */
+ fun setProgress(progress: Double)
+
+ /**
+ * Indicate that this job is complete
+ */
+ fun markComplete() = setProgress(1.0)
+
+ /**
+ * Get a [JobScope] that is responsible for [portion] part of the progress
+ * If [portion] is negative, the remaining progress is used
+ */
+ fun delegateProgress(portion: Double = -1.0): JobScope
+}
+
+inline fun <T> JobScope.delegateWork(portion: Double = -1.0, block: JobScope.() -> T): T {
+ delegateProgress(portion).apply {
+ val result = block()
+ markComplete()
+ return result
+ }
+}
+
+interface JobInternal : Job, JobScope {
+ /**
+ * Start or resumes the execution of this job
+ * and returns true if the job completed
+ *
+ * [worktime] is the maximum amount of time, in milliseconds,
+ * that this job may run for until suspension.
+ *
+ * If [worktime] is not positive, the job will complete
+ * without suspension and this method will always return true.
+ */
+ fun resume(worktime: Long): Boolean
+}
+
+/**
+ * An object that controls one or more jobs, ensuring that they don't stall the server too much.
+ * There is a configurable maxiumum amount of milliseconds that can be allocated to all jobs together in each server tick
+ * This object attempts to split that maximum amount of milliseconds equally between all jobs
+ */
+class BukkitJobDispatcher(private val plugin: ParcelsPlugin, var options: TickJobtimeOptions) : JobDispatcher {
+ // The currently registered bukkit scheduler task
+ private var bukkitTask: BukkitTask? = null
+ // The jobs.
+ private val _jobs = LinkedList<JobInternal>()
+ override val jobs: List<Job> = _jobs
+
+ override fun dispatch(function: JobFunction): Job {
+ val job: JobInternal = JobImpl(plugin, function)
+
+ if (bukkitTask == null) {
+ val completed = job.resume(options.jobTime.toLong())
+ if (completed) return job
+ bukkitTask = plugin.scheduleRepeating(0, options.tickInterval) { tickCoroutineJobs() }
+ }
+ _jobs.addFirst(job)
+ return job
+ }
+
+ private fun tickCoroutineJobs() {
+ val jobs = _jobs
+ if (jobs.isEmpty()) return
+ val tickStartTime = System.currentTimeMillis()
+
+ val iterator = jobs.listIterator(index = 0)
+ while (iterator.hasNext()) {
+ val time = System.currentTimeMillis()
+ val timeElapsed = time - tickStartTime
+ val timeLeft = options.jobTime - timeElapsed
+ if (timeLeft <= 0) return
+
+ val count = jobs.size - iterator.nextIndex()
+ val timePerJob = (timeLeft + count - 1) / count
+ val job = iterator.next()
+ val completed = job.resume(timePerJob)
+ if (completed) {
+ iterator.remove()
+ }
+ }
+
+ if (jobs.isEmpty()) {
+ bukkitTask?.cancel()
+ bukkitTask = null
+ }
+ }
+
+ override fun completeAllTasks() {
+ _jobs.forEach {
+ it.resume(-1)
+ }
+ _jobs.clear()
+ bukkitTask?.cancel()
+ bukkitTask = null
+ }
+
+}
+
+private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal {
+ override val coroutine: CoroutineJob = scope.launch(start = LAZY) { task() }
+
+ private var continuation: Continuation<Unit>? = null
+ private var nextSuspensionTime: Long = 0L
+ private var completeForcefully = false
+ private var isStarted = false
+
+ override val elapsedTime
+ get() =
+ if (coroutine.isCompleted) startTimeOrElapsedTime
+ else currentTimeMillis() - startTimeOrElapsedTime
+
+ override val isComplete get() = coroutine.isCompleted
+
+ private var _progress = 0.0
+ override val progress get() = _progress
+ override var completionException: Throwable? = null; private set
+
+ private var startTimeOrElapsedTime: Long = 0L // startTime before completed, elapsed time otherwise
+ private var onProgressUpdate: JobUpdateLister? = null
+ private var progressUpdateInterval: Int = 0
+ private var lastUpdateTime: Long = 0L
+ private var onCompleted: JobUpdateLister? = null
+
+ init {
+ coroutine.invokeOnCompletion { exception ->
+ // report any error that occurred
+ completionException = exception?.also {
+ if (it !is CancellationException)
+ logger.error("JobFunction generated an exception", it)
+ }
+
+ // convert to elapsed time here
+ startTimeOrElapsedTime = System.currentTimeMillis() - startTimeOrElapsedTime
+ onCompleted?.let { it(1.0, elapsedTime) }
+
+ onCompleted = null
+ onProgressUpdate = { prog, el -> }
+ }
+ }
+
+ override fun onProgressUpdate(minDelay: Int, minInterval: Int, asCompletionListener: Boolean, block: JobUpdateLister): Job {
+ onProgressUpdate?.let { throw IllegalStateException() }
+ if (asCompletionListener) onCompleted(block)
+ if (isComplete) return this
+ onProgressUpdate = block
+ progressUpdateInterval = minInterval
+ lastUpdateTime = System.currentTimeMillis() + minDelay - minInterval
+
+ return this
+ }
+
+ override fun onCompleted(block: JobUpdateLister): Job {
+ if (isComplete) {
+ block(1.0, startTimeOrElapsedTime)
+ return this
+ }
+
+ val cur = onCompleted
+ onCompleted = if (cur == null) {
+ block
+ } else {
+ fun Job.(prog: Double, el: Long) {
+ cur(prog, el)
+ block(prog, el)
+ }
+ }
+ return this
+ }
+
+ override suspend fun markSuspensionPoint() {
+ if (System.currentTimeMillis() >= nextSuspensionTime && !completeForcefully)
+ suspendCoroutineUninterceptedOrReturn { cont: Continuation<Unit> ->
+ continuation = cont
+ COROUTINE_SUSPENDED
+ }
+ }
+
+ override fun setProgress(progress: Double) {
+ this._progress = progress
+ val onProgressUpdate = onProgressUpdate ?: return
+ val time = System.currentTimeMillis()
+ if (time > lastUpdateTime + progressUpdateInterval) {
+ onProgressUpdate(progress, elapsedTime)
+ lastUpdateTime = time
+ }
+ }
+
+ override fun resume(worktime: Long): Boolean {
+ if (isComplete) return true
+
+ if (worktime > 0) {
+ nextSuspensionTime = currentTimeMillis() + worktime
+ } else {
+ completeForcefully = true
+ }
+
+ if (isStarted) {
+ continuation?.let {
+ continuation = null
+ it.resume(Unit)
+ return continuation == null
+ }
+ return true
+ }
+
+ isStarted = true
+ startTimeOrElapsedTime = System.currentTimeMillis()
+ coroutine.start()
+
+ return continuation == null
+ }
+
+ override suspend fun awaitCompletion() {
+ coroutine.join()
+ }
+
+ private fun delegateProgress(curPortion: Double, portion: Double): JobScope =
+ DelegateScope(progress, curPortion * (if (portion < 0) 1.0 - progress else portion).clampMin(0.0))
+
+ override fun delegateProgress(portion: Double): JobScope = delegateProgress(1.0, portion)
+
+ private inner class DelegateScope(val progressStart: Double, val portion: Double) : JobScope {
+ override val elapsedTime: Long
+ get() = this@JobImpl.elapsedTime
+
+ override suspend fun markSuspensionPoint() =
+ this@JobImpl.markSuspensionPoint()
+
+ override val progress: Double
+ get() = (this@JobImpl.progress - progressStart) / portion
+
+ override fun setProgress(progress: Double) =
+ this@JobImpl.setProgress(progressStart + progress * portion)
+
+ override fun delegateProgress(portion: Double): JobScope =
+ this@JobImpl.delegateProgress(this.portion, portion)
+ }
+}
diff --git a/src/main/kotlin/io/dico/parcels2/Parcel.kt b/src/main/kotlin/io/dico/parcels2/Parcel.kt index 4f89fe0..3527e15 100644 --- a/src/main/kotlin/io/dico/parcels2/Parcel.kt +++ b/src/main/kotlin/io/dico/parcels2/Parcel.kt @@ -1,66 +1,66 @@ -package io.dico.parcels2 - -import io.dico.parcels2.util.math.Vec2i -import io.dico.parcels2.util.math.Vec3i -import org.bukkit.Location -import org.joda.time.DateTime -import java.util.UUID - -/** - * Parcel implementation of ParcelData will update the database when changes are made. - * To change the data without updating the database, defer to the data delegate instance. - * - * This should be used for example in database query callbacks. - * However, this implementation is intentionally not thread-safe. - * Therefore, database query callbacks should schedule their updates using the bukkit scheduler. - */ -interface Parcel : ParcelData, Privileges { - val id: ParcelId - val world: ParcelWorld - val pos: Vec2i - val x: Int - val z: Int - val data: ParcelDataHolder - val infoString: String - val hasBlockVisitors: Boolean - val globalPrivileges: GlobalPrivileges? - - override val keyOfOwner: PlayerProfile.Real? - get() = owner as? PlayerProfile.Real - - fun copyData(newData: ParcelDataHolder, callerIsDatabase: Boolean = false) - - fun dispose() = copyData(ParcelDataHolder()) - - fun updateOwnerSign(force: Boolean = false) - - val homeLocation: Location get() = world.blockManager.getHomeLocation(id) -} - - - -interface ParcelData : RawPrivileges { - var owner: PlayerProfile? - val lastClaimTime: DateTime? - var isOwnerSignOutdated: Boolean - var interactableConfig: InteractableConfiguration - - //fun canBuild(player: OfflinePlayer, checkAdmin: Boolean = true, checkGlobal: Boolean = true): Boolean - - fun isOwner(uuid: UUID): Boolean { - return owner?.uuid == uuid - } - - fun isOwner(profile: PlayerProfile?): Boolean { - return owner == profile - } -} - -class ParcelDataHolder(addedMap: MutablePrivilegeMap = mutableMapOf()) - : ParcelData, PrivilegesHolder(addedMap) { - override var owner: PlayerProfile? = null - override var lastClaimTime: DateTime? = null - override var isOwnerSignOutdated = false - override var interactableConfig: InteractableConfiguration = BitmaskInteractableConfiguration() -} - +package io.dico.parcels2
+
+import io.dico.parcels2.util.math.Vec2i
+import io.dico.parcels2.util.math.Vec3i
+import org.bukkit.Location
+import org.joda.time.DateTime
+import java.util.UUID
+
+/**
+ * Parcel implementation of ParcelData will update the database when changes are made.
+ * To change the data without updating the database, defer to the data delegate instance.
+ *
+ * This should be used for example in database query callbacks.
+ * However, this implementation is intentionally not thread-safe.
+ * Therefore, database query callbacks should schedule their updates using the bukkit scheduler.
+ */
+interface Parcel : ParcelData, Privileges {
+ val id: ParcelId
+ val world: ParcelWorld
+ val pos: Vec2i
+ val x: Int
+ val z: Int
+ val data: ParcelDataHolder
+ val infoString: String
+ val hasBlockVisitors: Boolean
+ val globalPrivileges: GlobalPrivileges?
+
+ override val keyOfOwner: PlayerProfile.Real?
+ get() = owner as? PlayerProfile.Real
+
+ fun copyData(newData: ParcelDataHolder, callerIsDatabase: Boolean = false)
+
+ fun dispose() = copyData(ParcelDataHolder())
+
+ fun updateOwnerSign(force: Boolean = false)
+
+ val homeLocation: Location get() = world.blockManager.getHomeLocation(id)
+}
+
+
+
+interface ParcelData : RawPrivileges {
+ var owner: PlayerProfile?
+ val lastClaimTime: DateTime?
+ var isOwnerSignOutdated: Boolean
+ var interactableConfig: InteractableConfiguration
+
+ //fun canBuild(player: OfflinePlayer, checkAdmin: Boolean = true, checkGlobal: Boolean = true): Boolean
+
+ fun isOwner(uuid: UUID): Boolean {
+ return owner?.uuid == uuid
+ }
+
+ fun isOwner(profile: PlayerProfile?): Boolean {
+ return owner == profile
+ }
+}
+
+class ParcelDataHolder(addedMap: MutablePrivilegeMap = mutableMapOf())
+ : ParcelData, PrivilegesHolder(addedMap) {
+ override var owner: PlayerProfile? = null
+ override var lastClaimTime: DateTime? = null
+ override var isOwnerSignOutdated = false
+ override var interactableConfig: InteractableConfiguration = BitmaskInteractableConfiguration()
+}
+
diff --git a/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt b/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt index 109c5dc..63ec02c 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt @@ -1,103 +1,103 @@ -package io.dico.parcels2 - -import io.dico.parcels2.blockvisitor.RegionTraverser -import io.dico.parcels2.util.math.Region -import io.dico.parcels2.util.math.Vec2i -import io.dico.parcels2.util.math.Vec3i -import io.dico.parcels2.util.math.get -import kotlinx.coroutines.CoroutineScope -import org.bukkit.Chunk -import org.bukkit.Location -import org.bukkit.Material -import org.bukkit.World -import org.bukkit.block.Biome -import org.bukkit.block.Block -import org.bukkit.block.BlockFace -import org.bukkit.entity.Entity -import org.bukkit.generator.BlockPopulator -import org.bukkit.generator.ChunkGenerator -import java.util.Random - -abstract class ParcelGenerator : ChunkGenerator() { - abstract val worldName: String - - 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<BlockPopulator> { - return mutableListOf(object : BlockPopulator() { - override fun populate(world: World?, random: Random?, chunk: Chunk?) { - this@ParcelGenerator.populate(world, random, chunk) - } - }) - } - - abstract fun makeParcelLocatorAndBlockManager( - parcelProvider: ParcelProvider, - container: ParcelContainer, - coroutineScope: CoroutineScope, - jobDispatcher: JobDispatcher - ): Pair<ParcelLocator, ParcelBlockManager> -} - -interface ParcelBlockManager { - val world: World - val jobDispatcher: JobDispatcher - val parcelTraverser: RegionTraverser - - fun getRegionOrigin(parcel: ParcelId) = getRegion(parcel).origin.toVec2i() - - fun getHomeLocation(parcel: ParcelId): Location - - fun getRegion(parcel: ParcelId): Region - - fun getEntities(parcel: ParcelId): Collection<Entity> - - fun isParcelInfoSectionLoaded(parcel: ParcelId): Boolean - - fun updateParcelInfo(parcel: ParcelId, owner: PlayerProfile?) - - fun getParcelForInfoBlockInteraction(block: Vec3i, type: Material, face: BlockFace): Parcel? - - fun setBiome(parcel: ParcelId, biome: Biome): Job? - - fun clearParcel(parcel: ParcelId): Job? - - /** - * Used to update owner blocks in the corner of the parcel - */ - fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i> -} - -inline fun ParcelBlockManager.tryDoBlockOperation( - parcelProvider: ParcelProvider, - parcel: ParcelId, - traverser: RegionTraverser, - crossinline operation: suspend JobScope.(Block) -> Unit -) = parcelProvider.trySubmitBlockVisitor(Permit(), arrayOf(parcel)) { - val region = getRegion(parcel) - val blockCount = region.blockCount.toDouble() - val blocks = traverser.traverseRegion(region) - for ((index, vec) in blocks.withIndex()) { - markSuspensionPoint() - operation(world[vec]) - setProgress((index + 1) / blockCount) - } -} - -abstract class ParcelBlockManagerBase : ParcelBlockManager { - - override fun getEntities(parcel: ParcelId): Collection<Entity> { - val region = getRegion(parcel) - val center = region.center - val centerLoc = Location(world, center.x, center.y, center.z) - val centerDist = (center - region.origin).add(0.2, 0.2, 0.2) - return world.getNearbyEntities(centerLoc, centerDist.x, centerDist.y, centerDist.z) - } - -} +package io.dico.parcels2
+
+import io.dico.parcels2.blockvisitor.RegionTraverser
+import io.dico.parcels2.util.math.Region
+import io.dico.parcels2.util.math.Vec2i
+import io.dico.parcels2.util.math.Vec3i
+import io.dico.parcels2.util.math.get
+import kotlinx.coroutines.CoroutineScope
+import org.bukkit.Chunk
+import org.bukkit.Location
+import org.bukkit.Material
+import org.bukkit.World
+import org.bukkit.block.Biome
+import org.bukkit.block.Block
+import org.bukkit.block.BlockFace
+import org.bukkit.entity.Entity
+import org.bukkit.generator.BlockPopulator
+import org.bukkit.generator.ChunkGenerator
+import java.util.Random
+
+abstract class ParcelGenerator : ChunkGenerator() {
+ abstract val worldName: String
+
+ 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<BlockPopulator> {
+ return mutableListOf(object : BlockPopulator() {
+ override fun populate(world: World?, random: Random?, chunk: Chunk?) {
+ this@ParcelGenerator.populate(world, random, chunk)
+ }
+ })
+ }
+
+ abstract fun makeParcelLocatorAndBlockManager(
+ parcelProvider: ParcelProvider,
+ container: ParcelContainer,
+ coroutineScope: CoroutineScope,
+ jobDispatcher: JobDispatcher
+ ): Pair<ParcelLocator, ParcelBlockManager>
+}
+
+interface ParcelBlockManager {
+ val world: World
+ val jobDispatcher: JobDispatcher
+ val parcelTraverser: RegionTraverser
+
+ fun getRegionOrigin(parcel: ParcelId) = getRegion(parcel).origin.toVec2i()
+
+ fun getHomeLocation(parcel: ParcelId): Location
+
+ fun getRegion(parcel: ParcelId): Region
+
+ fun getEntities(parcel: ParcelId): Collection<Entity>
+
+ fun isParcelInfoSectionLoaded(parcel: ParcelId): Boolean
+
+ fun updateParcelInfo(parcel: ParcelId, owner: PlayerProfile?)
+
+ fun getParcelForInfoBlockInteraction(block: Vec3i, type: Material, face: BlockFace): Parcel?
+
+ fun setBiome(parcel: ParcelId, biome: Biome): Job?
+
+ fun clearParcel(parcel: ParcelId): Job?
+
+ /**
+ * Used to update owner blocks in the corner of the parcel
+ */
+ fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i>
+}
+
+inline fun ParcelBlockManager.tryDoBlockOperation(
+ parcelProvider: ParcelProvider,
+ parcel: ParcelId,
+ traverser: RegionTraverser,
+ crossinline operation: suspend JobScope.(Block) -> Unit
+) = parcelProvider.trySubmitBlockVisitor(Permit(), arrayOf(parcel)) {
+ val region = getRegion(parcel)
+ val blockCount = region.blockCount.toDouble()
+ val blocks = traverser.traverseRegion(region)
+ for ((index, vec) in blocks.withIndex()) {
+ markSuspensionPoint()
+ operation(world[vec])
+ setProgress((index + 1) / blockCount)
+ }
+}
+
+abstract class ParcelBlockManagerBase : ParcelBlockManager {
+
+ override fun getEntities(parcel: ParcelId): Collection<Entity> {
+ val region = getRegion(parcel)
+ val center = region.center
+ val centerLoc = Location(world, center.x, center.y, center.z)
+ val centerDist = (center - region.origin).add(0.2, 0.2, 0.2)
+ return world.getNearbyEntities(centerLoc, centerDist.x, centerDist.y, centerDist.z)
+ }
+
+}
diff --git a/src/main/kotlin/io/dico/parcels2/ParcelId.kt b/src/main/kotlin/io/dico/parcels2/ParcelId.kt index eef7129..77a1835 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelId.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelId.kt @@ -1,56 +1,56 @@ -@file:Suppress("FunctionName") - -package io.dico.parcels2 - -import io.dico.parcels2.util.math.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 == id.name || (uid != null && uid == id.uid) - - val bukkitWorld: World? get() = Bukkit.getWorld(name) ?: uid?.let { Bukkit.getWorld(it) } -} - -fun ParcelWorldId.parcelWorldIdToString() = "ParcelWorld($name)" - -fun ParcelWorldId(worldName: String, worldUid: UUID? = null): ParcelWorldId = ParcelWorldIdImpl(worldName, worldUid) -fun ParcelWorldId(world: World) = ParcelWorldId(world.name, world.uid) - -/** - * 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) - val idString get() = "$x,$z" - fun equals(id: ParcelId): Boolean = x == id.x && z == id.z && worldId.equals(id.worldId) -} - -fun ParcelId.parcelIdToString() = "Parcel(${worldId.name},$idString)" - -fun ParcelId(worldId: ParcelWorldId, pos: Vec2i) = ParcelId(worldId, pos.x, pos.z) -fun ParcelId(worldName: String, worldUid: UUID?, pos: Vec2i) = ParcelId(worldName, worldUid, pos.x, pos.z) -fun ParcelId(worldName: String, worldUid: UUID?, x: Int, z: Int) = ParcelId(ParcelWorldId(worldName, worldUid), x, z) -fun ParcelId(worldId: ParcelWorldId, x: Int, z: Int): ParcelId = ParcelIdImpl(worldId, x, z) - -private class ParcelWorldIdImpl(override val name: String, - override val uid: UUID?) : ParcelWorldId { - override fun toString() = parcelWorldIdToString() -} - -private class ParcelIdImpl(override val worldId: ParcelWorldId, - override val x: Int, - override val z: Int) : ParcelId { - override fun toString() = parcelIdToString() -} +@file:Suppress("FunctionName")
+
+package io.dico.parcels2
+
+import io.dico.parcels2.util.math.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 == id.name || (uid != null && uid == id.uid)
+
+ val bukkitWorld: World? get() = Bukkit.getWorld(name) ?: uid?.let { Bukkit.getWorld(it) }
+}
+
+fun ParcelWorldId.parcelWorldIdToString() = "ParcelWorld($name)"
+
+fun ParcelWorldId(worldName: String, worldUid: UUID? = null): ParcelWorldId = ParcelWorldIdImpl(worldName, worldUid)
+fun ParcelWorldId(world: World) = ParcelWorldId(world.name, world.uid)
+
+/**
+ * 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)
+ val idString get() = "$x,$z"
+ fun equals(id: ParcelId): Boolean = x == id.x && z == id.z && worldId.equals(id.worldId)
+}
+
+fun ParcelId.parcelIdToString() = "Parcel(${worldId.name},$idString)"
+
+fun ParcelId(worldId: ParcelWorldId, pos: Vec2i) = ParcelId(worldId, pos.x, pos.z)
+fun ParcelId(worldName: String, worldUid: UUID?, pos: Vec2i) = ParcelId(worldName, worldUid, pos.x, pos.z)
+fun ParcelId(worldName: String, worldUid: UUID?, x: Int, z: Int) = ParcelId(ParcelWorldId(worldName, worldUid), x, z)
+fun ParcelId(worldId: ParcelWorldId, x: Int, z: Int): ParcelId = ParcelIdImpl(worldId, x, z)
+
+private class ParcelWorldIdImpl(override val name: String,
+ override val uid: UUID?) : ParcelWorldId {
+ override fun toString() = parcelWorldIdToString()
+}
+
+private class ParcelIdImpl(override val worldId: ParcelWorldId,
+ override val x: Int,
+ override val z: Int) : ParcelId {
+ override fun toString() = parcelIdToString()
+}
diff --git a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt index c31b11a..36dfe1c 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt @@ -1,103 +1,103 @@ -package io.dico.parcels2 - -import io.dico.parcels2.options.RuntimeWorldOptions -import io.dico.parcels2.storage.Storage -import io.dico.parcels2.util.math.Vec2i -import io.dico.parcels2.util.math.floor -import org.bukkit.Location -import org.bukkit.World -import org.bukkit.block.Block -import org.bukkit.entity.Entity -import org.joda.time.DateTime -import java.lang.IllegalStateException -import java.util.UUID - -class Permit - -interface ParcelProvider { - val worlds: Map<String, ParcelWorld> - - fun getWorldById(id: ParcelWorldId): ParcelWorld? - - fun getParcelById(id: ParcelId): Parcel? - - fun getWorld(name: String): ParcelWorld? - - fun getWorld(world: World): ParcelWorld? = getWorld(world.name) - - fun getWorld(block: Block): ParcelWorld? = getWorld(block.world) - - fun getWorld(loc: Location): ParcelWorld? = getWorld(loc.world) - - fun getWorld(entity: Entity): ParcelWorld? = getWorld(entity.location) - - 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: 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() - - fun acquireBlockVisitorPermit(parcelId: ParcelId, with: Permit): Boolean - - @Throws(IllegalStateException::class) - fun releaseBlockVisitorPermit(parcelId: ParcelId, with: Permit) - - fun trySubmitBlockVisitor(permit: Permit, parcelIds: Array<out ParcelId>, function: JobFunction): Job? - - fun swapParcels(parcelId1: ParcelId, parcelId2: ParcelId): Job? -} - -interface ParcelLocator { - val world: World - - fun getParcelIdAt(x: Int, z: Int): ParcelId? - - fun getParcelAt(x: Int, z: Int): Parcel? - - fun getParcelAt(vec: Vec2i): Parcel? = getParcelAt(vec.x, vec.z) - - fun getParcelAt(loc: Location): Parcel? = getParcelAt(loc.x.floor(), loc.z.floor()).takeIf { loc.world == world } - - fun getParcelAt(entity: Entity): Parcel? = getParcelAt(entity.location).takeIf { entity.world == world } - - fun getParcelAt(block: Block): Parcel? = getParcelAt(block.x, block.z).takeIf { block.world == world } -} - -typealias ParcelContainerFactory = (ParcelWorld) -> ParcelContainer - -interface ParcelContainer { - - fun getParcelById(x: Int, z: Int): Parcel? - - fun getParcelById(id: Vec2i): Parcel? = getParcelById(id.x, id.z) - - fun getParcelById(id: ParcelId): Parcel? - - fun nextEmptyParcel(): Parcel? - -} - -interface ParcelWorld : ParcelLocator, ParcelContainer { - val id: ParcelWorldId - val name: String - val uid: UUID? - val options: RuntimeWorldOptions - val generator: ParcelGenerator - val storage: Storage - val container: ParcelContainer - val locator: ParcelLocator - val blockManager: ParcelBlockManager - val globalPrivileges: GlobalPrivilegesManager - - val creationTime: DateTime? -} +package io.dico.parcels2
+
+import io.dico.parcels2.options.RuntimeWorldOptions
+import io.dico.parcels2.storage.Storage
+import io.dico.parcels2.util.math.Vec2i
+import io.dico.parcels2.util.math.floor
+import org.bukkit.Location
+import org.bukkit.World
+import org.bukkit.block.Block
+import org.bukkit.entity.Entity
+import org.joda.time.DateTime
+import java.lang.IllegalStateException
+import java.util.UUID
+
+class Permit
+
+interface ParcelProvider {
+ val worlds: Map<String, ParcelWorld>
+
+ fun getWorldById(id: ParcelWorldId): ParcelWorld?
+
+ fun getParcelById(id: ParcelId): Parcel?
+
+ fun getWorld(name: String): ParcelWorld?
+
+ fun getWorld(world: World): ParcelWorld? = getWorld(world.name)
+
+ fun getWorld(block: Block): ParcelWorld? = getWorld(block.world)
+
+ fun getWorld(loc: Location): ParcelWorld? = getWorld(loc.world)
+
+ fun getWorld(entity: Entity): ParcelWorld? = getWorld(entity.location)
+
+ 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: 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()
+
+ fun acquireBlockVisitorPermit(parcelId: ParcelId, with: Permit): Boolean
+
+ @Throws(IllegalStateException::class)
+ fun releaseBlockVisitorPermit(parcelId: ParcelId, with: Permit)
+
+ fun trySubmitBlockVisitor(permit: Permit, parcelIds: Array<out ParcelId>, function: JobFunction): Job?
+
+ fun swapParcels(parcelId1: ParcelId, parcelId2: ParcelId): Job?
+}
+
+interface ParcelLocator {
+ val world: World
+
+ fun getParcelIdAt(x: Int, z: Int): ParcelId?
+
+ fun getParcelAt(x: Int, z: Int): Parcel?
+
+ fun getParcelAt(vec: Vec2i): Parcel? = getParcelAt(vec.x, vec.z)
+
+ fun getParcelAt(loc: Location): Parcel? = getParcelAt(loc.x.floor(), loc.z.floor()).takeIf { loc.world == world }
+
+ fun getParcelAt(entity: Entity): Parcel? = getParcelAt(entity.location).takeIf { entity.world == world }
+
+ fun getParcelAt(block: Block): Parcel? = getParcelAt(block.x, block.z).takeIf { block.world == world }
+}
+
+typealias ParcelContainerFactory = (ParcelWorld) -> ParcelContainer
+
+interface ParcelContainer {
+
+ fun getParcelById(x: Int, z: Int): Parcel?
+
+ fun getParcelById(id: Vec2i): Parcel? = getParcelById(id.x, id.z)
+
+ fun getParcelById(id: ParcelId): Parcel?
+
+ fun nextEmptyParcel(): Parcel?
+
+}
+
+interface ParcelWorld : ParcelLocator, ParcelContainer {
+ val id: ParcelWorldId
+ val name: String
+ val uid: UUID?
+ val options: RuntimeWorldOptions
+ val generator: ParcelGenerator
+ val storage: Storage
+ val container: ParcelContainer
+ val locator: ParcelLocator
+ val blockManager: ParcelBlockManager
+ val globalPrivileges: GlobalPrivilegesManager
+
+ val creationTime: DateTime?
+}
diff --git a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt index a6ebcd8..b2d52a9 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt @@ -1,153 +1,153 @@ -package io.dico.parcels2 - -import io.dico.dicore.Registrator -import io.dico.dicore.command.EOverridePolicy -import io.dico.dicore.command.ICommandDispatcher -import io.dico.parcels2.command.getParcelCommands -import io.dico.parcels2.defaultimpl.GlobalPrivilegesManagerImpl -import io.dico.parcels2.defaultimpl.ParcelProviderImpl -import io.dico.parcels2.listener.ParcelEntityTracker -import io.dico.parcels2.listener.ParcelListeners -import io.dico.parcels2.listener.WorldEditListener -import io.dico.parcels2.options.Options -import io.dico.parcels2.options.optionsMapper -import io.dico.parcels2.storage.Storage -import io.dico.parcels2.util.MainThreadDispatcher -import io.dico.parcels2.util.PluginScheduler -import io.dico.parcels2.util.ext.tryCreate -import io.dico.parcels2.util.isServerThread -import kotlinx.coroutines.CoroutineScope -import org.bukkit.Bukkit -import org.bukkit.generator.ChunkGenerator -import org.bukkit.plugin.Plugin -import org.bukkit.plugin.java.JavaPlugin -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import java.io.File -import kotlin.coroutines.CoroutineContext - -val logger: Logger = LoggerFactory.getLogger("ParcelsPlugin") -private inline val plogger get() = logger - -class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginScheduler { - lateinit var optionsFile: File; private set - lateinit var options: Options; private set - lateinit var parcelProvider: ParcelProvider; private set - lateinit var storage: Storage; private set - lateinit var globalPrivileges: GlobalPrivilegesManager; private set - - val registrator = Registrator(this) - lateinit var entityTracker: ParcelEntityTracker; private set - private var listeners: ParcelListeners? = null - private var cmdDispatcher: ICommandDispatcher? = null - - override val coroutineContext: CoroutineContext = MainThreadDispatcher(this) - override val plugin: Plugin get() = this - val jobDispatcher: JobDispatcher by lazy { BukkitJobDispatcher(this, options.tickJobtime) } - - override fun onEnable() { - plogger.info("Is server thread: ${isServerThread()}") - plogger.info("Debug enabled: ${plogger.isDebugEnabled}") - plogger.debug(System.getProperty("user.dir")) - if (!init()) { - Bukkit.getPluginManager().disablePlugin(this) - } - } - - override fun onDisable() { - val hasWorkers = jobDispatcher.jobs.isNotEmpty() - if (hasWorkers) { - plogger.warn("Parcels is attempting to complete all ${jobDispatcher.jobs.size} remaining jobs before shutdown...") - } - jobDispatcher.completeAllTasks() - if (hasWorkers) { - plogger.info("Parcels has completed the remaining jobs.") - } - - cmdDispatcher?.unregisterFromCommandMap() - } - - private fun init(): Boolean { - optionsFile = File(dataFolder, "options.yml") - options = Options() - parcelProvider = ParcelProviderImpl(this) - - try { - if (!loadOptions()) return false - - try { - storage = options.storage.newInstance() - storage.init() - } catch (ex: Exception) { - plogger.error("Failed to connect to database", ex) - return false - } - - globalPrivileges = GlobalPrivilegesManagerImpl(this) - entityTracker = ParcelEntityTracker(parcelProvider) - } catch (ex: Exception) { - plogger.error("Error loading options", ex) - return false - } - - registerListeners() - registerCommands() - - parcelProvider.loadWorlds() - return true - } - - fun loadOptions(): Boolean { - when { - optionsFile.exists() -> optionsMapper.readerForUpdating(options).readValue<Options>(optionsFile) - else -> run { - options.addWorld("parcels") - if (saveOptions()) { - plogger.warn("Created options file with a world template. Please review it before next start.") - } else { - plogger.error("Failed to save options file ${optionsFile.canonicalPath}") - } - return false - } - } - return true - } - - fun saveOptions(): Boolean { - if (optionsFile.tryCreate()) { - try { - optionsMapper.writeValue(optionsFile, options) - } catch (ex: Throwable) { - optionsFile.delete() - throw ex - } - return true - } - return false - } - - override fun getDefaultWorldGenerator(worldName: String, generatorId: String?): ChunkGenerator? { - return parcelProvider.getWorldGenerator(worldName) - } - - private fun registerCommands() { - cmdDispatcher = getParcelCommands(this).apply { - registerToCommandMap("parcels:", EOverridePolicy.FALLBACK_ONLY) - } - } - - private fun registerListeners() { - if (listeners == null) { - listeners = ParcelListeners(parcelProvider, entityTracker, storage) - registrator.registerListeners(listeners!!) - - val worldEditPlugin = server.pluginManager.getPlugin("WorldEdit") - if (worldEditPlugin != null) { - WorldEditListener.register(this, worldEditPlugin) - } - } - - scheduleRepeating(100, 5, entityTracker::tick) - } - +package io.dico.parcels2
+
+import io.dico.dicore.Registrator
+import io.dico.dicore.command.EOverridePolicy
+import io.dico.dicore.command.ICommandDispatcher
+import io.dico.parcels2.command.getParcelCommands
+import io.dico.parcels2.defaultimpl.GlobalPrivilegesManagerImpl
+import io.dico.parcels2.defaultimpl.ParcelProviderImpl
+import io.dico.parcels2.listener.ParcelEntityTracker
+import io.dico.parcels2.listener.ParcelListeners
+import io.dico.parcels2.listener.WorldEditListener
+import io.dico.parcels2.options.Options
+import io.dico.parcels2.options.optionsMapper
+import io.dico.parcels2.storage.Storage
+import io.dico.parcels2.util.MainThreadDispatcher
+import io.dico.parcels2.util.PluginScheduler
+import io.dico.parcels2.util.ext.tryCreate
+import io.dico.parcels2.util.isServerThread
+import kotlinx.coroutines.CoroutineScope
+import org.bukkit.Bukkit
+import org.bukkit.generator.ChunkGenerator
+import org.bukkit.plugin.Plugin
+import org.bukkit.plugin.java.JavaPlugin
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import java.io.File
+import kotlin.coroutines.CoroutineContext
+
+val logger: Logger = LoggerFactory.getLogger("ParcelsPlugin")
+private inline val plogger get() = logger
+
+class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginScheduler {
+ lateinit var optionsFile: File; private set
+ lateinit var options: Options; private set
+ lateinit var parcelProvider: ParcelProvider; private set
+ lateinit var storage: Storage; private set
+ lateinit var globalPrivileges: GlobalPrivilegesManager; private set
+
+ val registrator = Registrator(this)
+ lateinit var entityTracker: ParcelEntityTracker; private set
+ private var listeners: ParcelListeners? = null
+ private var cmdDispatcher: ICommandDispatcher? = null
+
+ override val coroutineContext: CoroutineContext = MainThreadDispatcher(this)
+ override val plugin: Plugin get() = this
+ val jobDispatcher: JobDispatcher by lazy { BukkitJobDispatcher(this, options.tickJobtime) }
+
+ override fun onEnable() {
+ plogger.info("Is server thread: ${isServerThread()}")
+ plogger.info("Debug enabled: ${plogger.isDebugEnabled}")
+ plogger.debug(System.getProperty("user.dir"))
+ if (!init()) {
+ Bukkit.getPluginManager().disablePlugin(this)
+ }
+ }
+
+ override fun onDisable() {
+ val hasWorkers = jobDispatcher.jobs.isNotEmpty()
+ if (hasWorkers) {
+ plogger.warn("Parcels is attempting to complete all ${jobDispatcher.jobs.size} remaining jobs before shutdown...")
+ }
+ jobDispatcher.completeAllTasks()
+ if (hasWorkers) {
+ plogger.info("Parcels has completed the remaining jobs.")
+ }
+
+ cmdDispatcher?.unregisterFromCommandMap()
+ }
+
+ private fun init(): Boolean {
+ optionsFile = File(dataFolder, "options.yml")
+ options = Options()
+ parcelProvider = ParcelProviderImpl(this)
+
+ try {
+ if (!loadOptions()) return false
+
+ try {
+ storage = options.storage.newInstance()
+ storage.init()
+ } catch (ex: Exception) {
+ plogger.error("Failed to connect to database", ex)
+ return false
+ }
+
+ globalPrivileges = GlobalPrivilegesManagerImpl(this)
+ entityTracker = ParcelEntityTracker(parcelProvider)
+ } catch (ex: Exception) {
+ plogger.error("Error loading options", ex)
+ return false
+ }
+
+ registerListeners()
+ registerCommands()
+
+ parcelProvider.loadWorlds()
+ return true
+ }
+
+ fun loadOptions(): Boolean {
+ when {
+ optionsFile.exists() -> optionsMapper.readerForUpdating(options).readValue<Options>(optionsFile)
+ else -> run {
+ options.addWorld("parcels")
+ if (saveOptions()) {
+ plogger.warn("Created options file with a world template. Please review it before next start.")
+ } else {
+ plogger.error("Failed to save options file ${optionsFile.canonicalPath}")
+ }
+ return false
+ }
+ }
+ return true
+ }
+
+ fun saveOptions(): Boolean {
+ if (optionsFile.tryCreate()) {
+ try {
+ optionsMapper.writeValue(optionsFile, options)
+ } catch (ex: Throwable) {
+ optionsFile.delete()
+ throw ex
+ }
+ return true
+ }
+ return false
+ }
+
+ override fun getDefaultWorldGenerator(worldName: String, generatorId: String?): ChunkGenerator? {
+ return parcelProvider.getWorldGenerator(worldName)
+ }
+
+ private fun registerCommands() {
+ cmdDispatcher = getParcelCommands(this).apply {
+ registerToCommandMap("parcels:", EOverridePolicy.FALLBACK_ONLY)
+ }
+ }
+
+ private fun registerListeners() {
+ if (listeners == null) {
+ listeners = ParcelListeners(parcelProvider, entityTracker, storage)
+ registrator.registerListeners(listeners!!)
+
+ val worldEditPlugin = server.pluginManager.getPlugin("WorldEdit")
+ if (worldEditPlugin != null) {
+ WorldEditListener.register(this, worldEditPlugin)
+ }
+ }
+
+ scheduleRepeating(100, 5, entityTracker::tick)
+ }
+
}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/PlayerProfile.kt b/src/main/kotlin/io/dico/parcels2/PlayerProfile.kt index e3e0f55..6c30c27 100644 --- a/src/main/kotlin/io/dico/parcels2/PlayerProfile.kt +++ b/src/main/kotlin/io/dico/parcels2/PlayerProfile.kt @@ -1,184 +1,184 @@ -@file:Suppress("unused", "UsePropertyAccessSyntax", "DEPRECATION") - -package io.dico.parcels2 - -import io.dico.parcels2.storage.Storage -import io.dico.parcels2.util.ext.PLAYER_NAME_PLACEHOLDER -import io.dico.parcels2.util.ext.isValid -import io.dico.parcels2.util.ext.uuid -import io.dico.parcels2.util.getOfflinePlayer -import io.dico.parcels2.util.getPlayerName -import org.bukkit.Bukkit -import org.bukkit.OfflinePlayer -import java.util.UUID - -interface PlayerProfile { - val uuid: UUID? get() = null - val name: String? - val nameOrBukkitName: String? - val notNullName: String - val isStar: Boolean get() = this is Star - val exists: Boolean get() = this is RealImpl - - fun matches(player: OfflinePlayer, allowNameMatch: Boolean = false): Boolean - - fun equals(other: PlayerProfile): Boolean - - override fun equals(other: Any?): Boolean - override fun hashCode(): Int - - val isFake: Boolean get() = this is Fake - val isReal: Boolean get() = this is Real - - companion object { - fun safe(uuid: UUID?, name: String?): PlayerProfile? { - if (uuid != null) return Real(uuid, name) - if (name != null) return invoke(name) - return null - } - - operator fun invoke(uuid: UUID?, name: String?): PlayerProfile { - return safe(uuid, name) ?: throw IllegalArgumentException("One of uuid and name must not be null") - } - - operator fun invoke(uuid: UUID): Real { - if (uuid == Star.uuid) return Star - return RealImpl(uuid, null) - } - - operator fun invoke(name: String): PlayerProfile { - if (name == Star.name) return Star - return Fake(name) - } - - operator fun invoke(player: OfflinePlayer): PlayerProfile { - return if (player.isValid) Real(player.uuid, player.name) else Fake(player.name) - } - - fun nameless(player: OfflinePlayer): Real { - if (!player.isValid) throw IllegalArgumentException("The given OfflinePlayer is not valid") - return RealImpl(player.uuid, null) - } - - fun byName(input: String, allowReal: Boolean = true, allowFake: Boolean = false): PlayerProfile { - if (!allowReal) { - if (!allowFake) throw IllegalArgumentException("at least one of allowReal and allowFake must be true") - return Fake(input) - } - - if (input == Star.name) return Star - - return getOfflinePlayer(input)?.let { PlayerProfile(it) } ?: Unresolved(input) - } - } - - interface Real : PlayerProfile { - override val uuid: UUID - override val nameOrBukkitName: String? - // If a player is online, their name is prioritized to get name changes right immediately - get() = Bukkit.getPlayer(uuid)?.name ?: name ?: getPlayerName(uuid) - override val notNullName: String - get() = nameOrBukkitName ?: PLAYER_NAME_PLACEHOLDER - - val player: OfflinePlayer? get() = getOfflinePlayer(uuid) - - override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean { - return uuid == player.uuid || (allowNameMatch && name?.let { it == player.name } == true) - } - - override fun equals(other: PlayerProfile): Boolean { - return other is Real && uuid == other.uuid - } - - companion object { - fun byName(name: String): PlayerProfile { - if (name == Star.name) return Star - return Unresolved(name) - } - - operator fun invoke(uuid: UUID, name: String?): Real { - if (name == Star.name || uuid == Star.uuid) return Star - return RealImpl(uuid, name) - } - - fun safe(uuid: UUID?, name: String?): Real? { - if (name == Star.name || uuid == Star.uuid) return Star - if (uuid == null) return null - return RealImpl(uuid, name) - } - - } - } - - object Star : BaseImpl(), Real { - override val name get() = "*" - override val nameOrBukkitName get() = name - override val notNullName get() = name - - // hopefully nobody will have this random UUID :) - override val uuid: UUID = UUID.fromString("7d09c4c6-117d-4f36-9778-c4d24618cee1") - - override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean { - return true - } - - override fun toString() = "Star" - } - - abstract class NameOnly(override val name: String) : BaseImpl() { - override val notNullName get() = name - override val nameOrBukkitName: String get() = name - - override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean { - return allowNameMatch && player.name == name - } - - override fun toString() = "${javaClass.simpleName}($name)" - } - - class Fake(name: String) : NameOnly(name) { - override fun equals(other: PlayerProfile): Boolean { - return other is Fake && other.name == name - } - } - - class Unresolved(name: String) : NameOnly(name) { - override fun equals(other: PlayerProfile): Boolean { - return other is Unresolved && name == other.name - } - - suspend fun tryResolveSuspendedly(storage: Storage): Real? { - return storage.getPlayerUuidForName(name).await()?.let { resolve(it) } - } - - fun resolve(uuid: UUID): Real { - return RealImpl(uuid, name) - } - - fun throwException(): Nothing { - throw IllegalArgumentException("A UUID for the player $name can not be found") - } - } - - abstract class BaseImpl : PlayerProfile { - override fun equals(other: Any?): Boolean { - return this === other || (other is PlayerProfile && equals(other)) - } - - override fun hashCode(): Int { - return uuid?.hashCode() ?: name!!.hashCode() - } - } - - private class RealImpl(override val uuid: UUID, override val name: String?) : BaseImpl(), Real { - override fun toString() = "Real($notNullName)" - } - -} - -suspend fun PlayerProfile.resolved(storage: Storage, resolveToFake: Boolean = false): PlayerProfile? = - when (this) { - is PlayerProfile.Unresolved -> tryResolveSuspendedly(storage) - ?: if (resolveToFake) PlayerProfile.Fake(name) else null - else -> this +@file:Suppress("unused", "UsePropertyAccessSyntax", "DEPRECATION")
+
+package io.dico.parcels2
+
+import io.dico.parcels2.storage.Storage
+import io.dico.parcels2.util.ext.PLAYER_NAME_PLACEHOLDER
+import io.dico.parcels2.util.ext.isValid
+import io.dico.parcels2.util.ext.uuid
+import io.dico.parcels2.util.getOfflinePlayer
+import io.dico.parcels2.util.getPlayerName
+import org.bukkit.Bukkit
+import org.bukkit.OfflinePlayer
+import java.util.UUID
+
+interface PlayerProfile {
+ val uuid: UUID? get() = null
+ val name: String?
+ val nameOrBukkitName: String?
+ val notNullName: String
+ val isStar: Boolean get() = this is Star
+ val exists: Boolean get() = this is RealImpl
+
+ fun matches(player: OfflinePlayer, allowNameMatch: Boolean = false): Boolean
+
+ fun equals(other: PlayerProfile): Boolean
+
+ override fun equals(other: Any?): Boolean
+ override fun hashCode(): Int
+
+ val isFake: Boolean get() = this is Fake
+ val isReal: Boolean get() = this is Real
+
+ companion object {
+ fun safe(uuid: UUID?, name: String?): PlayerProfile? {
+ if (uuid != null) return Real(uuid, name)
+ if (name != null) return invoke(name)
+ return null
+ }
+
+ operator fun invoke(uuid: UUID?, name: String?): PlayerProfile {
+ return safe(uuid, name) ?: throw IllegalArgumentException("One of uuid and name must not be null")
+ }
+
+ operator fun invoke(uuid: UUID): Real {
+ if (uuid == Star.uuid) return Star
+ return RealImpl(uuid, null)
+ }
+
+ operator fun invoke(name: String): PlayerProfile {
+ if (name == Star.name) return Star
+ return Fake(name)
+ }
+
+ operator fun invoke(player: OfflinePlayer): PlayerProfile {
+ return if (player.isValid) Real(player.uuid, player.name) else Fake(player.name)
+ }
+
+ fun nameless(player: OfflinePlayer): Real {
+ if (!player.isValid) throw IllegalArgumentException("The given OfflinePlayer is not valid")
+ return RealImpl(player.uuid, null)
+ }
+
+ fun byName(input: String, allowReal: Boolean = true, allowFake: Boolean = false): PlayerProfile {
+ if (!allowReal) {
+ if (!allowFake) throw IllegalArgumentException("at least one of allowReal and allowFake must be true")
+ return Fake(input)
+ }
+
+ if (input == Star.name) return Star
+
+ return getOfflinePlayer(input)?.let { PlayerProfile(it) } ?: Unresolved(input)
+ }
+ }
+
+ interface Real : PlayerProfile {
+ override val uuid: UUID
+ override val nameOrBukkitName: String?
+ // If a player is online, their name is prioritized to get name changes right immediately
+ get() = Bukkit.getPlayer(uuid)?.name ?: name ?: getPlayerName(uuid)
+ override val notNullName: String
+ get() = nameOrBukkitName ?: PLAYER_NAME_PLACEHOLDER
+
+ val player: OfflinePlayer? get() = getOfflinePlayer(uuid)
+
+ override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean {
+ return uuid == player.uuid || (allowNameMatch && name?.let { it == player.name } == true)
+ }
+
+ override fun equals(other: PlayerProfile): Boolean {
+ return other is Real && uuid == other.uuid
+ }
+
+ companion object {
+ fun byName(name: String): PlayerProfile {
+ if (name == Star.name) return Star
+ return Unresolved(name)
+ }
+
+ operator fun invoke(uuid: UUID, name: String?): Real {
+ if (name == Star.name || uuid == Star.uuid) return Star
+ return RealImpl(uuid, name)
+ }
+
+ fun safe(uuid: UUID?, name: String?): Real? {
+ if (name == Star.name || uuid == Star.uuid) return Star
+ if (uuid == null) return null
+ return RealImpl(uuid, name)
+ }
+
+ }
+ }
+
+ object Star : BaseImpl(), Real {
+ override val name get() = "*"
+ override val nameOrBukkitName get() = name
+ override val notNullName get() = name
+
+ // hopefully nobody will have this random UUID :)
+ override val uuid: UUID = UUID.fromString("7d09c4c6-117d-4f36-9778-c4d24618cee1")
+
+ override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean {
+ return true
+ }
+
+ override fun toString() = "Star"
+ }
+
+ abstract class NameOnly(override val name: String) : BaseImpl() {
+ override val notNullName get() = name
+ override val nameOrBukkitName: String get() = name
+
+ override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean {
+ return allowNameMatch && player.name == name
+ }
+
+ override fun toString() = "${javaClass.simpleName}($name)"
+ }
+
+ class Fake(name: String) : NameOnly(name) {
+ override fun equals(other: PlayerProfile): Boolean {
+ return other is Fake && other.name == name
+ }
+ }
+
+ class Unresolved(name: String) : NameOnly(name) {
+ override fun equals(other: PlayerProfile): Boolean {
+ return other is Unresolved && name == other.name
+ }
+
+ suspend fun tryResolveSuspendedly(storage: Storage): Real? {
+ return storage.getPlayerUuidForName(name).await()?.let { resolve(it) }
+ }
+
+ fun resolve(uuid: UUID): Real {
+ return RealImpl(uuid, name)
+ }
+
+ fun throwException(): Nothing {
+ throw IllegalArgumentException("A UUID for the player $name can not be found")
+ }
+ }
+
+ abstract class BaseImpl : PlayerProfile {
+ override fun equals(other: Any?): Boolean {
+ return this === other || (other is PlayerProfile && equals(other))
+ }
+
+ override fun hashCode(): Int {
+ return uuid?.hashCode() ?: name!!.hashCode()
+ }
+ }
+
+ private class RealImpl(override val uuid: UUID, override val name: String?) : BaseImpl(), Real {
+ override fun toString() = "Real($notNullName)"
+ }
+
+}
+
+suspend fun PlayerProfile.resolved(storage: Storage, resolveToFake: Boolean = false): PlayerProfile? =
+ when (this) {
+ is PlayerProfile.Unresolved -> tryResolveSuspendedly(storage)
+ ?: if (resolveToFake) PlayerProfile.Fake(name) else null
+ else -> this
}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/Privilege.kt b/src/main/kotlin/io/dico/parcels2/Privilege.kt index 135d7c1..892ed4c 100644 --- a/src/main/kotlin/io/dico/parcels2/Privilege.kt +++ b/src/main/kotlin/io/dico/parcels2/Privilege.kt @@ -1,125 +1,125 @@ -package io.dico.parcels2 - -import io.dico.parcels2.Privilege.DEFAULT -import java.util.Collections - -typealias PrivilegeKey = PlayerProfile.Real -typealias PrivilegeMap = Map<PrivilegeKey, Privilege> -typealias MutablePrivilegeMap = MutableMap<PrivilegeKey, Privilege> - -@Suppress("FunctionName") -fun MutablePrivilegeMap(): MutablePrivilegeMap = hashMapOf() - -@Suppress("UNCHECKED_CAST") -val EmptyPrivilegeMap = Collections.emptyMap<Any, Any>() as MutablePrivilegeMap - -enum class Privilege( - val number: Int, - val transient: Boolean = false -) { - BANNED(1), - DEFAULT(2), - CAN_BUILD(3), - CAN_MANAGE(4), - - OWNER(-1, transient = true), - ADMIN(-1, transient = true); - - fun implies(other: Privilege): Boolean = - when { - other > DEFAULT -> this >= other - other == DEFAULT -> this == other - else -> this <= other - } - - fun isChangeInDirection(positiveDirection: Boolean, update: Privilege): Boolean = - if (positiveDirection) update > this - else update < this - - fun requireNonTransient(): Privilege { - if (transient) { - throw IllegalArgumentException("Transient privilege $this is invalid") - } - return this - } - - companion object { - fun getByNumber(id: Int) = - when (id) { - 1 -> BANNED - 2 -> DEFAULT - 3 -> CAN_BUILD - 4 -> CAN_MANAGE - else -> null - } - } -} - -interface RawPrivileges { - val privilegeMap: PrivilegeMap - var privilegeOfStar: Privilege - - fun getRawStoredPrivilege(key: PrivilegeKey): Privilege - fun setRawStoredPrivilege(key: PrivilegeKey, privilege: Privilege): Boolean - fun hasAnyDeclaredPrivileges(): Boolean -} - -open class PrivilegesHolder(override var privilegeMap: MutablePrivilegeMap = EmptyPrivilegeMap) : RawPrivileges { - private var _privilegeOfStar: Privilege = DEFAULT - - override /*open*/ var privilegeOfStar: Privilege - get() = _privilegeOfStar - set(value) = run { _privilegeOfStar = value } - - protected val isEmpty - inline get() = privilegeMap === EmptyPrivilegeMap - - override fun getRawStoredPrivilege(key: PrivilegeKey) = - if (key.isStar) _privilegeOfStar - else privilegeMap.getOrDefault(key, _privilegeOfStar) - - override fun setRawStoredPrivilege(key: PrivilegeKey, privilege: Privilege): Boolean { - privilege.requireNonTransient() - - if (key.isStar) { - if (_privilegeOfStar == privilege) return false - _privilegeOfStar = privilege - return true - } - - if (isEmpty) { - if (privilege == DEFAULT) return false - privilegeMap = MutablePrivilegeMap() - } - - return if (privilege == DEFAULT) privilegeMap.remove(key) != null - else privilegeMap.put(key, privilege) != privilege - } - - override fun hasAnyDeclaredPrivileges(): Boolean { - return privilegeMap.isNotEmpty() || privilegeOfStar != DEFAULT - } - - fun copyPrivilegesFrom(other: PrivilegesHolder) { - privilegeMap = other.privilegeMap - privilegeOfStar = other.privilegeOfStar - } - -} - -private fun <K, V> MutableMap<K, V>.put(key: K, value: V, override: Boolean) { - if (override) this[key] = value - else putIfAbsent(key, value) -} - -fun RawPrivileges.filterProfilesWithPrivilegeTo(map: MutableMap<PrivilegeKey, Privilege>, privilege: Privilege) { - if (privilegeOfStar.implies(privilege)) { - map.putIfAbsent(PlayerProfile.Star, privilegeOfStar) - } - - for ((profile, declaredPrivilege) in privilegeMap) { - if (declaredPrivilege.implies(privilege)) { - map.putIfAbsent(profile, declaredPrivilege) - } - } -} +package io.dico.parcels2
+
+import io.dico.parcels2.Privilege.DEFAULT
+import java.util.Collections
+
+typealias PrivilegeKey = PlayerProfile.Real
+typealias PrivilegeMap = Map<PrivilegeKey, Privilege>
+typealias MutablePrivilegeMap = MutableMap<PrivilegeKey, Privilege>
+
+@Suppress("FunctionName")
+fun MutablePrivilegeMap(): MutablePrivilegeMap = hashMapOf()
+
+@Suppress("UNCHECKED_CAST")
+val EmptyPrivilegeMap = Collections.emptyMap<Any, Any>() as MutablePrivilegeMap
+
+enum class Privilege(
+ val number: Int,
+ val transient: Boolean = false
+) {
+ BANNED(1),
+ DEFAULT(2),
+ CAN_BUILD(3),
+ CAN_MANAGE(4),
+
+ OWNER(-1, transient = true),
+ ADMIN(-1, transient = true);
+
+ fun implies(other: Privilege): Boolean =
+ when {
+ other > DEFAULT -> this >= other
+ other == DEFAULT -> this == other
+ else -> this <= other
+ }
+
+ fun isChangeInDirection(positiveDirection: Boolean, update: Privilege): Boolean =
+ if (positiveDirection) update > this
+ else update < this
+
+ fun requireNonTransient(): Privilege {
+ if (transient) {
+ throw IllegalArgumentException("Transient privilege $this is invalid")
+ }
+ return this
+ }
+
+ companion object {
+ fun getByNumber(id: Int) =
+ when (id) {
+ 1 -> BANNED
+ 2 -> DEFAULT
+ 3 -> CAN_BUILD
+ 4 -> CAN_MANAGE
+ else -> null
+ }
+ }
+}
+
+interface RawPrivileges {
+ val privilegeMap: PrivilegeMap
+ var privilegeOfStar: Privilege
+
+ fun getRawStoredPrivilege(key: PrivilegeKey): Privilege
+ fun setRawStoredPrivilege(key: PrivilegeKey, privilege: Privilege): Boolean
+ fun hasAnyDeclaredPrivileges(): Boolean
+}
+
+open class PrivilegesHolder(override var privilegeMap: MutablePrivilegeMap = EmptyPrivilegeMap) : RawPrivileges {
+ private var _privilegeOfStar: Privilege = DEFAULT
+
+ override /*open*/ var privilegeOfStar: Privilege
+ get() = _privilegeOfStar
+ set(value) = run { _privilegeOfStar = value }
+
+ protected val isEmpty
+ inline get() = privilegeMap === EmptyPrivilegeMap
+
+ override fun getRawStoredPrivilege(key: PrivilegeKey) =
+ if (key.isStar) _privilegeOfStar
+ else privilegeMap.getOrDefault(key, _privilegeOfStar)
+
+ override fun setRawStoredPrivilege(key: PrivilegeKey, privilege: Privilege): Boolean {
+ privilege.requireNonTransient()
+
+ if (key.isStar) {
+ if (_privilegeOfStar == privilege) return false
+ _privilegeOfStar = privilege
+ return true
+ }
+
+ if (isEmpty) {
+ if (privilege == DEFAULT) return false
+ privilegeMap = MutablePrivilegeMap()
+ }
+
+ return if (privilege == DEFAULT) privilegeMap.remove(key) != null
+ else privilegeMap.put(key, privilege) != privilege
+ }
+
+ override fun hasAnyDeclaredPrivileges(): Boolean {
+ return privilegeMap.isNotEmpty() || privilegeOfStar != DEFAULT
+ }
+
+ fun copyPrivilegesFrom(other: PrivilegesHolder) {
+ privilegeMap = other.privilegeMap
+ privilegeOfStar = other.privilegeOfStar
+ }
+
+}
+
+private fun <K, V> MutableMap<K, V>.put(key: K, value: V, override: Boolean) {
+ if (override) this[key] = value
+ else putIfAbsent(key, value)
+}
+
+fun RawPrivileges.filterProfilesWithPrivilegeTo(map: MutableMap<PrivilegeKey, Privilege>, privilege: Privilege) {
+ if (privilegeOfStar.implies(privilege)) {
+ map.putIfAbsent(PlayerProfile.Star, privilegeOfStar)
+ }
+
+ for ((profile, declaredPrivilege) in privilegeMap) {
+ if (declaredPrivilege.implies(privilege)) {
+ map.putIfAbsent(profile, declaredPrivilege)
+ }
+ }
+}
diff --git a/src/main/kotlin/io/dico/parcels2/Privileges.kt b/src/main/kotlin/io/dico/parcels2/Privileges.kt index 632f1a4..6cdd6d0 100644 --- a/src/main/kotlin/io/dico/parcels2/Privileges.kt +++ b/src/main/kotlin/io/dico/parcels2/Privileges.kt @@ -1,64 +1,64 @@ -package io.dico.parcels2 - -import io.dico.parcels2.PrivilegeChangeResult.* -import io.dico.parcels2.util.ext.PERM_ADMIN_MANAGE -import io.dico.parcels2.util.ext.PERM_BAN_BYPASS -import io.dico.parcels2.util.ext.PERM_BUILD_ANYWHERE -import org.bukkit.OfflinePlayer -import org.bukkit.entity.Player - -interface Privileges : RawPrivileges { - val keyOfOwner: PlayerProfile.Real? - - fun getStoredPrivilege(key: PrivilegeKey): Privilege { - return if (key == keyOfOwner) Privilege.OWNER - else getRawStoredPrivilege(key) - } - - override var privilegeOfStar: Privilege - get() = getStoredPrivilege(PlayerProfile.Star) - set(value) { - setRawStoredPrivilege(PlayerProfile.Star, value) - } -} - -val OfflinePlayer.privilegeKey: PrivilegeKey - inline get() = PlayerProfile.nameless(this) - -fun Privileges.getEffectivePrivilege(player: OfflinePlayer, adminPerm: String): Privilege = - if (player is Player && player.hasPermission(adminPerm)) Privilege.ADMIN - else getStoredPrivilege(player.privilegeKey) - -fun Privileges.canManage(player: OfflinePlayer) = getEffectivePrivilege(player, PERM_ADMIN_MANAGE) >= Privilege.CAN_MANAGE -fun Privileges.canManageFast(player: OfflinePlayer) = getStoredPrivilege(player.privilegeKey) >= Privilege.CAN_MANAGE -fun Privileges.canBuild(player: OfflinePlayer) = getEffectivePrivilege(player, PERM_BUILD_ANYWHERE) >= Privilege.CAN_BUILD -fun Privileges.canBuildFast(player: OfflinePlayer) = getStoredPrivilege(player.privilegeKey) >= Privilege.CAN_BUILD -fun Privileges.canEnter(player: OfflinePlayer) = getEffectivePrivilege(player, PERM_BAN_BYPASS) >= Privilege.DEFAULT -fun Privileges.canEnterFast(player: OfflinePlayer) = getStoredPrivilege(player.privilegeKey) >= Privilege.DEFAULT - -enum class PrivilegeChangeResult { - SUCCESS, FAIL, FAIL_OWNER -} - -fun Privileges.changePrivilege(key: PrivilegeKey, positive: Boolean, update: Privilege): PrivilegeChangeResult = - when { - key == keyOfOwner -> FAIL_OWNER - getRawStoredPrivilege(key).isChangeInDirection(positive, update) && setRawStoredPrivilege(key, update) -> SUCCESS - else -> FAIL - } - -fun Privileges.allowManage(key: PrivilegeKey) = changePrivilege(key, true, Privilege.CAN_MANAGE) -fun Privileges.disallowManage(key: PrivilegeKey) = changePrivilege(key, false, Privilege.CAN_BUILD) -fun Privileges.allowBuild(key: PrivilegeKey) = changePrivilege(key, true, Privilege.CAN_BUILD) -fun Privileges.disallowBuild(key: PrivilegeKey) = changePrivilege(key, false, Privilege.DEFAULT) -fun Privileges.allowEnter(key: PrivilegeKey) = changePrivilege(key, true, Privilege.DEFAULT) -fun Privileges.disallowEnter(key: PrivilegeKey) = changePrivilege(key, false, Privilege.BANNED) - -interface GlobalPrivileges : RawPrivileges, Privileges { - override val keyOfOwner: PlayerProfile.Real -} - -interface GlobalPrivilegesManager { - operator fun get(owner: PlayerProfile.Real): GlobalPrivileges - operator fun get(owner: OfflinePlayer): GlobalPrivileges = get(owner.privilegeKey) -} +package io.dico.parcels2
+
+import io.dico.parcels2.PrivilegeChangeResult.*
+import io.dico.parcels2.util.ext.PERM_ADMIN_MANAGE
+import io.dico.parcels2.util.ext.PERM_BAN_BYPASS
+import io.dico.parcels2.util.ext.PERM_BUILD_ANYWHERE
+import org.bukkit.OfflinePlayer
+import org.bukkit.entity.Player
+
+interface Privileges : RawPrivileges {
+ val keyOfOwner: PlayerProfile.Real?
+
+ fun getStoredPrivilege(key: PrivilegeKey): Privilege {
+ return if (key == keyOfOwner) Privilege.OWNER
+ else getRawStoredPrivilege(key)
+ }
+
+ override var privilegeOfStar: Privilege
+ get() = getStoredPrivilege(PlayerProfile.Star)
+ set(value) {
+ setRawStoredPrivilege(PlayerProfile.Star, value)
+ }
+}
+
+val OfflinePlayer.privilegeKey: PrivilegeKey
+ inline get() = PlayerProfile.nameless(this)
+
+fun Privileges.getEffectivePrivilege(player: OfflinePlayer, adminPerm: String): Privilege =
+ if (player is Player && player.hasPermission(adminPerm)) Privilege.ADMIN
+ else getStoredPrivilege(player.privilegeKey)
+
+fun Privileges.canManage(player: OfflinePlayer) = getEffectivePrivilege(player, PERM_ADMIN_MANAGE) >= Privilege.CAN_MANAGE
+fun Privileges.canManageFast(player: OfflinePlayer) = getStoredPrivilege(player.privilegeKey) >= Privilege.CAN_MANAGE
+fun Privileges.canBuild(player: OfflinePlayer) = getEffectivePrivilege(player, PERM_BUILD_ANYWHERE) >= Privilege.CAN_BUILD
+fun Privileges.canBuildFast(player: OfflinePlayer) = getStoredPrivilege(player.privilegeKey) >= Privilege.CAN_BUILD
+fun Privileges.canEnter(player: OfflinePlayer) = getEffectivePrivilege(player, PERM_BAN_BYPASS) >= Privilege.DEFAULT
+fun Privileges.canEnterFast(player: OfflinePlayer) = getStoredPrivilege(player.privilegeKey) >= Privilege.DEFAULT
+
+enum class PrivilegeChangeResult {
+ SUCCESS, FAIL, FAIL_OWNER
+}
+
+fun Privileges.changePrivilege(key: PrivilegeKey, positive: Boolean, update: Privilege): PrivilegeChangeResult =
+ when {
+ key == keyOfOwner -> FAIL_OWNER
+ getRawStoredPrivilege(key).isChangeInDirection(positive, update) && setRawStoredPrivilege(key, update) -> SUCCESS
+ else -> FAIL
+ }
+
+fun Privileges.allowManage(key: PrivilegeKey) = changePrivilege(key, true, Privilege.CAN_MANAGE)
+fun Privileges.disallowManage(key: PrivilegeKey) = changePrivilege(key, false, Privilege.CAN_BUILD)
+fun Privileges.allowBuild(key: PrivilegeKey) = changePrivilege(key, true, Privilege.CAN_BUILD)
+fun Privileges.disallowBuild(key: PrivilegeKey) = changePrivilege(key, false, Privilege.DEFAULT)
+fun Privileges.allowEnter(key: PrivilegeKey) = changePrivilege(key, true, Privilege.DEFAULT)
+fun Privileges.disallowEnter(key: PrivilegeKey) = changePrivilege(key, false, Privilege.BANNED)
+
+interface GlobalPrivileges : RawPrivileges, Privileges {
+ override val keyOfOwner: PlayerProfile.Real
+}
+
+interface GlobalPrivilegesManager {
+ operator fun get(owner: PlayerProfile.Real): GlobalPrivileges
+ operator fun get(owner: OfflinePlayer): GlobalPrivileges = get(owner.privilegeKey)
+}
diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt index 8f2c565..83567c5 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt @@ -1,61 +1,61 @@ -package io.dico.parcels2.blockvisitor - -import io.dico.parcels2.util.math.Vec3i -import io.dico.parcels2.util.ext.getMaterialsWithWoodTypePrefix -import io.dico.parcels2.util.ext.getMaterialsWithWoolColorPrefix -import org.bukkit.Material -import org.bukkit.Material.* -import org.bukkit.block.BlockFace -import org.bukkit.block.data.BlockData -import org.bukkit.block.data.Directional -import java.util.EnumSet - -private val attachables = EnumSet.of( - REPEATER, COMPARATOR, - *getMaterialsWithWoodTypePrefix("PRESSURE_PLATE"), - STONE_PRESSURE_PLATE, LIGHT_WEIGHTED_PRESSURE_PLATE, HEAVY_WEIGHTED_PRESSURE_PLATE, - *getMaterialsWithWoodTypePrefix("BUTTON"), - STONE_BUTTON, LEVER, - *getMaterialsWithWoodTypePrefix("DOOR"), IRON_DOOR, - ACTIVATOR_RAIL, POWERED_RAIL, DETECTOR_RAIL, RAIL, - PISTON, STICKY_PISTON, - REDSTONE_TORCH, REDSTONE_WALL_TORCH, REDSTONE_WIRE, - TRIPWIRE, TRIPWIRE_HOOK, - - BROWN_MUSHROOM, RED_MUSHROOM, CACTUS, CARROT, COCOA, - WHEAT, DEAD_BUSH, CHORUS_FLOWER, DANDELION, SUGAR_CANE, - TALL_GRASS, TALL_SEAGRASS, NETHER_WART, MELON_STEM, - PUMPKIN_STEM, SUNFLOWER, POTATO, LILY_PAD, VINE, - *getMaterialsWithWoodTypePrefix("SAPLING"), - - SAND, RED_SAND, DRAGON_EGG, ANVIL, - *getMaterialsWithWoolColorPrefix("CONCRETE_POWDER"), - - *getMaterialsWithWoolColorPrefix("CARPET"), - CAKE, FIRE, - FLOWER_POT, - LADDER, - // NETHER_PORTAL, fuck nether portals - FLOWER_POT, - SNOW, - TORCH, WALL_TORCH, - *getMaterialsWithWoolColorPrefix("BANNER"), - *getMaterialsWithWoolColorPrefix("WALL_BANNER"), - SIGN, WALL_SIGN -) - -fun isAttachable(type: Material) = attachables.contains(type) - -fun getSupportingBlock(data: BlockData): Vec3i = when (data) { - //is MultipleFacing -> // fuck it xD this is good enough - - is Directional -> Vec3i.convert(when (data.material) { - // exceptions - COCOA -> data.facing - OAK_DOOR, BIRCH_DOOR, SPRUCE_DOOR, JUNGLE_DOOR, ACACIA_DOOR, DARK_OAK_DOOR, IRON_DOOR -> BlockFace.DOWN - - else -> data.facing.oppositeFace - }) - - else -> Vec3i.down -} +package io.dico.parcels2.blockvisitor
+
+import io.dico.parcels2.util.math.Vec3i
+import io.dico.parcels2.util.ext.getMaterialsWithWoodTypePrefix
+import io.dico.parcels2.util.ext.getMaterialsWithWoolColorPrefix
+import org.bukkit.Material
+import org.bukkit.Material.*
+import org.bukkit.block.BlockFace
+import org.bukkit.block.data.BlockData
+import org.bukkit.block.data.Directional
+import java.util.EnumSet
+
+private val attachables = EnumSet.of(
+ REPEATER, COMPARATOR,
+ *getMaterialsWithWoodTypePrefix("PRESSURE_PLATE"),
+ STONE_PRESSURE_PLATE, LIGHT_WEIGHTED_PRESSURE_PLATE, HEAVY_WEIGHTED_PRESSURE_PLATE,
+ *getMaterialsWithWoodTypePrefix("BUTTON"),
+ STONE_BUTTON, LEVER,
+ *getMaterialsWithWoodTypePrefix("DOOR"), IRON_DOOR,
+ ACTIVATOR_RAIL, POWERED_RAIL, DETECTOR_RAIL, RAIL,
+ PISTON, STICKY_PISTON,
+ REDSTONE_TORCH, REDSTONE_WALL_TORCH, REDSTONE_WIRE,
+ TRIPWIRE, TRIPWIRE_HOOK,
+
+ BROWN_MUSHROOM, RED_MUSHROOM, CACTUS, CARROT, COCOA,
+ WHEAT, DEAD_BUSH, CHORUS_FLOWER, DANDELION, SUGAR_CANE,
+ TALL_GRASS, TALL_SEAGRASS, NETHER_WART, MELON_STEM,
+ PUMPKIN_STEM, SUNFLOWER, POTATO, LILY_PAD, VINE,
+ *getMaterialsWithWoodTypePrefix("SAPLING"),
+
+ SAND, RED_SAND, DRAGON_EGG, ANVIL,
+ *getMaterialsWithWoolColorPrefix("CONCRETE_POWDER"),
+
+ *getMaterialsWithWoolColorPrefix("CARPET"),
+ CAKE, FIRE,
+ FLOWER_POT,
+ LADDER,
+ // NETHER_PORTAL, fuck nether portals
+ FLOWER_POT,
+ SNOW,
+ TORCH, WALL_TORCH,
+ *getMaterialsWithWoolColorPrefix("BANNER"),
+ *getMaterialsWithWoolColorPrefix("WALL_BANNER"),
+ SIGN, WALL_SIGN
+)
+
+fun isAttachable(type: Material) = attachables.contains(type)
+
+fun getSupportingBlock(data: BlockData): Vec3i = when (data) {
+ //is MultipleFacing -> // fuck it xD this is good enough
+
+ is Directional -> Vec3i.convert(when (data.material) {
+ // exceptions
+ COCOA -> data.facing
+ OAK_DOOR, BIRCH_DOOR, SPRUCE_DOOR, JUNGLE_DOOR, ACACIA_DOOR, DARK_OAK_DOOR, IRON_DOOR -> BlockFace.DOWN
+
+ else -> data.facing.oppositeFace
+ })
+
+ else -> Vec3i.down
+}
diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/ExtraBlockChange.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/ExtraBlockChange.kt index 3f7e070..ddfec27 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/ExtraBlockChange.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/ExtraBlockChange.kt @@ -1,38 +1,38 @@ -package io.dico.parcels2.blockvisitor - -import org.bukkit.block.Block -import org.bukkit.block.BlockState -import org.bukkit.block.Sign -import kotlin.reflect.KClass - -interface ExtraBlockChange { - fun update(block: Block) -} - -abstract class BlockStateChange<T : BlockState> : ExtraBlockChange { - abstract val stateClass: KClass<T> - - abstract fun update(state: T) - - override fun update(block: Block) { - val state = block.state - if (stateClass.isInstance(state)) { - @Suppress("UNCHECKED_CAST") - update(state as T) - } - } -} - -class SignStateChange(state: Sign) : BlockStateChange<Sign>() { - val lines = state.lines - - override val stateClass: KClass<Sign> - get() = Sign::class - - override fun update(state: Sign) { - for (i in lines.indices) { - val line = lines[i] - state.setLine(i, line) - } - } -} +package io.dico.parcels2.blockvisitor
+
+import org.bukkit.block.Block
+import org.bukkit.block.BlockState
+import org.bukkit.block.Sign
+import kotlin.reflect.KClass
+
+interface ExtraBlockChange {
+ fun update(block: Block)
+}
+
+abstract class BlockStateChange<T : BlockState> : ExtraBlockChange {
+ abstract val stateClass: KClass<T>
+
+ abstract fun update(state: T)
+
+ override fun update(block: Block) {
+ val state = block.state
+ if (stateClass.isInstance(state)) {
+ @Suppress("UNCHECKED_CAST")
+ update(state as T)
+ }
+ }
+}
+
+class SignStateChange(state: Sign) : BlockStateChange<Sign>() {
+ val lines = state.lines
+
+ override val stateClass: KClass<Sign>
+ get() = Sign::class
+
+ override fun update(state: Sign) {
+ for (i in lines.indices) {
+ val line = lines[i]
+ state.setLine(i, line)
+ }
+ }
+}
diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt index b749b36..6525655 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt @@ -1,323 +1,323 @@ -package io.dico.parcels2.blockvisitor - -import io.dico.parcels2.util.math.Dimension -import io.dico.parcels2.util.math.Region -import io.dico.parcels2.util.math.Vec3i -import io.dico.parcels2.util.math.clampMax - -private typealias Scope = SequenceScope<Vec3i> -/* -class ParcelTraverser( - val parcelProvider: ParcelProvider, - val delegate: RegionTraverser, - scope: CoroutineScope -) : RegionTraverser(), CoroutineScope by scope { - - class OccupiedException(parcelId: ParcelId) : Exception("Parcel $parcelId is occupied") - - /** - * Traverse the blocks of parcel's land - * The iterator must be exhausted, else the permit to traverse it will not be reclaimed. - * - * @throws OccupiedException if a parcel is maintained with the given parcel id and an - * iterator exists for it that has not been exhausted - */ - fun traverseParcel(parcelId: ParcelId): Iterator<Vec3i> { - val world = parcelProvider.getWorldById(parcelId.worldId) - ?: throw IllegalArgumentException() - val parcel = parcelProvider.getParcelById(parcelId) - - val medium = if (parcel != null) { - if (parcel.hasBlockVisitors || parcel !is ParcelImpl) { - throw OccupiedException(parcelId) - } - parcel.hasBlockVisitors = true - TraverserMedium { parcel.hasBlockVisitors = false } - } else { - TraverserMedium.DoNothing - } - - val region = world.blockManager.getRegion(parcelId) - return traverseRegion(region, world.world.maxHeight, medium) - } - - override suspend fun Scope.build(region: Region, medium: TraverserMedium) { - with(delegate) { - return build(region, medium) - } - } - -} - -@Suppress("FunctionName") -inline fun TraverserMedium(crossinline whenComplete: () -> Unit) = - object : TraverserMedium { - override fun iterationCompleted() { - whenComplete() - } - } - -/** - * An object that is able to communicate with an iterator returned by [RegionTraverser] - * - */ -interface TraverserMedium { - - /** - * Called by the traverser during first [Iterator.hasNext] call that returns false - */ - fun iterationCompleted() - - /** - * The default [TraverserMedium], which does nothing. - */ - object DoNothing : TraverserMedium { - override fun iterationCompleted() {} - } -}*/ - -sealed class RegionTraverser { - - /** - * Get an iterator traversing [region] using this traverser. - * Depending on the implementation, [region] might be traversed in a specific order and direction. - */ - fun traverseRegion( - region: Region, - worldHeight: Int = 256/*, - medium: TraverserMedium = TraverserMedium.DoNothing*/ - ): Iterator<Vec3i> = iterator { build(validify(region, worldHeight)/*, medium*/) } - - abstract suspend fun Scope.build(region: Region/*, medium: TraverserMedium = TraverserMedium.DoNothing*/) - - companion object { - val upward = Directional(TraverseDirection(1, 1, 1), TraverseOrderFactory.createWith(Dimension.Y, Dimension.X)) - val downward = Directional(TraverseDirection(1, -1, 1), TraverseOrderFactory.createWith(Dimension.Y, Dimension.X)) - val toClear get() = downward - val toFill get() = upward - - /** - * The returned [RegionTraverser] will traverse the regions - * * below and including absolute level [y] first, in [upward] direction. - * * above absolute level [y] last, in [downward] direction. - */ - fun convergingTo(y: Int) = Slicing(y, upward, downward, true) - - /** - * The returned [RegionTraverser] will traverse the regions - * * above absolute level [y] first, in [upward] direction. - * * below and including absolute level [y] second, in [downward] direction. - */ - fun separatingFrom(y: Int) = Slicing(y, downward, upward, false) - - private fun validify(region: Region, worldHeight: Int): Region { - if (region.origin.y < 0) { - val origin = region.origin withY 0 - val size = region.size.withY((region.size.y + region.origin.y).clampMax(worldHeight)) - return Region(origin, size) - } - - if (region.origin.y + region.size.y > worldHeight) { - val size = region.size.withY(worldHeight - region.origin.y) - return Region(region.origin, size) - } - - return region - } - - } - - class Directional( - val direction: TraverseDirection, - val order: TraverseOrder - ) : RegionTraverser() { - - private inline fun iterate(max: Int, increasing: Boolean, action: (Int) -> Unit) { - for (i in 0..max) { - action(if (increasing) i else max - i) - } - } - - override suspend fun Scope.build(region: Region/*, medium: TraverserMedium*/) { - val order = order - val (primary, secondary, tertiary) = order.toArray() - val (origin, size) = region - - val maxOfPrimary = size[primary] - 1 - val maxOfSecondary = size[secondary] - 1 - val maxOfTertiary = size[tertiary] - 1 - - val isPrimaryIncreasing = direction.isIncreasing(primary) - val isSecondaryIncreasing = direction.isIncreasing(secondary) - val isTertiaryIncreasing = direction.isIncreasing(tertiary) - - iterate(maxOfPrimary, isPrimaryIncreasing) { p -> - iterate(maxOfSecondary, isSecondaryIncreasing) { s -> - iterate(maxOfTertiary, isTertiaryIncreasing) { t -> - yield(order.add(origin, p, s, t)) - } - } - } - - /*medium.iterationCompleted()*/ - } - - } - - class Slicing( - val bottomSectionMaxY: Int, - val bottomTraverser: RegionTraverser, - val topTraverser: RegionTraverser, - val bottomFirst: Boolean = true - ) : RegionTraverser() { - - private fun slice(region: Region, atY: Int): Pair<Region, Region?> { - if (atY < region.size.y + 1) { - val bottom = Region(region.origin, region.size.withY(atY + 1)) - val top = Region(region.origin.withY(atY + 1), region.size.addY(-atY - 1)) - return bottom to top - } - return region to null - } - - override suspend fun Scope.build(region: Region/*, medium: TraverserMedium*/) { - val (bottom, top) = slice(region, bottomSectionMaxY) - - if (bottomFirst) { - with(bottomTraverser) { build(bottom) } - top?.let { with(topTraverser) { build(it) } } - } else { - top?.let { with(topTraverser) { build(it) } } - with(bottomTraverser) { build(bottom) } - } - - /*medium.iterationCompleted()*/ - } - } - - /** - * Returns [Directional] instance that would be responsible for - * emitting the given position if it is contained in a region. - * [Directional] instance has a set order and direction - */ - fun childForPosition(position: Vec3i): Directional { - var cur = this - while (true) { - when (cur) { - /*is ParcelTraverser -> cur = cur.delegate*/ - is Directional -> return cur - is Slicing -> - cur = - if (position.y <= cur.bottomSectionMaxY) cur.bottomTraverser - else cur.topTraverser - } - } - } - - /** - * Returns true if and only if this traverser would visit the given - * [block] position before the given [current] position. - * If at least one of [block] and [current] is not contained in a - * region being traversed the result is undefined. - */ - fun comesFirst(current: Vec3i, block: Vec3i): Boolean { - var cur = this - while (true) { - when (cur) { - /*is ParcelTraverser -> cur = cur.delegate*/ - is Directional -> return cur.direction.comesFirst(current, block) - is Slicing -> { - val border = cur.bottomSectionMaxY - cur = when { - current.y <= border && block.y <= border -> cur.bottomTraverser - current.y <= border -> return !cur.bottomFirst - block.y <= border -> return cur.bottomFirst - else -> cur.topTraverser - } - } - } - } - } - -} - -object TraverseOrderFactory { - private fun isSwap(primary: Dimension, secondary: Dimension) = secondary.ordinal != (primary.ordinal + 1) % 3 - - fun createWith(primary: Dimension, secondary: Dimension): TraverseOrder { - // tertiary is implicit - if (primary == secondary) throw IllegalArgumentException() - return TraverseOrder(primary, isSwap(primary, secondary)) - } -} - -inline class TraverseOrder(val orderNum: Int) { - constructor(first: Dimension, swap: Boolean) - : this(if (swap) first.ordinal + 3 else first.ordinal) - - @Suppress("NOTHING_TO_INLINE") - private inline fun element(index: Int) = Dimension[(orderNum + index) % 3] - - private val swap inline get() = orderNum >= 3 - - /** - * The slowest changing dimension - */ - val primary: Dimension get() = element(0) - - /** - * Second slowest changing dimension - */ - val secondary: Dimension get() = element(if (swap) 2 else 1) - - /** - * Dimension that changes every block - */ - val tertiary: Dimension get() = element(if (swap) 1 else 2) - - /** - * All 3 dimensions in this order - */ - fun toArray() = arrayOf(primary, secondary, tertiary) - - fun add(vec: Vec3i, p: Int, s: Int, t: Int): Vec3i = - // optimize this, will be called lots - when (orderNum) { - 0 -> vec.add(p, s, t) // xyz - 1 -> vec.add(t, p, s) // yzx - 2 -> vec.add(s, t, p) // zxy - 3 -> vec.add(p, t, s) // xzy - 4 -> vec.add(s, p, t) // yxz - 5 -> vec.add(t, s, p) // zyx - else -> error("Invalid orderNum $orderNum") - } -} - -inline class TraverseDirection(val bits: Int) { - fun isIncreasing(dimension: Dimension) = (1 shl dimension.ordinal) and bits != 0 - - fun comesFirst(current: Vec3i, block: Vec3i, dimension: Dimension): Boolean = - if (isIncreasing(dimension)) - block[dimension] <= current[dimension] - else - block[dimension] >= current[dimension] - - fun comesFirst(current: Vec3i, block: Vec3i) = - comesFirst(current, block, Dimension.X) - && comesFirst(current, block, Dimension.Y) - && comesFirst(current, block, Dimension.Z) - - companion object { - operator fun invoke(x: Int, y: Int, z: Int) = invoke(Vec3i(x, y, z)) - - operator fun invoke(block: Vec3i): TraverseDirection { - if (block.x == 0 || block.y == 0 || block.z == 0) throw IllegalArgumentException() - var bits = 0 - if (block.x > 0) bits = bits or 1 - if (block.y > 0) bits = bits or 2 - if (block.z > 0) bits = bits or 4 - return TraverseDirection(bits) - } - } - -} +package io.dico.parcels2.blockvisitor
+
+import io.dico.parcels2.util.math.Dimension
+import io.dico.parcels2.util.math.Region
+import io.dico.parcels2.util.math.Vec3i
+import io.dico.parcels2.util.math.clampMax
+
+private typealias Scope = SequenceScope<Vec3i>
+/*
+class ParcelTraverser(
+ val parcelProvider: ParcelProvider,
+ val delegate: RegionTraverser,
+ scope: CoroutineScope
+) : RegionTraverser(), CoroutineScope by scope {
+
+ class OccupiedException(parcelId: ParcelId) : Exception("Parcel $parcelId is occupied")
+
+ /**
+ * Traverse the blocks of parcel's land
+ * The iterator must be exhausted, else the permit to traverse it will not be reclaimed.
+ *
+ * @throws OccupiedException if a parcel is maintained with the given parcel id and an
+ * iterator exists for it that has not been exhausted
+ */
+ fun traverseParcel(parcelId: ParcelId): Iterator<Vec3i> {
+ val world = parcelProvider.getWorldById(parcelId.worldId)
+ ?: throw IllegalArgumentException()
+ val parcel = parcelProvider.getParcelById(parcelId)
+
+ val medium = if (parcel != null) {
+ if (parcel.hasBlockVisitors || parcel !is ParcelImpl) {
+ throw OccupiedException(parcelId)
+ }
+ parcel.hasBlockVisitors = true
+ TraverserMedium { parcel.hasBlockVisitors = false }
+ } else {
+ TraverserMedium.DoNothing
+ }
+
+ val region = world.blockManager.getRegion(parcelId)
+ return traverseRegion(region, world.world.maxHeight, medium)
+ }
+
+ override suspend fun Scope.build(region: Region, medium: TraverserMedium) {
+ with(delegate) {
+ return build(region, medium)
+ }
+ }
+
+}
+
+@Suppress("FunctionName")
+inline fun TraverserMedium(crossinline whenComplete: () -> Unit) =
+ object : TraverserMedium {
+ override fun iterationCompleted() {
+ whenComplete()
+ }
+ }
+
+/**
+ * An object that is able to communicate with an iterator returned by [RegionTraverser]
+ *
+ */
+interface TraverserMedium {
+
+ /**
+ * Called by the traverser during first [Iterator.hasNext] call that returns false
+ */
+ fun iterationCompleted()
+
+ /**
+ * The default [TraverserMedium], which does nothing.
+ */
+ object DoNothing : TraverserMedium {
+ override fun iterationCompleted() {}
+ }
+}*/
+
+sealed class RegionTraverser {
+
+ /**
+ * Get an iterator traversing [region] using this traverser.
+ * Depending on the implementation, [region] might be traversed in a specific order and direction.
+ */
+ fun traverseRegion(
+ region: Region,
+ worldHeight: Int = 256/*,
+ medium: TraverserMedium = TraverserMedium.DoNothing*/
+ ): Iterator<Vec3i> = iterator { build(validify(region, worldHeight)/*, medium*/) }
+
+ abstract suspend fun Scope.build(region: Region/*, medium: TraverserMedium = TraverserMedium.DoNothing*/)
+
+ companion object {
+ val upward = Directional(TraverseDirection(1, 1, 1), TraverseOrderFactory.createWith(Dimension.Y, Dimension.X))
+ val downward = Directional(TraverseDirection(1, -1, 1), TraverseOrderFactory.createWith(Dimension.Y, Dimension.X))
+ val toClear get() = downward
+ val toFill get() = upward
+
+ /**
+ * The returned [RegionTraverser] will traverse the regions
+ * * below and including absolute level [y] first, in [upward] direction.
+ * * above absolute level [y] last, in [downward] direction.
+ */
+ fun convergingTo(y: Int) = Slicing(y, upward, downward, true)
+
+ /**
+ * The returned [RegionTraverser] will traverse the regions
+ * * above absolute level [y] first, in [upward] direction.
+ * * below and including absolute level [y] second, in [downward] direction.
+ */
+ fun separatingFrom(y: Int) = Slicing(y, downward, upward, false)
+
+ private fun validify(region: Region, worldHeight: Int): Region {
+ if (region.origin.y < 0) {
+ val origin = region.origin withY 0
+ val size = region.size.withY((region.size.y + region.origin.y).clampMax(worldHeight))
+ return Region(origin, size)
+ }
+
+ if (region.origin.y + region.size.y > worldHeight) {
+ val size = region.size.withY(worldHeight - region.origin.y)
+ return Region(region.origin, size)
+ }
+
+ return region
+ }
+
+ }
+
+ class Directional(
+ val direction: TraverseDirection,
+ val order: TraverseOrder
+ ) : RegionTraverser() {
+
+ private inline fun iterate(max: Int, increasing: Boolean, action: (Int) -> Unit) {
+ for (i in 0..max) {
+ action(if (increasing) i else max - i)
+ }
+ }
+
+ override suspend fun Scope.build(region: Region/*, medium: TraverserMedium*/) {
+ val order = order
+ val (primary, secondary, tertiary) = order.toArray()
+ val (origin, size) = region
+
+ val maxOfPrimary = size[primary] - 1
+ val maxOfSecondary = size[secondary] - 1
+ val maxOfTertiary = size[tertiary] - 1
+
+ val isPrimaryIncreasing = direction.isIncreasing(primary)
+ val isSecondaryIncreasing = direction.isIncreasing(secondary)
+ val isTertiaryIncreasing = direction.isIncreasing(tertiary)
+
+ iterate(maxOfPrimary, isPrimaryIncreasing) { p ->
+ iterate(maxOfSecondary, isSecondaryIncreasing) { s ->
+ iterate(maxOfTertiary, isTertiaryIncreasing) { t ->
+ yield(order.add(origin, p, s, t))
+ }
+ }
+ }
+
+ /*medium.iterationCompleted()*/
+ }
+
+ }
+
+ class Slicing(
+ val bottomSectionMaxY: Int,
+ val bottomTraverser: RegionTraverser,
+ val topTraverser: RegionTraverser,
+ val bottomFirst: Boolean = true
+ ) : RegionTraverser() {
+
+ private fun slice(region: Region, atY: Int): Pair<Region, Region?> {
+ if (atY < region.size.y + 1) {
+ val bottom = Region(region.origin, region.size.withY(atY + 1))
+ val top = Region(region.origin.withY(atY + 1), region.size.addY(-atY - 1))
+ return bottom to top
+ }
+ return region to null
+ }
+
+ override suspend fun Scope.build(region: Region/*, medium: TraverserMedium*/) {
+ val (bottom, top) = slice(region, bottomSectionMaxY)
+
+ if (bottomFirst) {
+ with(bottomTraverser) { build(bottom) }
+ top?.let { with(topTraverser) { build(it) } }
+ } else {
+ top?.let { with(topTraverser) { build(it) } }
+ with(bottomTraverser) { build(bottom) }
+ }
+
+ /*medium.iterationCompleted()*/
+ }
+ }
+
+ /**
+ * Returns [Directional] instance that would be responsible for
+ * emitting the given position if it is contained in a region.
+ * [Directional] instance has a set order and direction
+ */
+ fun childForPosition(position: Vec3i): Directional {
+ var cur = this
+ while (true) {
+ when (cur) {
+ /*is ParcelTraverser -> cur = cur.delegate*/
+ is Directional -> return cur
+ is Slicing ->
+ cur =
+ if (position.y <= cur.bottomSectionMaxY) cur.bottomTraverser
+ else cur.topTraverser
+ }
+ }
+ }
+
+ /**
+ * Returns true if and only if this traverser would visit the given
+ * [block] position before the given [current] position.
+ * If at least one of [block] and [current] is not contained in a
+ * region being traversed the result is undefined.
+ */
+ fun comesFirst(current: Vec3i, block: Vec3i): Boolean {
+ var cur = this
+ while (true) {
+ when (cur) {
+ /*is ParcelTraverser -> cur = cur.delegate*/
+ is Directional -> return cur.direction.comesFirst(current, block)
+ is Slicing -> {
+ val border = cur.bottomSectionMaxY
+ cur = when {
+ current.y <= border && block.y <= border -> cur.bottomTraverser
+ current.y <= border -> return !cur.bottomFirst
+ block.y <= border -> return cur.bottomFirst
+ else -> cur.topTraverser
+ }
+ }
+ }
+ }
+ }
+
+}
+
+object TraverseOrderFactory {
+ private fun isSwap(primary: Dimension, secondary: Dimension) = secondary.ordinal != (primary.ordinal + 1) % 3
+
+ fun createWith(primary: Dimension, secondary: Dimension): TraverseOrder {
+ // tertiary is implicit
+ if (primary == secondary) throw IllegalArgumentException()
+ return TraverseOrder(primary, isSwap(primary, secondary))
+ }
+}
+
+inline class TraverseOrder(val orderNum: Int) {
+ constructor(first: Dimension, swap: Boolean)
+ : this(if (swap) first.ordinal + 3 else first.ordinal)
+
+ @Suppress("NOTHING_TO_INLINE")
+ private inline fun element(index: Int) = Dimension[(orderNum + index) % 3]
+
+ private val swap inline get() = orderNum >= 3
+
+ /**
+ * The slowest changing dimension
+ */
+ val primary: Dimension get() = element(0)
+
+ /**
+ * Second slowest changing dimension
+ */
+ val secondary: Dimension get() = element(if (swap) 2 else 1)
+
+ /**
+ * Dimension that changes every block
+ */
+ val tertiary: Dimension get() = element(if (swap) 1 else 2)
+
+ /**
+ * All 3 dimensions in this order
+ */
+ fun toArray() = arrayOf(primary, secondary, tertiary)
+
+ fun add(vec: Vec3i, p: Int, s: Int, t: Int): Vec3i =
+ // optimize this, will be called lots
+ when (orderNum) {
+ 0 -> vec.add(p, s, t) // xyz
+ 1 -> vec.add(t, p, s) // yzx
+ 2 -> vec.add(s, t, p) // zxy
+ 3 -> vec.add(p, t, s) // xzy
+ 4 -> vec.add(s, p, t) // yxz
+ 5 -> vec.add(t, s, p) // zyx
+ else -> error("Invalid orderNum $orderNum")
+ }
+}
+
+inline class TraverseDirection(val bits: Int) {
+ fun isIncreasing(dimension: Dimension) = (1 shl dimension.ordinal) and bits != 0
+
+ fun comesFirst(current: Vec3i, block: Vec3i, dimension: Dimension): Boolean =
+ if (isIncreasing(dimension))
+ block[dimension] <= current[dimension]
+ else
+ block[dimension] >= current[dimension]
+
+ fun comesFirst(current: Vec3i, block: Vec3i) =
+ comesFirst(current, block, Dimension.X)
+ && comesFirst(current, block, Dimension.Y)
+ && comesFirst(current, block, Dimension.Z)
+
+ companion object {
+ operator fun invoke(x: Int, y: Int, z: Int) = invoke(Vec3i(x, y, z))
+
+ operator fun invoke(block: Vec3i): TraverseDirection {
+ if (block.x == 0 || block.y == 0 || block.z == 0) throw IllegalArgumentException()
+ var bits = 0
+ if (block.x > 0) bits = bits or 1
+ if (block.y > 0) bits = bits or 2
+ if (block.z > 0) bits = bits or 4
+ return TraverseDirection(bits)
+ }
+ }
+
+}
diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt index df3cfab..39b4345 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt @@ -1,121 +1,121 @@ -package io.dico.parcels2.blockvisitor - -import io.dico.parcels2.JobFunction -import io.dico.parcels2.JobScope -import io.dico.parcels2.util.math.Region -import io.dico.parcels2.util.math.Vec3i -import io.dico.parcels2.util.math.get -import org.bukkit.Bukkit -import org.bukkit.Material -import org.bukkit.World -import org.bukkit.block.Sign -import org.bukkit.block.data.BlockData - -private val air = Bukkit.createBlockData(Material.AIR) - -class Schematic { - val size: Vec3i get() = _size!! - private var _size: Vec3i? = null - set(value) { - field?.let { throw IllegalStateException() } - field = value - } - - private var blockDatas: Array<BlockData?>? = null - private val extra = mutableListOf<Pair<Vec3i, ExtraBlockChange>>() - private var isLoaded = false; private set - private val traverser: RegionTraverser = RegionTraverser.upward - - suspend fun JobScope.load(world: World, region: Region) { - _size = region.size - - val data = arrayOfNulls<BlockData>(region.blockCount).also { blockDatas = it } - val blocks = traverser.traverseRegion(region) - val total = region.blockCount.toDouble() - - loop@ for ((index, vec) in blocks.withIndex()) { - markSuspensionPoint() - setProgress(index / total) - - val block = world[vec] - if (block.y > 255) continue - val blockData = block.blockData - data[index] = blockData - - val extraChange = when (blockData.material) { - Material.SIGN, - Material.WALL_SIGN -> SignStateChange(block.state as Sign) - else -> continue@loop - } - - extra += (vec - region.origin) to extraChange - } - - isLoaded = true - } - - suspend fun JobScope.paste(world: World, position: Vec3i) { - if (!isLoaded) throw IllegalStateException() - - val region = Region(position, _size!!) - val blocks = traverser.traverseRegion(region, worldHeight = world.maxHeight) - val blockDatas = blockDatas!! - var postponed = hashMapOf<Vec3i, BlockData>() - - val total = region.blockCount.toDouble() - var processed = 0 - - for ((index, vec) in blocks.withIndex()) { - markSuspensionPoint() - setProgress(index / total) - - val block = world[vec] - val type = blockDatas[index] ?: air - if (type !== air && isAttachable(type.material)) { - val supportingBlock = vec + getSupportingBlock(type) - - if (!postponed.containsKey(supportingBlock) && traverser.comesFirst(vec, supportingBlock)) { - block.blockData = type - setProgress(++processed / total) - } else { - postponed[vec] = type - } - - } else { - block.blockData = type - setProgress(++processed / total) - } - } - - while (!postponed.isEmpty()) { - markSuspensionPoint() - val newMap = hashMapOf<Vec3i, BlockData>() - for ((vec, type) in postponed) { - val supportingBlock = vec + getSupportingBlock(type) - if (supportingBlock in postponed && supportingBlock != vec) { - newMap[vec] = type - } else { - world[vec].blockData = type - setProgress(++processed / total) - } - } - postponed = newMap - } - - // Should be negligible so we don't track progress - for ((vec, extraChange) in extra) { - markSuspensionPoint() - val block = world[position + vec] - extraChange.update(block) - } - } - - fun getLoadTask(world: World, region: Region): JobFunction = { - load(world, region) - } - - fun getPasteTask(world: World, position: Vec3i): JobFunction = { - paste(world, position) - } - -} +package io.dico.parcels2.blockvisitor
+
+import io.dico.parcels2.JobFunction
+import io.dico.parcels2.JobScope
+import io.dico.parcels2.util.math.Region
+import io.dico.parcels2.util.math.Vec3i
+import io.dico.parcels2.util.math.get
+import org.bukkit.Bukkit
+import org.bukkit.Material
+import org.bukkit.World
+import org.bukkit.block.Sign
+import org.bukkit.block.data.BlockData
+
+private val air = Bukkit.createBlockData(Material.AIR)
+
+class Schematic {
+ val size: Vec3i get() = _size!!
+ private var _size: Vec3i? = null
+ set(value) {
+ field?.let { throw IllegalStateException() }
+ field = value
+ }
+
+ private var blockDatas: Array<BlockData?>? = null
+ private val extra = mutableListOf<Pair<Vec3i, ExtraBlockChange>>()
+ private var isLoaded = false; private set
+ private val traverser: RegionTraverser = RegionTraverser.upward
+
+ suspend fun JobScope.load(world: World, region: Region) {
+ _size = region.size
+
+ val data = arrayOfNulls<BlockData>(region.blockCount).also { blockDatas = it }
+ val blocks = traverser.traverseRegion(region)
+ val total = region.blockCount.toDouble()
+
+ loop@ for ((index, vec) in blocks.withIndex()) {
+ markSuspensionPoint()
+ setProgress(index / total)
+
+ val block = world[vec]
+ if (block.y > 255) continue
+ val blockData = block.blockData
+ data[index] = blockData
+
+ val extraChange = when (blockData.material) {
+ Material.SIGN,
+ Material.WALL_SIGN -> SignStateChange(block.state as Sign)
+ else -> continue@loop
+ }
+
+ extra += (vec - region.origin) to extraChange
+ }
+
+ isLoaded = true
+ }
+
+ suspend fun JobScope.paste(world: World, position: Vec3i) {
+ if (!isLoaded) throw IllegalStateException()
+
+ val region = Region(position, _size!!)
+ val blocks = traverser.traverseRegion(region, worldHeight = world.maxHeight)
+ val blockDatas = blockDatas!!
+ var postponed = hashMapOf<Vec3i, BlockData>()
+
+ val total = region.blockCount.toDouble()
+ var processed = 0
+
+ for ((index, vec) in blocks.withIndex()) {
+ markSuspensionPoint()
+ setProgress(index / total)
+
+ val block = world[vec]
+ val type = blockDatas[index] ?: air
+ if (type !== air && isAttachable(type.material)) {
+ val supportingBlock = vec + getSupportingBlock(type)
+
+ if (!postponed.containsKey(supportingBlock) && traverser.comesFirst(vec, supportingBlock)) {
+ block.blockData = type
+ setProgress(++processed / total)
+ } else {
+ postponed[vec] = type
+ }
+
+ } else {
+ block.blockData = type
+ setProgress(++processed / total)
+ }
+ }
+
+ while (!postponed.isEmpty()) {
+ markSuspensionPoint()
+ val newMap = hashMapOf<Vec3i, BlockData>()
+ for ((vec, type) in postponed) {
+ val supportingBlock = vec + getSupportingBlock(type)
+ if (supportingBlock in postponed && supportingBlock != vec) {
+ newMap[vec] = type
+ } else {
+ world[vec].blockData = type
+ setProgress(++processed / total)
+ }
+ }
+ postponed = newMap
+ }
+
+ // Should be negligible so we don't track progress
+ for ((vec, extraChange) in extra) {
+ markSuspensionPoint()
+ val block = world[position + vec]
+ extraChange.update(block)
+ }
+ }
+
+ fun getLoadTask(world: World, region: Region): JobFunction = {
+ load(world, region)
+ }
+
+ fun getPasteTask(world: World, position: Vec3i): JobFunction = {
+ paste(world, position)
+ }
+
+}
diff --git a/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt b/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt index eaa9f57..32f4299 100644 --- a/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt +++ b/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt @@ -1,64 +1,64 @@ -package io.dico.parcels2.command - -import io.dico.dicore.command.* -import io.dico.dicore.command.registration.reflect.ICommandInterceptor -import io.dico.dicore.command.registration.reflect.ICommandReceiver -import io.dico.parcels2.* -import io.dico.parcels2.PlayerProfile.Real -import io.dico.parcels2.PlayerProfile.Unresolved -import io.dico.parcels2.util.ext.hasPermAdminManage -import io.dico.parcels2.util.ext.parcelLimit -import org.bukkit.entity.Player -import org.bukkit.plugin.Plugin -import java.lang.reflect.Method - -abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandInterceptor { - - override fun getReceiver(context: ExecutionContext, target: Method, cmdName: String): ICommandReceiver { - return getParcelCommandReceiver(plugin.parcelProvider, context, target, cmdName) - } - - override fun getCoroutineContext(context: ExecutionContext?, target: Method?, cmdName: String?): Any { - return plugin.coroutineContext - } - - protected fun checkConnected(action: String) { - if (!plugin.storage.isConnected) err("Parcels cannot $action right now because of a database error") - } - - protected suspend fun checkParcelLimit(player: Player, world: ParcelWorld) { - if (player.hasPermAdminManage) return - val numOwnedParcels = plugin.storage.getOwnedParcels(PlayerProfile(player)).await() - .filter { it.worldId.equals(world.id) }.size - - val limit = player.parcelLimit - if (numOwnedParcels >= limit) { - err("You have enough plots for now") - } - } - - protected suspend fun toPrivilegeKey(profile: PlayerProfile): PrivilegeKey = when (profile) { - is Real -> profile - is Unresolved -> profile.tryResolveSuspendedly(plugin.storage) - ?: throw CommandException() - else -> throw CommandException() - } - - protected fun areYouSureMessage(context: ExecutionContext): String { - val command = (context.route + context.original).joinToString(" ") + " -sure" - return "Are you sure? You cannot undo this action!\n" + - "Run \"/$command\" if you want to go through with this." - } - - protected fun Job.reportProgressUpdates(context: ExecutionContext, action: String): Job = - onProgressUpdate(1000, 1000) { progress, elapsedTime -> - val alt = context.getFormat(EMessageType.NUMBER) - val main = context.getFormat(EMessageType.INFORMATIVE) - context.sendMessage( - EMessageType.INFORMATIVE, false, "$action progress: $alt%.02f$main%%, $alt%.2f${main}s elapsed" - .format(progress * 100, elapsedTime / 1000.0) - ) - } -} - +package io.dico.parcels2.command
+
+import io.dico.dicore.command.*
+import io.dico.dicore.command.registration.reflect.ICommandInterceptor
+import io.dico.dicore.command.registration.reflect.ICommandReceiver
+import io.dico.parcels2.*
+import io.dico.parcels2.PlayerProfile.Real
+import io.dico.parcels2.PlayerProfile.Unresolved
+import io.dico.parcels2.util.ext.hasPermAdminManage
+import io.dico.parcels2.util.ext.parcelLimit
+import org.bukkit.entity.Player
+import org.bukkit.plugin.Plugin
+import java.lang.reflect.Method
+
+abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandInterceptor {
+
+ override fun getReceiver(context: ExecutionContext, target: Method, cmdName: String): ICommandReceiver {
+ return getParcelCommandReceiver(plugin.parcelProvider, context, target, cmdName)
+ }
+
+ override fun getCoroutineContext(context: ExecutionContext?, target: Method?, cmdName: String?): Any {
+ return plugin.coroutineContext
+ }
+
+ protected fun checkConnected(action: String) {
+ if (!plugin.storage.isConnected) err("Parcels cannot $action right now because of a database error")
+ }
+
+ protected suspend fun checkParcelLimit(player: Player, world: ParcelWorld) {
+ if (player.hasPermAdminManage) return
+ val numOwnedParcels = plugin.storage.getOwnedParcels(PlayerProfile(player)).await()
+ .filter { it.worldId.equals(world.id) }.size
+
+ val limit = player.parcelLimit
+ if (numOwnedParcels >= limit) {
+ err("You have enough plots for now")
+ }
+ }
+
+ protected suspend fun toPrivilegeKey(profile: PlayerProfile): PrivilegeKey = when (profile) {
+ is Real -> profile
+ is Unresolved -> profile.tryResolveSuspendedly(plugin.storage)
+ ?: throw CommandException()
+ else -> throw CommandException()
+ }
+
+ protected fun areYouSureMessage(context: ExecutionContext): String {
+ val command = (context.route + context.original).joinToString(" ") + " -sure"
+ return "Are you sure? You cannot undo this action!\n" +
+ "Run \"/$command\" if you want to go through with this."
+ }
+
+ protected fun Job.reportProgressUpdates(context: ExecutionContext, action: String): Job =
+ onProgressUpdate(1000, 1000) { progress, elapsedTime ->
+ val alt = context.getFormat(EMessageType.NUMBER)
+ val main = context.getFormat(EMessageType.INFORMATIVE)
+ context.sendMessage(
+ EMessageType.INFORMATIVE, false, "$action progress: $alt%.02f$main%%, $alt%.2f${main}s elapsed"
+ .format(progress * 100, elapsedTime / 1000.0)
+ )
+ }
+}
+
fun err(message: String): Nothing = throw CommandException(message)
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsAdmin.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsAdmin.kt index e700d6d..38adc43 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsAdmin.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsAdmin.kt @@ -1,95 +1,95 @@ -package io.dico.parcels2.command - -import io.dico.dicore.command.CommandException -import io.dico.dicore.command.ExecutionContext -import io.dico.dicore.command.Validate -import io.dico.dicore.command.annotation.Cmd -import io.dico.dicore.command.annotation.Flag -import io.dico.parcels2.* -import io.dico.parcels2.command.ParcelTarget.TargetKind -import io.dico.parcels2.defaultimpl.DefaultParcelContainer -import io.dico.parcels2.util.ext.PERM_ADMIN_MANAGE - -class CommandsAdmin(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { - - @Cmd("setowner") - @RequireParcelPrivilege(Privilege.ADMIN) - suspend fun ParcelScope.cmdSetowner(@ProfileKind(ProfileKind.ANY) target: PlayerProfile): Any? { - val profile = target.resolved(plugin.storage, resolveToFake = true)!! - parcel.owner = profile - - val fakeString = if (profile.isFake) " (fake)" else "" - return "${profile.notNullName}$fakeString is the new owner of (${parcel.id.idString})" - } - - @Cmd("update_all_owner_signs") - fun cmdUpdateAllOwnerSigns(context: ExecutionContext): Any? { - Validate.isAuthorized(context.sender, PERM_ADMIN_MANAGE) - plugin.jobDispatcher.dispatch { - fun getParcelCount(world: ParcelWorld) = (world.options.axisLimit * 2 + 1).let { it * it } - val parcelCount = plugin.parcelProvider.worlds.values.sumBy { getParcelCount(it) }.toDouble() - var processed = 0 - for (world in plugin.parcelProvider.worlds.values) { - markSuspensionPoint() - - val container = world.container as? DefaultParcelContainer - if (container == null) { - processed += getParcelCount(world) - setProgress(processed / parcelCount) - continue - } - - for (parcel in container.getAllParcels()) { - parcel.updateOwnerSign(force = true) - processed++ - setProgress(processed / parcelCount) - } - } - }.reportProgressUpdates(context, "Updating") - return null - } - - @Cmd("dispose") - @RequireParcelPrivilege(Privilege.ADMIN) - fun ParcelScope.cmdDispose(): Any? { - parcel.dispose() - return "Data of (${parcel.id.idString}) has been disposed" - } - - @Cmd("reset") - @RequireParcelPrivilege(Privilege.ADMIN) - fun ParcelScope.cmdReset(context: ExecutionContext, @Flag sure: Boolean): Any? { - Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel") - if (!sure) return areYouSureMessage(context) - - parcel.dispose() - world.blockManager.clearParcel(parcel.id)?.reportProgressUpdates(context, "Reset") - return "Data of (${parcel.id.idString}) has been disposed" - } - - @Cmd("swap") - @RequireParcelPrivilege(Privilege.ADMIN) - fun ParcelScope.cmdSwap( - context: ExecutionContext, - @TargetKind(TargetKind.ID) target: ParcelTarget, - @Flag sure: Boolean - ): Any? { - Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel") - if (!sure) return areYouSureMessage(context) - - val parcel2 = (target as ParcelTarget.ByID).getParcel() - ?: throw CommandException("Invalid parcel target") - - // Validate.isTrue(parcel2.world == world, "Parcel must be in the same world") - Validate.isTrue(!parcel2.hasBlockVisitors, "A process is already running in that parcel") - - val data = parcel.data - parcel.copyData(parcel2.data) - parcel2.copyData(data) - - val job = plugin.parcelProvider.swapParcels(parcel.id, parcel2.id)?.reportProgressUpdates(context, "Swap") - Validate.notNull(job, "A process is already running in some parcel (internal error)") - return null - } - +package io.dico.parcels2.command
+
+import io.dico.dicore.command.CommandException
+import io.dico.dicore.command.ExecutionContext
+import io.dico.dicore.command.Validate
+import io.dico.dicore.command.annotation.Cmd
+import io.dico.dicore.command.annotation.Flag
+import io.dico.parcels2.*
+import io.dico.parcels2.command.ParcelTarget.TargetKind
+import io.dico.parcels2.defaultimpl.DefaultParcelContainer
+import io.dico.parcels2.util.ext.PERM_ADMIN_MANAGE
+
+class CommandsAdmin(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
+
+ @Cmd("setowner")
+ @RequireParcelPrivilege(Privilege.ADMIN)
+ suspend fun ParcelScope.cmdSetowner(@ProfileKind(ProfileKind.ANY) target: PlayerProfile): Any? {
+ val profile = target.resolved(plugin.storage, resolveToFake = true)!!
+ parcel.owner = profile
+
+ val fakeString = if (profile.isFake) " (fake)" else ""
+ return "${profile.notNullName}$fakeString is the new owner of (${parcel.id.idString})"
+ }
+
+ @Cmd("update_all_owner_signs")
+ fun cmdUpdateAllOwnerSigns(context: ExecutionContext): Any? {
+ Validate.isAuthorized(context.sender, PERM_ADMIN_MANAGE)
+ plugin.jobDispatcher.dispatch {
+ fun getParcelCount(world: ParcelWorld) = (world.options.axisLimit * 2 + 1).let { it * it }
+ val parcelCount = plugin.parcelProvider.worlds.values.sumBy { getParcelCount(it) }.toDouble()
+ var processed = 0
+ for (world in plugin.parcelProvider.worlds.values) {
+ markSuspensionPoint()
+
+ val container = world.container as? DefaultParcelContainer
+ if (container == null) {
+ processed += getParcelCount(world)
+ setProgress(processed / parcelCount)
+ continue
+ }
+
+ for (parcel in container.getAllParcels()) {
+ parcel.updateOwnerSign(force = true)
+ processed++
+ setProgress(processed / parcelCount)
+ }
+ }
+ }.reportProgressUpdates(context, "Updating")
+ return null
+ }
+
+ @Cmd("dispose")
+ @RequireParcelPrivilege(Privilege.ADMIN)
+ fun ParcelScope.cmdDispose(): Any? {
+ parcel.dispose()
+ return "Data of (${parcel.id.idString}) has been disposed"
+ }
+
+ @Cmd("reset")
+ @RequireParcelPrivilege(Privilege.ADMIN)
+ fun ParcelScope.cmdReset(context: ExecutionContext, @Flag sure: Boolean): Any? {
+ Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel")
+ if (!sure) return areYouSureMessage(context)
+
+ parcel.dispose()
+ world.blockManager.clearParcel(parcel.id)?.reportProgressUpdates(context, "Reset")
+ return "Data of (${parcel.id.idString}) has been disposed"
+ }
+
+ @Cmd("swap")
+ @RequireParcelPrivilege(Privilege.ADMIN)
+ fun ParcelScope.cmdSwap(
+ context: ExecutionContext,
+ @TargetKind(TargetKind.ID) target: ParcelTarget,
+ @Flag sure: Boolean
+ ): Any? {
+ Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel")
+ if (!sure) return areYouSureMessage(context)
+
+ val parcel2 = (target as ParcelTarget.ByID).getParcel()
+ ?: throw CommandException("Invalid parcel target")
+
+ // Validate.isTrue(parcel2.world == world, "Parcel must be in the same world")
+ Validate.isTrue(!parcel2.hasBlockVisitors, "A process is already running in that parcel")
+
+ val data = parcel.data
+ parcel.copyData(parcel2.data)
+ parcel2.copyData(data)
+
+ val job = plugin.parcelProvider.swapParcels(parcel.id, parcel2.id)?.reportProgressUpdates(context, "Swap")
+ Validate.notNull(job, "A process is already running in some parcel (internal error)")
+ return null
+ }
+
}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsAdminPrivilegesGlobal.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsAdminPrivilegesGlobal.kt index cee3e62..a2bd8d1 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsAdminPrivilegesGlobal.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsAdminPrivilegesGlobal.kt @@ -1,132 +1,132 @@ -@file:Suppress("NON_EXHAUSTIVE_WHEN") - -package io.dico.parcels2.command - -import io.dico.dicore.command.ExecutionContext -import io.dico.dicore.command.Validate -import io.dico.dicore.command.annotation.Cmd -import io.dico.dicore.command.annotation.Desc -import io.dico.parcels2.* -import io.dico.parcels2.Privilege.BANNED -import io.dico.parcels2.Privilege.CAN_BUILD -import io.dico.parcels2.PrivilegeChangeResult.* -import io.dico.parcels2.defaultimpl.InfoBuilder -import io.dico.parcels2.util.ext.PERM_ADMIN_MANAGE -import org.bukkit.OfflinePlayer - -class CommandsAdminPrivilegesGlobal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { - private val data - inline get() = plugin.globalPrivileges - - private fun checkContext(context: ExecutionContext, owner: OfflinePlayer, changing: Boolean = true): OfflinePlayer { - if (changing) { - checkConnected("have privileges changed") - } - val sender = context.sender - if (sender !== owner) { - Validate.isAuthorized(sender, PERM_ADMIN_MANAGE) - } - return owner - } - - @Cmd("list", aliases = ["l"]) - @Desc( - "List globally declared privileges, players you", - "allowed to build on or banned from all your parcels", - shortVersion = "lists globally declared privileges" - ) - fun cmdList(context: ExecutionContext, owner: OfflinePlayer): Any? { - checkContext(context, owner, changing = false) - val map = plugin.globalPrivileges[owner] - Validate.isTrue(map.hasAnyDeclaredPrivileges(), "This user has not declared any global privileges") - - return StringBuilder().apply { - with(InfoBuilder) { - appendProfilesWithPrivilege("Globally Allowed", map, null, CAN_BUILD) - appendProfilesWithPrivilege("Globally Banned", map, null, BANNED) - } - }.toString() - } - - @Cmd("entrust") - @Desc( - "Allows a player to manage globally", - shortVersion = "allows a player to manage globally" - ) - suspend fun cmdEntrust(context: ExecutionContext, owner: OfflinePlayer, player: PlayerProfile): Any? = - when (data[checkContext(context, owner)].allowManage(toPrivilegeKey(player))) { - FAIL_OWNER -> err("The target cannot be the owner themselves") - FAIL -> err("${player.name} is already allowed to manage globally") - SUCCESS -> "${player.name} is now allowed to manage globally" - } - - @Cmd("distrust") - @Desc( - "Disallows a player to manage globally,", - "they will still be able to build", - shortVersion = "disallows a player to manage globally" - ) - suspend fun cmdDistrust(context: ExecutionContext, owner: OfflinePlayer, player: PlayerProfile): Any? = - when (data[checkContext(context, owner)].disallowManage(toPrivilegeKey(player))) { - FAIL_OWNER -> err("The target cannot be the owner themselves") - FAIL -> err("${player.name} is not currently allowed to manage globally") - SUCCESS -> "${player.name} is not allowed to manage globally anymore" - } - - @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" - ) - suspend fun cmdAllow(context: ExecutionContext, owner: OfflinePlayer, player: PlayerProfile): Any? = - when (data[checkContext(context, owner)].allowBuild(toPrivilegeKey(player))) { - FAIL_OWNER -> err("The target cannot be the owner themselves") - FAIL -> err("${player.name} is already allowed globally") - SUCCESS -> "${player.name} is now allowed to build globally" - } - - @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" - ) - suspend fun cmdDisallow(context: ExecutionContext, owner: OfflinePlayer, player: PlayerProfile): Any? = - when (data[checkContext(context, owner)].disallowBuild(toPrivilegeKey(player))) { - FAIL_OWNER -> err("The target cannot be the owner themselves") - FAIL -> err("${player.name} is not currently allowed globally") - SUCCESS -> "${player.name} is not allowed to build globally 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" - ) - suspend fun cmdBan(context: ExecutionContext, owner: OfflinePlayer, player: PlayerProfile): Any? = - when (data[checkContext(context, owner)].disallowEnter(toPrivilegeKey(player))) { - FAIL_OWNER -> err("The target cannot be the owner themselves") - FAIL -> err("${player.name} is already banned globally") - SUCCESS -> "${player.name} is now banned globally" - } - - @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" - ) - suspend fun cmdUnban(context: ExecutionContext, owner: OfflinePlayer, player: PlayerProfile): Any? = - when (data[checkContext(context, owner)].allowEnter(toPrivilegeKey(player))) { - FAIL_OWNER -> err("The target cannot be the owner themselves") - FAIL -> err("${player.name} is not currently banned globally") - SUCCESS -> "${player.name} is not banned globally anymore" - } - +@file:Suppress("NON_EXHAUSTIVE_WHEN")
+
+package io.dico.parcels2.command
+
+import io.dico.dicore.command.ExecutionContext
+import io.dico.dicore.command.Validate
+import io.dico.dicore.command.annotation.Cmd
+import io.dico.dicore.command.annotation.Desc
+import io.dico.parcels2.*
+import io.dico.parcels2.Privilege.BANNED
+import io.dico.parcels2.Privilege.CAN_BUILD
+import io.dico.parcels2.PrivilegeChangeResult.*
+import io.dico.parcels2.defaultimpl.InfoBuilder
+import io.dico.parcels2.util.ext.PERM_ADMIN_MANAGE
+import org.bukkit.OfflinePlayer
+
+class CommandsAdminPrivilegesGlobal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
+ private val data
+ inline get() = plugin.globalPrivileges
+
+ private fun checkContext(context: ExecutionContext, owner: OfflinePlayer, changing: Boolean = true): OfflinePlayer {
+ if (changing) {
+ checkConnected("have privileges changed")
+ }
+ val sender = context.sender
+ if (sender !== owner) {
+ Validate.isAuthorized(sender, PERM_ADMIN_MANAGE)
+ }
+ return owner
+ }
+
+ @Cmd("list", aliases = ["l"])
+ @Desc(
+ "List globally declared privileges, players you",
+ "allowed to build on or banned from all your parcels",
+ shortVersion = "lists globally declared privileges"
+ )
+ fun cmdList(context: ExecutionContext, owner: OfflinePlayer): Any? {
+ checkContext(context, owner, changing = false)
+ val map = plugin.globalPrivileges[owner]
+ Validate.isTrue(map.hasAnyDeclaredPrivileges(), "This user has not declared any global privileges")
+
+ return StringBuilder().apply {
+ with(InfoBuilder) {
+ appendProfilesWithPrivilege("Globally Allowed", map, null, CAN_BUILD)
+ appendProfilesWithPrivilege("Globally Banned", map, null, BANNED)
+ }
+ }.toString()
+ }
+
+ @Cmd("entrust")
+ @Desc(
+ "Allows a player to manage globally",
+ shortVersion = "allows a player to manage globally"
+ )
+ suspend fun cmdEntrust(context: ExecutionContext, owner: OfflinePlayer, player: PlayerProfile): Any? =
+ when (data[checkContext(context, owner)].allowManage(toPrivilegeKey(player))) {
+ FAIL_OWNER -> err("The target cannot be the owner themselves")
+ FAIL -> err("${player.name} is already allowed to manage globally")
+ SUCCESS -> "${player.name} is now allowed to manage globally"
+ }
+
+ @Cmd("distrust")
+ @Desc(
+ "Disallows a player to manage globally,",
+ "they will still be able to build",
+ shortVersion = "disallows a player to manage globally"
+ )
+ suspend fun cmdDistrust(context: ExecutionContext, owner: OfflinePlayer, player: PlayerProfile): Any? =
+ when (data[checkContext(context, owner)].disallowManage(toPrivilegeKey(player))) {
+ FAIL_OWNER -> err("The target cannot be the owner themselves")
+ FAIL -> err("${player.name} is not currently allowed to manage globally")
+ SUCCESS -> "${player.name} is not allowed to manage globally anymore"
+ }
+
+ @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"
+ )
+ suspend fun cmdAllow(context: ExecutionContext, owner: OfflinePlayer, player: PlayerProfile): Any? =
+ when (data[checkContext(context, owner)].allowBuild(toPrivilegeKey(player))) {
+ FAIL_OWNER -> err("The target cannot be the owner themselves")
+ FAIL -> err("${player.name} is already allowed globally")
+ SUCCESS -> "${player.name} is now allowed to build globally"
+ }
+
+ @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"
+ )
+ suspend fun cmdDisallow(context: ExecutionContext, owner: OfflinePlayer, player: PlayerProfile): Any? =
+ when (data[checkContext(context, owner)].disallowBuild(toPrivilegeKey(player))) {
+ FAIL_OWNER -> err("The target cannot be the owner themselves")
+ FAIL -> err("${player.name} is not currently allowed globally")
+ SUCCESS -> "${player.name} is not allowed to build globally 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"
+ )
+ suspend fun cmdBan(context: ExecutionContext, owner: OfflinePlayer, player: PlayerProfile): Any? =
+ when (data[checkContext(context, owner)].disallowEnter(toPrivilegeKey(player))) {
+ FAIL_OWNER -> err("The target cannot be the owner themselves")
+ FAIL -> err("${player.name} is already banned globally")
+ SUCCESS -> "${player.name} is now banned globally"
+ }
+
+ @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"
+ )
+ suspend fun cmdUnban(context: ExecutionContext, owner: OfflinePlayer, player: PlayerProfile): Any? =
+ when (data[checkContext(context, owner)].allowEnter(toPrivilegeKey(player))) {
+ FAIL_OWNER -> err("The target cannot be the owner themselves")
+ FAIL -> err("${player.name} is not currently banned globally")
+ SUCCESS -> "${player.name} is not banned globally 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 e0bde7d..3f166ad 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt @@ -1,162 +1,162 @@ -package io.dico.parcels2.command - -import io.dico.dicore.Formatting -import io.dico.dicore.command.* -import io.dico.dicore.command.IContextFilter.Priority.PERMISSION -import io.dico.dicore.command.annotation.Cmd -import io.dico.dicore.command.annotation.PreprocessArgs -import io.dico.dicore.command.annotation.RequireParameters -import io.dico.dicore.command.parameter.ArgumentBuffer -import io.dico.parcels2.* -import io.dico.parcels2.blockvisitor.RegionTraverser -import io.dico.parcels2.util.ext.PERM_ADMIN_MANAGE -import io.dico.parcels2.util.ext.PERM_BAN_BYPASS -import io.dico.parcels2.util.ext.PERM_BUILD_ANYWHERE -import kotlinx.coroutines.launch -import org.bukkit.Bukkit -import org.bukkit.Material -import org.bukkit.block.BlockFace -import org.bukkit.block.data.Directional -import org.bukkit.command.CommandSender -import org.bukkit.entity.Player -import java.util.Random - -class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { - - @Cmd("reloadoptions") - fun reloadOptions() { - plugin.loadOptions() - } - - @Cmd("tpworld") - fun tpWorld(sender: Player, worldName: String): String { - if (worldName == "list") { - return Bukkit.getWorlds().joinToString("\n- ", "- ", "") - } - val world = Bukkit.getWorld(worldName) ?: err("World $worldName is not loaded") - sender.teleport(world.spawnLocation) - return "Teleported you to $worldName spawn" - } - - @Cmd("make_mess") - @RequireParcelPrivilege(Privilege.OWNER) - fun ParcelScope.cmdMakeMess(context: ExecutionContext) { - Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel") - - val server = plugin.server - val blockDatas = arrayOf( - server.createBlockData(Material.BLUE_WOOL), - server.createBlockData(Material.LIME_WOOL), - server.createBlockData(Material.GLASS), - server.createBlockData(Material.STONE_SLAB), - server.createBlockData(Material.STONE), - server.createBlockData(Material.QUARTZ_BLOCK), - server.createBlockData(Material.BROWN_CONCRETE) - ) - val random = Random() - - world.blockManager.tryDoBlockOperation(plugin.parcelProvider, parcel.id, traverser = RegionTraverser.upward) { block -> - block.blockData = blockDatas[random.nextInt(7)] - }?.onProgressUpdate(1000, 1000) { progress, elapsedTime -> - context.sendMessage( - EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed" - .format(progress * 100, elapsedTime / 1000.0) - ) - } - } - - @Cmd("directionality", aliases = ["dir"]) - fun cmdDirectionality(sender: Player, context: ExecutionContext, material: Material): Any? { - val senderLoc = sender.location - val block = senderLoc.add(senderLoc.direction.setY(0).normalize().multiply(2).toLocation(sender.world)).block - - val blockData = Bukkit.createBlockData(material) - if (blockData is Directional) { - blockData.facing = BlockFace.SOUTH - } - - block.blockData = blockData - return if (blockData is Directional) "The block is facing south" else "The block is not directional, however it implements " + - blockData.javaClass.interfaces!!.contentToString() - } - - @Cmd("jobs") - fun cmdJobs(): Any? { - val workers = plugin.jobDispatcher.jobs - println(workers.map { it.coroutine }.joinToString(separator = "\n")) - return "Task count: ${workers.size}" - } - - @Cmd("complete_jobs") - fun cmdCompleteJobs(): Any? = cmdJobs().also { - plugin.launch { plugin.jobDispatcher.completeAllTasks() } - } - - @Cmd("message") - @PreprocessArgs - fun cmdMessage(sender: CommandSender, message: String): Any? { - // testing @PreprocessArgs which merges "hello there" into a single argument - sender.sendMessage(Formatting.translate(message)) - return null - } - - @Cmd("hasperm") - fun cmdHasperm(target: Player, permission: String): Any? { - return target.hasPermission(permission).toString() - } - - @Cmd("permissions") - fun cmdPermissions(context: ExecutionContext, of: Player, vararg address: String): Any? { - val target = context.address.dispatcherForTree.getDeepChild(ArgumentBuffer(address)) - Validate.isTrue(target.depth == address.size && target.hasCommand(), "Not found: /${address.joinToString(separator = " ")}") - return getPermissionsOf(target).joinToString(separator = "\n") { "$it: ${of.hasPermission(it)}" } - } - - @Cmd("privilege") - @RequireParameters(1) - suspend fun ParcelScope.cmdPrivilege(target: PlayerProfile, adminPerm: String?): Any? { - val key = toPrivilegeKey(target) - - val perm = when (adminPerm) { - "none" -> null - "build" -> PERM_BUILD_ANYWHERE - "manage", null -> PERM_ADMIN_MANAGE - "enter" -> PERM_BAN_BYPASS - else -> err("adminPerm should be build, manager or enter") - } - - val privilege = if (perm == null) { - parcel.getStoredPrivilege(key) - } else { - if (key is PlayerProfile.Star) err("* can't have permissions") - parcel.getEffectivePrivilege(key.player!!, perm) - } - - return privilege.toString() - } - - private fun getPermissionsOf(address: ICommandAddress) = getPermissionsOf(address, emptyArray(), mutableListOf()) - - private fun getPermissionsOf(address: ICommandAddress, path: Array<String>, result: MutableList<String>): List<String> { - val command = address.command ?: return result - - var inherited = false - for (filter in command.contextFilters) { - when (filter) { - is PermissionContextFilter -> { - if (path.isEmpty()) result.add(filter.permission) - else if (filter.isInheritable) result.add(filter.getInheritedPermission(path)) - } - is InheritingContextFilter -> { - if (filter.priority == PERMISSION && address.hasParent() && !inherited) { - inherited = true - getPermissionsOf(address.parent, arrayOf(address.mainKey, *path), result) - } - } - } - } - - return result - } - +package io.dico.parcels2.command
+
+import io.dico.dicore.Formatting
+import io.dico.dicore.command.*
+import io.dico.dicore.command.IContextFilter.Priority.PERMISSION
+import io.dico.dicore.command.annotation.Cmd
+import io.dico.dicore.command.annotation.PreprocessArgs
+import io.dico.dicore.command.annotation.RequireParameters
+import io.dico.dicore.command.parameter.ArgumentBuffer
+import io.dico.parcels2.*
+import io.dico.parcels2.blockvisitor.RegionTraverser
+import io.dico.parcels2.util.ext.PERM_ADMIN_MANAGE
+import io.dico.parcels2.util.ext.PERM_BAN_BYPASS
+import io.dico.parcels2.util.ext.PERM_BUILD_ANYWHERE
+import kotlinx.coroutines.launch
+import org.bukkit.Bukkit
+import org.bukkit.Material
+import org.bukkit.block.BlockFace
+import org.bukkit.block.data.Directional
+import org.bukkit.command.CommandSender
+import org.bukkit.entity.Player
+import java.util.Random
+
+class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
+
+ @Cmd("reloadoptions")
+ fun reloadOptions() {
+ plugin.loadOptions()
+ }
+
+ @Cmd("tpworld")
+ fun tpWorld(sender: Player, worldName: String): String {
+ if (worldName == "list") {
+ return Bukkit.getWorlds().joinToString("\n- ", "- ", "")
+ }
+ val world = Bukkit.getWorld(worldName) ?: err("World $worldName is not loaded")
+ sender.teleport(world.spawnLocation)
+ return "Teleported you to $worldName spawn"
+ }
+
+ @Cmd("make_mess")
+ @RequireParcelPrivilege(Privilege.OWNER)
+ fun ParcelScope.cmdMakeMess(context: ExecutionContext) {
+ Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel")
+
+ val server = plugin.server
+ val blockDatas = arrayOf(
+ server.createBlockData(Material.BLUE_WOOL),
+ server.createBlockData(Material.LIME_WOOL),
+ server.createBlockData(Material.GLASS),
+ server.createBlockData(Material.STONE_SLAB),
+ server.createBlockData(Material.STONE),
+ server.createBlockData(Material.QUARTZ_BLOCK),
+ server.createBlockData(Material.BROWN_CONCRETE)
+ )
+ val random = Random()
+
+ world.blockManager.tryDoBlockOperation(plugin.parcelProvider, parcel.id, traverser = RegionTraverser.upward) { block ->
+ block.blockData = blockDatas[random.nextInt(7)]
+ }?.onProgressUpdate(1000, 1000) { progress, elapsedTime ->
+ context.sendMessage(
+ EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed"
+ .format(progress * 100, elapsedTime / 1000.0)
+ )
+ }
+ }
+
+ @Cmd("directionality", aliases = ["dir"])
+ fun cmdDirectionality(sender: Player, context: ExecutionContext, material: Material): Any? {
+ val senderLoc = sender.location
+ val block = senderLoc.add(senderLoc.direction.setY(0).normalize().multiply(2).toLocation(sender.world)).block
+
+ val blockData = Bukkit.createBlockData(material)
+ if (blockData is Directional) {
+ blockData.facing = BlockFace.SOUTH
+ }
+
+ block.blockData = blockData
+ return if (blockData is Directional) "The block is facing south" else "The block is not directional, however it implements " +
+ blockData.javaClass.interfaces!!.contentToString()
+ }
+
+ @Cmd("jobs")
+ fun cmdJobs(): Any? {
+ val workers = plugin.jobDispatcher.jobs
+ println(workers.map { it.coroutine }.joinToString(separator = "\n"))
+ return "Task count: ${workers.size}"
+ }
+
+ @Cmd("complete_jobs")
+ fun cmdCompleteJobs(): Any? = cmdJobs().also {
+ plugin.launch { plugin.jobDispatcher.completeAllTasks() }
+ }
+
+ @Cmd("message")
+ @PreprocessArgs
+ fun cmdMessage(sender: CommandSender, message: String): Any? {
+ // testing @PreprocessArgs which merges "hello there" into a single argument
+ sender.sendMessage(Formatting.translate(message))
+ return null
+ }
+
+ @Cmd("hasperm")
+ fun cmdHasperm(target: Player, permission: String): Any? {
+ return target.hasPermission(permission).toString()
+ }
+
+ @Cmd("permissions")
+ fun cmdPermissions(context: ExecutionContext, of: Player, vararg address: String): Any? {
+ val target = context.address.dispatcherForTree.getDeepChild(ArgumentBuffer(address))
+ Validate.isTrue(target.depth == address.size && target.hasCommand(), "Not found: /${address.joinToString(separator = " ")}")
+ return getPermissionsOf(target).joinToString(separator = "\n") { "$it: ${of.hasPermission(it)}" }
+ }
+
+ @Cmd("privilege")
+ @RequireParameters(1)
+ suspend fun ParcelScope.cmdPrivilege(target: PlayerProfile, adminPerm: String?): Any? {
+ val key = toPrivilegeKey(target)
+
+ val perm = when (adminPerm) {
+ "none" -> null
+ "build" -> PERM_BUILD_ANYWHERE
+ "manage", null -> PERM_ADMIN_MANAGE
+ "enter" -> PERM_BAN_BYPASS
+ else -> err("adminPerm should be build, manager or enter")
+ }
+
+ val privilege = if (perm == null) {
+ parcel.getStoredPrivilege(key)
+ } else {
+ if (key is PlayerProfile.Star) err("* can't have permissions")
+ parcel.getEffectivePrivilege(key.player!!, perm)
+ }
+
+ return privilege.toString()
+ }
+
+ private fun getPermissionsOf(address: ICommandAddress) = getPermissionsOf(address, emptyArray(), mutableListOf())
+
+ private fun getPermissionsOf(address: ICommandAddress, path: Array<String>, result: MutableList<String>): List<String> {
+ val command = address.command ?: return result
+
+ var inherited = false
+ for (filter in command.contextFilters) {
+ when (filter) {
+ is PermissionContextFilter -> {
+ if (path.isEmpty()) result.add(filter.permission)
+ else if (filter.isInheritable) result.add(filter.getInheritedPermission(path))
+ }
+ is InheritingContextFilter -> {
+ if (filter.priority == PERMISSION && address.hasParent() && !inherited) {
+ inherited = true
+ getPermissionsOf(address.parent, arrayOf(address.mainKey, *path), result)
+ }
+ }
+ }
+ }
+
+ return result
+ }
+
}
\ 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 c918b80..61e8d9e 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt @@ -1,142 +1,142 @@ -package io.dico.parcels2.command - -import io.dico.dicore.command.ExecutionContext -import io.dico.dicore.command.Validate -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.ParcelsPlugin -import io.dico.parcels2.PlayerProfile -import io.dico.parcels2.Privilege -import io.dico.parcels2.command.ParcelTarget.TargetKind -import io.dico.parcels2.util.ext.hasParcelHomeOthers -import io.dico.parcels2.util.ext.hasPermAdminManage -import io.dico.parcels2.util.ext.uuid -import org.bukkit.block.Biome -import org.bukkit.entity.Player - -class CommandsGeneral(plugin: ParcelsPlugin, parent: SpecialCommandAddress) : AbstractParcelCommands(plugin) { - - @Cmd("auto") - @Desc( - "Finds the unclaimed parcel nearest to origin,", - "and gives it to you", - shortVersion = "sets you up with a fresh, unclaimed parcel" - ) - suspend fun WorldScope.cmdAuto(player: Player): Any? { - checkConnected("be claimed") - checkParcelLimit(player, world) - - val parcel = world.nextEmptyParcel() - ?: err("This world is full, please ask an admin to upsize it") - parcel.owner = PlayerProfile(uuid = player.uuid) - player.teleport(parcel.homeLocation) - return "Enjoy your new parcel!" - } - - @Cmd("info", aliases = ["i"]) - @Desc( - "Displays general information", - "about the parcel you're on", - shortVersion = "displays information about this parcel" - ) - fun ParcelScope.cmdInfo(player: Player) = parcel.infoString - - init { - parent.addSpeciallyTreatedKeys("home", "h") - } - - @Cmd("home", aliases = ["h"]) - @Desc( - "Teleports you to your parcels,", - "unless another player was specified.", - "You can specify an index number if you have", - "more than one parcel", - shortVersion = "teleports you to parcels" - ) - @RequireParameters(0) - suspend fun cmdHome( - player: Player, - @TargetKind(TargetKind.OWNER_REAL) target: ParcelTarget - ): Any? { - return cmdGoto(player, target) - } - - @Cmd("tp", aliases = ["teleport"]) - suspend fun cmdTp( - player: Player, - @TargetKind(TargetKind.ID) target: ParcelTarget - ): Any? { - return cmdGoto(player, target) - } - - @Cmd("goto") - suspend fun cmdGoto( - player: Player, - @TargetKind(TargetKind.ANY) target: ParcelTarget - ): Any? { - if (target is ParcelTarget.ByOwner) { - target.resolveOwner(plugin.storage) - if (!target.owner.matches(player) && !player.hasParcelHomeOthers) { - err("You do not have permission to teleport to other people's parcels") - } - } - - val match = target.getParcelSuspend(plugin.storage) - ?: err("The specified parcel could not be matched") - player.teleport(match.homeLocation) - return null - } - - @Cmd("goto_fake") - suspend fun cmdGotoFake( - player: Player, - @TargetKind(TargetKind.OWNER_FAKE) target: ParcelTarget - ): Any? { - return cmdGoto(player, target) - } - - @Cmd("claim") - @Desc( - "If this parcel is unowned, makes you the owner", - shortVersion = "claims this parcel" - ) - suspend fun ParcelScope.cmdClaim(player: Player): Any? { - checkConnected("be claimed") - parcel.owner.takeIf { !player.hasPermAdminManage }?.let { - err(if (it.matches(player)) "You already own this parcel" else "This parcel is not available") - } - - checkParcelLimit(player, world) - parcel.owner = PlayerProfile(player) - return "Enjoy your new parcel!" - } - - @Cmd("unclaim") - @Desc("Unclaims this parcel") - @RequireParcelPrivilege(Privilege.OWNER) - fun ParcelScope.cmdUnclaim(player: Player): Any? { - checkConnected("be unclaimed") - parcel.dispose() - return "Your parcel has been disposed" - } - - @Cmd("clear") - @RequireParcelPrivilege(Privilege.OWNER) - fun ParcelScope.cmdClear(context: ExecutionContext, @Flag sure: Boolean): Any? { - Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel") - if (!sure) return areYouSureMessage(context) - world.blockManager.clearParcel(parcel.id)?.reportProgressUpdates(context, "Clear") - return null - } - - @Cmd("setbiome") - @RequireParcelPrivilege(Privilege.OWNER) - fun ParcelScope.cmdSetbiome(context: ExecutionContext, biome: Biome): Any? { - Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel") - world.blockManager.setBiome(parcel.id, biome)?.reportProgressUpdates(context, "Biome change") - return null - } - +package io.dico.parcels2.command
+
+import io.dico.dicore.command.ExecutionContext
+import io.dico.dicore.command.Validate
+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.ParcelsPlugin
+import io.dico.parcels2.PlayerProfile
+import io.dico.parcels2.Privilege
+import io.dico.parcels2.command.ParcelTarget.TargetKind
+import io.dico.parcels2.util.ext.hasParcelHomeOthers
+import io.dico.parcels2.util.ext.hasPermAdminManage
+import io.dico.parcels2.util.ext.uuid
+import org.bukkit.block.Biome
+import org.bukkit.entity.Player
+
+class CommandsGeneral(plugin: ParcelsPlugin, parent: SpecialCommandAddress) : AbstractParcelCommands(plugin) {
+
+ @Cmd("auto")
+ @Desc(
+ "Finds the unclaimed parcel nearest to origin,",
+ "and gives it to you",
+ shortVersion = "sets you up with a fresh, unclaimed parcel"
+ )
+ suspend fun WorldScope.cmdAuto(player: Player): Any? {
+ checkConnected("be claimed")
+ checkParcelLimit(player, world)
+
+ val parcel = world.nextEmptyParcel()
+ ?: err("This world is full, please ask an admin to upsize it")
+ parcel.owner = PlayerProfile(uuid = player.uuid)
+ player.teleport(parcel.homeLocation)
+ return "Enjoy your new parcel!"
+ }
+
+ @Cmd("info", aliases = ["i"])
+ @Desc(
+ "Displays general information",
+ "about the parcel you're on",
+ shortVersion = "displays information about this parcel"
+ )
+ fun ParcelScope.cmdInfo(player: Player) = parcel.infoString
+
+ init {
+ parent.addSpeciallyTreatedKeys("home", "h")
+ }
+
+ @Cmd("home", aliases = ["h"])
+ @Desc(
+ "Teleports you to your parcels,",
+ "unless another player was specified.",
+ "You can specify an index number if you have",
+ "more than one parcel",
+ shortVersion = "teleports you to parcels"
+ )
+ @RequireParameters(0)
+ suspend fun cmdHome(
+ player: Player,
+ @TargetKind(TargetKind.OWNER_REAL) target: ParcelTarget
+ ): Any? {
+ return cmdGoto(player, target)
+ }
+
+ @Cmd("tp", aliases = ["teleport"])
+ suspend fun cmdTp(
+ player: Player,
+ @TargetKind(TargetKind.ID) target: ParcelTarget
+ ): Any? {
+ return cmdGoto(player, target)
+ }
+
+ @Cmd("goto")
+ suspend fun cmdGoto(
+ player: Player,
+ @TargetKind(TargetKind.ANY) target: ParcelTarget
+ ): Any? {
+ if (target is ParcelTarget.ByOwner) {
+ target.resolveOwner(plugin.storage)
+ if (!target.owner.matches(player) && !player.hasParcelHomeOthers) {
+ err("You do not have permission to teleport to other people's parcels")
+ }
+ }
+
+ val match = target.getParcelSuspend(plugin.storage)
+ ?: err("The specified parcel could not be matched")
+ player.teleport(match.homeLocation)
+ return null
+ }
+
+ @Cmd("goto_fake")
+ suspend fun cmdGotoFake(
+ player: Player,
+ @TargetKind(TargetKind.OWNER_FAKE) target: ParcelTarget
+ ): Any? {
+ return cmdGoto(player, target)
+ }
+
+ @Cmd("claim")
+ @Desc(
+ "If this parcel is unowned, makes you the owner",
+ shortVersion = "claims this parcel"
+ )
+ suspend fun ParcelScope.cmdClaim(player: Player): Any? {
+ checkConnected("be claimed")
+ parcel.owner.takeIf { !player.hasPermAdminManage }?.let {
+ err(if (it.matches(player)) "You already own this parcel" else "This parcel is not available")
+ }
+
+ checkParcelLimit(player, world)
+ parcel.owner = PlayerProfile(player)
+ return "Enjoy your new parcel!"
+ }
+
+ @Cmd("unclaim")
+ @Desc("Unclaims this parcel")
+ @RequireParcelPrivilege(Privilege.OWNER)
+ fun ParcelScope.cmdUnclaim(player: Player): Any? {
+ checkConnected("be unclaimed")
+ parcel.dispose()
+ return "Your parcel has been disposed"
+ }
+
+ @Cmd("clear")
+ @RequireParcelPrivilege(Privilege.OWNER)
+ fun ParcelScope.cmdClear(context: ExecutionContext, @Flag sure: Boolean): Any? {
+ Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel")
+ if (!sure) return areYouSureMessage(context)
+ world.blockManager.clearParcel(parcel.id)?.reportProgressUpdates(context, "Clear")
+ return null
+ }
+
+ @Cmd("setbiome")
+ @RequireParcelPrivilege(Privilege.OWNER)
+ fun ParcelScope.cmdSetbiome(context: ExecutionContext, biome: Biome): Any? {
+ Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel")
+ world.blockManager.setBiome(parcel.id, biome)?.reportProgressUpdates(context, "Biome change")
+ return null
+ }
+
}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsPrivilegesGlobal.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsPrivilegesGlobal.kt index 3b4234c..33ffb8b 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsPrivilegesGlobal.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsPrivilegesGlobal.kt @@ -1,77 +1,77 @@ -package io.dico.parcels2.command - -import io.dico.dicore.command.ExecutionContext -import io.dico.dicore.command.annotation.Cmd -import io.dico.dicore.command.annotation.Desc -import io.dico.parcels2.ParcelsPlugin -import io.dico.parcels2.PlayerProfile -import org.bukkit.entity.Player - -class CommandsPrivilegesGlobal(plugin: ParcelsPlugin, - val adminVersion: CommandsAdminPrivilegesGlobal) : AbstractParcelCommands(plugin) { - @Cmd("list", aliases = ["l"]) - @Desc( - "List globally declared privileges, players you", - "allowed to build on or banned from all your parcels", - shortVersion = "lists globally declared privileges" - ) - fun cmdList(sender: Player, context: ExecutionContext) = - adminVersion.cmdList(context, sender) - - @Cmd("entrust") - @Desc( - "Allows a player to manage globally", - shortVersion = "allows a player to manage globally" - ) - suspend fun cmdEntrust(sender: Player, context: ExecutionContext, player: PlayerProfile) = - adminVersion.cmdEntrust(context, sender, player) - - @Cmd("distrust") - @Desc( - "Disallows a player to manage globally,", - "they will still be able to build", - shortVersion = "disallows a player to manage globally" - ) - suspend fun cmdDistrust(sender: Player, context: ExecutionContext, player: PlayerProfile) = - adminVersion.cmdDistrust(context, sender, 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" - ) - suspend fun cmdAllow(sender: Player, context: ExecutionContext, player: PlayerProfile) = - adminVersion.cmdAllow(context, sender, player) - - @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" - ) - suspend fun cmdDisallow(sender: Player, context: ExecutionContext, player: PlayerProfile) = - adminVersion.cmdDisallow(context, sender, player) - - @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" - ) - suspend fun cmdBan(sender: Player, context: ExecutionContext, player: PlayerProfile) = - adminVersion.cmdBan(context, sender, player) - - @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" - ) - suspend fun cmdUnban(sender: Player, context: ExecutionContext, player: PlayerProfile) = - adminVersion.cmdUnban(context, sender, player) +package io.dico.parcels2.command
+
+import io.dico.dicore.command.ExecutionContext
+import io.dico.dicore.command.annotation.Cmd
+import io.dico.dicore.command.annotation.Desc
+import io.dico.parcels2.ParcelsPlugin
+import io.dico.parcels2.PlayerProfile
+import org.bukkit.entity.Player
+
+class CommandsPrivilegesGlobal(plugin: ParcelsPlugin,
+ val adminVersion: CommandsAdminPrivilegesGlobal) : AbstractParcelCommands(plugin) {
+ @Cmd("list", aliases = ["l"])
+ @Desc(
+ "List globally declared privileges, players you",
+ "allowed to build on or banned from all your parcels",
+ shortVersion = "lists globally declared privileges"
+ )
+ fun cmdList(sender: Player, context: ExecutionContext) =
+ adminVersion.cmdList(context, sender)
+
+ @Cmd("entrust")
+ @Desc(
+ "Allows a player to manage globally",
+ shortVersion = "allows a player to manage globally"
+ )
+ suspend fun cmdEntrust(sender: Player, context: ExecutionContext, player: PlayerProfile) =
+ adminVersion.cmdEntrust(context, sender, player)
+
+ @Cmd("distrust")
+ @Desc(
+ "Disallows a player to manage globally,",
+ "they will still be able to build",
+ shortVersion = "disallows a player to manage globally"
+ )
+ suspend fun cmdDistrust(sender: Player, context: ExecutionContext, player: PlayerProfile) =
+ adminVersion.cmdDistrust(context, sender, 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"
+ )
+ suspend fun cmdAllow(sender: Player, context: ExecutionContext, player: PlayerProfile) =
+ adminVersion.cmdAllow(context, sender, player)
+
+ @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"
+ )
+ suspend fun cmdDisallow(sender: Player, context: ExecutionContext, player: PlayerProfile) =
+ adminVersion.cmdDisallow(context, sender, player)
+
+ @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"
+ )
+ suspend fun cmdBan(sender: Player, context: ExecutionContext, player: PlayerProfile) =
+ adminVersion.cmdBan(context, sender, player)
+
+ @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"
+ )
+ suspend fun cmdUnban(sender: Player, context: ExecutionContext, player: PlayerProfile) =
+ adminVersion.cmdUnban(context, sender, player)
}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsPrivilegesLocal.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsPrivilegesLocal.kt index 21910b1..e3aba22 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsPrivilegesLocal.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsPrivilegesLocal.kt @@ -1,144 +1,144 @@ -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.* -import io.dico.parcels2.PrivilegeChangeResult.* -import io.dico.parcels2.util.ext.PERM_ADMIN_MANAGE -import io.dico.parcels2.util.ext.hasPermAdminManage -import org.bukkit.entity.Player - -class CommandsPrivilegesLocal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { - - private fun ParcelScope.checkPrivilege(sender: Player, key: PrivilegeKey) { - val senderPrivilege = parcel.getEffectivePrivilege(sender, PERM_ADMIN_MANAGE) - val targetPrivilege = parcel.getStoredPrivilege(key) - Validate.isTrue(senderPrivilege > targetPrivilege, "You may not change the privilege of ${key.notNullName}") - } - - private fun ParcelScope.checkOwned(sender: Player) { - Validate.isTrue(parcel.owner != null || sender.hasPermAdminManage, "This parcel is unowned") - } - - @Cmd("entrust") - @Desc( - "Allows a player to manage this parcel", - shortVersion = "allows a player to manage this parcel" - ) - @RequireParcelPrivilege(Privilege.OWNER) - suspend fun ParcelScope.cmdEntrust(sender: Player, player: PlayerProfile): Any? { - checkConnected("have privileges changed") - checkOwned(sender) - - val key = toPrivilegeKey(player) - return when (parcel.allowManage(key)) { - FAIL_OWNER -> err("The target already owns the parcel") - FAIL -> err("${player.name} is already allowed to manage this parcel") - SUCCESS -> "${player.name} is now allowed to manage this parcel" - } - } - - @Cmd("distrust") - @Desc( - "Disallows a player to manage this parcel,", - "they will still be able to build", - shortVersion = "disallows a player to manage this parcel" - ) - @RequireParcelPrivilege(Privilege.OWNER) - suspend fun ParcelScope.cmdDistrust(sender: Player, player: PlayerProfile): Any? { - checkConnected("have privileges changed") - checkOwned(sender) - - val key = toPrivilegeKey(player) - return when (parcel.disallowManage(key)) { - FAIL_OWNER -> err("The target owns the parcel and can't be distrusted") - FAIL -> err("${player.name} is not currently allowed to manage this parcel") - SUCCESS -> "${player.name} is not allowed to manage this parcel anymore" - } - } - - @Cmd("allow", aliases = ["add", "permit"]) - @Desc( - "Allows a player to build on this parcel", - shortVersion = "allows a player to build on this parcel" - ) - @RequireParcelPrivilege(Privilege.CAN_MANAGE) - suspend fun ParcelScope.cmdAllow(sender: Player, player: PlayerProfile): Any? { - checkConnected("have privileges changed") - checkOwned(sender) - - val key = toPrivilegeKey(player) - checkPrivilege(sender, key) - - return when (parcel.allowBuild(key)) { - FAIL_OWNER -> err("The target already owns the parcel") - FAIL -> err("${player.name} is already allowed to build on this parcel") - SUCCESS -> "${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" - ) - @RequireParcelPrivilege(Privilege.CAN_MANAGE) - suspend fun ParcelScope.cmdDisallow(sender: Player, player: PlayerProfile): Any? { - checkConnected("have privileges changed") - checkOwned(sender) - - val key = toPrivilegeKey(player) - checkPrivilege(sender, key) - - return when (parcel.disallowBuild(key)) { - FAIL_OWNER -> err("The target owns the parcel") - FAIL -> err("${player.name} is not currently allowed to build on this parcel") - SUCCESS -> "${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" - ) - @RequireParcelPrivilege(Privilege.CAN_MANAGE) - suspend fun ParcelScope.cmdBan(sender: Player, player: PlayerProfile): Any? { - checkConnected("have privileges changed") - checkOwned(sender) - - val key = toPrivilegeKey(player) - checkPrivilege(sender, key) - - return when (parcel.disallowEnter(key)) { - FAIL_OWNER -> err("The target owns the parcel") - FAIL -> err("${player.name} is already banned from this parcel") - SUCCESS -> "${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" - ) - @RequireParcelPrivilege(Privilege.CAN_MANAGE) - suspend fun ParcelScope.cmdUnban(sender: Player, player: PlayerProfile): Any? { - checkConnected("have privileges changed") - checkOwned(sender) - - val key = toPrivilegeKey(player) - checkPrivilege(sender, key) - - return when (parcel.allowEnter(key)) { - FAIL_OWNER -> err("The target owns the parcel") - FAIL -> err("${player.name} is not currently banned from this parcel") - SUCCESS -> "${player.name} is not banned from this parcel anymore" - } - } - +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.*
+import io.dico.parcels2.PrivilegeChangeResult.*
+import io.dico.parcels2.util.ext.PERM_ADMIN_MANAGE
+import io.dico.parcels2.util.ext.hasPermAdminManage
+import org.bukkit.entity.Player
+
+class CommandsPrivilegesLocal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
+
+ private fun ParcelScope.checkPrivilege(sender: Player, key: PrivilegeKey) {
+ val senderPrivilege = parcel.getEffectivePrivilege(sender, PERM_ADMIN_MANAGE)
+ val targetPrivilege = parcel.getStoredPrivilege(key)
+ Validate.isTrue(senderPrivilege > targetPrivilege, "You may not change the privilege of ${key.notNullName}")
+ }
+
+ private fun ParcelScope.checkOwned(sender: Player) {
+ Validate.isTrue(parcel.owner != null || sender.hasPermAdminManage, "This parcel is unowned")
+ }
+
+ @Cmd("entrust")
+ @Desc(
+ "Allows a player to manage this parcel",
+ shortVersion = "allows a player to manage this parcel"
+ )
+ @RequireParcelPrivilege(Privilege.OWNER)
+ suspend fun ParcelScope.cmdEntrust(sender: Player, player: PlayerProfile): Any? {
+ checkConnected("have privileges changed")
+ checkOwned(sender)
+
+ val key = toPrivilegeKey(player)
+ return when (parcel.allowManage(key)) {
+ FAIL_OWNER -> err("The target already owns the parcel")
+ FAIL -> err("${player.name} is already allowed to manage this parcel")
+ SUCCESS -> "${player.name} is now allowed to manage this parcel"
+ }
+ }
+
+ @Cmd("distrust")
+ @Desc(
+ "Disallows a player to manage this parcel,",
+ "they will still be able to build",
+ shortVersion = "disallows a player to manage this parcel"
+ )
+ @RequireParcelPrivilege(Privilege.OWNER)
+ suspend fun ParcelScope.cmdDistrust(sender: Player, player: PlayerProfile): Any? {
+ checkConnected("have privileges changed")
+ checkOwned(sender)
+
+ val key = toPrivilegeKey(player)
+ return when (parcel.disallowManage(key)) {
+ FAIL_OWNER -> err("The target owns the parcel and can't be distrusted")
+ FAIL -> err("${player.name} is not currently allowed to manage this parcel")
+ SUCCESS -> "${player.name} is not allowed to manage this parcel anymore"
+ }
+ }
+
+ @Cmd("allow", aliases = ["add", "permit"])
+ @Desc(
+ "Allows a player to build on this parcel",
+ shortVersion = "allows a player to build on this parcel"
+ )
+ @RequireParcelPrivilege(Privilege.CAN_MANAGE)
+ suspend fun ParcelScope.cmdAllow(sender: Player, player: PlayerProfile): Any? {
+ checkConnected("have privileges changed")
+ checkOwned(sender)
+
+ val key = toPrivilegeKey(player)
+ checkPrivilege(sender, key)
+
+ return when (parcel.allowBuild(key)) {
+ FAIL_OWNER -> err("The target already owns the parcel")
+ FAIL -> err("${player.name} is already allowed to build on this parcel")
+ SUCCESS -> "${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"
+ )
+ @RequireParcelPrivilege(Privilege.CAN_MANAGE)
+ suspend fun ParcelScope.cmdDisallow(sender: Player, player: PlayerProfile): Any? {
+ checkConnected("have privileges changed")
+ checkOwned(sender)
+
+ val key = toPrivilegeKey(player)
+ checkPrivilege(sender, key)
+
+ return when (parcel.disallowBuild(key)) {
+ FAIL_OWNER -> err("The target owns the parcel")
+ FAIL -> err("${player.name} is not currently allowed to build on this parcel")
+ SUCCESS -> "${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"
+ )
+ @RequireParcelPrivilege(Privilege.CAN_MANAGE)
+ suspend fun ParcelScope.cmdBan(sender: Player, player: PlayerProfile): Any? {
+ checkConnected("have privileges changed")
+ checkOwned(sender)
+
+ val key = toPrivilegeKey(player)
+ checkPrivilege(sender, key)
+
+ return when (parcel.disallowEnter(key)) {
+ FAIL_OWNER -> err("The target owns the parcel")
+ FAIL -> err("${player.name} is already banned from this parcel")
+ SUCCESS -> "${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"
+ )
+ @RequireParcelPrivilege(Privilege.CAN_MANAGE)
+ suspend fun ParcelScope.cmdUnban(sender: Player, player: PlayerProfile): Any? {
+ checkConnected("have privileges changed")
+ checkOwned(sender)
+
+ val key = toPrivilegeKey(player)
+ checkPrivilege(sender, key)
+
+ return when (parcel.allowEnter(key)) {
+ FAIL_OWNER -> err("The target owns the parcel")
+ FAIL -> err("${player.name} is not currently banned from this parcel")
+ SUCCESS -> "${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/ParcelCommandBuilder.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt index 721ce2d..155f4f1 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt @@ -1,137 +1,137 @@ -package io.dico.parcels2.command - -import io.dico.dicore.command.* -import io.dico.dicore.command.parameter.ArgumentBuffer -import io.dico.dicore.command.predef.DefaultGroupCommand -import io.dico.dicore.command.registration.reflect.ReflectiveRegistration -import io.dico.parcels2.Interactables -import io.dico.parcels2.ParcelsPlugin -import io.dico.parcels2.logger -import io.dico.parcels2.util.ext.hasPermAdminManage -import org.bukkit.command.CommandSender -import java.util.LinkedList -import java.util.Queue - -@Suppress("UsePropertyAccessSyntax") -fun getParcelCommands(plugin: ParcelsPlugin): ICommandDispatcher = CommandBuilder().apply { - val parcelsAddress = SpecialCommandAddress() - - setChatHandler(ParcelsChatHandler()) - addParameterType(false, ParcelParameterType(plugin.parcelProvider)) - addParameterType(false, ProfileParameterType()) - addParameterType(true, ParcelTarget.PType(plugin.parcelProvider, parcelsAddress)) - - group(parcelsAddress, "parcel", "plot", "plots", "p") { - addContextFilter(IContextFilter.inheritablePermission("parcels.command")) - registerCommands(CommandsGeneral(plugin, parcelsAddress)) - registerCommands(CommandsPrivilegesLocal(plugin)) - - group("option", "opt", "o") { - setGroupDescription( - "changes interaction options for this parcel", - "Sets whether players who are not allowed to", - "build here can interact with certain things." - ) - - group("interact", "i") { - val command = ParcelOptionsInteractCommand(plugin) - Interactables.classesById.forEach { - addSubCommand(it.name, command) - } - } - } - - val adminPrivilegesGlobal = CommandsAdminPrivilegesGlobal(plugin) - - group("global", "g") { - registerCommands(CommandsPrivilegesGlobal(plugin, adminVersion = adminPrivilegesGlobal)) - } - - group("admin", "a") { - setCommand(AdminGroupCommand()) - registerCommands(CommandsAdmin(plugin)) - - group("global", "g") { - registerCommands(adminPrivilegesGlobal) - } - } - - if (!logger.isDebugEnabled) return@group - - group("debug", "d") { - registerCommands(CommandsDebug(plugin)) - } - } - - generateHelpAndSyntaxCommands(parcelsAddress) -}.getDispatcher() - -private inline fun CommandBuilder.group(name: String, vararg aliases: String, config: CommandBuilder.() -> Unit) { - group(name, *aliases) - config() - parent() -} - -private inline fun CommandBuilder.group(address: ICommandAddress, name: String, vararg aliases: String, config: CommandBuilder.() -> Unit) { - group(address, name, *aliases) - config() - parent() -} - -private fun CommandBuilder.generateHelpAndSyntaxCommands(root: ICommandAddress): CommandBuilder { - generateCommands(root, "help", "syntax") - return this -} - -private fun generateCommands(address: ICommandAddress, vararg names: String) { - val addresses: Queue<ICommandAddress> = LinkedList() - addresses.offer(address) - - while (addresses.isNotEmpty()) { - val cur = addresses.poll() - cur.childrenMainKeys.mapTo(addresses) { cur.getChild(it) } - if (cur.hasCommand()) { - ReflectiveRegistration.generateCommands(cur, names) - } - } -} - -class SpecialCommandAddress : ChildCommandAddress() { - private val speciallyTreatedKeys = mutableListOf<String>() - - // Used to allow /p h:1 syntax, which is the same as what PlotMe uses. - var speciallyParsedIndex: Int? = null; private set - - fun addSpeciallyTreatedKeys(vararg keys: String) { - for (key in keys) { - speciallyTreatedKeys.add(key + ":") - } - } - - // h:1 - @Throws(CommandException::class) - override fun getChild(context: ExecutionContext, buffer: ArgumentBuffer): ChildCommandAddress? { - speciallyParsedIndex = null - - val key = buffer.next() ?: return null - for (specialKey in speciallyTreatedKeys) { - if (key.startsWith(specialKey)) { - val result = getChild(specialKey.substring(0, specialKey.length - 1)) - ?: return null - - val text = key.substring(specialKey.length) - val num = text.toIntOrNull() ?: throw CommandException("$text is not a number") - speciallyParsedIndex = num - - return result - } - } - - return super.getChild(key) - } - -} - -private class AdminGroupCommand : DefaultGroupCommand() { - override fun isVisibleTo(sender: CommandSender) = sender.hasPermAdminManage -} +package io.dico.parcels2.command
+
+import io.dico.dicore.command.*
+import io.dico.dicore.command.parameter.ArgumentBuffer
+import io.dico.dicore.command.predef.DefaultGroupCommand
+import io.dico.dicore.command.registration.reflect.ReflectiveRegistration
+import io.dico.parcels2.Interactables
+import io.dico.parcels2.ParcelsPlugin
+import io.dico.parcels2.logger
+import io.dico.parcels2.util.ext.hasPermAdminManage
+import org.bukkit.command.CommandSender
+import java.util.LinkedList
+import java.util.Queue
+
+@Suppress("UsePropertyAccessSyntax")
+fun getParcelCommands(plugin: ParcelsPlugin): ICommandDispatcher = CommandBuilder().apply {
+ val parcelsAddress = SpecialCommandAddress()
+
+ setChatHandler(ParcelsChatHandler())
+ addParameterType(false, ParcelParameterType(plugin.parcelProvider))
+ addParameterType(false, ProfileParameterType())
+ addParameterType(true, ParcelTarget.PType(plugin.parcelProvider, parcelsAddress))
+
+ group(parcelsAddress, "parcel", "plot", "plots", "p") {
+ addContextFilter(IContextFilter.inheritablePermission("parcels.command"))
+ registerCommands(CommandsGeneral(plugin, parcelsAddress))
+ registerCommands(CommandsPrivilegesLocal(plugin))
+
+ group("option", "opt", "o") {
+ setGroupDescription(
+ "changes interaction options for this parcel",
+ "Sets whether players who are not allowed to",
+ "build here can interact with certain things."
+ )
+
+ group("interact", "i") {
+ val command = ParcelOptionsInteractCommand(plugin)
+ Interactables.classesById.forEach {
+ addSubCommand(it.name, command)
+ }
+ }
+ }
+
+ val adminPrivilegesGlobal = CommandsAdminPrivilegesGlobal(plugin)
+
+ group("global", "g") {
+ registerCommands(CommandsPrivilegesGlobal(plugin, adminVersion = adminPrivilegesGlobal))
+ }
+
+ group("admin", "a") {
+ setCommand(AdminGroupCommand())
+ registerCommands(CommandsAdmin(plugin))
+
+ group("global", "g") {
+ registerCommands(adminPrivilegesGlobal)
+ }
+ }
+
+ if (!logger.isDebugEnabled) return@group
+
+ group("debug", "d") {
+ registerCommands(CommandsDebug(plugin))
+ }
+ }
+
+ generateHelpAndSyntaxCommands(parcelsAddress)
+}.getDispatcher()
+
+private inline fun CommandBuilder.group(name: String, vararg aliases: String, config: CommandBuilder.() -> Unit) {
+ group(name, *aliases)
+ config()
+ parent()
+}
+
+private inline fun CommandBuilder.group(address: ICommandAddress, name: String, vararg aliases: String, config: CommandBuilder.() -> Unit) {
+ group(address, name, *aliases)
+ config()
+ parent()
+}
+
+private fun CommandBuilder.generateHelpAndSyntaxCommands(root: ICommandAddress): CommandBuilder {
+ generateCommands(root, "help", "syntax")
+ return this
+}
+
+private fun generateCommands(address: ICommandAddress, vararg names: String) {
+ val addresses: Queue<ICommandAddress> = LinkedList()
+ addresses.offer(address)
+
+ while (addresses.isNotEmpty()) {
+ val cur = addresses.poll()
+ cur.childrenMainKeys.mapTo(addresses) { cur.getChild(it) }
+ if (cur.hasCommand()) {
+ ReflectiveRegistration.generateCommands(cur, names)
+ }
+ }
+}
+
+class SpecialCommandAddress : ChildCommandAddress() {
+ private val speciallyTreatedKeys = mutableListOf<String>()
+
+ // Used to allow /p h:1 syntax, which is the same as what PlotMe uses.
+ var speciallyParsedIndex: Int? = null; private set
+
+ fun addSpeciallyTreatedKeys(vararg keys: String) {
+ for (key in keys) {
+ speciallyTreatedKeys.add(key + ":")
+ }
+ }
+
+ // h:1
+ @Throws(CommandException::class)
+ override fun getChild(context: ExecutionContext, buffer: ArgumentBuffer): ChildCommandAddress? {
+ speciallyParsedIndex = null
+
+ val key = buffer.next() ?: return null
+ for (specialKey in speciallyTreatedKeys) {
+ if (key.startsWith(specialKey)) {
+ val result = getChild(specialKey.substring(0, specialKey.length - 1))
+ ?: return null
+
+ val text = key.substring(specialKey.length)
+ val num = text.toIntOrNull() ?: throw CommandException("$text is not a number")
+ speciallyParsedIndex = num
+
+ return result
+ }
+ }
+
+ return super.getChild(key)
+ }
+
+}
+
+private class AdminGroupCommand : DefaultGroupCommand() {
+ override fun isVisibleTo(sender: CommandSender) = sender.hasPermAdminManage
+}
diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt index 15548b4..e41fd37 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt @@ -1,68 +1,68 @@ -package io.dico.parcels2.command - -import io.dico.dicore.command.CommandException -import io.dico.dicore.command.ExecutionContext -import io.dico.dicore.command.registration.reflect.ICommandReceiver -import io.dico.dicore.command.Validate -import io.dico.parcels2.* -import io.dico.parcels2.Privilege.* -import io.dico.parcels2.util.ext.hasPermAdminManage -import io.dico.parcels2.util.ext.uuid -import org.bukkit.entity.Player -import java.lang.reflect.Method -import kotlin.reflect.full.extensionReceiverParameter -import kotlin.reflect.full.findAnnotation -import kotlin.reflect.jvm.jvmErasure -import kotlin.reflect.jvm.kotlinFunction - -@Target(AnnotationTarget.FUNCTION) -@Retention(AnnotationRetention.RUNTIME) -annotation class RequireParcelPrivilege(val privilege: Privilege) - -/* -@Target(AnnotationTarget.FUNCTION) -@Retention(AnnotationRetention.RUNTIME) -annotation class SuspensionTimeout(val millis: Int) -*/ - -open class WorldScope(val world: ParcelWorld) : ICommandReceiver -open class ParcelScope(val parcel: Parcel) : WorldScope(parcel.world) { - fun checkCanManage(player: Player, action: String) = Validate.isTrue(parcel.canManage(player), "You must own this parcel to $action") -} - -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 - val require = function.findAnnotation<RequireParcelPrivilege>() - - return when (receiverType.jvmErasure) { - ParcelScope::class -> ParcelScope(parcelProvider.getParcelRequired(player, require?.privilege)) - WorldScope::class -> WorldScope(parcelProvider.getWorldRequired(player, require?.privilege == ADMIN)) - else -> throw InternalError("Invalid command receiver type") - } -} - -fun ParcelProvider.getWorldRequired(player: Player, admin: Boolean = false): ParcelWorld { - if (admin) Validate.isTrue(player.hasPermAdminManage, "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 ParcelProvider.getParcelRequired(player: Player, privilege: Privilege? = null): Parcel { - val parcel = getWorldRequired(player, admin = privilege == ADMIN).getParcelAt(player) - ?: throw CommandException("You must be in a parcel to use that command") - - if (!player.hasPermAdminManage) { - @Suppress("NON_EXHAUSTIVE_WHEN") - when (privilege) { - OWNER -> - Validate.isTrue(parcel.isOwner(player.uuid), "You must own this parcel to use that command") - CAN_MANAGE -> - Validate.isTrue(parcel.canManage(player), "You must have management privileges on this parcel to use that command") - } - } - - return parcel -} - +package io.dico.parcels2.command
+
+import io.dico.dicore.command.CommandException
+import io.dico.dicore.command.ExecutionContext
+import io.dico.dicore.command.registration.reflect.ICommandReceiver
+import io.dico.dicore.command.Validate
+import io.dico.parcels2.*
+import io.dico.parcels2.Privilege.*
+import io.dico.parcels2.util.ext.hasPermAdminManage
+import io.dico.parcels2.util.ext.uuid
+import org.bukkit.entity.Player
+import java.lang.reflect.Method
+import kotlin.reflect.full.extensionReceiverParameter
+import kotlin.reflect.full.findAnnotation
+import kotlin.reflect.jvm.jvmErasure
+import kotlin.reflect.jvm.kotlinFunction
+
+@Target(AnnotationTarget.FUNCTION)
+@Retention(AnnotationRetention.RUNTIME)
+annotation class RequireParcelPrivilege(val privilege: Privilege)
+
+/*
+@Target(AnnotationTarget.FUNCTION)
+@Retention(AnnotationRetention.RUNTIME)
+annotation class SuspensionTimeout(val millis: Int)
+*/
+
+open class WorldScope(val world: ParcelWorld) : ICommandReceiver
+open class ParcelScope(val parcel: Parcel) : WorldScope(parcel.world) {
+ fun checkCanManage(player: Player, action: String) = Validate.isTrue(parcel.canManage(player), "You must own this parcel to $action")
+}
+
+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
+ val require = function.findAnnotation<RequireParcelPrivilege>()
+
+ return when (receiverType.jvmErasure) {
+ ParcelScope::class -> ParcelScope(parcelProvider.getParcelRequired(player, require?.privilege))
+ WorldScope::class -> WorldScope(parcelProvider.getWorldRequired(player, require?.privilege == ADMIN))
+ else -> throw InternalError("Invalid command receiver type")
+ }
+}
+
+fun ParcelProvider.getWorldRequired(player: Player, admin: Boolean = false): ParcelWorld {
+ if (admin) Validate.isTrue(player.hasPermAdminManage, "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 ParcelProvider.getParcelRequired(player: Player, privilege: Privilege? = null): Parcel {
+ val parcel = getWorldRequired(player, admin = privilege == ADMIN).getParcelAt(player)
+ ?: throw CommandException("You must be in a parcel to use that command")
+
+ if (!player.hasPermAdminManage) {
+ @Suppress("NON_EXHAUSTIVE_WHEN")
+ when (privilege) {
+ OWNER ->
+ Validate.isTrue(parcel.isOwner(player.uuid), "You must own this parcel to use that command")
+ CAN_MANAGE ->
+ Validate.isTrue(parcel.canManage(player), "You must have management privileges on this parcel to use that command")
+ }
+ }
+
+ return parcel
+}
+
diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelOptionsInteractCommand.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelOptionsInteractCommand.kt index 23087dc..fbe4333 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelOptionsInteractCommand.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelOptionsInteractCommand.kt @@ -1,56 +1,56 @@ -package io.dico.parcels2.command - -import io.dico.dicore.command.* -import io.dico.dicore.command.parameter.type.ParameterTypes -import io.dico.parcels2.Interactables -import io.dico.parcels2.ParcelsPlugin -import io.dico.parcels2.Privilege -import org.bukkit.command.CommandSender -import org.bukkit.entity.Player - -class ParcelOptionsInteractCommand(val plugin: ParcelsPlugin) : Command() { - - init { - setShortDescription("View and/or change the setting") - setDescription(shortDescription) - addContextFilter(IContextFilter.PLAYER_ONLY) - addContextFilter(IContextFilter.INHERIT_PERMISSIONS) - addParameter("allowed", "new setting", ParameterTypes.BOOLEAN) - requiredParameters(0) - } - - override fun execute(sender: CommandSender, context: ExecutionContext): String? { - if (!plugin.storage.isConnected) err("Parcels cannot have their options changed right now because of a database error") - - val interactableClass = Interactables[context.address.mainKey] - val allowed: Boolean? = context.get("allowed") - - val parcel = plugin.parcelProvider.getParcelRequired(sender as Player, - if (allowed == null) Privilege.DEFAULT else Privilege.CAN_MANAGE) - - if (allowed == null) { - val setting = parcel.interactableConfig.isInteractable(interactableClass) - val default = setting == interactableClass.interactableByDefault - - val canColor = context.address.chatHandler.getChatFormatForType(EMessageType.BAD_NEWS) - val cannotColor = context.address.chatHandler.getChatFormatForType(EMessageType.GOOD_NEWS) - val resetColor = context.address.chatHandler.getChatFormatForType(EMessageType.RESULT) - - val settingString = (if (setting) "${canColor}can" else "${cannotColor}cannot") + resetColor - val defaultString = if (default) " (default)" else "" - - return "Players $settingString interact with ${interactableClass.name} on this parcel$defaultString" - } - - val change = parcel.interactableConfig.setInteractable(interactableClass, allowed) - - val interactableClassName = interactableClass.name - return when { - allowed && change -> "Other players can now interact with $interactableClassName" - allowed && !change -> err("Other players could already interact with $interactableClassName") - change -> "Other players can not interact with $interactableClassName anymore" - else -> err("Other players were not allowed to interact with $interactableClassName") - } - } - -} +package io.dico.parcels2.command
+
+import io.dico.dicore.command.*
+import io.dico.dicore.command.parameter.type.ParameterTypes
+import io.dico.parcels2.Interactables
+import io.dico.parcels2.ParcelsPlugin
+import io.dico.parcels2.Privilege
+import org.bukkit.command.CommandSender
+import org.bukkit.entity.Player
+
+class ParcelOptionsInteractCommand(val plugin: ParcelsPlugin) : Command() {
+
+ init {
+ setShortDescription("View and/or change the setting")
+ setDescription(shortDescription)
+ addContextFilter(IContextFilter.PLAYER_ONLY)
+ addContextFilter(IContextFilter.INHERIT_PERMISSIONS)
+ addParameter("allowed", "new setting", ParameterTypes.BOOLEAN)
+ requiredParameters(0)
+ }
+
+ override fun execute(sender: CommandSender, context: ExecutionContext): String? {
+ if (!plugin.storage.isConnected) err("Parcels cannot have their options changed right now because of a database error")
+
+ val interactableClass = Interactables[context.address.mainKey]
+ val allowed: Boolean? = context.get("allowed")
+
+ val parcel = plugin.parcelProvider.getParcelRequired(sender as Player,
+ if (allowed == null) Privilege.DEFAULT else Privilege.CAN_MANAGE)
+
+ if (allowed == null) {
+ val setting = parcel.interactableConfig.isInteractable(interactableClass)
+ val default = setting == interactableClass.interactableByDefault
+
+ val canColor = context.address.chatHandler.getChatFormatForType(EMessageType.BAD_NEWS)
+ val cannotColor = context.address.chatHandler.getChatFormatForType(EMessageType.GOOD_NEWS)
+ val resetColor = context.address.chatHandler.getChatFormatForType(EMessageType.RESULT)
+
+ val settingString = (if (setting) "${canColor}can" else "${cannotColor}cannot") + resetColor
+ val defaultString = if (default) " (default)" else ""
+
+ return "Players $settingString interact with ${interactableClass.name} on this parcel$defaultString"
+ }
+
+ val change = parcel.interactableConfig.setInteractable(interactableClass, allowed)
+
+ val interactableClassName = interactableClass.name
+ return when {
+ allowed && change -> "Other players can now interact with $interactableClassName"
+ allowed && !change -> err("Other players could already interact with $interactableClassName")
+ change -> "Other players can not interact with $interactableClassName anymore"
+ else -> err("Other players were not allowed to interact with $interactableClassName")
+ }
+ }
+
+}
diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt index c7083a1..730625e 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt @@ -1,83 +1,83 @@ -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.* -import io.dico.parcels2.command.ProfileKind.Companion.ANY -import io.dico.parcels2.command.ProfileKind.Companion.FAKE -import io.dico.parcels2.command.ProfileKind.Companion.REAL -import org.bukkit.Location -import org.bukkit.command.CommandSender -import org.bukkit.entity.Player - -fun invalidInput(parameter: Parameter<*, *>, message: String): Nothing { - throw CommandException("invalid input for ${parameter.name}: $message") -} - -fun ParcelProvider.getTargetWorld(input: String?, sender: CommandSender, parameter: Parameter<*, *>): ParcelWorld { - val worldName = input - ?.takeUnless { it.isEmpty() } - ?: (sender as? Player)?.world?.name - ?: invalidInput(parameter, "console cannot omit the world name") - - return getWorld(worldName) - ?: invalidInput(parameter, "$worldName is not a parcel world") -} - -class ParcelParameterType(val parcelProvider: ParcelProvider) : ParameterType<Parcel, Void>(Parcel::class.java) { - val regex = Regex.fromLiteral("((.+)->)?([0-9]+):([0-9]+)") - - override fun parse(parameter: Parameter<Parcel, Void>, sender: CommandSender, buffer: ArgumentBuffer): Parcel { - val matchResult = regex.matchEntire(buffer.next()!!) - ?: invalidInput(parameter, "must match (w->)?a:b (/${regex.pattern}/)") - - val world = parcelProvider.getTargetWorld(matchResult.groupValues[2], sender, parameter) - - val x = matchResult.groupValues[3].toIntOrNull() - ?: invalidInput(parameter, "couldn't parse int") - - val z = matchResult.groupValues[4].toIntOrNull() - ?: invalidInput(parameter, "couldn't parse int") - - return world.getParcelById(x, z) - ?: invalidInput(parameter, "parcel id is out of range") - } - -} - -annotation class ProfileKind(val kind: Int) { - companion object : ParameterConfig<ProfileKind, Int>(ProfileKind::class.java) { - const val REAL = 1 - const val FAKE = 2 - const val ANY = REAL or FAKE - - override fun toParameterInfo(annotation: ProfileKind): Int { - return annotation.kind - } - } -} - -class ProfileParameterType : ParameterType<PlayerProfile, Int>(PlayerProfile::class.java, ProfileKind) { - - override fun parse(parameter: Parameter<PlayerProfile, Int>, sender: CommandSender, buffer: ArgumentBuffer): PlayerProfile { - val info = parameter.paramInfo ?: REAL - val allowReal = (info and REAL) != 0 - val allowFake = (info and FAKE) != 0 - - val input = buffer.next()!! - return PlayerProfile.byName(input, allowReal, allowFake) - } - - override fun complete( - parameter: Parameter<PlayerProfile, Int>, - sender: CommandSender, - location: Location?, - buffer: ArgumentBuffer - ): MutableList<String> { - logger.info("Completing PlayerProfile: ${buffer.next()}") - return super.complete(parameter, sender, location, buffer) - } -} +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.*
+import io.dico.parcels2.command.ProfileKind.Companion.ANY
+import io.dico.parcels2.command.ProfileKind.Companion.FAKE
+import io.dico.parcels2.command.ProfileKind.Companion.REAL
+import org.bukkit.Location
+import org.bukkit.command.CommandSender
+import org.bukkit.entity.Player
+
+fun invalidInput(parameter: Parameter<*, *>, message: String): Nothing {
+ throw CommandException("invalid input for ${parameter.name}: $message")
+}
+
+fun ParcelProvider.getTargetWorld(input: String?, sender: CommandSender, parameter: Parameter<*, *>): ParcelWorld {
+ val worldName = input
+ ?.takeUnless { it.isEmpty() }
+ ?: (sender as? Player)?.world?.name
+ ?: invalidInput(parameter, "console cannot omit the world name")
+
+ return getWorld(worldName)
+ ?: invalidInput(parameter, "$worldName is not a parcel world")
+}
+
+class ParcelParameterType(val parcelProvider: ParcelProvider) : ParameterType<Parcel, Void>(Parcel::class.java) {
+ val regex = Regex.fromLiteral("((.+)->)?([0-9]+):([0-9]+)")
+
+ override fun parse(parameter: Parameter<Parcel, Void>, sender: CommandSender, buffer: ArgumentBuffer): Parcel {
+ val matchResult = regex.matchEntire(buffer.next()!!)
+ ?: invalidInput(parameter, "must match (w->)?a:b (/${regex.pattern}/)")
+
+ val world = parcelProvider.getTargetWorld(matchResult.groupValues[2], sender, parameter)
+
+ val x = matchResult.groupValues[3].toIntOrNull()
+ ?: invalidInput(parameter, "couldn't parse int")
+
+ val z = matchResult.groupValues[4].toIntOrNull()
+ ?: invalidInput(parameter, "couldn't parse int")
+
+ return world.getParcelById(x, z)
+ ?: invalidInput(parameter, "parcel id is out of range")
+ }
+
+}
+
+annotation class ProfileKind(val kind: Int) {
+ companion object : ParameterConfig<ProfileKind, Int>(ProfileKind::class.java) {
+ const val REAL = 1
+ const val FAKE = 2
+ const val ANY = REAL or FAKE
+
+ override fun toParameterInfo(annotation: ProfileKind): Int {
+ return annotation.kind
+ }
+ }
+}
+
+class ProfileParameterType : ParameterType<PlayerProfile, Int>(PlayerProfile::class.java, ProfileKind) {
+
+ override fun parse(parameter: Parameter<PlayerProfile, Int>, sender: CommandSender, buffer: ArgumentBuffer): PlayerProfile {
+ val info = parameter.paramInfo ?: REAL
+ val allowReal = (info and REAL) != 0
+ val allowFake = (info and FAKE) != 0
+
+ val input = buffer.next()!!
+ return PlayerProfile.byName(input, allowReal, allowFake)
+ }
+
+ override fun complete(
+ parameter: Parameter<PlayerProfile, Int>,
+ sender: CommandSender,
+ location: Location?,
+ buffer: ArgumentBuffer
+ ): MutableList<String> {
+ logger.info("Completing PlayerProfile: ${buffer.next()}")
+ return super.complete(parameter, sender, location, buffer)
+ }
+}
diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt index 8891912..c39c4b6 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt @@ -1,191 +1,191 @@ -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.Parcel -import io.dico.parcels2.ParcelProvider -import io.dico.parcels2.ParcelWorld -import io.dico.parcels2.PlayerProfile -import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.DEFAULT_KIND -import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.ID -import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.OWNER -import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.OWNER_FAKE -import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.OWNER_REAL -import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.PREFER_OWNED_FOR_DEFAULT -import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.REAL -import io.dico.parcels2.storage.Storage -import io.dico.parcels2.util.math.Vec2i -import io.dico.parcels2.util.math.floor -import org.bukkit.command.CommandSender -import org.bukkit.entity.Player - -sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDefault: Boolean) { - - abstract suspend fun getParcelSuspend(storage: Storage): Parcel? - - class ByID(world: ParcelWorld, val id: Vec2i?, parsedKind: Int, isDefault: Boolean) : ParcelTarget(world, parsedKind, isDefault) { - override suspend fun getParcelSuspend(storage: Storage): Parcel? = getParcel() - fun getParcel() = id?.let { world.getParcelById(it) } - val isPath: Boolean get() = id == null - } - - class ByOwner( - world: ParcelWorld, - owner: PlayerProfile, - val index: Int, - parsedKind: Int, - isDefault: Boolean, - val onResolveFailure: (() -> Unit)? = null - ) : ParcelTarget(world, parsedKind, isDefault) { - init { - if (index < 0) throw IllegalArgumentException("Invalid parcel home index: $index") - } - - var owner = owner; private set - - suspend fun resolveOwner(storage: Storage): Boolean { - val owner = owner - if (owner is PlayerProfile.Unresolved) { - this.owner = owner.tryResolveSuspendedly(storage) ?: if (parsedKind and OWNER_FAKE != 0) PlayerProfile.Fake(owner.name) - else run { onResolveFailure?.invoke(); return false } - } - return true - } - - override suspend fun getParcelSuspend(storage: Storage): Parcel? { - onResolveFailure?.let { resolveOwner(storage) } - - val ownedParcelsSerialized = storage.getOwnedParcels(owner).await() - val ownedParcels = ownedParcelsSerialized - .filter { it.worldId.equals(world.id) } - .map { world.getParcelById(it.x, it.z) } - - return ownedParcels.getOrNull(index) - } - } - - annotation class TargetKind(val kind: Int) { - companion object : ParameterConfig<TargetKind, Int>(TargetKind::class.java) { - const val ID = 1 // ID - const val OWNER_REAL = 2 // an owner backed by a UUID - const val OWNER_FAKE = 4 // 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 = 8 // if the kind can be ID and OWNER_REAL, prefer OWNER_REAL for default - // instead of parcel that the player is in - - override fun toParameterInfo(annotation: TargetKind): Int { - return annotation.kind - } - } - } - - class PType(val parcelProvider: ParcelProvider, val parcelAddress: SpecialCommandAddress? = null) : - ParameterType<ParcelTarget, Int>(ParcelTarget::class.java, TargetKind) { - - override fun parse(parameter: Parameter<ParcelTarget, Int>, 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") - parcelProvider.getWorld(player.world) - ?: invalidInput(parameter, "You cannot omit the world if you're not in a parcel world") - } else { - 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 OWNER, that is, an owner and index") - return ByID(world, getId(parameter, input), kind, false) - } - - 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, kind, sender, input) - return ByOwner(world, owner, index, kind, false, onResolveFailure = { invalidInput(parameter, "The player $input does not exist") }) - } - - 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<*, *>, kind: Int, sender: CommandSender, input: String): Pair<PlayerProfile, Int> { - val splitIdx = input.indexOf(':') - val ownerString: String - val index: Int? - - val speciallyParsedIndex = parcelAddress?.speciallyParsedIndex - - if (splitIdx == -1) { - - if (speciallyParsedIndex == null) { - // just the index. - index = input.toIntOrNull() - ownerString = if (index == null) input else "" - } else { - // just the owner. - index = speciallyParsedIndex - ownerString = input - } - - } else { - if (speciallyParsedIndex != null) { - invalidInput(parameter, "Duplicate home index") - } - - ownerString = input.substring(0, splitIdx) - - val indexString = input.substring(splitIdx + 1) - index = indexString.toIntOrNull() - ?: invalidInput(parameter, "The home index must be an integer, $indexString is not an integer") - } - - val owner = if (ownerString.isEmpty()) - PlayerProfile(requirePlayer(sender, parameter, "the player")) - else - PlayerProfile.byName(ownerString, allowReal = kind and OWNER_REAL != 0, allowFake = kind and OWNER_FAKE != 0) - - return owner to (index ?: 0) - } - - private fun requirePlayer(sender: CommandSender, parameter: Parameter<*, *>, objName: String): Player { - if (sender !is Player) invalidInput(parameter, "console cannot omit the $objName") - return sender - } - - override fun getDefaultValue(parameter: Parameter<ParcelTarget, Int>, 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 = 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.getParcelIdAt(it.x.floor(), it.z.floor())?.pos } - return ByID(world, id, kind, true) - } - - return ByOwner(world, PlayerProfile(player), parcelAddress?.speciallyParsedIndex ?: 0, kind, true) - } - } - -} +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.Parcel
+import io.dico.parcels2.ParcelProvider
+import io.dico.parcels2.ParcelWorld
+import io.dico.parcels2.PlayerProfile
+import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.DEFAULT_KIND
+import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.ID
+import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.OWNER
+import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.OWNER_FAKE
+import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.OWNER_REAL
+import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.PREFER_OWNED_FOR_DEFAULT
+import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.REAL
+import io.dico.parcels2.storage.Storage
+import io.dico.parcels2.util.math.Vec2i
+import io.dico.parcels2.util.math.floor
+import org.bukkit.command.CommandSender
+import org.bukkit.entity.Player
+
+sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDefault: Boolean) {
+
+ abstract suspend fun getParcelSuspend(storage: Storage): Parcel?
+
+ class ByID(world: ParcelWorld, val id: Vec2i?, parsedKind: Int, isDefault: Boolean) : ParcelTarget(world, parsedKind, isDefault) {
+ override suspend fun getParcelSuspend(storage: Storage): Parcel? = getParcel()
+ fun getParcel() = id?.let { world.getParcelById(it) }
+ val isPath: Boolean get() = id == null
+ }
+
+ class ByOwner(
+ world: ParcelWorld,
+ owner: PlayerProfile,
+ val index: Int,
+ parsedKind: Int,
+ isDefault: Boolean,
+ val onResolveFailure: (() -> Unit)? = null
+ ) : ParcelTarget(world, parsedKind, isDefault) {
+ init {
+ if (index < 0) throw IllegalArgumentException("Invalid parcel home index: $index")
+ }
+
+ var owner = owner; private set
+
+ suspend fun resolveOwner(storage: Storage): Boolean {
+ val owner = owner
+ if (owner is PlayerProfile.Unresolved) {
+ this.owner = owner.tryResolveSuspendedly(storage) ?: if (parsedKind and OWNER_FAKE != 0) PlayerProfile.Fake(owner.name)
+ else run { onResolveFailure?.invoke(); return false }
+ }
+ return true
+ }
+
+ override suspend fun getParcelSuspend(storage: Storage): Parcel? {
+ onResolveFailure?.let { resolveOwner(storage) }
+
+ val ownedParcelsSerialized = storage.getOwnedParcels(owner).await()
+ val ownedParcels = ownedParcelsSerialized
+ .filter { it.worldId.equals(world.id) }
+ .map { world.getParcelById(it.x, it.z) }
+
+ return ownedParcels.getOrNull(index)
+ }
+ }
+
+ annotation class TargetKind(val kind: Int) {
+ companion object : ParameterConfig<TargetKind, Int>(TargetKind::class.java) {
+ const val ID = 1 // ID
+ const val OWNER_REAL = 2 // an owner backed by a UUID
+ const val OWNER_FAKE = 4 // 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 = 8 // if the kind can be ID and OWNER_REAL, prefer OWNER_REAL for default
+ // instead of parcel that the player is in
+
+ override fun toParameterInfo(annotation: TargetKind): Int {
+ return annotation.kind
+ }
+ }
+ }
+
+ class PType(val parcelProvider: ParcelProvider, val parcelAddress: SpecialCommandAddress? = null) :
+ ParameterType<ParcelTarget, Int>(ParcelTarget::class.java, TargetKind) {
+
+ override fun parse(parameter: Parameter<ParcelTarget, Int>, 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")
+ parcelProvider.getWorld(player.world)
+ ?: invalidInput(parameter, "You cannot omit the world if you're not in a parcel world")
+ } else {
+ 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 OWNER, that is, an owner and index")
+ return ByID(world, getId(parameter, input), kind, false)
+ }
+
+ 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, kind, sender, input)
+ return ByOwner(world, owner, index, kind, false, onResolveFailure = { invalidInput(parameter, "The player $input does not exist") })
+ }
+
+ 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<*, *>, kind: Int, sender: CommandSender, input: String): Pair<PlayerProfile, Int> {
+ val splitIdx = input.indexOf(':')
+ val ownerString: String
+ val index: Int?
+
+ val speciallyParsedIndex = parcelAddress?.speciallyParsedIndex
+
+ if (splitIdx == -1) {
+
+ if (speciallyParsedIndex == null) {
+ // just the index.
+ index = input.toIntOrNull()
+ ownerString = if (index == null) input else ""
+ } else {
+ // just the owner.
+ index = speciallyParsedIndex
+ ownerString = input
+ }
+
+ } else {
+ if (speciallyParsedIndex != null) {
+ invalidInput(parameter, "Duplicate home index")
+ }
+
+ ownerString = input.substring(0, splitIdx)
+
+ val indexString = input.substring(splitIdx + 1)
+ index = indexString.toIntOrNull()
+ ?: invalidInput(parameter, "The home index must be an integer, $indexString is not an integer")
+ }
+
+ val owner = if (ownerString.isEmpty())
+ PlayerProfile(requirePlayer(sender, parameter, "the player"))
+ else
+ PlayerProfile.byName(ownerString, allowReal = kind and OWNER_REAL != 0, allowFake = kind and OWNER_FAKE != 0)
+
+ return owner to (index ?: 0)
+ }
+
+ private fun requirePlayer(sender: CommandSender, parameter: Parameter<*, *>, objName: String): Player {
+ if (sender !is Player) invalidInput(parameter, "console cannot omit the $objName")
+ return sender
+ }
+
+ override fun getDefaultValue(parameter: Parameter<ParcelTarget, Int>, 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 = 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.getParcelIdAt(it.x.floor(), it.z.floor())?.pos }
+ return ByID(world, id, kind, true)
+ }
+
+ return ByOwner(world, PlayerProfile(player), parcelAddress?.speciallyParsedIndex ?: 0, kind, true)
+ }
+ }
+
+}
diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelsChatHandler.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelsChatHandler.kt index b616f77..52f1104 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelsChatHandler.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelsChatHandler.kt @@ -1,24 +1,24 @@ -package io.dico.parcels2.command - -import io.dico.dicore.Formatting -import io.dico.dicore.command.EMessageType -import io.dico.dicore.command.ExecutionContext -import io.dico.dicore.command.chat.AbstractChatHandler -import io.dico.parcels2.util.ext.plus - -class ParcelsChatHandler : AbstractChatHandler() { - - override fun getMessagePrefixForType(type: EMessageType?): String { - return Formatting.RED + "[Parcels] " - } - - override fun createMessage(context: ExecutionContext, type: EMessageType, message: String?): String? { - if (message.isNullOrEmpty()) return null - var result = getChatFormatForType(type) + message - if (context.address.mainKey != "info") { - result = getMessagePrefixForType(type) + result - } - return result - } - +package io.dico.parcels2.command
+
+import io.dico.dicore.Formatting
+import io.dico.dicore.command.EMessageType
+import io.dico.dicore.command.ExecutionContext
+import io.dico.dicore.command.chat.AbstractChatHandler
+import io.dico.parcels2.util.ext.plus
+
+class ParcelsChatHandler : AbstractChatHandler() {
+
+ override fun getMessagePrefixForType(type: EMessageType?): String {
+ return Formatting.RED + "[Parcels] "
+ }
+
+ override fun createMessage(context: ExecutionContext, type: EMessageType, message: String?): String? {
+ if (message.isNullOrEmpty()) return null
+ var result = getChatFormatForType(type) + message
+ if (context.address.mainKey != "info") {
+ result = getMessagePrefixForType(type) + result
+ }
+ return result
+ }
+
}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt index 1193af3..b49cad4 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt @@ -1,73 +1,73 @@ -package io.dico.parcels2.defaultimpl - -import io.dico.parcels2.Parcel -import io.dico.parcels2.ParcelContainer -import io.dico.parcels2.ParcelId -import io.dico.parcels2.ParcelWorld - -class DefaultParcelContainer(val world: ParcelWorld) : ParcelContainer { - private var parcels: Array<Array<Parcel>> - - 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<Array<Parcel>> { - 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 getParcelById(id: ParcelId): Parcel? { - if (!world.id.equals(id.worldId)) throw IllegalArgumentException() - return when (id) { - is Parcel -> id - else -> getParcelById(id.x, id.z) - } - } - - override fun nextEmptyParcel(): Parcel? { - return walkInCircle().find { it.owner == null } - } - - private fun walkInCircle(): Iterable<Parcel> = Iterable { - iterator { - val center = world.options.axisLimit - yield(parcels[center][center]) - 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 getAllParcels(): Iterator<Parcel> = iterator { - for (array in parcels) { - yieldAll(array.iterator()) - } - } - +package io.dico.parcels2.defaultimpl
+
+import io.dico.parcels2.Parcel
+import io.dico.parcels2.ParcelContainer
+import io.dico.parcels2.ParcelId
+import io.dico.parcels2.ParcelWorld
+
+class DefaultParcelContainer(val world: ParcelWorld) : ParcelContainer {
+ private var parcels: Array<Array<Parcel>>
+
+ 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<Array<Parcel>> {
+ 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 getParcelById(id: ParcelId): Parcel? {
+ if (!world.id.equals(id.worldId)) throw IllegalArgumentException()
+ return when (id) {
+ is Parcel -> id
+ else -> getParcelById(id.x, id.z)
+ }
+ }
+
+ override fun nextEmptyParcel(): Parcel? {
+ return walkInCircle().find { it.owner == null }
+ }
+
+ private fun walkInCircle(): Iterable<Parcel> = Iterable {
+ iterator {
+ val center = world.options.axisLimit
+ yield(parcels[center][center])
+ 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 getAllParcels(): Iterator<Parcel> = iterator {
+ 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 index 9e43c05..caa3f1f 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt @@ -1,378 +1,378 @@ -package io.dico.parcels2.defaultimpl - -import io.dico.parcels2.* -import io.dico.parcels2.blockvisitor.RegionTraverser -import io.dico.parcels2.options.DefaultGeneratorOptions -import io.dico.parcels2.util.math.* -import kotlinx.coroutines.CoroutineScope -import org.bukkit.* -import org.bukkit.block.Biome -import org.bukkit.block.BlockFace -import org.bukkit.block.Skull -import org.bukkit.block.data.type.Slab -import org.bukkit.block.data.type.WallSign -import java.util.Random - -private val airType = Bukkit.createBlockData(Material.AIR) - -private const val chunkSize = 16 - -class DefaultParcelGenerator( - override val worldName: String, - private val o: DefaultGeneratorOptions -) : ParcelGenerator() { - private var _world: World? = null - override val world: World - get() { - if (_world == null) { - val world = Bukkit.getWorld(worldName) - maxHeight = world.maxHeight - _world = world - return world - } - return _world!! - } - - private var maxHeight = 0 - 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 <T> 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 makeParcelLocatorAndBlockManager( - parcelProvider: ParcelProvider, - container: ParcelContainer, - coroutineScope: CoroutineScope, - jobDispatcher: JobDispatcher - ): Pair<ParcelLocator, ParcelBlockManager> { - val impl = ParcelLocatorAndBlockManagerImpl(parcelProvider, container, coroutineScope, jobDispatcher) - return impl to impl - } - - private inline fun <T> 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 + 1, (absZ - modZ) / sectionSize + 1) - } - return null - } - - @Suppress("DEPRECATION") - private inner class ParcelLocatorAndBlockManagerImpl( - val parcelProvider: ParcelProvider, - val container: ParcelContainer, - coroutineScope: CoroutineScope, - override val jobDispatcher: JobDispatcher - ) : ParcelBlockManagerBase(), ParcelLocator, CoroutineScope by coroutineScope { - - override val world: World get() = this@DefaultParcelGenerator.world - val worldId = parcelProvider.getWorld(world)?.id ?: ParcelWorldId(world) - override val parcelTraverser: RegionTraverser = RegionTraverser.convergingTo(o.floorHeight) - - private val cornerWallType = when { - o.wallType is Slab -> (o.wallType.clone() as Slab).apply { type = Slab.Type.DOUBLE } - o.wallType.material.name.endsWith("CARPET") -> { - Bukkit.createBlockData(Material.getMaterial(o.wallType.material.name.substringBefore("CARPET") + "WOOL")) - } - else -> null - } - - 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(worldId, idx, idz) } - } - - - private fun checkParcelId(parcel: ParcelId): ParcelId { - if (!parcel.worldId.equals(worldId)) { - throw IllegalArgumentException() - } - return parcel - } - - override fun getRegionOrigin(parcel: ParcelId): Vec2i { - checkParcelId(parcel) - return Vec2i( - sectionSize * (parcel.x - 1) + pathOffset + o.offsetX, - sectionSize * (parcel.z - 1) + pathOffset + o.offsetZ - ) - } - - override fun getRegion(parcel: ParcelId): Region { - val origin = getRegionOrigin(parcel) - return Region( - Vec3i(origin.x, 0, origin.z), - Vec3i(o.parcelSize, maxHeight, o.parcelSize) - ) - } - - override fun getHomeLocation(parcel: ParcelId): Location { - val origin = getRegionOrigin(parcel) - val x = origin.x + (o.parcelSize - 1) / 2.0 - val z = origin.z - 2 - return Location(world, x + 0.5, o.floorHeight + 1.0, z + 0.5, 0F, 0F) - } - - override fun getParcelForInfoBlockInteraction(block: Vec3i, type: Material, face: BlockFace): Parcel? { - if (block.y != o.floorHeight + 1) return null - - val expectedParcelOrigin = when (type) { - Material.WALL_SIGN -> Vec2i(block.x + 1, block.z + 2) - o.wallType.material, cornerWallType?.material -> { - if (face != BlockFace.NORTH || world[block + Vec3i.convert(BlockFace.NORTH)].type == Material.WALL_SIGN) { - return null - } - - Vec2i(block.x + 1, block.z + 1) - } - else -> return null - } - - return getParcelAt(expectedParcelOrigin.x, expectedParcelOrigin.z) - ?.takeIf { expectedParcelOrigin == getRegionOrigin(it.id) } - ?.also { parcel -> - if (type != Material.WALL_SIGN && parcel.owner != null) { - updateParcelInfo(parcel.id, parcel.owner) - parcel.isOwnerSignOutdated = false - } - } - } - - override fun isParcelInfoSectionLoaded(parcel: ParcelId): Boolean { - val wallBlockChunk = getRegionOrigin(parcel).add(-1, -1).toChunk() - return world.isChunkLoaded(wallBlockChunk.x, wallBlockChunk.z) - } - - override fun updateParcelInfo(parcel: ParcelId, owner: PlayerProfile?) { - val b = getRegionOrigin(parcel) - - val wallBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 1) - val signBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 2) - 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 { - cornerWallType?.let { wallBlock.blockData = it } - signBlock.blockData = (Bukkit.createBlockData(Material.WALL_SIGN) as WallSign).apply { facing = 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.AIR - skullBlock.type = Material.PLAYER_HEAD - val skull = skullBlock.state as Skull - if (owner is PlayerProfile.Real) { - skull.owningPlayer = Bukkit.getOfflinePlayer(owner.uuid) - - } else if (!skull.setOwner(owner.name)) { - skullBlock.type = Material.AIR - return - } - - skull.rotation = BlockFace.SOUTH - skull.update() - } - } - - private fun trySubmitBlockVisitor(vararg parcels: ParcelId, function: JobFunction): Job? { - parcels.forEach { checkParcelId(it) } - return parcelProvider.trySubmitBlockVisitor(Permit(), parcels, function) - } - - override fun setBiome(parcel: ParcelId, biome: Biome) = trySubmitBlockVisitor(checkParcelId(parcel)) { - val world = world - val b = getRegionOrigin(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) = trySubmitBlockVisitor(checkParcelId(parcel)) { - val region = getRegion(parcel) - val blocks = parcelTraverser.traverseRegion(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 getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i> { - /* - * Get the offsets for the world out of the way - * to simplify the calculation that follows. - */ - - val x = chunk.x.shl(4) - (o.offsetX + pathOffset) - val z = chunk.z.shl(4) - (o.offsetZ + pathOffset) - - /* Locations of wall corners (where owner blocks are placed) are defined as: - * - * x umod sectionSize == sectionSize-1 - * - * This check needs to be made for all 16 slices of the chunk in 2 dimensions - * How to optimize this? - * Let's take the expression - * - * x umod sectionSize - * - * And call it modX - * x can be shifted (chunkSize -1) times to attempt to get a modX of 0. - * This means that if the modX is 1, and sectionSize == (chunkSize-1), there would be a match at the last shift. - * To check that there are any matches, we can see if the following holds: - * - * modX >= ((sectionSize-1) - (chunkSize-1)) - * - * Which can be simplified to: - * modX >= sectionSize - chunkSize - * - * if sectionSize == chunkSize, this expression can be simplified to - * modX >= 0 - * which is always true. This is expected. - * To get the total number of matches on a dimension, we can evaluate the following: - * - * (modX - (sectionSize - chunkSize) + sectionSize) / sectionSize - * - * We add sectionSize to the lhs because, if the other part of the lhs is 0, we need at least 1. - * This can be simplified to: - * - * (modX + chunkSize) / sectionSize - */ - - val sectionSize = sectionSize - - val modX = x umod sectionSize - val matchesOnDimensionX = (modX + chunkSize) / sectionSize - if (matchesOnDimensionX <= 0) return emptyList() - - val modZ = z umod sectionSize - val matchesOnDimensionZ = (modZ + chunkSize) / sectionSize - if (matchesOnDimensionZ <= 0) return emptyList() - - /* - * Now we need to find the first id within the matches, - * and then return the subsequent matches in a rectangle following it. - * - * On each dimension, get the distance to the first match, which is equal to (sectionSize-1 - modX) - * and add it to the coordinate value - */ - val firstX = x + (sectionSize - 1 - modX) - val firstZ = z + (sectionSize - 1 - modZ) - - val firstIdX = (firstX + 1) / sectionSize + 1 - val firstIdZ = (firstZ + 1) / sectionSize + 1 - - if (matchesOnDimensionX == 1 && matchesOnDimensionZ == 1) { - // fast-path optimization - return listOf(Vec2i(firstIdX, firstIdZ)) - } - - return (0 until matchesOnDimensionX).flatMap { idOffsetX -> - (0 until matchesOnDimensionZ).map { idOffsetZ -> Vec2i(firstIdX + idOffsetX, firstIdZ + idOffsetZ) } - } - } - - } - +package io.dico.parcels2.defaultimpl
+
+import io.dico.parcels2.*
+import io.dico.parcels2.blockvisitor.RegionTraverser
+import io.dico.parcels2.options.DefaultGeneratorOptions
+import io.dico.parcels2.util.math.*
+import kotlinx.coroutines.CoroutineScope
+import org.bukkit.*
+import org.bukkit.block.Biome
+import org.bukkit.block.BlockFace
+import org.bukkit.block.Skull
+import org.bukkit.block.data.type.Slab
+import org.bukkit.block.data.type.WallSign
+import java.util.Random
+
+private val airType = Bukkit.createBlockData(Material.AIR)
+
+private const val chunkSize = 16
+
+class DefaultParcelGenerator(
+ override val worldName: String,
+ private val o: DefaultGeneratorOptions
+) : ParcelGenerator() {
+ private var _world: World? = null
+ override val world: World
+ get() {
+ if (_world == null) {
+ val world = Bukkit.getWorld(worldName)
+ maxHeight = world.maxHeight
+ _world = world
+ return world
+ }
+ return _world!!
+ }
+
+ private var maxHeight = 0
+ 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 <T> 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 makeParcelLocatorAndBlockManager(
+ parcelProvider: ParcelProvider,
+ container: ParcelContainer,
+ coroutineScope: CoroutineScope,
+ jobDispatcher: JobDispatcher
+ ): Pair<ParcelLocator, ParcelBlockManager> {
+ val impl = ParcelLocatorAndBlockManagerImpl(parcelProvider, container, coroutineScope, jobDispatcher)
+ return impl to impl
+ }
+
+ private inline fun <T> 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 + 1, (absZ - modZ) / sectionSize + 1)
+ }
+ return null
+ }
+
+ @Suppress("DEPRECATION")
+ private inner class ParcelLocatorAndBlockManagerImpl(
+ val parcelProvider: ParcelProvider,
+ val container: ParcelContainer,
+ coroutineScope: CoroutineScope,
+ override val jobDispatcher: JobDispatcher
+ ) : ParcelBlockManagerBase(), ParcelLocator, CoroutineScope by coroutineScope {
+
+ override val world: World get() = this@DefaultParcelGenerator.world
+ val worldId = parcelProvider.getWorld(world)?.id ?: ParcelWorldId(world)
+ override val parcelTraverser: RegionTraverser = RegionTraverser.convergingTo(o.floorHeight)
+
+ private val cornerWallType = when {
+ o.wallType is Slab -> (o.wallType.clone() as Slab).apply { type = Slab.Type.DOUBLE }
+ o.wallType.material.name.endsWith("CARPET") -> {
+ Bukkit.createBlockData(Material.getMaterial(o.wallType.material.name.substringBefore("CARPET") + "WOOL"))
+ }
+ else -> null
+ }
+
+ 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(worldId, idx, idz) }
+ }
+
+
+ private fun checkParcelId(parcel: ParcelId): ParcelId {
+ if (!parcel.worldId.equals(worldId)) {
+ throw IllegalArgumentException()
+ }
+ return parcel
+ }
+
+ override fun getRegionOrigin(parcel: ParcelId): Vec2i {
+ checkParcelId(parcel)
+ return Vec2i(
+ sectionSize * (parcel.x - 1) + pathOffset + o.offsetX,
+ sectionSize * (parcel.z - 1) + pathOffset + o.offsetZ
+ )
+ }
+
+ override fun getRegion(parcel: ParcelId): Region {
+ val origin = getRegionOrigin(parcel)
+ return Region(
+ Vec3i(origin.x, 0, origin.z),
+ Vec3i(o.parcelSize, maxHeight, o.parcelSize)
+ )
+ }
+
+ override fun getHomeLocation(parcel: ParcelId): Location {
+ val origin = getRegionOrigin(parcel)
+ val x = origin.x + (o.parcelSize - 1) / 2.0
+ val z = origin.z - 2
+ return Location(world, x + 0.5, o.floorHeight + 1.0, z + 0.5, 0F, 0F)
+ }
+
+ override fun getParcelForInfoBlockInteraction(block: Vec3i, type: Material, face: BlockFace): Parcel? {
+ if (block.y != o.floorHeight + 1) return null
+
+ val expectedParcelOrigin = when (type) {
+ Material.WALL_SIGN -> Vec2i(block.x + 1, block.z + 2)
+ o.wallType.material, cornerWallType?.material -> {
+ if (face != BlockFace.NORTH || world[block + Vec3i.convert(BlockFace.NORTH)].type == Material.WALL_SIGN) {
+ return null
+ }
+
+ Vec2i(block.x + 1, block.z + 1)
+ }
+ else -> return null
+ }
+
+ return getParcelAt(expectedParcelOrigin.x, expectedParcelOrigin.z)
+ ?.takeIf { expectedParcelOrigin == getRegionOrigin(it.id) }
+ ?.also { parcel ->
+ if (type != Material.WALL_SIGN && parcel.owner != null) {
+ updateParcelInfo(parcel.id, parcel.owner)
+ parcel.isOwnerSignOutdated = false
+ }
+ }
+ }
+
+ override fun isParcelInfoSectionLoaded(parcel: ParcelId): Boolean {
+ val wallBlockChunk = getRegionOrigin(parcel).add(-1, -1).toChunk()
+ return world.isChunkLoaded(wallBlockChunk.x, wallBlockChunk.z)
+ }
+
+ override fun updateParcelInfo(parcel: ParcelId, owner: PlayerProfile?) {
+ val b = getRegionOrigin(parcel)
+
+ val wallBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 1)
+ val signBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 2)
+ 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 {
+ cornerWallType?.let { wallBlock.blockData = it }
+ signBlock.blockData = (Bukkit.createBlockData(Material.WALL_SIGN) as WallSign).apply { facing = 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.AIR
+ skullBlock.type = Material.PLAYER_HEAD
+ val skull = skullBlock.state as Skull
+ if (owner is PlayerProfile.Real) {
+ skull.owningPlayer = Bukkit.getOfflinePlayer(owner.uuid)
+
+ } else if (!skull.setOwner(owner.name)) {
+ skullBlock.type = Material.AIR
+ return
+ }
+
+ skull.rotation = BlockFace.SOUTH
+ skull.update()
+ }
+ }
+
+ private fun trySubmitBlockVisitor(vararg parcels: ParcelId, function: JobFunction): Job? {
+ parcels.forEach { checkParcelId(it) }
+ return parcelProvider.trySubmitBlockVisitor(Permit(), parcels, function)
+ }
+
+ override fun setBiome(parcel: ParcelId, biome: Biome) = trySubmitBlockVisitor(checkParcelId(parcel)) {
+ val world = world
+ val b = getRegionOrigin(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) = trySubmitBlockVisitor(checkParcelId(parcel)) {
+ val region = getRegion(parcel)
+ val blocks = parcelTraverser.traverseRegion(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 getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i> {
+ /*
+ * Get the offsets for the world out of the way
+ * to simplify the calculation that follows.
+ */
+
+ val x = chunk.x.shl(4) - (o.offsetX + pathOffset)
+ val z = chunk.z.shl(4) - (o.offsetZ + pathOffset)
+
+ /* Locations of wall corners (where owner blocks are placed) are defined as:
+ *
+ * x umod sectionSize == sectionSize-1
+ *
+ * This check needs to be made for all 16 slices of the chunk in 2 dimensions
+ * How to optimize this?
+ * Let's take the expression
+ *
+ * x umod sectionSize
+ *
+ * And call it modX
+ * x can be shifted (chunkSize -1) times to attempt to get a modX of 0.
+ * This means that if the modX is 1, and sectionSize == (chunkSize-1), there would be a match at the last shift.
+ * To check that there are any matches, we can see if the following holds:
+ *
+ * modX >= ((sectionSize-1) - (chunkSize-1))
+ *
+ * Which can be simplified to:
+ * modX >= sectionSize - chunkSize
+ *
+ * if sectionSize == chunkSize, this expression can be simplified to
+ * modX >= 0
+ * which is always true. This is expected.
+ * To get the total number of matches on a dimension, we can evaluate the following:
+ *
+ * (modX - (sectionSize - chunkSize) + sectionSize) / sectionSize
+ *
+ * We add sectionSize to the lhs because, if the other part of the lhs is 0, we need at least 1.
+ * This can be simplified to:
+ *
+ * (modX + chunkSize) / sectionSize
+ */
+
+ val sectionSize = sectionSize
+
+ val modX = x umod sectionSize
+ val matchesOnDimensionX = (modX + chunkSize) / sectionSize
+ if (matchesOnDimensionX <= 0) return emptyList()
+
+ val modZ = z umod sectionSize
+ val matchesOnDimensionZ = (modZ + chunkSize) / sectionSize
+ if (matchesOnDimensionZ <= 0) return emptyList()
+
+ /*
+ * Now we need to find the first id within the matches,
+ * and then return the subsequent matches in a rectangle following it.
+ *
+ * On each dimension, get the distance to the first match, which is equal to (sectionSize-1 - modX)
+ * and add it to the coordinate value
+ */
+ val firstX = x + (sectionSize - 1 - modX)
+ val firstZ = z + (sectionSize - 1 - modZ)
+
+ val firstIdX = (firstX + 1) / sectionSize + 1
+ val firstIdZ = (firstZ + 1) / sectionSize + 1
+
+ if (matchesOnDimensionX == 1 && matchesOnDimensionZ == 1) {
+ // fast-path optimization
+ return listOf(Vec2i(firstIdX, firstIdZ))
+ }
+
+ return (0 until matchesOnDimensionX).flatMap { idOffsetX ->
+ (0 until matchesOnDimensionZ).map { idOffsetZ -> Vec2i(firstIdX + idOffsetX, firstIdZ + idOffsetZ) }
+ }
+ }
+
+ }
+
}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalPrivilegesManagerImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalPrivilegesManagerImpl.kt index 769cee6..670dd94 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalPrivilegesManagerImpl.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalPrivilegesManagerImpl.kt @@ -1,28 +1,28 @@ -@file:Suppress("UNCHECKED_CAST") - -package io.dico.parcels2.defaultimpl - -import io.dico.parcels2.* -import io.dico.parcels2.util.ext.alsoIfTrue -import java.util.Collections - -class GlobalPrivilegesManagerImpl(val plugin: ParcelsPlugin) : GlobalPrivilegesManager { - private val map = mutableMapOf<PlayerProfile, GlobalPrivileges>() - - override fun get(owner: PlayerProfile.Real): GlobalPrivileges { - return map[owner] ?: GlobalPrivilegesImpl(owner).also { map[owner] = it } - } - - private inner class GlobalPrivilegesImpl(override val keyOfOwner: PlayerProfile.Real) : PrivilegesHolder(), GlobalPrivileges { - override var privilegeOfStar: Privilege - get() = super<GlobalPrivileges>.privilegeOfStar - set(value) = run { super<GlobalPrivileges>.privilegeOfStar = value } - - override fun setRawStoredPrivilege(key: PrivilegeKey, privilege: Privilege): Boolean { - return super.setRawStoredPrivilege(key, privilege).alsoIfTrue { - plugin.storage.setGlobalPrivilege(keyOfOwner, key, privilege) - } - } - } - +@file:Suppress("UNCHECKED_CAST")
+
+package io.dico.parcels2.defaultimpl
+
+import io.dico.parcels2.*
+import io.dico.parcels2.util.ext.alsoIfTrue
+import java.util.Collections
+
+class GlobalPrivilegesManagerImpl(val plugin: ParcelsPlugin) : GlobalPrivilegesManager {
+ private val map = mutableMapOf<PlayerProfile, GlobalPrivileges>()
+
+ override fun get(owner: PlayerProfile.Real): GlobalPrivileges {
+ return map[owner] ?: GlobalPrivilegesImpl(owner).also { map[owner] = it }
+ }
+
+ private inner class GlobalPrivilegesImpl(override val keyOfOwner: PlayerProfile.Real) : PrivilegesHolder(), GlobalPrivileges {
+ override var privilegeOfStar: Privilege
+ get() = super<GlobalPrivileges>.privilegeOfStar
+ set(value) = run { super<GlobalPrivileges>.privilegeOfStar = value }
+
+ override fun setRawStoredPrivilege(key: PrivilegeKey, privilege: Privilege): Boolean {
+ return super.setRawStoredPrivilege(key, privilege).alsoIfTrue {
+ plugin.storage.setGlobalPrivilege(keyOfOwner, key, privilege)
+ }
+ }
+ }
+
}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/InfoBuilder.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/InfoBuilder.kt index a3704c7..99df82a 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/InfoBuilder.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/InfoBuilder.kt @@ -1,88 +1,88 @@ -package io.dico.parcels2.defaultimpl - -import io.dico.dicore.Formatting -import io.dico.parcels2.Privilege -import io.dico.parcels2.PrivilegeKey -import io.dico.parcels2.RawPrivileges -import io.dico.parcels2.filterProfilesWithPrivilegeTo - -object InfoBuilder { - val infoStringColor1 = Formatting.GREEN - val infoStringColor2 = Formatting.AQUA - - inline fun StringBuilder.appendField(field: StringBuilder.() -> Unit, value: StringBuilder.() -> Unit) { - append(infoStringColor1) - field() - append(": ") - append(infoStringColor2) - value() - append(' ') - } - - inline fun StringBuilder.appendField(name: String, value: StringBuilder.() -> Unit) { - appendField({ append(name) }, value) - } - - inline fun StringBuilder.appendFieldWithCount(name: String, count: Int, value: StringBuilder.() -> Unit) { - appendField({ - append(name) - append('(') - append(infoStringColor2) - append(count) - append(infoStringColor1) - append(')') - }, value) - } - - fun StringBuilder.appendProfilesWithPrivilege(fieldName: String, local: RawPrivileges, global: RawPrivileges?, privilege: Privilege) { - val map = linkedMapOf<PrivilegeKey, Privilege>() - local.filterProfilesWithPrivilegeTo(map, privilege) - val localCount = map.size - global?.filterProfilesWithPrivilegeTo(map, privilege) - appendPrivilegeProfiles(fieldName, map, localCount) - } - - fun StringBuilder.appendPrivilegeProfiles(fieldName: String, map: LinkedHashMap<PrivilegeKey, Privilege>, localCount: Int) { - if (map.isEmpty()) return - - appendFieldWithCount(fieldName, map.size) { - // first [localCount] entries are local - val separator = "$infoStringColor1, $infoStringColor2" - val iterator = map.iterator() - - if (localCount != 0) { - appendPrivilegeEntry(false, iterator.next().toPair()) - repeat(localCount - 1) { - append(separator) - appendPrivilegeEntry(false, iterator.next().toPair()) - } - - } else if (iterator.hasNext()) { - // ensure there is never a leading or trailing separator - appendPrivilegeEntry(true, iterator.next().toPair()) - } - - iterator.forEach { next -> - append(separator) - appendPrivilegeEntry(true, next.toPair()) - } - } - } - - fun StringBuilder.appendPrivilegeEntry(global: Boolean, pair: Pair<PrivilegeKey, Privilege>) { - val (key, priv) = pair - - append(key.notNullName) - - // suffix. Maybe T should be M for mod or something. T means they have CAN_MANAGE privilege. - append( - when { - global && priv == Privilege.CAN_MANAGE -> " (G) (T)" - global -> " (G)" - priv == Privilege.CAN_MANAGE -> " (T)" - else -> "" - } - ) - } - +package io.dico.parcels2.defaultimpl
+
+import io.dico.dicore.Formatting
+import io.dico.parcels2.Privilege
+import io.dico.parcels2.PrivilegeKey
+import io.dico.parcels2.RawPrivileges
+import io.dico.parcels2.filterProfilesWithPrivilegeTo
+
+object InfoBuilder {
+ val infoStringColor1 = Formatting.GREEN
+ val infoStringColor2 = Formatting.AQUA
+
+ inline fun StringBuilder.appendField(field: StringBuilder.() -> Unit, value: StringBuilder.() -> Unit) {
+ append(infoStringColor1)
+ field()
+ append(": ")
+ append(infoStringColor2)
+ value()
+ append(' ')
+ }
+
+ inline fun StringBuilder.appendField(name: String, value: StringBuilder.() -> Unit) {
+ appendField({ append(name) }, value)
+ }
+
+ inline fun StringBuilder.appendFieldWithCount(name: String, count: Int, value: StringBuilder.() -> Unit) {
+ appendField({
+ append(name)
+ append('(')
+ append(infoStringColor2)
+ append(count)
+ append(infoStringColor1)
+ append(')')
+ }, value)
+ }
+
+ fun StringBuilder.appendProfilesWithPrivilege(fieldName: String, local: RawPrivileges, global: RawPrivileges?, privilege: Privilege) {
+ val map = linkedMapOf<PrivilegeKey, Privilege>()
+ local.filterProfilesWithPrivilegeTo(map, privilege)
+ val localCount = map.size
+ global?.filterProfilesWithPrivilegeTo(map, privilege)
+ appendPrivilegeProfiles(fieldName, map, localCount)
+ }
+
+ fun StringBuilder.appendPrivilegeProfiles(fieldName: String, map: LinkedHashMap<PrivilegeKey, Privilege>, localCount: Int) {
+ if (map.isEmpty()) return
+
+ appendFieldWithCount(fieldName, map.size) {
+ // first [localCount] entries are local
+ val separator = "$infoStringColor1, $infoStringColor2"
+ val iterator = map.iterator()
+
+ if (localCount != 0) {
+ appendPrivilegeEntry(false, iterator.next().toPair())
+ repeat(localCount - 1) {
+ append(separator)
+ appendPrivilegeEntry(false, iterator.next().toPair())
+ }
+
+ } else if (iterator.hasNext()) {
+ // ensure there is never a leading or trailing separator
+ appendPrivilegeEntry(true, iterator.next().toPair())
+ }
+
+ iterator.forEach { next ->
+ append(separator)
+ appendPrivilegeEntry(true, next.toPair())
+ }
+ }
+ }
+
+ fun StringBuilder.appendPrivilegeEntry(global: Boolean, pair: Pair<PrivilegeKey, Privilege>) {
+ val (key, priv) = pair
+
+ append(key.notNullName)
+
+ // suffix. Maybe T should be M for mod or something. T means they have CAN_MANAGE privilege.
+ append(
+ when {
+ global && priv == Privilege.CAN_MANAGE -> " (G) (T)"
+ global -> " (G)"
+ priv == Privilege.CAN_MANAGE -> " (T)"
+ else -> ""
+ }
+ )
+ }
+
}
\ 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 index 48a7fee..da004d6 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt @@ -1,223 +1,223 @@ -package io.dico.parcels2.defaultimpl - -import io.dico.parcels2.* -import io.dico.parcels2.blockvisitor.Schematic -import io.dico.parcels2.util.schedule -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import org.bukkit.Bukkit -import org.bukkit.WorldCreator -import org.joda.time.DateTime - -class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider { - inline val options get() = plugin.options - override val worlds: Map<String, ParcelWorld> get() = _worlds - private val _worlds: MutableMap<String, ParcelWorld> = hashMapOf() - private val _generators: MutableMap<String, ParcelGenerator> = 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?.newInstance(worldName)?.also { _generators[worldName] = it } - } - - override fun loadWorlds() { - if (_worldsLoaded) throw IllegalStateException() - _worldsLoaded = true - loadWorlds0() - } - - private fun loadWorlds0() { - if (Bukkit.getWorlds().isEmpty()) { - plugin.schedule(::loadWorlds0) - plugin.logger.warning("Scheduling to load worlds in the next tick because no bukkit worlds are loaded yet") - return - } - - val newlyCreatedWorlds = mutableListOf<ParcelWorld>() - for ((worldName, worldOptions) in options.worlds.entries) { - var parcelWorld = _worlds[worldName] - if (parcelWorld != null) continue - - val generator: ParcelGenerator = getWorldGenerator(worldName)!! - val worldExists = Bukkit.getWorld(worldName) != null - val bukkitWorld = - if (worldExists) Bukkit.getWorld(worldName)!! - else { - logger.info("Creating world $worldName") - WorldCreator(worldName).generator(generator).createWorld() - } - - parcelWorld = ParcelWorldImpl(plugin, bukkitWorld, generator, worldOptions.runtime,::DefaultParcelContainer) - - if (!worldExists) { - val time = DateTime.now() - plugin.storage.setWorldCreationTime(parcelWorld.id, time) - parcelWorld.creationTime = time - newlyCreatedWorlds.add(parcelWorld) - } else { - GlobalScope.launch(context = Dispatchers.Unconfined) { - parcelWorld.creationTime = plugin.storage.getWorldCreationTime(parcelWorld.id).await() ?: DateTime.now() - } - } - - _worlds[worldName] = parcelWorld - } - - loadStoredData(newlyCreatedWorlds.toSet()) - } - - private fun loadStoredData(newlyCreatedWorlds: Collection<ParcelWorld> = emptyList()) { - plugin.launch(Dispatchers.Default) { - val migration = plugin.options.migration - if (migration.enabled) { - migration.instance?.newInstance()?.apply { - logger.warn("Migrating database now...") - migrateTo(plugin.storage).join() - logger.warn("Migration completed") - - if (migration.disableWhenComplete) { - migration.enabled = false - plugin.saveOptions() - } - } - } - - logger.info("Loading all parcel data...") - - val job1 = launch { - val channel = plugin.storage.transmitAllParcelData() - while (true) { - val (id, data) = channel.receiveOrNull() ?: break - val parcel = getParcelById(id) ?: continue - data?.let { parcel.copyData(it, callerIsDatabase = true) } - } - } - - val channel2 = plugin.storage.transmitAllGlobalPrivileges() - while (true) { - val (profile, data) = channel2.receiveOrNull() ?: break - if (profile !is PrivilegeKey) { - logger.error("Received profile that is not a privilege key: ${profile.javaClass}, $profile") - continue - } - (plugin.globalPrivileges[profile] as PrivilegesHolder).copyPrivilegesFrom(data) - } - - job1.join() - - logger.info("Loading data completed") - _dataIsLoaded = true - } - } - - override fun acquireBlockVisitorPermit(parcelId: ParcelId, with: Permit): Boolean { - val parcel = getParcelById(parcelId) as? ParcelImpl ?: return true - return parcel.acquireBlockVisitorPermit(with) - } - - override fun releaseBlockVisitorPermit(parcelId: ParcelId, with: Permit) { - val parcel = getParcelById(parcelId) as? ParcelImpl ?: return - parcel.releaseBlockVisitorPermit(with) - } - - override fun trySubmitBlockVisitor(permit: Permit, vararg parcelIds: ParcelId, function: JobFunction): Job? { - val withPermit = parcelIds.filter { acquireBlockVisitorPermit(it, permit) } - if (withPermit.size != parcelIds.size) { - withPermit.forEach { releaseBlockVisitorPermit(it, permit) } - return null - } - - val job = plugin.jobDispatcher.dispatch(function) - - plugin.launch { - job.awaitCompletion() - withPermit.forEach { releaseBlockVisitorPermit(it, permit) } - } - - return job - } - - override fun swapParcels(parcelId1: ParcelId, parcelId2: ParcelId): Job? { - val blockManager1 = getWorldById(parcelId1.worldId)?.blockManager ?: return null - val blockManager2 = getWorldById(parcelId2.worldId)?.blockManager ?: return null - - return trySubmitBlockVisitor(Permit(), parcelId1, parcelId2) { - var region1 = blockManager1.getRegion(parcelId1) - var region2 = blockManager2.getRegion(parcelId2) - - val size = region1.size.clampMax(region2.size) - if (size != region1.size) { - region1 = region1.withSize(size) - region2 = region2.withSize(size) - } - - val schematicOf1 = delegateWork(0.25) { Schematic().apply { load(blockManager1.world, region1) } } - val schematicOf2 = delegateWork(0.25) { Schematic().apply { load(blockManager2.world, region2) } } - delegateWork(0.25) { with(schematicOf1) { paste(blockManager2.world, region2.origin) } } - delegateWork(0.25) { with(schematicOf2) { paste(blockManager1.world, region1.origin) } } - } - } - - /* - 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.globalPrivileges, - ::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.transmitAllParcelData() - 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() - } - - } - */ +package io.dico.parcels2.defaultimpl
+
+import io.dico.parcels2.*
+import io.dico.parcels2.blockvisitor.Schematic
+import io.dico.parcels2.util.schedule
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import org.bukkit.Bukkit
+import org.bukkit.WorldCreator
+import org.joda.time.DateTime
+
+class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
+ inline val options get() = plugin.options
+ override val worlds: Map<String, ParcelWorld> get() = _worlds
+ private val _worlds: MutableMap<String, ParcelWorld> = hashMapOf()
+ private val _generators: MutableMap<String, ParcelGenerator> = 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?.newInstance(worldName)?.also { _generators[worldName] = it }
+ }
+
+ override fun loadWorlds() {
+ if (_worldsLoaded) throw IllegalStateException()
+ _worldsLoaded = true
+ loadWorlds0()
+ }
+
+ private fun loadWorlds0() {
+ if (Bukkit.getWorlds().isEmpty()) {
+ plugin.schedule(::loadWorlds0)
+ plugin.logger.warning("Scheduling to load worlds in the next tick because no bukkit worlds are loaded yet")
+ return
+ }
+
+ val newlyCreatedWorlds = mutableListOf<ParcelWorld>()
+ for ((worldName, worldOptions) in options.worlds.entries) {
+ var parcelWorld = _worlds[worldName]
+ if (parcelWorld != null) continue
+
+ val generator: ParcelGenerator = getWorldGenerator(worldName)!!
+ val worldExists = Bukkit.getWorld(worldName) != null
+ val bukkitWorld =
+ if (worldExists) Bukkit.getWorld(worldName)!!
+ else {
+ logger.info("Creating world $worldName")
+ WorldCreator(worldName).generator(generator).createWorld()
+ }
+
+ parcelWorld = ParcelWorldImpl(plugin, bukkitWorld, generator, worldOptions.runtime,::DefaultParcelContainer)
+
+ if (!worldExists) {
+ val time = DateTime.now()
+ plugin.storage.setWorldCreationTime(parcelWorld.id, time)
+ parcelWorld.creationTime = time
+ newlyCreatedWorlds.add(parcelWorld)
+ } else {
+ GlobalScope.launch(context = Dispatchers.Unconfined) {
+ parcelWorld.creationTime = plugin.storage.getWorldCreationTime(parcelWorld.id).await() ?: DateTime.now()
+ }
+ }
+
+ _worlds[worldName] = parcelWorld
+ }
+
+ loadStoredData(newlyCreatedWorlds.toSet())
+ }
+
+ private fun loadStoredData(newlyCreatedWorlds: Collection<ParcelWorld> = emptyList()) {
+ plugin.launch(Dispatchers.Default) {
+ val migration = plugin.options.migration
+ if (migration.enabled) {
+ migration.instance?.newInstance()?.apply {
+ logger.warn("Migrating database now...")
+ migrateTo(plugin.storage).join()
+ logger.warn("Migration completed")
+
+ if (migration.disableWhenComplete) {
+ migration.enabled = false
+ plugin.saveOptions()
+ }
+ }
+ }
+
+ logger.info("Loading all parcel data...")
+
+ val job1 = launch {
+ val channel = plugin.storage.transmitAllParcelData()
+ while (true) {
+ val (id, data) = channel.receiveOrNull() ?: break
+ val parcel = getParcelById(id) ?: continue
+ data?.let { parcel.copyData(it, callerIsDatabase = true) }
+ }
+ }
+
+ val channel2 = plugin.storage.transmitAllGlobalPrivileges()
+ while (true) {
+ val (profile, data) = channel2.receiveOrNull() ?: break
+ if (profile !is PrivilegeKey) {
+ logger.error("Received profile that is not a privilege key: ${profile.javaClass}, $profile")
+ continue
+ }
+ (plugin.globalPrivileges[profile] as PrivilegesHolder).copyPrivilegesFrom(data)
+ }
+
+ job1.join()
+
+ logger.info("Loading data completed")
+ _dataIsLoaded = true
+ }
+ }
+
+ override fun acquireBlockVisitorPermit(parcelId: ParcelId, with: Permit): Boolean {
+ val parcel = getParcelById(parcelId) as? ParcelImpl ?: return true
+ return parcel.acquireBlockVisitorPermit(with)
+ }
+
+ override fun releaseBlockVisitorPermit(parcelId: ParcelId, with: Permit) {
+ val parcel = getParcelById(parcelId) as? ParcelImpl ?: return
+ parcel.releaseBlockVisitorPermit(with)
+ }
+
+ override fun trySubmitBlockVisitor(permit: Permit, vararg parcelIds: ParcelId, function: JobFunction): Job? {
+ val withPermit = parcelIds.filter { acquireBlockVisitorPermit(it, permit) }
+ if (withPermit.size != parcelIds.size) {
+ withPermit.forEach { releaseBlockVisitorPermit(it, permit) }
+ return null
+ }
+
+ val job = plugin.jobDispatcher.dispatch(function)
+
+ plugin.launch {
+ job.awaitCompletion()
+ withPermit.forEach { releaseBlockVisitorPermit(it, permit) }
+ }
+
+ return job
+ }
+
+ override fun swapParcels(parcelId1: ParcelId, parcelId2: ParcelId): Job? {
+ val blockManager1 = getWorldById(parcelId1.worldId)?.blockManager ?: return null
+ val blockManager2 = getWorldById(parcelId2.worldId)?.blockManager ?: return null
+
+ return trySubmitBlockVisitor(Permit(), parcelId1, parcelId2) {
+ var region1 = blockManager1.getRegion(parcelId1)
+ var region2 = blockManager2.getRegion(parcelId2)
+
+ val size = region1.size.clampMax(region2.size)
+ if (size != region1.size) {
+ region1 = region1.withSize(size)
+ region2 = region2.withSize(size)
+ }
+
+ val schematicOf1 = delegateWork(0.25) { Schematic().apply { load(blockManager1.world, region1) } }
+ val schematicOf2 = delegateWork(0.25) { Schematic().apply { load(blockManager2.world, region2) } }
+ delegateWork(0.25) { with(schematicOf1) { paste(blockManager2.world, region2.origin) } }
+ delegateWork(0.25) { with(schematicOf2) { paste(blockManager1.world, region1.origin) } }
+ }
+ }
+
+ /*
+ 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.globalPrivileges,
+ ::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.transmitAllParcelData()
+ 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 index 531a25f..6ce4f26 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt @@ -1,75 +1,75 @@ -@file:Suppress("CanBePrimaryConstructorProperty", "UsePropertyAccessSyntax") - -package io.dico.parcels2.defaultimpl - -import io.dico.parcels2.* -import io.dico.parcels2.options.RuntimeWorldOptions -import io.dico.parcels2.storage.Storage -import kotlinx.coroutines.CoroutineScope -import org.bukkit.GameRule -import org.bukkit.World -import org.joda.time.DateTime -import java.util.UUID - -class ParcelWorldImpl( - val plugin: ParcelsPlugin, - override val world: World, - override val generator: ParcelGenerator, - override var options: RuntimeWorldOptions, - containerFactory: ParcelContainerFactory -) : ParcelWorld, ParcelWorldId, ParcelContainer, ParcelLocator { - override val id: ParcelWorldId get() = this - override val uid: UUID? get() = world.uid - - override val storage get() = plugin.storage - override val globalPrivileges get() = plugin.globalPrivileges - - init { - if (generator.world != world) { - throw IllegalArgumentException() - } - } - - override val name: String = world.name!! - override val container: ParcelContainer = containerFactory(this) - override val locator: ParcelLocator - override val blockManager: ParcelBlockManager - - init { - val (locator, blockManager) = generator.makeParcelLocatorAndBlockManager(plugin.parcelProvider, container, plugin, plugin.jobDispatcher) - this.locator = locator - this.blockManager = blockManager - enforceOptions() - } - - fun enforceOptions() { - if (options.dayTime) { - world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false) - world.setTime(6000) - } - - if (options.noWeather) { - world.setStorm(false) - world.setThundering(false) - world.weatherDuration = Int.MAX_VALUE - } - - world.setGameRule(GameRule.DO_TILE_DROPS, options.doTileDrops) - } - - // Accessed by ParcelProviderImpl - override var creationTime: DateTime? = null - - - override fun getParcelAt(x: Int, z: Int): Parcel? = locator.getParcelAt(x, z) - - override fun getParcelIdAt(x: Int, z: Int): ParcelId? = locator.getParcelIdAt(x, z) - - override fun getParcelById(x: Int, z: Int): Parcel? = container.getParcelById(x, z) - - override fun getParcelById(id: ParcelId): Parcel? = container.getParcelById(id) - - override fun nextEmptyParcel(): Parcel? = container.nextEmptyParcel() - - override fun toString() = parcelWorldIdToString() -} +@file:Suppress("CanBePrimaryConstructorProperty", "UsePropertyAccessSyntax")
+
+package io.dico.parcels2.defaultimpl
+
+import io.dico.parcels2.*
+import io.dico.parcels2.options.RuntimeWorldOptions
+import io.dico.parcels2.storage.Storage
+import kotlinx.coroutines.CoroutineScope
+import org.bukkit.GameRule
+import org.bukkit.World
+import org.joda.time.DateTime
+import java.util.UUID
+
+class ParcelWorldImpl(
+ val plugin: ParcelsPlugin,
+ override val world: World,
+ override val generator: ParcelGenerator,
+ override var options: RuntimeWorldOptions,
+ containerFactory: ParcelContainerFactory
+) : ParcelWorld, ParcelWorldId, ParcelContainer, ParcelLocator {
+ override val id: ParcelWorldId get() = this
+ override val uid: UUID? get() = world.uid
+
+ override val storage get() = plugin.storage
+ override val globalPrivileges get() = plugin.globalPrivileges
+
+ init {
+ if (generator.world != world) {
+ throw IllegalArgumentException()
+ }
+ }
+
+ override val name: String = world.name!!
+ override val container: ParcelContainer = containerFactory(this)
+ override val locator: ParcelLocator
+ override val blockManager: ParcelBlockManager
+
+ init {
+ val (locator, blockManager) = generator.makeParcelLocatorAndBlockManager(plugin.parcelProvider, container, plugin, plugin.jobDispatcher)
+ this.locator = locator
+ this.blockManager = blockManager
+ enforceOptions()
+ }
+
+ fun enforceOptions() {
+ if (options.dayTime) {
+ world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false)
+ world.setTime(6000)
+ }
+
+ if (options.noWeather) {
+ world.setStorm(false)
+ world.setThundering(false)
+ world.weatherDuration = Int.MAX_VALUE
+ }
+
+ world.setGameRule(GameRule.DO_TILE_DROPS, options.doTileDrops)
+ }
+
+ // Accessed by ParcelProviderImpl
+ override var creationTime: DateTime? = null
+
+
+ override fun getParcelAt(x: Int, z: Int): Parcel? = locator.getParcelAt(x, z)
+
+ override fun getParcelIdAt(x: Int, z: Int): ParcelId? = locator.getParcelIdAt(x, z)
+
+ override fun getParcelById(x: Int, z: Int): Parcel? = container.getParcelById(x, z)
+
+ override fun getParcelById(id: ParcelId): Parcel? = container.getParcelById(id)
+
+ override fun nextEmptyParcel(): Parcel? = container.nextEmptyParcel()
+
+ override fun toString() = parcelWorldIdToString()
+}
diff --git a/src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt b/src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt index 198e0e7..78b8b03 100644 --- a/src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt +++ b/src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt @@ -1,61 +1,61 @@ -package io.dico.parcels2.listener - -import io.dico.parcels2.Parcel -import io.dico.parcels2.ParcelProvider -import io.dico.parcels2.util.ext.editLoop -import org.bukkit.entity.Entity - -class ParcelEntityTracker(val parcelProvider: ParcelProvider) { - val map = mutableMapOf<Entity, Parcel?>() - - fun untrack(entity: Entity) { - map.remove(entity) - } - - fun track(entity: Entity, parcel: Parcel?) { - map[entity] = parcel - } - - /* - * Tracks entities. If the entity is dead, they are removed from the collection. - * If the entity is found to have left the parcel it was created in, it will be removed from the world and from the list. - * If it is still in the parcel it was created in, and it is on the ground, it is removed from the list. - * - * Start after 5 seconds, run every 0.25 seconds - */ - fun tick() { - map.editLoop { entity, parcel -> - if (entity.isDead) { - remove(); return@editLoop - } - - if (parcel != null && parcel.hasBlockVisitors) { - remove() - - val newParcel = parcelProvider.getParcelAt(entity.location) - if (newParcel !== parcel && (newParcel == null || !newParcel.hasBlockVisitors)) { - entity.remove() - } - - return@editLoop - } - - val newParcel = parcelProvider.getParcelAt(entity.location) - if (newParcel !== parcel && (newParcel == null || !newParcel.hasBlockVisitors)) { - remove() - entity.remove() - } - } - } - - fun swapParcels(parcel1: Parcel, parcel2: Parcel) { - map.editLoop { -> - if (value === parcel1) { - value = parcel2 - } else if (value === parcel2) { - value = parcel1 - } - } - } - +package io.dico.parcels2.listener
+
+import io.dico.parcels2.Parcel
+import io.dico.parcels2.ParcelProvider
+import io.dico.parcels2.util.ext.editLoop
+import org.bukkit.entity.Entity
+
+class ParcelEntityTracker(val parcelProvider: ParcelProvider) {
+ val map = mutableMapOf<Entity, Parcel?>()
+
+ fun untrack(entity: Entity) {
+ map.remove(entity)
+ }
+
+ fun track(entity: Entity, parcel: Parcel?) {
+ map[entity] = parcel
+ }
+
+ /*
+ * Tracks entities. If the entity is dead, they are removed from the collection.
+ * If the entity is found to have left the parcel it was created in, it will be removed from the world and from the list.
+ * If it is still in the parcel it was created in, and it is on the ground, it is removed from the list.
+ *
+ * Start after 5 seconds, run every 0.25 seconds
+ */
+ fun tick() {
+ map.editLoop { entity, parcel ->
+ if (entity.isDead) {
+ remove(); return@editLoop
+ }
+
+ if (parcel != null && parcel.hasBlockVisitors) {
+ remove()
+
+ val newParcel = parcelProvider.getParcelAt(entity.location)
+ if (newParcel !== parcel && (newParcel == null || !newParcel.hasBlockVisitors)) {
+ entity.remove()
+ }
+
+ return@editLoop
+ }
+
+ val newParcel = parcelProvider.getParcelAt(entity.location)
+ if (newParcel !== parcel && (newParcel == null || !newParcel.hasBlockVisitors)) {
+ remove()
+ entity.remove()
+ }
+ }
+ }
+
+ fun swapParcels(parcel1: Parcel, parcel2: Parcel) {
+ map.editLoop { ->
+ if (value === parcel1) {
+ value = parcel2
+ } else if (value === parcel2) {
+ value = parcel1
+ }
+ }
+ }
+
}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt b/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt index 9c9bdc2..8bef7d1 100644 --- a/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt +++ b/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt @@ -1,661 +1,661 @@ -package io.dico.parcels2.listener - -import gnu.trove.TLongCollection -import gnu.trove.set.hash.TLongHashSet -import io.dico.dicore.Formatting -import io.dico.dicore.ListenerMarker -import io.dico.dicore.RegistratorListener -import io.dico.parcels2.* -import io.dico.parcels2.storage.Storage -import io.dico.parcels2.util.ext.* -import io.dico.parcels2.util.math.* -import org.bukkit.Location -import org.bukkit.Material.* -import org.bukkit.World -import org.bukkit.block.Biome -import org.bukkit.block.Block -import org.bukkit.block.data.Directional -import org.bukkit.block.data.type.Bed -import org.bukkit.entity.* -import org.bukkit.entity.minecart.ExplosiveMinecart -import org.bukkit.event.EventPriority -import org.bukkit.event.EventPriority.NORMAL -import org.bukkit.event.block.* -import org.bukkit.event.entity.* -import org.bukkit.event.hanging.HangingBreakByEntityEvent -import org.bukkit.event.hanging.HangingBreakEvent -import org.bukkit.event.hanging.HangingPlaceEvent -import org.bukkit.event.inventory.InventoryInteractEvent -import org.bukkit.event.player.* -import org.bukkit.event.vehicle.VehicleMoveEvent -import org.bukkit.event.weather.WeatherChangeEvent -import org.bukkit.event.world.ChunkLoadEvent -import org.bukkit.event.world.StructureGrowEvent -import org.bukkit.inventory.InventoryHolder -import java.util.EnumSet - -class ParcelListeners( - val parcelProvider: ParcelProvider, - val entityTracker: ParcelEntityTracker, - val storage: Storage -) { - private fun canBuildOnArea(user: Player, area: Parcel?) = - if (area == null) user.hasPermBuildAnywhere else area.canBuild(user) - - private fun canInteract(user: Player, area: Parcel?, interactClass: String) = - canBuildOnArea(user, area) || (area != null && area.interactableConfig(interactClass)) - - /** - * Get the world and parcel that the block resides in - * the parcel is nullable, and often named area because that means path. - * returns null if not in a registered parcel world - should always return in that case to not affect other worlds. - */ - private fun getWorldAndArea(block: Block): Pair<ParcelWorld, Parcel?>? { - val world = parcelProvider.getWorld(block.world) ?: return null - return world to world.getParcelAt(block) - } - - - /* - * Prevents players from entering plots they are banned from - */ - @field:ListenerMarker(priority = NORMAL) - val onPlayerMoveEvent = RegistratorListener<PlayerMoveEvent> l@{ event -> - val user = event.player - if (user.hasPermBanBypass) return@l - val toLoc = event.to - val parcel = parcelProvider.getParcelAt(toLoc) ?: return@l - - if (!parcel.canEnterFast(user)) { - val region = parcel.world.blockManager.getRegion(parcel.id) - val dimension = region.getFirstUncontainedDimensionOf(Vec3i(event.from)) - - if (dimension == null) { - user.teleport(parcel.homeLocation) - user.sendParcelMessage(nopermit = true, message = "You are banned from this parcel") - - } else { - val speed = getPlayerSpeed(user) - val from = Vec3d(event.from) - val to = Vec3d(toLoc).with(dimension, from[dimension]) - - var newTo = to - dimension.otherDimensions.forEach { - val delta = to[it] - from[it] - newTo = newTo.add(it, delta * 100 * if (it == Dimension.Y) 0.5 else speed) - } - - event.to = Location( - toLoc.world, - newTo.x, newTo.y.clampMin(0.0).clampMax(255.0), newTo.z, - toLoc.yaw, toLoc.pitch - ) - } - } - } - - /* - * Prevents players from breaking blocks outside of their parcels - * Prevents containers from dropping their contents when broken, if configured - */ - @field:ListenerMarker(priority = NORMAL) - val onBlockBreakEvent = RegistratorListener<BlockBreakEvent> l@{ event -> - val (world, area) = getWorldAndArea(event.block) ?: return@l - if (!canBuildOnArea(event.player, area)) { - event.isCancelled = true; return@l - } - - if (!world.options.dropEntityItems) { - val state = event.block.state - if (state is InventoryHolder) { - state.inventory.clear() - state.update() - } - } - } - - /* - * Prevents players from placing blocks outside of their parcels - */ - @field:ListenerMarker(priority = NORMAL) - val onBlockPlaceEvent = RegistratorListener<BlockPlaceEvent> l@{ event -> - val (_, area) = getWorldAndArea(event.block) ?: return@l - if (!canBuildOnArea(event.player, area)) { - event.isCancelled = true - } - - area?.updateOwnerSign() - } - - /* - * Control pistons - */ - @field:ListenerMarker(priority = NORMAL) - val onBlockPistonExtendEvent = RegistratorListener<BlockPistonExtendEvent> l@{ event -> - checkPistonMovement(event, event.blocks) - } - - @field:ListenerMarker(priority = NORMAL) - val onBlockPistonRetractEvent = RegistratorListener<BlockPistonRetractEvent> l@{ event -> - checkPistonMovement(event, event.blocks) - } - - // Doing some unnecessary optimizations here.. - //@formatter:off - private inline fun Column(x: Int, z: Int): Long = x.toLong() or (z.toLong().shl(32)) - - private inline val Long.columnX get() = and(0xFFFF_FFFFL).toInt() - private inline val Long.columnZ get() = ushr(32).and(0xFFFF_FFFFL).toInt() - 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<Block>) { - val world = parcelProvider.getWorld(event.block.world) ?: return - val direction = event.direction - val columns = TLongHashSet(blocks.size * 2) - - blocks.forEach { - columns.add(Column(it.x, it.z)) - it.getRelative(direction).let { columns.add(Column(it.x, it.z)) } - } - - columns.troveForEach { - val area = world.getParcelAt(it.columnX, it.columnZ) - if (area == null || area.hasBlockVisitors) { - event.isCancelled = true - return - } - } - } - - /* - * Prevents explosions if enabled by the configs for that world - */ - @field:ListenerMarker(priority = NORMAL) - val onExplosionPrimeEvent = RegistratorListener<ExplosionPrimeEvent> l@{ event -> - val (world, area) = getWorldAndArea(event.entity.location.block) ?: return@l - if (area != null && area.hasBlockVisitors) { - event.radius = 0F; event.isCancelled = true - } else if (world.options.disableExplosions) { - event.radius = 0F - } - } - - /* - * Prevents creepers and tnt minecarts from exploding if explosions are disabled - */ - @field:ListenerMarker(priority = NORMAL) - val onEntityExplodeEvent = RegistratorListener<EntityExplodeEvent> l@{ event -> - entityTracker.untrack(event.entity) - val world = parcelProvider.getWorld(event.entity.world) ?: return@l - if (world.options.disableExplosions || world.getParcelAt(event.entity).let { it != null && it.hasBlockVisitors }) { - event.isCancelled = true - } - } - - /* - * Prevents liquids from flowing out of plots - */ - @field:ListenerMarker(priority = NORMAL) - val onBlockFromToEvent = RegistratorListener<BlockFromToEvent> l@{ event -> - val (_, area) = getWorldAndArea(event.toBlock) ?: return@l - if (area == null || area.hasBlockVisitors) event.isCancelled = true - } - - private val bedTypes = EnumSet.copyOf(getMaterialsWithWoolColorPrefix("BED").toList()) - /* - * Prevents players from placing liquids, using flint and steel, changing redstone components, - * using inputs (unless allowed by the plot), - * and using items disabled in the configuration for that world. - * Prevents player from using beds in HELL or SKY biomes if explosions are disabled. - */ - @Suppress("NON_EXHAUSTIVE_WHEN") - @field:ListenerMarker(priority = NORMAL) - val onPlayerInteractEvent = RegistratorListener<PlayerInteractEvent> l@{ event -> - val user = event.player - val world = parcelProvider.getWorld(user.world) ?: return@l - val clickedBlock = event.clickedBlock - val parcel = clickedBlock?.let { world.getParcelAt(it) } - - if (!user.hasPermBuildAnywhere && parcel != null && !parcel.canEnter(user)) { - user.sendParcelMessage(nopermit = true, message = "You cannot interact with parcels you're banned from") - event.isCancelled = true; return@l - } - - when (event.action) { - Action.RIGHT_CLICK_BLOCK -> run { - val type = clickedBlock.type - - val interactableClass = Interactables[type] - if (interactableClass != null && !parcel.effectiveInteractableConfig.isInteractable(type) && (parcel == null || !parcel.canBuild(user))) { - user.sendParcelMessage(nopermit = true, message = "You cannot interact with ${interactableClass.name} here") - event.isCancelled = true - return@l - } - - if (bedTypes.contains(type)) { - val bed = clickedBlock.blockData as Bed - val head = if (bed.part == Bed.Part.FOOT) clickedBlock.getRelative(bed.facing) else clickedBlock - when (head.biome) { - Biome.NETHER, Biome.THE_END -> { - if (world.options.disableExplosions) { - user.sendParcelMessage(nopermit = true, message = "You cannot use this bed because it would explode") - event.isCancelled = true; return@l - } - } - } - - if (!canBuildOnArea(user, parcel)) { - user.sendParcelMessage(nopermit = true, message = "You may not sleep here") - event.isCancelled = true; return@l - } - } - - onPlayerRightClick(event, world, parcel) - - if (!event.isCancelled && parcel == null) { - world.blockManager.getParcelForInfoBlockInteraction(Vec3i(clickedBlock), type, event.blockFace) - ?.apply { user.sendMessage(Formatting.GREEN + infoString) } - } - } - - Action.RIGHT_CLICK_AIR -> onPlayerRightClick(event, world, parcel) - Action.PHYSICAL -> if (!canBuildOnArea(user, parcel) && !(parcel != null && parcel.interactableConfig("pressure_plates"))) { - user.sendParcelMessage(nopermit = true, message = "You cannot use inputs in this parcel") - event.isCancelled = true; return@l - } - } - } - - // private val blockPlaceInteractItems = EnumSet.of(LAVA_BUCKET, WATER_BUCKET, BUCKET, FLINT_AND_STEEL) - - @Suppress("NON_EXHAUSTIVE_WHEN") - private fun onPlayerRightClick(event: PlayerInteractEvent, world: ParcelWorld, parcel: Parcel?) { - if (event.hasItem()) { - val item = event.item.type - if (world.options.blockedItems.contains(item)) { - event.player.sendParcelMessage(nopermit = true, message = "You cannot use this item because it is disabled in this world") - event.isCancelled = true; return - } - - when (item) { - LAVA_BUCKET, WATER_BUCKET, BUCKET, FLINT_AND_STEEL -> { - val block = event.clickedBlock.getRelative(event.blockFace) - val otherParcel = world.getParcelAt(block) - if (!canBuildOnArea(event.player, otherParcel)) { - event.isCancelled = true - } - } - } - } - } - - /* - * Prevents players from breeding mobs, entering or opening boats/minecarts, - * rotating item frames, doing stuff with leashes, and putting stuff on armor stands. - */ - @Suppress("NON_EXHAUSTIVE_WHEN") - @field:ListenerMarker(priority = NORMAL) - val onPlayerInteractEntityEvent = RegistratorListener<PlayerInteractEntityEvent> l@{ event -> - val (_, area) = getWorldAndArea(event.rightClicked.location.block) ?: return@l - if (canBuildOnArea(event.player, area)) return@l - when (event.rightClicked.type) { - EntityType.BOAT, - EntityType.MINECART, - EntityType.MINECART_CHEST, - EntityType.MINECART_COMMAND, - EntityType.MINECART_FURNACE, - EntityType.MINECART_HOPPER, - EntityType.MINECART_MOB_SPAWNER, - EntityType.MINECART_TNT, - - EntityType.ARMOR_STAND, - EntityType.PAINTING, - EntityType.ITEM_FRAME, - EntityType.LEASH_HITCH, - - EntityType.CHICKEN, - EntityType.COW, - EntityType.HORSE, - EntityType.SHEEP, - EntityType.VILLAGER, - EntityType.WOLF -> event.isCancelled = true - } - } - - /* - * Prevents endermen from griefing. - * Prevents sand blocks from exiting the parcel in which they became an entity. - */ - @field:ListenerMarker(priority = NORMAL) - val onEntityChangeBlockEvent = RegistratorListener<EntityChangeBlockEvent> l@{ event -> - val (_, area) = getWorldAndArea(event.block) ?: return@l - if (event.entity.type == EntityType.ENDERMAN || area == null || area.hasBlockVisitors) { - event.isCancelled = true; return@l - } - - if (event.entity.type == EntityType.FALLING_BLOCK) { - // a sand block started falling. Track it and delete it if it gets out of this parcel. - entityTracker.track(event.entity, area) - } - } - - /* - * Prevents portals from being created if set so in the configs for that world - */ - @field:ListenerMarker(priority = NORMAL) - val onEntityCreatePortalEvent = RegistratorListener<EntityCreatePortalEvent> l@{ event -> - val world = parcelProvider.getWorld(event.entity.world) ?: return@l - if (world.options.blockPortalCreation) event.isCancelled = true - } - - /* - * Prevents players from dropping items - */ - @field:ListenerMarker(priority = NORMAL) - val onPlayerDropItemEvent = RegistratorListener<PlayerDropItemEvent> l@{ event -> - val (_, area) = getWorldAndArea(event.itemDrop.location.block) ?: return@l - if (!canInteract(event.player, area, "containers")) event.isCancelled = true - } - - /* - * Prevents players from picking up items - */ - @field:ListenerMarker(priority = NORMAL) - val onEntityPickupItemEvent = RegistratorListener<EntityPickupItemEvent> l@{ event -> - val user = event.entity as? Player ?: return@l - val (_, area) = getWorldAndArea(event.item.location.block) ?: return@l - if (!canInteract(user, area, "containers")) event.isCancelled = true - } - - /* - * Prevents players from editing inventories - */ - @field:ListenerMarker(priority = NORMAL, events = ["inventory.InventoryClickEvent", "inventory.InventoryDragEvent"]) - val onInventoryClickEvent = RegistratorListener<InventoryInteractEvent> l@{ event -> - val user = event.whoClicked as? Player ?: return@l - if ((event.inventory ?: return@l).holder === user) return@l // inventory null: hotbar - val (_, area) = getWorldAndArea(event.inventory.location.block) ?: return@l - if (!canInteract(user, area, "containers")) { - event.isCancelled = true - } - } - - /* - * Cancels weather changes and sets the weather to sunny if requested by the config for that world. - */ - @field:ListenerMarker(priority = NORMAL) - val onWeatherChangeEvent = RegistratorListener<WeatherChangeEvent> l@{ event -> - val world = parcelProvider.getWorld(event.world) ?: return@l - if (world.options.noWeather && event.toWeatherState()) { - event.isCancelled = true - } - } - - private fun resetWeather(world: World) { - world.setStorm(false) - world.isThundering = false - world.weatherDuration = Int.MAX_VALUE - } - -// TODO: BlockFormEvent, BlockSpreadEvent, BlockFadeEvent, Fireworks - - /* - * Prevents natural blocks forming - */ - @ListenerMarker(priority = NORMAL) - val onBlockFormEvent = RegistratorListener<BlockFormEvent> l@{ event -> - val block = event.block - val (world, area) = getWorldAndArea(block) ?: return@l - - // prevent any generation whatsoever on paths - if (area == null) { - event.isCancelled = true; return@l - } - - val hasEntity = event is EntityBlockFormEvent - val player = (event as? EntityBlockFormEvent)?.entity as? Player - - val cancel: Boolean = when (event.newState.type) { - - // prevent ice generation from Frost Walkers enchantment - FROSTED_ICE -> player != null && !area.canBuild(player) - - // prevent snow generation from weather - SNOW -> !hasEntity && world.options.preventWeatherBlockChanges - - else -> false - } - - if (cancel) { - event.isCancelled = true - } - } - - /* - * Prevents mobs (living entities) from spawning if that is disabled for that world in the config. - */ - @field:ListenerMarker(priority = NORMAL) - val onEntitySpawnEvent = RegistratorListener<EntitySpawnEvent> l@{ event -> - val world = parcelProvider.getWorld(event.entity.world) ?: return@l - if (event.entity is Creature && world.options.blockMobSpawning) { - event.isCancelled = true - } else if (world.getParcelAt(event.entity).let { it != null && it.hasBlockVisitors }) { - event.isCancelled = true - } - } - - /* - * Prevents minecarts/boats from moving outside a plot - */ - @field:ListenerMarker(priority = NORMAL) - val onVehicleMoveEvent = RegistratorListener<VehicleMoveEvent> l@{ event -> - val (_, area) = getWorldAndArea(event.to.block) ?: return@l - if (area == null) { - event.vehicle.passengers.forEach { - if (it.type == EntityType.PLAYER) { - (it as Player).sendParcelMessage(except = true, message = "Your ride ends here") - } else it.remove() - } - event.vehicle.eject() - event.vehicle.remove() - } else if (area.hasBlockVisitors) { - event.to.subtract(event.to).add(event.from) - } - } - - /* - * Prevents players from removing items from item frames - * Prevents TNT Minecarts and creepers from destroying entities (This event is called BEFORE EntityExplodeEvent GG) - * Actually doesn't prevent this because the entities are destroyed anyway, even though the code works? - */ - @field:ListenerMarker(priority = NORMAL) - val onEntityDamageByEntityEvent = RegistratorListener<EntityDamageByEntityEvent> l@{ event -> - 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 - } - - val user = event.damager as? Player - ?: (event.damager as? Projectile)?.let { it.shooter as? Player } - ?: return@l - - if (!canBuildOnArea(user, world.getParcelAt(event.entity))) { - event.isCancelled = true - } - } - - @field:ListenerMarker(priority = NORMAL) - val onHangingBreakEvent = RegistratorListener<HangingBreakEvent> l@{ event -> - 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.getParcelAt(event.entity).let { it != null && it.hasBlockVisitors }) { - event.isCancelled = true - } - } - - /* - * Prevents players from deleting paintings and item frames - * This appears to take care of shooting with a bow, throwing snowballs or throwing ender pearls. - */ - @field:ListenerMarker(priority = NORMAL) - val onHangingBreakByEntityEvent = RegistratorListener<HangingBreakByEntityEvent> l@{ event -> - val world = parcelProvider.getWorld(event.entity.world) ?: return@l - val user = event.remover as? Player ?: return@l - if (!canBuildOnArea(user, world.getParcelAt(event.entity))) { - event.isCancelled = true - } - } - - /* - * Prevents players from placing paintings and item frames - */ - @field:ListenerMarker(priority = NORMAL) - val onHangingPlaceEvent = RegistratorListener<HangingPlaceEvent> l@{ event -> - val world = parcelProvider.getWorld(event.entity.world) ?: return@l - val block = event.block.getRelative(event.blockFace) - if (!canBuildOnArea(event.player, world.getParcelAt(block))) { - event.isCancelled = true - } - } - - /* - * Prevents stuff from growing outside of plots - */ - @field:ListenerMarker(priority = NORMAL) - val onStructureGrowEvent = RegistratorListener<StructureGrowEvent> l@{ event -> - val (world, area) = getWorldAndArea(event.location.block) ?: return@l - if (area == null) { - event.isCancelled = true; return@l - } - - if (!event.player.hasPermBuildAnywhere && !area.canBuild(event.player)) { - event.isCancelled = true; return@l - } - - event.blocks.removeIf { world.getParcelAt(it.block) !== area } - } - - /* - * Prevents dispensers/droppers from dispensing out of parcels - */ - @field:ListenerMarker(priority = NORMAL) - val onBlockDispenseEvent = RegistratorListener<BlockDispenseEvent> l@{ event -> - val block = event.block - if (!block.type.let { it == DISPENSER || it == DROPPER }) return@l - val world = parcelProvider.getWorld(block.world) ?: return@l - val data = block.blockData as Directional - val targetBlock = block.getRelative(data.facing) - if (world.getParcelAt(targetBlock) == null) { - event.isCancelled = true - } - } - - /* - * Track spawned items, making sure they don't leave the parcel. - */ - @field:ListenerMarker(priority = NORMAL) - val onItemSpawnEvent = RegistratorListener<ItemSpawnEvent> l@{ event -> - val (_, area) = getWorldAndArea(event.location.block) ?: return@l - if (area == null) event.isCancelled = true - else entityTracker.track(event.entity, area) - } - - /* - * Prevents endermen and endermite from teleporting outside their parcel - */ - @field:ListenerMarker(priority = NORMAL) - val onEntityTeleportEvent = RegistratorListener<EntityTeleportEvent> l@{ event -> - val (world, area) = getWorldAndArea(event.from.block) ?: return@l - if (area !== world.getParcelAt(event.to)) { - event.isCancelled = true - } - } - - /* - * Prevents projectiles from flying out of parcels - * Prevents players from firing projectiles if they cannot build - */ - @field:ListenerMarker(priority = NORMAL) - val onProjectileLaunchEvent = RegistratorListener<ProjectileLaunchEvent> l@{ event -> - val (_, area) = getWorldAndArea(event.entity.location.block) ?: return@l - if (area == null || (event.entity.shooter as? Player)?.let { !canBuildOnArea(it, area) } == true) { - event.isCancelled = true - } else { - entityTracker.track(event.entity, area) - } - } - - /* - * Prevents entities from dropping items upon death, if configured that way - */ - @field:ListenerMarker(priority = NORMAL) - val onEntityDeathEvent = RegistratorListener<EntityDeathEvent> l@{ event -> - entityTracker.untrack(event.entity) - val world = parcelProvider.getWorld(event.entity.world) ?: return@l - if (!world.options.dropEntityItems) { - event.drops.clear() - event.droppedExp = 0 - } - } - - /* - * Assigns players their default game mode upon entering the world - */ - @field:ListenerMarker(priority = NORMAL) - val onPlayerChangedWorldEvent = RegistratorListener<PlayerChangedWorldEvent> l@{ event -> - val world = parcelProvider.getWorld(event.player.world) ?: return@l - if (world.options.gameMode != null && !event.player.hasPermGamemodeBypass) { - event.player.gameMode = world.options.gameMode - } - } - - /** - * Updates owner signs of parcels that get loaded if it is marked outdated - */ - @ListenerMarker(priority = EventPriority.NORMAL) - val onChunkLoadEvent = RegistratorListener<ChunkLoadEvent> l@{ event -> - val world = parcelProvider.getWorld(event.chunk.world) ?: return@l - val parcels = world.blockManager.getParcelsWithOwnerBlockIn(event.chunk) - if (parcels.isEmpty()) return@l - - parcels.forEach { id -> - val parcel = world.getParcelById(id)?.takeIf { it.isOwnerSignOutdated } ?: return@forEach - world.blockManager.updateParcelInfo(parcel.id, parcel.owner) - parcel.isOwnerSignOutdated = false - } - - } - - @ListenerMarker - val onPlayerJoinEvent = RegistratorListener<PlayerJoinEvent> l@{ event -> - storage.updatePlayerName(event.player.uuid, event.player.name) - } - - /** - * Attempts to prevent redstone contraptions from breaking while they are being swapped - * Might remove if it causes lag - */ - @ListenerMarker - val onBlockRedstoneEvent = RegistratorListener<BlockRedstoneEvent> l@{ event -> - val (_, area) = getWorldAndArea(event.block) ?: return@l - if (area == null || area.hasBlockVisitors) { - event.newCurrent = event.oldCurrent - } - } - - - private fun getPlayerSpeed(player: Player): Double = - if (player.isFlying) { - player.flySpeed * if (player.isSprinting) 21.6 else 10.92 - } else { - player.walkSpeed * when { - player.isSprinting -> 5.612 - player.isSneaking -> 1.31 - else -> 4.317 - } / 1.5 //? - } / 20.0 - +package io.dico.parcels2.listener
+
+import gnu.trove.TLongCollection
+import gnu.trove.set.hash.TLongHashSet
+import io.dico.dicore.Formatting
+import io.dico.dicore.ListenerMarker
+import io.dico.dicore.RegistratorListener
+import io.dico.parcels2.*
+import io.dico.parcels2.storage.Storage
+import io.dico.parcels2.util.ext.*
+import io.dico.parcels2.util.math.*
+import org.bukkit.Location
+import org.bukkit.Material.*
+import org.bukkit.World
+import org.bukkit.block.Biome
+import org.bukkit.block.Block
+import org.bukkit.block.data.Directional
+import org.bukkit.block.data.type.Bed
+import org.bukkit.entity.*
+import org.bukkit.entity.minecart.ExplosiveMinecart
+import org.bukkit.event.EventPriority
+import org.bukkit.event.EventPriority.NORMAL
+import org.bukkit.event.block.*
+import org.bukkit.event.entity.*
+import org.bukkit.event.hanging.HangingBreakByEntityEvent
+import org.bukkit.event.hanging.HangingBreakEvent
+import org.bukkit.event.hanging.HangingPlaceEvent
+import org.bukkit.event.inventory.InventoryInteractEvent
+import org.bukkit.event.player.*
+import org.bukkit.event.vehicle.VehicleMoveEvent
+import org.bukkit.event.weather.WeatherChangeEvent
+import org.bukkit.event.world.ChunkLoadEvent
+import org.bukkit.event.world.StructureGrowEvent
+import org.bukkit.inventory.InventoryHolder
+import java.util.EnumSet
+
+class ParcelListeners(
+ val parcelProvider: ParcelProvider,
+ val entityTracker: ParcelEntityTracker,
+ val storage: Storage
+) {
+ private fun canBuildOnArea(user: Player, area: Parcel?) =
+ if (area == null) user.hasPermBuildAnywhere else area.canBuild(user)
+
+ private fun canInteract(user: Player, area: Parcel?, interactClass: String) =
+ canBuildOnArea(user, area) || (area != null && area.interactableConfig(interactClass))
+
+ /**
+ * Get the world and parcel that the block resides in
+ * the parcel is nullable, and often named area because that means path.
+ * returns null if not in a registered parcel world - should always return in that case to not affect other worlds.
+ */
+ private fun getWorldAndArea(block: Block): Pair<ParcelWorld, Parcel?>? {
+ val world = parcelProvider.getWorld(block.world) ?: return null
+ return world to world.getParcelAt(block)
+ }
+
+
+ /*
+ * Prevents players from entering plots they are banned from
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onPlayerMoveEvent = RegistratorListener<PlayerMoveEvent> l@{ event ->
+ val user = event.player
+ if (user.hasPermBanBypass) return@l
+ val toLoc = event.to
+ val parcel = parcelProvider.getParcelAt(toLoc) ?: return@l
+
+ if (!parcel.canEnterFast(user)) {
+ val region = parcel.world.blockManager.getRegion(parcel.id)
+ val dimension = region.getFirstUncontainedDimensionOf(Vec3i(event.from))
+
+ if (dimension == null) {
+ user.teleport(parcel.homeLocation)
+ user.sendParcelMessage(nopermit = true, message = "You are banned from this parcel")
+
+ } else {
+ val speed = getPlayerSpeed(user)
+ val from = Vec3d(event.from)
+ val to = Vec3d(toLoc).with(dimension, from[dimension])
+
+ var newTo = to
+ dimension.otherDimensions.forEach {
+ val delta = to[it] - from[it]
+ newTo = newTo.add(it, delta * 100 * if (it == Dimension.Y) 0.5 else speed)
+ }
+
+ event.to = Location(
+ toLoc.world,
+ newTo.x, newTo.y.clampMin(0.0).clampMax(255.0), newTo.z,
+ toLoc.yaw, toLoc.pitch
+ )
+ }
+ }
+ }
+
+ /*
+ * Prevents players from breaking blocks outside of their parcels
+ * Prevents containers from dropping their contents when broken, if configured
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onBlockBreakEvent = RegistratorListener<BlockBreakEvent> l@{ event ->
+ val (world, area) = getWorldAndArea(event.block) ?: return@l
+ if (!canBuildOnArea(event.player, area)) {
+ event.isCancelled = true; return@l
+ }
+
+ if (!world.options.dropEntityItems) {
+ val state = event.block.state
+ if (state is InventoryHolder) {
+ state.inventory.clear()
+ state.update()
+ }
+ }
+ }
+
+ /*
+ * Prevents players from placing blocks outside of their parcels
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onBlockPlaceEvent = RegistratorListener<BlockPlaceEvent> l@{ event ->
+ val (_, area) = getWorldAndArea(event.block) ?: return@l
+ if (!canBuildOnArea(event.player, area)) {
+ event.isCancelled = true
+ }
+
+ area?.updateOwnerSign()
+ }
+
+ /*
+ * Control pistons
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onBlockPistonExtendEvent = RegistratorListener<BlockPistonExtendEvent> l@{ event ->
+ checkPistonMovement(event, event.blocks)
+ }
+
+ @field:ListenerMarker(priority = NORMAL)
+ val onBlockPistonRetractEvent = RegistratorListener<BlockPistonRetractEvent> l@{ event ->
+ checkPistonMovement(event, event.blocks)
+ }
+
+ // Doing some unnecessary optimizations here..
+ //@formatter:off
+ private inline fun Column(x: Int, z: Int): Long = x.toLong() or (z.toLong().shl(32))
+
+ private inline val Long.columnX get() = and(0xFFFF_FFFFL).toInt()
+ private inline val Long.columnZ get() = ushr(32).and(0xFFFF_FFFFL).toInt()
+ 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<Block>) {
+ val world = parcelProvider.getWorld(event.block.world) ?: return
+ val direction = event.direction
+ val columns = TLongHashSet(blocks.size * 2)
+
+ blocks.forEach {
+ columns.add(Column(it.x, it.z))
+ it.getRelative(direction).let { columns.add(Column(it.x, it.z)) }
+ }
+
+ columns.troveForEach {
+ val area = world.getParcelAt(it.columnX, it.columnZ)
+ if (area == null || area.hasBlockVisitors) {
+ event.isCancelled = true
+ return
+ }
+ }
+ }
+
+ /*
+ * Prevents explosions if enabled by the configs for that world
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onExplosionPrimeEvent = RegistratorListener<ExplosionPrimeEvent> l@{ event ->
+ val (world, area) = getWorldAndArea(event.entity.location.block) ?: return@l
+ if (area != null && area.hasBlockVisitors) {
+ event.radius = 0F; event.isCancelled = true
+ } else if (world.options.disableExplosions) {
+ event.radius = 0F
+ }
+ }
+
+ /*
+ * Prevents creepers and tnt minecarts from exploding if explosions are disabled
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onEntityExplodeEvent = RegistratorListener<EntityExplodeEvent> l@{ event ->
+ entityTracker.untrack(event.entity)
+ val world = parcelProvider.getWorld(event.entity.world) ?: return@l
+ if (world.options.disableExplosions || world.getParcelAt(event.entity).let { it != null && it.hasBlockVisitors }) {
+ event.isCancelled = true
+ }
+ }
+
+ /*
+ * Prevents liquids from flowing out of plots
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onBlockFromToEvent = RegistratorListener<BlockFromToEvent> l@{ event ->
+ val (_, area) = getWorldAndArea(event.toBlock) ?: return@l
+ if (area == null || area.hasBlockVisitors) event.isCancelled = true
+ }
+
+ private val bedTypes = EnumSet.copyOf(getMaterialsWithWoolColorPrefix("BED").toList())
+ /*
+ * Prevents players from placing liquids, using flint and steel, changing redstone components,
+ * using inputs (unless allowed by the plot),
+ * and using items disabled in the configuration for that world.
+ * Prevents player from using beds in HELL or SKY biomes if explosions are disabled.
+ */
+ @Suppress("NON_EXHAUSTIVE_WHEN")
+ @field:ListenerMarker(priority = NORMAL)
+ val onPlayerInteractEvent = RegistratorListener<PlayerInteractEvent> l@{ event ->
+ val user = event.player
+ val world = parcelProvider.getWorld(user.world) ?: return@l
+ val clickedBlock = event.clickedBlock
+ val parcel = clickedBlock?.let { world.getParcelAt(it) }
+
+ if (!user.hasPermBuildAnywhere && parcel != null && !parcel.canEnter(user)) {
+ user.sendParcelMessage(nopermit = true, message = "You cannot interact with parcels you're banned from")
+ event.isCancelled = true; return@l
+ }
+
+ when (event.action) {
+ Action.RIGHT_CLICK_BLOCK -> run {
+ val type = clickedBlock.type
+
+ val interactableClass = Interactables[type]
+ if (interactableClass != null && !parcel.effectiveInteractableConfig.isInteractable(type) && (parcel == null || !parcel.canBuild(user))) {
+ user.sendParcelMessage(nopermit = true, message = "You cannot interact with ${interactableClass.name} here")
+ event.isCancelled = true
+ return@l
+ }
+
+ if (bedTypes.contains(type)) {
+ val bed = clickedBlock.blockData as Bed
+ val head = if (bed.part == Bed.Part.FOOT) clickedBlock.getRelative(bed.facing) else clickedBlock
+ when (head.biome) {
+ Biome.NETHER, Biome.THE_END -> {
+ if (world.options.disableExplosions) {
+ user.sendParcelMessage(nopermit = true, message = "You cannot use this bed because it would explode")
+ event.isCancelled = true; return@l
+ }
+ }
+ }
+
+ if (!canBuildOnArea(user, parcel)) {
+ user.sendParcelMessage(nopermit = true, message = "You may not sleep here")
+ event.isCancelled = true; return@l
+ }
+ }
+
+ onPlayerRightClick(event, world, parcel)
+
+ if (!event.isCancelled && parcel == null) {
+ world.blockManager.getParcelForInfoBlockInteraction(Vec3i(clickedBlock), type, event.blockFace)
+ ?.apply { user.sendMessage(Formatting.GREEN + infoString) }
+ }
+ }
+
+ Action.RIGHT_CLICK_AIR -> onPlayerRightClick(event, world, parcel)
+ Action.PHYSICAL -> if (!canBuildOnArea(user, parcel) && !(parcel != null && parcel.interactableConfig("pressure_plates"))) {
+ user.sendParcelMessage(nopermit = true, message = "You cannot use inputs in this parcel")
+ event.isCancelled = true; return@l
+ }
+ }
+ }
+
+ // private val blockPlaceInteractItems = EnumSet.of(LAVA_BUCKET, WATER_BUCKET, BUCKET, FLINT_AND_STEEL)
+
+ @Suppress("NON_EXHAUSTIVE_WHEN")
+ private fun onPlayerRightClick(event: PlayerInteractEvent, world: ParcelWorld, parcel: Parcel?) {
+ if (event.hasItem()) {
+ val item = event.item.type
+ if (world.options.blockedItems.contains(item)) {
+ event.player.sendParcelMessage(nopermit = true, message = "You cannot use this item because it is disabled in this world")
+ event.isCancelled = true; return
+ }
+
+ when (item) {
+ LAVA_BUCKET, WATER_BUCKET, BUCKET, FLINT_AND_STEEL -> {
+ val block = event.clickedBlock.getRelative(event.blockFace)
+ val otherParcel = world.getParcelAt(block)
+ if (!canBuildOnArea(event.player, otherParcel)) {
+ event.isCancelled = true
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * Prevents players from breeding mobs, entering or opening boats/minecarts,
+ * rotating item frames, doing stuff with leashes, and putting stuff on armor stands.
+ */
+ @Suppress("NON_EXHAUSTIVE_WHEN")
+ @field:ListenerMarker(priority = NORMAL)
+ val onPlayerInteractEntityEvent = RegistratorListener<PlayerInteractEntityEvent> l@{ event ->
+ val (_, area) = getWorldAndArea(event.rightClicked.location.block) ?: return@l
+ if (canBuildOnArea(event.player, area)) return@l
+ when (event.rightClicked.type) {
+ EntityType.BOAT,
+ EntityType.MINECART,
+ EntityType.MINECART_CHEST,
+ EntityType.MINECART_COMMAND,
+ EntityType.MINECART_FURNACE,
+ EntityType.MINECART_HOPPER,
+ EntityType.MINECART_MOB_SPAWNER,
+ EntityType.MINECART_TNT,
+
+ EntityType.ARMOR_STAND,
+ EntityType.PAINTING,
+ EntityType.ITEM_FRAME,
+ EntityType.LEASH_HITCH,
+
+ EntityType.CHICKEN,
+ EntityType.COW,
+ EntityType.HORSE,
+ EntityType.SHEEP,
+ EntityType.VILLAGER,
+ EntityType.WOLF -> event.isCancelled = true
+ }
+ }
+
+ /*
+ * Prevents endermen from griefing.
+ * Prevents sand blocks from exiting the parcel in which they became an entity.
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onEntityChangeBlockEvent = RegistratorListener<EntityChangeBlockEvent> l@{ event ->
+ val (_, area) = getWorldAndArea(event.block) ?: return@l
+ if (event.entity.type == EntityType.ENDERMAN || area == null || area.hasBlockVisitors) {
+ event.isCancelled = true; return@l
+ }
+
+ if (event.entity.type == EntityType.FALLING_BLOCK) {
+ // a sand block started falling. Track it and delete it if it gets out of this parcel.
+ entityTracker.track(event.entity, area)
+ }
+ }
+
+ /*
+ * Prevents portals from being created if set so in the configs for that world
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onEntityCreatePortalEvent = RegistratorListener<EntityCreatePortalEvent> l@{ event ->
+ val world = parcelProvider.getWorld(event.entity.world) ?: return@l
+ if (world.options.blockPortalCreation) event.isCancelled = true
+ }
+
+ /*
+ * Prevents players from dropping items
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onPlayerDropItemEvent = RegistratorListener<PlayerDropItemEvent> l@{ event ->
+ val (_, area) = getWorldAndArea(event.itemDrop.location.block) ?: return@l
+ if (!canInteract(event.player, area, "containers")) event.isCancelled = true
+ }
+
+ /*
+ * Prevents players from picking up items
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onEntityPickupItemEvent = RegistratorListener<EntityPickupItemEvent> l@{ event ->
+ val user = event.entity as? Player ?: return@l
+ val (_, area) = getWorldAndArea(event.item.location.block) ?: return@l
+ if (!canInteract(user, area, "containers")) event.isCancelled = true
+ }
+
+ /*
+ * Prevents players from editing inventories
+ */
+ @field:ListenerMarker(priority = NORMAL, events = ["inventory.InventoryClickEvent", "inventory.InventoryDragEvent"])
+ val onInventoryClickEvent = RegistratorListener<InventoryInteractEvent> l@{ event ->
+ val user = event.whoClicked as? Player ?: return@l
+ if ((event.inventory ?: return@l).holder === user) return@l // inventory null: hotbar
+ val (_, area) = getWorldAndArea(event.inventory.location.block) ?: return@l
+ if (!canInteract(user, area, "containers")) {
+ event.isCancelled = true
+ }
+ }
+
+ /*
+ * Cancels weather changes and sets the weather to sunny if requested by the config for that world.
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onWeatherChangeEvent = RegistratorListener<WeatherChangeEvent> l@{ event ->
+ val world = parcelProvider.getWorld(event.world) ?: return@l
+ if (world.options.noWeather && event.toWeatherState()) {
+ event.isCancelled = true
+ }
+ }
+
+ private fun resetWeather(world: World) {
+ world.setStorm(false)
+ world.isThundering = false
+ world.weatherDuration = Int.MAX_VALUE
+ }
+
+// TODO: BlockFormEvent, BlockSpreadEvent, BlockFadeEvent, Fireworks
+
+ /*
+ * Prevents natural blocks forming
+ */
+ @ListenerMarker(priority = NORMAL)
+ val onBlockFormEvent = RegistratorListener<BlockFormEvent> l@{ event ->
+ val block = event.block
+ val (world, area) = getWorldAndArea(block) ?: return@l
+
+ // prevent any generation whatsoever on paths
+ if (area == null) {
+ event.isCancelled = true; return@l
+ }
+
+ val hasEntity = event is EntityBlockFormEvent
+ val player = (event as? EntityBlockFormEvent)?.entity as? Player
+
+ val cancel: Boolean = when (event.newState.type) {
+
+ // prevent ice generation from Frost Walkers enchantment
+ FROSTED_ICE -> player != null && !area.canBuild(player)
+
+ // prevent snow generation from weather
+ SNOW -> !hasEntity && world.options.preventWeatherBlockChanges
+
+ else -> false
+ }
+
+ if (cancel) {
+ event.isCancelled = true
+ }
+ }
+
+ /*
+ * Prevents mobs (living entities) from spawning if that is disabled for that world in the config.
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onEntitySpawnEvent = RegistratorListener<EntitySpawnEvent> l@{ event ->
+ val world = parcelProvider.getWorld(event.entity.world) ?: return@l
+ if (event.entity is Creature && world.options.blockMobSpawning) {
+ event.isCancelled = true
+ } else if (world.getParcelAt(event.entity).let { it != null && it.hasBlockVisitors }) {
+ event.isCancelled = true
+ }
+ }
+
+ /*
+ * Prevents minecarts/boats from moving outside a plot
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onVehicleMoveEvent = RegistratorListener<VehicleMoveEvent> l@{ event ->
+ val (_, area) = getWorldAndArea(event.to.block) ?: return@l
+ if (area == null) {
+ event.vehicle.passengers.forEach {
+ if (it.type == EntityType.PLAYER) {
+ (it as Player).sendParcelMessage(except = true, message = "Your ride ends here")
+ } else it.remove()
+ }
+ event.vehicle.eject()
+ event.vehicle.remove()
+ } else if (area.hasBlockVisitors) {
+ event.to.subtract(event.to).add(event.from)
+ }
+ }
+
+ /*
+ * Prevents players from removing items from item frames
+ * Prevents TNT Minecarts and creepers from destroying entities (This event is called BEFORE EntityExplodeEvent GG)
+ * Actually doesn't prevent this because the entities are destroyed anyway, even though the code works?
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onEntityDamageByEntityEvent = RegistratorListener<EntityDamageByEntityEvent> l@{ event ->
+ 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
+ }
+
+ val user = event.damager as? Player
+ ?: (event.damager as? Projectile)?.let { it.shooter as? Player }
+ ?: return@l
+
+ if (!canBuildOnArea(user, world.getParcelAt(event.entity))) {
+ event.isCancelled = true
+ }
+ }
+
+ @field:ListenerMarker(priority = NORMAL)
+ val onHangingBreakEvent = RegistratorListener<HangingBreakEvent> l@{ event ->
+ 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.getParcelAt(event.entity).let { it != null && it.hasBlockVisitors }) {
+ event.isCancelled = true
+ }
+ }
+
+ /*
+ * Prevents players from deleting paintings and item frames
+ * This appears to take care of shooting with a bow, throwing snowballs or throwing ender pearls.
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onHangingBreakByEntityEvent = RegistratorListener<HangingBreakByEntityEvent> l@{ event ->
+ val world = parcelProvider.getWorld(event.entity.world) ?: return@l
+ val user = event.remover as? Player ?: return@l
+ if (!canBuildOnArea(user, world.getParcelAt(event.entity))) {
+ event.isCancelled = true
+ }
+ }
+
+ /*
+ * Prevents players from placing paintings and item frames
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onHangingPlaceEvent = RegistratorListener<HangingPlaceEvent> l@{ event ->
+ val world = parcelProvider.getWorld(event.entity.world) ?: return@l
+ val block = event.block.getRelative(event.blockFace)
+ if (!canBuildOnArea(event.player, world.getParcelAt(block))) {
+ event.isCancelled = true
+ }
+ }
+
+ /*
+ * Prevents stuff from growing outside of plots
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onStructureGrowEvent = RegistratorListener<StructureGrowEvent> l@{ event ->
+ val (world, area) = getWorldAndArea(event.location.block) ?: return@l
+ if (area == null) {
+ event.isCancelled = true; return@l
+ }
+
+ if (!event.player.hasPermBuildAnywhere && !area.canBuild(event.player)) {
+ event.isCancelled = true; return@l
+ }
+
+ event.blocks.removeIf { world.getParcelAt(it.block) !== area }
+ }
+
+ /*
+ * Prevents dispensers/droppers from dispensing out of parcels
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onBlockDispenseEvent = RegistratorListener<BlockDispenseEvent> l@{ event ->
+ val block = event.block
+ if (!block.type.let { it == DISPENSER || it == DROPPER }) return@l
+ val world = parcelProvider.getWorld(block.world) ?: return@l
+ val data = block.blockData as Directional
+ val targetBlock = block.getRelative(data.facing)
+ if (world.getParcelAt(targetBlock) == null) {
+ event.isCancelled = true
+ }
+ }
+
+ /*
+ * Track spawned items, making sure they don't leave the parcel.
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onItemSpawnEvent = RegistratorListener<ItemSpawnEvent> l@{ event ->
+ val (_, area) = getWorldAndArea(event.location.block) ?: return@l
+ if (area == null) event.isCancelled = true
+ else entityTracker.track(event.entity, area)
+ }
+
+ /*
+ * Prevents endermen and endermite from teleporting outside their parcel
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onEntityTeleportEvent = RegistratorListener<EntityTeleportEvent> l@{ event ->
+ val (world, area) = getWorldAndArea(event.from.block) ?: return@l
+ if (area !== world.getParcelAt(event.to)) {
+ event.isCancelled = true
+ }
+ }
+
+ /*
+ * Prevents projectiles from flying out of parcels
+ * Prevents players from firing projectiles if they cannot build
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onProjectileLaunchEvent = RegistratorListener<ProjectileLaunchEvent> l@{ event ->
+ val (_, area) = getWorldAndArea(event.entity.location.block) ?: return@l
+ if (area == null || (event.entity.shooter as? Player)?.let { !canBuildOnArea(it, area) } == true) {
+ event.isCancelled = true
+ } else {
+ entityTracker.track(event.entity, area)
+ }
+ }
+
+ /*
+ * Prevents entities from dropping items upon death, if configured that way
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onEntityDeathEvent = RegistratorListener<EntityDeathEvent> l@{ event ->
+ entityTracker.untrack(event.entity)
+ val world = parcelProvider.getWorld(event.entity.world) ?: return@l
+ if (!world.options.dropEntityItems) {
+ event.drops.clear()
+ event.droppedExp = 0
+ }
+ }
+
+ /*
+ * Assigns players their default game mode upon entering the world
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onPlayerChangedWorldEvent = RegistratorListener<PlayerChangedWorldEvent> l@{ event ->
+ val world = parcelProvider.getWorld(event.player.world) ?: return@l
+ if (world.options.gameMode != null && !event.player.hasPermGamemodeBypass) {
+ event.player.gameMode = world.options.gameMode
+ }
+ }
+
+ /**
+ * Updates owner signs of parcels that get loaded if it is marked outdated
+ */
+ @ListenerMarker(priority = EventPriority.NORMAL)
+ val onChunkLoadEvent = RegistratorListener<ChunkLoadEvent> l@{ event ->
+ val world = parcelProvider.getWorld(event.chunk.world) ?: return@l
+ val parcels = world.blockManager.getParcelsWithOwnerBlockIn(event.chunk)
+ if (parcels.isEmpty()) return@l
+
+ parcels.forEach { id ->
+ val parcel = world.getParcelById(id)?.takeIf { it.isOwnerSignOutdated } ?: return@forEach
+ world.blockManager.updateParcelInfo(parcel.id, parcel.owner)
+ parcel.isOwnerSignOutdated = false
+ }
+
+ }
+
+ @ListenerMarker
+ val onPlayerJoinEvent = RegistratorListener<PlayerJoinEvent> l@{ event ->
+ storage.updatePlayerName(event.player.uuid, event.player.name)
+ }
+
+ /**
+ * Attempts to prevent redstone contraptions from breaking while they are being swapped
+ * Might remove if it causes lag
+ */
+ @ListenerMarker
+ val onBlockRedstoneEvent = RegistratorListener<BlockRedstoneEvent> l@{ event ->
+ val (_, area) = getWorldAndArea(event.block) ?: return@l
+ if (area == null || area.hasBlockVisitors) {
+ event.newCurrent = event.oldCurrent
+ }
+ }
+
+
+ private fun getPlayerSpeed(player: Player): Double =
+ if (player.isFlying) {
+ player.flySpeed * if (player.isSprinting) 21.6 else 10.92
+ } else {
+ player.walkSpeed * when {
+ player.isSprinting -> 5.612
+ player.isSneaking -> 1.31
+ else -> 4.317
+ } / 1.5 //?
+ } / 20.0
+
}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/listener/WorldEditListener.kt b/src/main/kotlin/io/dico/parcels2/listener/WorldEditListener.kt index 4d35a53..fc31305 100644 --- a/src/main/kotlin/io/dico/parcels2/listener/WorldEditListener.kt +++ b/src/main/kotlin/io/dico/parcels2/listener/WorldEditListener.kt @@ -1,79 +1,79 @@ -package io.dico.parcels2.listener - -import com.sk89q.worldedit.EditSession.Stage.BEFORE_REORDER -import com.sk89q.worldedit.Vector -import com.sk89q.worldedit.Vector2D -import com.sk89q.worldedit.WorldEdit -import com.sk89q.worldedit.bukkit.WorldEditPlugin -import com.sk89q.worldedit.event.extent.EditSessionEvent -import com.sk89q.worldedit.extent.AbstractDelegateExtent -import com.sk89q.worldedit.extent.Extent -import com.sk89q.worldedit.util.eventbus.EventHandler.Priority.VERY_EARLY -import com.sk89q.worldedit.util.eventbus.Subscribe -import com.sk89q.worldedit.world.biome.BaseBiome -import com.sk89q.worldedit.world.block.BlockStateHolder -import io.dico.parcels2.ParcelWorld -import io.dico.parcels2.ParcelsPlugin -import io.dico.parcels2.canBuildFast -import io.dico.parcels2.util.ext.hasPermBuildAnywhere -import io.dico.parcels2.util.ext.sendParcelMessage -import org.bukkit.entity.Player -import org.bukkit.plugin.Plugin - -class WorldEditListener(val parcels: ParcelsPlugin, val worldEdit: WorldEdit) { - - @Subscribe(priority = VERY_EARLY) - fun onEditSession(event: EditSessionEvent) { - val worldName = event.world?.name ?: return - val world = parcels.parcelProvider.getWorld(worldName) ?: return - if (event.stage == BEFORE_REORDER) return - - val actor = event.actor - if (actor == null || !actor.isPlayer) return - - val player = parcels.server.getPlayer(actor.uniqueId) - if (player.hasPermBuildAnywhere) return - - event.extent = ParcelsExtent(event.extent, world, player) - } - - private class ParcelsExtent(extent: Extent, - val world: ParcelWorld, - val player: Player) : AbstractDelegateExtent(extent) { - private var messageSent = false - - private fun canBuild(x: Int, z: Int): Boolean { - world.getParcelAt(x, z)?.let { parcel -> - if (parcel.canBuildFast(player)) { - return true - } - } - - if (!messageSent) { - messageSent = true - player.sendParcelMessage(except = true, message = "You can't use WorldEdit there") - } - - return false - } - - override fun setBlock(location: Vector, block: BlockStateHolder<*>): Boolean { - return canBuild(location.blockX, location.blockZ) && super.setBlock(location, block) - } - - override fun setBiome(coord: Vector2D, biome: BaseBiome): Boolean { - return canBuild(coord.blockX, coord.blockZ) && super.setBiome(coord, biome) - } - - } - - companion object { - fun register(parcels: ParcelsPlugin, worldEditPlugin: Plugin) { - if (worldEditPlugin !is WorldEditPlugin) return - val worldEdit = worldEditPlugin.worldEdit - val listener = WorldEditListener(parcels, worldEdit) - worldEdit.eventBus.register(listener) - } - } - +package io.dico.parcels2.listener
+
+import com.sk89q.worldedit.EditSession.Stage.BEFORE_REORDER
+import com.sk89q.worldedit.Vector
+import com.sk89q.worldedit.Vector2D
+import com.sk89q.worldedit.WorldEdit
+import com.sk89q.worldedit.bukkit.WorldEditPlugin
+import com.sk89q.worldedit.event.extent.EditSessionEvent
+import com.sk89q.worldedit.extent.AbstractDelegateExtent
+import com.sk89q.worldedit.extent.Extent
+import com.sk89q.worldedit.util.eventbus.EventHandler.Priority.VERY_EARLY
+import com.sk89q.worldedit.util.eventbus.Subscribe
+import com.sk89q.worldedit.world.biome.BaseBiome
+import com.sk89q.worldedit.world.block.BlockStateHolder
+import io.dico.parcels2.ParcelWorld
+import io.dico.parcels2.ParcelsPlugin
+import io.dico.parcels2.canBuildFast
+import io.dico.parcels2.util.ext.hasPermBuildAnywhere
+import io.dico.parcels2.util.ext.sendParcelMessage
+import org.bukkit.entity.Player
+import org.bukkit.plugin.Plugin
+
+class WorldEditListener(val parcels: ParcelsPlugin, val worldEdit: WorldEdit) {
+
+ @Subscribe(priority = VERY_EARLY)
+ fun onEditSession(event: EditSessionEvent) {
+ val worldName = event.world?.name ?: return
+ val world = parcels.parcelProvider.getWorld(worldName) ?: return
+ if (event.stage == BEFORE_REORDER) return
+
+ val actor = event.actor
+ if (actor == null || !actor.isPlayer) return
+
+ val player = parcels.server.getPlayer(actor.uniqueId)
+ if (player.hasPermBuildAnywhere) return
+
+ event.extent = ParcelsExtent(event.extent, world, player)
+ }
+
+ private class ParcelsExtent(extent: Extent,
+ val world: ParcelWorld,
+ val player: Player) : AbstractDelegateExtent(extent) {
+ private var messageSent = false
+
+ private fun canBuild(x: Int, z: Int): Boolean {
+ world.getParcelAt(x, z)?.let { parcel ->
+ if (parcel.canBuildFast(player)) {
+ return true
+ }
+ }
+
+ if (!messageSent) {
+ messageSent = true
+ player.sendParcelMessage(except = true, message = "You can't use WorldEdit there")
+ }
+
+ return false
+ }
+
+ override fun setBlock(location: Vector, block: BlockStateHolder<*>): Boolean {
+ return canBuild(location.blockX, location.blockZ) && super.setBlock(location, block)
+ }
+
+ override fun setBiome(coord: Vector2D, biome: BaseBiome): Boolean {
+ return canBuild(coord.blockX, coord.blockZ) && super.setBiome(coord, biome)
+ }
+
+ }
+
+ companion object {
+ fun register(parcels: ParcelsPlugin, worldEditPlugin: Plugin) {
+ if (worldEditPlugin !is WorldEditPlugin) return
+ val worldEdit = worldEditPlugin.worldEdit
+ val listener = WorldEditListener(parcels, worldEdit)
+ worldEdit.eventBus.register(listener)
+ }
+ }
+
}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/options/GeneratorOptions.kt b/src/main/kotlin/io/dico/parcels2/options/GeneratorOptions.kt index d0626dc..a6a57e5 100644 --- a/src/main/kotlin/io/dico/parcels2/options/GeneratorOptions.kt +++ b/src/main/kotlin/io/dico/parcels2/options/GeneratorOptions.kt @@ -1,36 +1,36 @@ -package io.dico.parcels2.options - -import io.dico.parcels2.ParcelGenerator -import io.dico.parcels2.defaultimpl.DefaultParcelGenerator -import org.bukkit.Bukkit -import org.bukkit.Material -import org.bukkit.block.Biome -import org.bukkit.block.data.BlockData -import kotlin.reflect.KClass - -object GeneratorOptionsFactories : PolymorphicOptionsFactories<ParcelGenerator>("name", GeneratorOptions::class, DefaultGeneratorOptionsFactory()) - -class GeneratorOptions (name: String = "default", options: Any = DefaultGeneratorOptions()) : PolymorphicOptions<ParcelGenerator>(name, options, GeneratorOptionsFactories) { - fun newInstance(worldName: String) = factory.newInstance(key, options, worldName) -} - -private class DefaultGeneratorOptionsFactory : PolymorphicOptionsFactory<ParcelGenerator> { - override val supportedKeys: List<String> = listOf("default") - override val optionsClass: KClass<out Any> get() = DefaultGeneratorOptions::class - - override fun newInstance(key: String, options: Any, vararg extra: Any?): ParcelGenerator { - return DefaultParcelGenerator(extra.first() as String, options as DefaultGeneratorOptions) - } -} - -class DefaultGeneratorOptions(val defaultBiome: Biome = Biome.JUNGLE, - val wallType: BlockData = Bukkit.createBlockData(Material.STONE_SLAB), - val floorType: BlockData = Bukkit.createBlockData(Material.QUARTZ_BLOCK), - val fillType: BlockData = Bukkit.createBlockData(Material.QUARTZ_BLOCK), - val pathMainType: BlockData = Bukkit.createBlockData(Material.SANDSTONE), - val pathAltType: BlockData = Bukkit.createBlockData(Material.REDSTONE_BLOCK), - val parcelSize: Int = 101, - val pathSize: Int = 9, - val floorHeight: Int = 64, - val offsetX: Int = 0, +package io.dico.parcels2.options
+
+import io.dico.parcels2.ParcelGenerator
+import io.dico.parcels2.defaultimpl.DefaultParcelGenerator
+import org.bukkit.Bukkit
+import org.bukkit.Material
+import org.bukkit.block.Biome
+import org.bukkit.block.data.BlockData
+import kotlin.reflect.KClass
+
+object GeneratorOptionsFactories : PolymorphicOptionsFactories<ParcelGenerator>("name", GeneratorOptions::class, DefaultGeneratorOptionsFactory())
+
+class GeneratorOptions (name: String = "default", options: Any = DefaultGeneratorOptions()) : PolymorphicOptions<ParcelGenerator>(name, options, GeneratorOptionsFactories) {
+ fun newInstance(worldName: String) = factory.newInstance(key, options, worldName)
+}
+
+private class DefaultGeneratorOptionsFactory : PolymorphicOptionsFactory<ParcelGenerator> {
+ override val supportedKeys: List<String> = listOf("default")
+ override val optionsClass: KClass<out Any> get() = DefaultGeneratorOptions::class
+
+ override fun newInstance(key: String, options: Any, vararg extra: Any?): ParcelGenerator {
+ return DefaultParcelGenerator(extra.first() as String, options as DefaultGeneratorOptions)
+ }
+}
+
+class DefaultGeneratorOptions(val defaultBiome: Biome = Biome.JUNGLE,
+ val wallType: BlockData = Bukkit.createBlockData(Material.STONE_SLAB),
+ val floorType: BlockData = Bukkit.createBlockData(Material.QUARTZ_BLOCK),
+ val fillType: BlockData = Bukkit.createBlockData(Material.QUARTZ_BLOCK),
+ val pathMainType: BlockData = Bukkit.createBlockData(Material.SANDSTONE),
+ val pathAltType: BlockData = Bukkit.createBlockData(Material.REDSTONE_BLOCK),
+ val parcelSize: Int = 101,
+ val pathSize: Int = 9,
+ val floorHeight: Int = 64,
+ val offsetX: Int = 0,
val offsetZ: Int = 0)
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/options/MigrationOptions.kt b/src/main/kotlin/io/dico/parcels2/options/MigrationOptions.kt index 5e36099..7dd752e 100644 --- a/src/main/kotlin/io/dico/parcels2/options/MigrationOptions.kt +++ b/src/main/kotlin/io/dico/parcels2/options/MigrationOptions.kt @@ -1,22 +1,22 @@ -package io.dico.parcels2.options - -import io.dico.parcels2.storage.migration.Migration -import io.dico.parcels2.storage.migration.plotme.PlotmeMigration -import kotlin.reflect.KClass - -object MigrationOptionsFactories : PolymorphicOptionsFactories<Migration>("kind", MigrationOptions::class, PlotmeMigrationFactory()) - -class MigrationOptions(kind: String = "plotme-0.17", options: Any = PlotmeMigrationOptions()) : SimplePolymorphicOptions<Migration>(kind, options, MigrationOptionsFactories) - -private class PlotmeMigrationFactory : PolymorphicOptionsFactory<Migration> { - override val supportedKeys = listOf("plotme-0.17") - override val optionsClass: KClass<out Any> get() = PlotmeMigrationOptions::class - - override fun newInstance(key: String, options: Any, vararg extra: Any?): Migration { - return PlotmeMigration(options as PlotmeMigrationOptions) - } -} - -class PlotmeMigrationOptions(val worldsFromTo: Map<String, String> = mapOf("plotworld" to "parcels"), - val storage: StorageOptions = StorageOptions(options = DataConnectionOptions(database = "plotme")), +package io.dico.parcels2.options
+
+import io.dico.parcels2.storage.migration.Migration
+import io.dico.parcels2.storage.migration.plotme.PlotmeMigration
+import kotlin.reflect.KClass
+
+object MigrationOptionsFactories : PolymorphicOptionsFactories<Migration>("kind", MigrationOptions::class, PlotmeMigrationFactory())
+
+class MigrationOptions(kind: String = "plotme-0.17", options: Any = PlotmeMigrationOptions()) : SimplePolymorphicOptions<Migration>(kind, options, MigrationOptionsFactories)
+
+private class PlotmeMigrationFactory : PolymorphicOptionsFactory<Migration> {
+ override val supportedKeys = listOf("plotme-0.17")
+ override val optionsClass: KClass<out Any> get() = PlotmeMigrationOptions::class
+
+ override fun newInstance(key: String, options: Any, vararg extra: Any?): Migration {
+ return PlotmeMigration(options as PlotmeMigrationOptions)
+ }
+}
+
+class PlotmeMigrationOptions(val worldsFromTo: Map<String, String> = mapOf("plotworld" to "parcels"),
+ val storage: StorageOptions = StorageOptions(options = DataConnectionOptions(database = "plotme")),
val tableNamesUppercase: Boolean = false)
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/options/Options.kt b/src/main/kotlin/io/dico/parcels2/options/Options.kt index 35d48ba..412c783 100644 --- a/src/main/kotlin/io/dico/parcels2/options/Options.kt +++ b/src/main/kotlin/io/dico/parcels2/options/Options.kt @@ -1,58 +1,58 @@ -package io.dico.parcels2.options - -import io.dico.parcels2.TickJobtimeOptions -import org.bukkit.GameMode -import org.bukkit.Material -import java.io.Reader -import java.io.Writer -import java.util.EnumSet - -class Options { - var worlds: Map<String, WorldOptions> = hashMapOf() - private set - var storage: StorageOptions = StorageOptions() - var tickJobtime: TickJobtimeOptions = TickJobtimeOptions(20, 1) - var migration = MigrationOptionsHolder() - - fun addWorld(name: String, - generatorOptions: GeneratorOptions? = null, - worldOptions: RuntimeWorldOptions? = null) { - val optionsHolder = WorldOptions( - generatorOptions ?: GeneratorOptions(), - worldOptions ?: RuntimeWorldOptions() - ) - - (worlds as MutableMap).put(name, optionsHolder) - } - - fun writeTo(writer: Writer) = optionsMapper.writeValue(writer, this) - - fun mergeFrom(reader: Reader) = optionsMapper.readerForUpdating(this).readValue<Options>(reader) - - override fun toString(): String = optionsMapper.writeValueAsString(this) - -} - -class WorldOptions(val generator: GeneratorOptions, - var runtime: RuntimeWorldOptions = RuntimeWorldOptions()) - -class RuntimeWorldOptions(var gameMode: GameMode? = GameMode.CREATIVE, - var dayTime: Boolean = true, - var noWeather: Boolean = true, - var preventWeatherBlockChanges: Boolean = true, - var preventBlockSpread: Boolean = true, // TODO - var dropEntityItems: Boolean = true, - var doTileDrops: Boolean = false, - var disableExplosions: Boolean = true, - var blockPortalCreation: Boolean = true, - var blockMobSpawning: Boolean = true, - var blockedItems: Set<Material> = EnumSet.of(Material.FLINT_AND_STEEL, Material.SNOWBALL), - var axisLimit: Int = 10) - -class DataFileOptions(val location: String = "/flatfile-storage/") - -class MigrationOptionsHolder { - var enabled = false - var disableWhenComplete = true - var instance: MigrationOptions? = MigrationOptions() +package io.dico.parcels2.options
+
+import io.dico.parcels2.TickJobtimeOptions
+import org.bukkit.GameMode
+import org.bukkit.Material
+import java.io.Reader
+import java.io.Writer
+import java.util.EnumSet
+
+class Options {
+ var worlds: Map<String, WorldOptions> = hashMapOf()
+ private set
+ var storage: StorageOptions = StorageOptions()
+ var tickJobtime: TickJobtimeOptions = TickJobtimeOptions(20, 1)
+ var migration = MigrationOptionsHolder()
+
+ fun addWorld(name: String,
+ generatorOptions: GeneratorOptions? = null,
+ worldOptions: RuntimeWorldOptions? = null) {
+ val optionsHolder = WorldOptions(
+ generatorOptions ?: GeneratorOptions(),
+ worldOptions ?: RuntimeWorldOptions()
+ )
+
+ (worlds as MutableMap).put(name, optionsHolder)
+ }
+
+ fun writeTo(writer: Writer) = optionsMapper.writeValue(writer, this)
+
+ fun mergeFrom(reader: Reader) = optionsMapper.readerForUpdating(this).readValue<Options>(reader)
+
+ override fun toString(): String = optionsMapper.writeValueAsString(this)
+
+}
+
+class WorldOptions(val generator: GeneratorOptions,
+ var runtime: RuntimeWorldOptions = RuntimeWorldOptions())
+
+class RuntimeWorldOptions(var gameMode: GameMode? = GameMode.CREATIVE,
+ var dayTime: Boolean = true,
+ var noWeather: Boolean = true,
+ var preventWeatherBlockChanges: Boolean = true,
+ var preventBlockSpread: Boolean = true, // TODO
+ var dropEntityItems: Boolean = true,
+ var doTileDrops: Boolean = false,
+ var disableExplosions: Boolean = true,
+ var blockPortalCreation: Boolean = true,
+ var blockMobSpawning: Boolean = true,
+ var blockedItems: Set<Material> = EnumSet.of(Material.FLINT_AND_STEEL, Material.SNOWBALL),
+ var axisLimit: Int = 10)
+
+class DataFileOptions(val location: String = "/flatfile-storage/")
+
+class MigrationOptionsHolder {
+ var enabled = false
+ var disableWhenComplete = true
+ var instance: MigrationOptions? = MigrationOptions()
}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/options/OptionsMapper.kt b/src/main/kotlin/io/dico/parcels2/options/OptionsMapper.kt index 671a25f..d741617 100644 --- a/src/main/kotlin/io/dico/parcels2/options/OptionsMapper.kt +++ b/src/main/kotlin/io/dico/parcels2/options/OptionsMapper.kt @@ -1,66 +1,66 @@ -package io.dico.parcels2.options - -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.PropertyNamingStrategy -import com.fasterxml.jackson.databind.SerializerProvider -import com.fasterxml.jackson.databind.deser.std.StdDeserializer -import com.fasterxml.jackson.databind.ser.std.StdSerializer -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory -import com.fasterxml.jackson.module.kotlin.KotlinModule -import org.bukkit.Bukkit -import org.bukkit.block.data.BlockData - -val optionsMapper = ObjectMapper(YAMLFactory()).apply { - propertyNamingStrategy = PropertyNamingStrategy.KEBAB_CASE - - val kotlinModule = KotlinModule() - - with(kotlinModule) { - /* - setSerializerModifier(object : BeanSerializerModifier() { - @Suppress("UNCHECKED_CAST") - override fun modifySerializer(config: SerializationConfig?, beanDesc: BeanDescription, serializer: JsonSerializer<*>): JsonSerializer<*> { - - val newSerializer = if (GeneratorOptions::class.isSuperclassOf(beanDesc.beanClass.kotlin)) { - GeneratorOptionsSerializer(serializer as JsonSerializer<GeneratorOptions>) - } else { - serializer - } - - return super.modifySerializer(config, beanDesc, newSerializer) - } - })*/ - - addSerializer(BlockDataSerializer()) - addDeserializer(BlockData::class.java, BlockDataDeserializer()) - - GeneratorOptionsFactories.registerSerialization(this) - StorageOptionsFactories.registerSerialization(this) - MigrationOptionsFactories.registerSerialization(this) - } - - registerModule(kotlinModule) -} - -private class BlockDataSerializer : StdSerializer<BlockData>(BlockData::class.java) { - - override fun serialize(value: BlockData, gen: JsonGenerator, provider: SerializerProvider) { - gen.writeString(value.asString) - } - -} - -private class BlockDataDeserializer : StdDeserializer<BlockData>(BlockData::class.java) { - - override fun deserialize(p: JsonParser, ctxt: DeserializationContext): BlockData? { - try { - return Bukkit.createBlockData(p.valueAsString) - } catch (ex: Exception) { - throw RuntimeException("Exception occurred at ${p.currentLocation}", ex) - } - } - -} +package io.dico.parcels2.options
+
+import com.fasterxml.jackson.core.JsonGenerator
+import com.fasterxml.jackson.core.JsonParser
+import com.fasterxml.jackson.databind.DeserializationContext
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.databind.PropertyNamingStrategy
+import com.fasterxml.jackson.databind.SerializerProvider
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer
+import com.fasterxml.jackson.databind.ser.std.StdSerializer
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
+import com.fasterxml.jackson.module.kotlin.KotlinModule
+import org.bukkit.Bukkit
+import org.bukkit.block.data.BlockData
+
+val optionsMapper = ObjectMapper(YAMLFactory()).apply {
+ propertyNamingStrategy = PropertyNamingStrategy.KEBAB_CASE
+
+ val kotlinModule = KotlinModule()
+
+ with(kotlinModule) {
+ /*
+ setSerializerModifier(object : BeanSerializerModifier() {
+ @Suppress("UNCHECKED_CAST")
+ override fun modifySerializer(config: SerializationConfig?, beanDesc: BeanDescription, serializer: JsonSerializer<*>): JsonSerializer<*> {
+
+ val newSerializer = if (GeneratorOptions::class.isSuperclassOf(beanDesc.beanClass.kotlin)) {
+ GeneratorOptionsSerializer(serializer as JsonSerializer<GeneratorOptions>)
+ } else {
+ serializer
+ }
+
+ return super.modifySerializer(config, beanDesc, newSerializer)
+ }
+ })*/
+
+ addSerializer(BlockDataSerializer())
+ addDeserializer(BlockData::class.java, BlockDataDeserializer())
+
+ GeneratorOptionsFactories.registerSerialization(this)
+ StorageOptionsFactories.registerSerialization(this)
+ MigrationOptionsFactories.registerSerialization(this)
+ }
+
+ registerModule(kotlinModule)
+}
+
+private class BlockDataSerializer : StdSerializer<BlockData>(BlockData::class.java) {
+
+ override fun serialize(value: BlockData, gen: JsonGenerator, provider: SerializerProvider) {
+ gen.writeString(value.asString)
+ }
+
+}
+
+private class BlockDataDeserializer : StdDeserializer<BlockData>(BlockData::class.java) {
+
+ override fun deserialize(p: JsonParser, ctxt: DeserializationContext): BlockData? {
+ try {
+ return Bukkit.createBlockData(p.valueAsString)
+ } catch (ex: Exception) {
+ throw RuntimeException("Exception occurred at ${p.currentLocation}", ex)
+ }
+ }
+
+}
diff --git a/src/main/kotlin/io/dico/parcels2/options/PolymorphicOptions.kt b/src/main/kotlin/io/dico/parcels2/options/PolymorphicOptions.kt index aa60f39..f65efa1 100644 --- a/src/main/kotlin/io/dico/parcels2/options/PolymorphicOptions.kt +++ b/src/main/kotlin/io/dico/parcels2/options/PolymorphicOptions.kt @@ -1,89 +1,89 @@ -package io.dico.parcels2.options - -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonDeserializer -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.SerializerProvider -import com.fasterxml.jackson.databind.module.SimpleModule -import com.fasterxml.jackson.databind.ser.std.StdSerializer -import io.dico.parcels2.logger -import kotlin.reflect.KClass - -abstract class PolymorphicOptions<T : Any>(val key: String, - val options: Any, - factories: PolymorphicOptionsFactories<T>) { - val factory = factories.getFactory(key)!! -} - -abstract class SimplePolymorphicOptions<T : Any>(key: String, options: Any, factories: PolymorphicOptionsFactories<T>) - : PolymorphicOptions<T>(key, options, factories) { - fun newInstance(): T = factory.newInstance(key, options) -} - -interface PolymorphicOptionsFactory<T : Any> { - val supportedKeys: List<String> - val optionsClass: KClass<out Any> - fun newInstance(key: String, options: Any, vararg extra: Any?): T -} - -@Suppress("UNCHECKED_CAST") -abstract class PolymorphicOptionsFactories<T : Any>(val serializeKeyAs: String, - rootClass: KClass<out PolymorphicOptions<T>>, - vararg defaultFactories: PolymorphicOptionsFactory<T>) { - val rootClass = rootClass as KClass<PolymorphicOptions<T>> - private val map: MutableMap<String, PolymorphicOptionsFactory<T>> = linkedMapOf() - val availableKeys: Collection<String> get() = map.keys - - fun registerFactory(factory: PolymorphicOptionsFactory<T>) = factory.supportedKeys.forEach { map.putIfAbsent(it.toLowerCase(), factory) } - - fun getFactory(key: String): PolymorphicOptionsFactory<T>? = map[key.toLowerCase()] - - fun registerSerialization(module: SimpleModule) { - module.addSerializer(PolymorphicOptionsSerializer(this)) - module.addDeserializer(rootClass.java, PolymorphicOptionsDeserializer(this)) - } - - init { - defaultFactories.forEach { registerFactory(it) } - } -} - - -private class PolymorphicOptionsDeserializer<T : Any>(val factories: PolymorphicOptionsFactories<T>) : JsonDeserializer<PolymorphicOptions<T>>() { - - override fun deserialize(p: JsonParser, ctxt: DeserializationContext?): PolymorphicOptions<T> { - val node = p.readValueAsTree<JsonNode>() - val key = node.get(factories.serializeKeyAs).asText() - val factory = getFactory(key) - val optionsNode = node.get("options") - val options = p.codec.treeToValue(optionsNode, factory.optionsClass.java) - return factories.rootClass.constructors.first().call(key, options) - } - - private fun getFactory(key: String): PolymorphicOptionsFactory<T> { - factories.getFactory(key)?.let { return it } - - logger.warn("Unknown ${factories.rootClass.simpleName} ${factories.serializeKeyAs}: $key. " + - "\nAvailable options: ${factories.availableKeys}") - - val default = factories.getFactory(factories.availableKeys.first()) - ?: throw IllegalStateException("No default ${factories.rootClass.simpleName} factory registered.") - return default - } - -} - -private class PolymorphicOptionsSerializer<T : Any>(val factories: PolymorphicOptionsFactories<T>) : StdSerializer<PolymorphicOptions<T>>(factories.rootClass.java) { - - override fun serialize(value: PolymorphicOptions<T>, gen: JsonGenerator, sp: SerializerProvider?) { - with(gen) { - writeStartObject() - writeStringField(factories.serializeKeyAs, value.key) - writeFieldName("options") - writeObject(value.options) - writeEndObject() - } - } +package io.dico.parcels2.options
+
+import com.fasterxml.jackson.core.JsonGenerator
+import com.fasterxml.jackson.core.JsonParser
+import com.fasterxml.jackson.databind.DeserializationContext
+import com.fasterxml.jackson.databind.JsonDeserializer
+import com.fasterxml.jackson.databind.JsonNode
+import com.fasterxml.jackson.databind.SerializerProvider
+import com.fasterxml.jackson.databind.module.SimpleModule
+import com.fasterxml.jackson.databind.ser.std.StdSerializer
+import io.dico.parcels2.logger
+import kotlin.reflect.KClass
+
+abstract class PolymorphicOptions<T : Any>(val key: String,
+ val options: Any,
+ factories: PolymorphicOptionsFactories<T>) {
+ val factory = factories.getFactory(key)!!
+}
+
+abstract class SimplePolymorphicOptions<T : Any>(key: String, options: Any, factories: PolymorphicOptionsFactories<T>)
+ : PolymorphicOptions<T>(key, options, factories) {
+ fun newInstance(): T = factory.newInstance(key, options)
+}
+
+interface PolymorphicOptionsFactory<T : Any> {
+ val supportedKeys: List<String>
+ val optionsClass: KClass<out Any>
+ fun newInstance(key: String, options: Any, vararg extra: Any?): T
+}
+
+@Suppress("UNCHECKED_CAST")
+abstract class PolymorphicOptionsFactories<T : Any>(val serializeKeyAs: String,
+ rootClass: KClass<out PolymorphicOptions<T>>,
+ vararg defaultFactories: PolymorphicOptionsFactory<T>) {
+ val rootClass = rootClass as KClass<PolymorphicOptions<T>>
+ private val map: MutableMap<String, PolymorphicOptionsFactory<T>> = linkedMapOf()
+ val availableKeys: Collection<String> get() = map.keys
+
+ fun registerFactory(factory: PolymorphicOptionsFactory<T>) = factory.supportedKeys.forEach { map.putIfAbsent(it.toLowerCase(), factory) }
+
+ fun getFactory(key: String): PolymorphicOptionsFactory<T>? = map[key.toLowerCase()]
+
+ fun registerSerialization(module: SimpleModule) {
+ module.addSerializer(PolymorphicOptionsSerializer(this))
+ module.addDeserializer(rootClass.java, PolymorphicOptionsDeserializer(this))
+ }
+
+ init {
+ defaultFactories.forEach { registerFactory(it) }
+ }
+}
+
+
+private class PolymorphicOptionsDeserializer<T : Any>(val factories: PolymorphicOptionsFactories<T>) : JsonDeserializer<PolymorphicOptions<T>>() {
+
+ override fun deserialize(p: JsonParser, ctxt: DeserializationContext?): PolymorphicOptions<T> {
+ val node = p.readValueAsTree<JsonNode>()
+ val key = node.get(factories.serializeKeyAs).asText()
+ val factory = getFactory(key)
+ val optionsNode = node.get("options")
+ val options = p.codec.treeToValue(optionsNode, factory.optionsClass.java)
+ return factories.rootClass.constructors.first().call(key, options)
+ }
+
+ private fun getFactory(key: String): PolymorphicOptionsFactory<T> {
+ factories.getFactory(key)?.let { return it }
+
+ logger.warn("Unknown ${factories.rootClass.simpleName} ${factories.serializeKeyAs}: $key. " +
+ "\nAvailable options: ${factories.availableKeys}")
+
+ val default = factories.getFactory(factories.availableKeys.first())
+ ?: throw IllegalStateException("No default ${factories.rootClass.simpleName} factory registered.")
+ return default
+ }
+
+}
+
+private class PolymorphicOptionsSerializer<T : Any>(val factories: PolymorphicOptionsFactories<T>) : StdSerializer<PolymorphicOptions<T>>(factories.rootClass.java) {
+
+ override fun serialize(value: PolymorphicOptions<T>, gen: JsonGenerator, sp: SerializerProvider?) {
+ with(gen) {
+ writeStartObject()
+ writeStringField(factories.serializeKeyAs, value.key)
+ writeFieldName("options")
+ writeObject(value.options)
+ writeEndObject()
+ }
+ }
}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/options/StorageOptions.kt b/src/main/kotlin/io/dico/parcels2/options/StorageOptions.kt index 3d68701..29aab7a 100644 --- a/src/main/kotlin/io/dico/parcels2/options/StorageOptions.kt +++ b/src/main/kotlin/io/dico/parcels2/options/StorageOptions.kt @@ -1,59 +1,59 @@ -package io.dico.parcels2.options - -import com.zaxxer.hikari.HikariDataSource -import io.dico.parcels2.logger -import io.dico.parcels2.storage.Storage -import io.dico.parcels2.storage.BackedStorage -import io.dico.parcels2.storage.exposed.ExposedBacking -import io.dico.parcels2.storage.getHikariConfig -import javax.sql.DataSource - -object StorageOptionsFactories : PolymorphicOptionsFactories<Storage>("dialect", StorageOptions::class, ConnectionStorageFactory()) - -class StorageOptions(dialect: String = "mariadb", options: Any = DataConnectionOptions()) : SimplePolymorphicOptions<Storage>(dialect, options, StorageOptionsFactories) { - - fun getDataSourceFactory(): DataSourceFactory? { - return when (factory) { - is ConnectionStorageFactory -> factory.getDataSourceFactory(key, options) - else -> return null - } - } -} - -typealias DataSourceFactory = () -> DataSource - -private class ConnectionStorageFactory : PolymorphicOptionsFactory<Storage> { - override val optionsClass = DataConnectionOptions::class - override val supportedKeys: List<String> = listOf("postgresql", "mariadb") - - fun getDataSourceFactory(key: String, options: Any): DataSourceFactory { - val hikariConfig = getHikariConfig(key, options as DataConnectionOptions) - return { HikariDataSource(hikariConfig) } - } - - override fun newInstance(key: String, options: Any, vararg extra: Any?): Storage { - return BackedStorage(ExposedBacking(getDataSourceFactory(key, options), (options as DataConnectionOptions).poolSize)) - } -} - -data class DataConnectionOptions(val address: String = "localhost", - val database: String = "parcels", - val username: String = "root", - val password: String = "", - val poolSize: Int = 4) { - - fun splitAddressAndPort(defaultPort: Int = 3306): Pair<String, Int>? { - val idx = address.indexOf(":").takeUnless { it == -1 } ?: return Pair(address, defaultPort) - - val addressName = address.substring(0, idx).takeUnless { it.isBlank() } ?: return null.also { - logger.error("(Invalidly) blank address in data storage options") - } - - val port = address.substring(idx + 1).toIntOrNull() ?: return null.also { - logger.error("Invalid port number in data storage options: $it, using $defaultPort as default") - } - - return Pair(addressName, port) - } - +package io.dico.parcels2.options
+
+import com.zaxxer.hikari.HikariDataSource
+import io.dico.parcels2.logger
+import io.dico.parcels2.storage.Storage
+import io.dico.parcels2.storage.BackedStorage
+import io.dico.parcels2.storage.exposed.ExposedBacking
+import io.dico.parcels2.storage.getHikariConfig
+import javax.sql.DataSource
+
+object StorageOptionsFactories : PolymorphicOptionsFactories<Storage>("dialect", StorageOptions::class, ConnectionStorageFactory())
+
+class StorageOptions(dialect: String = "mariadb", options: Any = DataConnectionOptions()) : SimplePolymorphicOptions<Storage>(dialect, options, StorageOptionsFactories) {
+
+ fun getDataSourceFactory(): DataSourceFactory? {
+ return when (factory) {
+ is ConnectionStorageFactory -> factory.getDataSourceFactory(key, options)
+ else -> return null
+ }
+ }
+}
+
+typealias DataSourceFactory = () -> DataSource
+
+private class ConnectionStorageFactory : PolymorphicOptionsFactory<Storage> {
+ override val optionsClass = DataConnectionOptions::class
+ override val supportedKeys: List<String> = listOf("postgresql", "mariadb")
+
+ fun getDataSourceFactory(key: String, options: Any): DataSourceFactory {
+ val hikariConfig = getHikariConfig(key, options as DataConnectionOptions)
+ return { HikariDataSource(hikariConfig) }
+ }
+
+ override fun newInstance(key: String, options: Any, vararg extra: Any?): Storage {
+ return BackedStorage(ExposedBacking(getDataSourceFactory(key, options), (options as DataConnectionOptions).poolSize))
+ }
+}
+
+data class DataConnectionOptions(val address: String = "localhost",
+ val database: String = "parcels",
+ val username: String = "root",
+ val password: String = "",
+ val poolSize: Int = 4) {
+
+ fun splitAddressAndPort(defaultPort: Int = 3306): Pair<String, Int>? {
+ val idx = address.indexOf(":").takeUnless { it == -1 } ?: return Pair(address, defaultPort)
+
+ val addressName = address.substring(0, idx).takeUnless { it.isBlank() } ?: return null.also {
+ logger.error("(Invalidly) blank address in data storage options")
+ }
+
+ val port = address.substring(idx + 1).toIntOrNull() ?: return null.also {
+ logger.error("Invalid port number in data storage options: $it, using $defaultPort as default")
+ }
+
+ return Pair(addressName, port)
+ }
+
}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt index 12d5bc7..63a5db6 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt @@ -1,69 +1,69 @@ -package io.dico.parcels2.storage - -import io.dico.parcels2.* -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.Job -import kotlinx.coroutines.channels.ReceiveChannel -import kotlinx.coroutines.channels.SendChannel -import org.joda.time.DateTime -import java.util.UUID -import kotlin.coroutines.CoroutineContext - -interface Backing { - - val name: String - - val isConnected: Boolean - - val coroutineContext: CoroutineContext - - fun launchJob(job: Backing.() -> Unit): Job - - fun <T> launchFuture(future: Backing.() -> T): Deferred<T> - - fun <T> openChannel(future: Backing.(SendChannel<T>) -> Unit): ReceiveChannel<T> - - fun <T> openChannelForWriting(future: Backing.(T) -> Unit): SendChannel<T> - - - fun init() - - fun shutdown() - - - fun getWorldCreationTime(worldId: ParcelWorldId): DateTime? - - fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime) - - fun getPlayerUuidForName(name: String): UUID? - - fun updatePlayerName(uuid: UUID, name: String) - - fun transmitParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>) - - fun transmitAllParcelData(channel: SendChannel<DataPair>) - - fun readParcelData(parcel: ParcelId): ParcelDataHolder? - - fun getOwnedParcels(user: PlayerProfile): List<ParcelId> - - fun getNumParcels(user: PlayerProfile): Int = getOwnedParcels(user).size - - - fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?) - - fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) - - fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean) - - fun setLocalPrivilege(parcel: ParcelId, player: PlayerProfile, privilege: Privilege) - - fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration) - - - fun transmitAllGlobalPrivileges(channel: SendChannel<PrivilegePair<PlayerProfile>>) - - fun readGlobalPrivileges(owner: PlayerProfile): PrivilegesHolder? - - fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege) -} +package io.dico.parcels2.storage
+
+import io.dico.parcels2.*
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.channels.ReceiveChannel
+import kotlinx.coroutines.channels.SendChannel
+import org.joda.time.DateTime
+import java.util.UUID
+import kotlin.coroutines.CoroutineContext
+
+interface Backing {
+
+ val name: String
+
+ val isConnected: Boolean
+
+ val coroutineContext: CoroutineContext
+
+ fun launchJob(job: Backing.() -> Unit): Job
+
+ fun <T> launchFuture(future: Backing.() -> T): Deferred<T>
+
+ fun <T> openChannel(future: Backing.(SendChannel<T>) -> Unit): ReceiveChannel<T>
+
+ fun <T> openChannelForWriting(future: Backing.(T) -> Unit): SendChannel<T>
+
+
+ fun init()
+
+ fun shutdown()
+
+
+ fun getWorldCreationTime(worldId: ParcelWorldId): DateTime?
+
+ fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime)
+
+ fun getPlayerUuidForName(name: String): UUID?
+
+ fun updatePlayerName(uuid: UUID, name: String)
+
+ fun transmitParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>)
+
+ fun transmitAllParcelData(channel: SendChannel<DataPair>)
+
+ fun readParcelData(parcel: ParcelId): ParcelDataHolder?
+
+ fun getOwnedParcels(user: PlayerProfile): List<ParcelId>
+
+ fun getNumParcels(user: PlayerProfile): Int = getOwnedParcels(user).size
+
+
+ fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?)
+
+ fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?)
+
+ fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean)
+
+ fun setLocalPrivilege(parcel: ParcelId, player: PlayerProfile, privilege: Privilege)
+
+ fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration)
+
+
+ fun transmitAllGlobalPrivileges(channel: SendChannel<PrivilegePair<PlayerProfile>>)
+
+ fun readGlobalPrivileges(owner: PlayerProfile): PrivilegesHolder?
+
+ fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege)
+}
diff --git a/src/main/kotlin/io/dico/parcels2/storage/DataConverters.kt b/src/main/kotlin/io/dico/parcels2/storage/DataConverters.kt index 80f41b2..54f7677 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/DataConverters.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/DataConverters.kt @@ -1,38 +1,38 @@ -package io.dico.parcels2.storage - -import java.lang.IllegalArgumentException -import java.nio.ByteBuffer -import java.util.UUID - -/* For putting it into the database */ -fun UUID.toByteArray(): ByteArray = - ByteBuffer.allocate(16).apply { - putLong(mostSignificantBits) - putLong(leastSignificantBits) - }.array() - -/* For getting it out of the database */ -fun ByteArray.toUUID(): UUID = - ByteBuffer.wrap(this).run { - val mostSignificantBits = getLong() - val leastSignificantBits = getLong() - UUID(mostSignificantBits, leastSignificantBits) - } - -/* For putting it into the database */ -fun IntArray.toByteArray(): ByteArray = - ByteBuffer.allocate(size * Int.SIZE_BYTES).also { buf -> - buf.asIntBuffer().put(this) - }.array() - -/* For getting it out of the database */ -fun ByteArray.toIntArray(): IntArray { - if (this.size % Int.SIZE_BYTES != 0) - throw IllegalArgumentException("Size must be divisible by ${Int.SIZE_BYTES}") - - return ByteBuffer.wrap(this).run { - IntArray(remaining() / 4).also { array -> - asIntBuffer().get(array) - } - } +package io.dico.parcels2.storage
+
+import java.lang.IllegalArgumentException
+import java.nio.ByteBuffer
+import java.util.UUID
+
+/* For putting it into the database */
+fun UUID.toByteArray(): ByteArray =
+ ByteBuffer.allocate(16).apply {
+ putLong(mostSignificantBits)
+ putLong(leastSignificantBits)
+ }.array()
+
+/* For getting it out of the database */
+fun ByteArray.toUUID(): UUID =
+ ByteBuffer.wrap(this).run {
+ val mostSignificantBits = getLong()
+ val leastSignificantBits = getLong()
+ UUID(mostSignificantBits, leastSignificantBits)
+ }
+
+/* For putting it into the database */
+fun IntArray.toByteArray(): ByteArray =
+ ByteBuffer.allocate(size * Int.SIZE_BYTES).also { buf ->
+ buf.asIntBuffer().put(this)
+ }.array()
+
+/* For getting it out of the database */
+fun ByteArray.toIntArray(): IntArray {
+ if (this.size % Int.SIZE_BYTES != 0)
+ throw IllegalArgumentException("Size must be divisible by ${Int.SIZE_BYTES}")
+
+ return ByteBuffer.wrap(this).run {
+ IntArray(remaining() / 4).also { array ->
+ asIntBuffer().get(array)
+ }
+ }
}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/storage/Hikari.kt b/src/main/kotlin/io/dico/parcels2/storage/Hikari.kt index 480d533..f3030f6 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Hikari.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Hikari.kt @@ -1,73 +1,73 @@ -package io.dico.parcels2.storage - -import com.zaxxer.hikari.HikariConfig -import io.dico.parcels2.options.DataConnectionOptions - -fun getHikariConfig(dialectName: String, - dco: DataConnectionOptions): HikariConfig = HikariConfig().apply { - - val (address, port) = dco.splitAddressAndPort() ?: throw IllegalArgumentException("Invalid address: ${dco.address}") - - when (dialectName) { - "postgresql" -> run { - dataSourceClassName = "org.postgresql.ds.PGSimpleDataSource" - dataSourceProperties["serverName"] = address - dataSourceProperties["portNumber"] = port.toString() - dataSourceProperties["databaseName"] = dco.database - } - - "mariadb" -> run { - dataSourceClassName = "org.mariadb.jdbc.MariaDbDataSource" - dataSourceProperties["serverName"] = address - dataSourceProperties["port"] = port.toString() - dataSourceProperties["databaseName"] = dco.database - dataSourceProperties["properties"] = "useUnicode=true;characterEncoding=utf8" - } - - else -> throw IllegalArgumentException("Unsupported dialect: $dialectName") - } - - poolName = "parcels" - maximumPoolSize = dco.poolSize - username = dco.username - password = dco.password - connectionTimeout = 15000 - leakDetectionThreshold = 10000 - connectionTestQuery = "SELECT 1" - - - /* - - addDataSourceProperty("serverName", address) - addDataSourceProperty("port", port.toString()) - addDataSourceProperty("databaseName", dco.database) - - // copied from github.com/lucko/LuckPerms - if (dialectName.toLowerCase() == "mariadb") { - addDataSourceProperty("properties", "useUnicode=true;characterEncoding=utf8") - } else if (dialectName.toLowerCase() == "h2") { - dataSourceProperties.remove("serverName") - dataSourceProperties.remove("port") - dataSourceProperties.remove("databaseName") - addDataSourceProperty("url", "jdbc:h2:${if (address.isBlank()) "" else "tcp://$address/"}~/${dco.database}") - } else if (dialectName.toLowerCase() == "mysql") { - // doesn't exist on the MariaDB driver - addDataSourceProperty("cachePrepStmts", "true") - addDataSourceProperty("alwaysSendSetIsolation", "false") - addDataSourceProperty("cacheServerConfiguration", "true") - addDataSourceProperty("elideSetAutoCommits", "true") - addDataSourceProperty("useLocalSessionState", "true") - - // already set as default on mariadb - addDataSourceProperty("useServerPrepStmts", "true") - addDataSourceProperty("prepStmtCacheSize", "250") - addDataSourceProperty("prepStmtCacheSqlLimit", "2048") - addDataSourceProperty("cacheCallableStmts", "true") - - // make sure unicode characters can be used. - addDataSourceProperty("characterEncoding", "utf8") - addDataSourceProperty("useUnicode", "true") - } else { - - }*/ -} +package io.dico.parcels2.storage
+
+import com.zaxxer.hikari.HikariConfig
+import io.dico.parcels2.options.DataConnectionOptions
+
+fun getHikariConfig(dialectName: String,
+ dco: DataConnectionOptions): HikariConfig = HikariConfig().apply {
+
+ val (address, port) = dco.splitAddressAndPort() ?: throw IllegalArgumentException("Invalid address: ${dco.address}")
+
+ when (dialectName) {
+ "postgresql" -> run {
+ dataSourceClassName = "org.postgresql.ds.PGSimpleDataSource"
+ dataSourceProperties["serverName"] = address
+ dataSourceProperties["portNumber"] = port.toString()
+ dataSourceProperties["databaseName"] = dco.database
+ }
+
+ "mariadb" -> run {
+ dataSourceClassName = "org.mariadb.jdbc.MariaDbDataSource"
+ dataSourceProperties["serverName"] = address
+ dataSourceProperties["port"] = port.toString()
+ dataSourceProperties["databaseName"] = dco.database
+ dataSourceProperties["properties"] = "useUnicode=true;characterEncoding=utf8"
+ }
+
+ else -> throw IllegalArgumentException("Unsupported dialect: $dialectName")
+ }
+
+ poolName = "parcels"
+ maximumPoolSize = dco.poolSize
+ username = dco.username
+ password = dco.password
+ connectionTimeout = 15000
+ leakDetectionThreshold = 10000
+ connectionTestQuery = "SELECT 1"
+
+
+ /*
+
+ addDataSourceProperty("serverName", address)
+ addDataSourceProperty("port", port.toString())
+ addDataSourceProperty("databaseName", dco.database)
+
+ // copied from github.com/lucko/LuckPerms
+ if (dialectName.toLowerCase() == "mariadb") {
+ addDataSourceProperty("properties", "useUnicode=true;characterEncoding=utf8")
+ } else if (dialectName.toLowerCase() == "h2") {
+ dataSourceProperties.remove("serverName")
+ dataSourceProperties.remove("port")
+ dataSourceProperties.remove("databaseName")
+ addDataSourceProperty("url", "jdbc:h2:${if (address.isBlank()) "" else "tcp://$address/"}~/${dco.database}")
+ } else if (dialectName.toLowerCase() == "mysql") {
+ // doesn't exist on the MariaDB driver
+ addDataSourceProperty("cachePrepStmts", "true")
+ addDataSourceProperty("alwaysSendSetIsolation", "false")
+ addDataSourceProperty("cacheServerConfiguration", "true")
+ addDataSourceProperty("elideSetAutoCommits", "true")
+ addDataSourceProperty("useLocalSessionState", "true")
+
+ // already set as default on mariadb
+ addDataSourceProperty("useServerPrepStmts", "true")
+ addDataSourceProperty("prepStmtCacheSize", "250")
+ addDataSourceProperty("prepStmtCacheSqlLimit", "2048")
+ addDataSourceProperty("cacheCallableStmts", "true")
+
+ // make sure unicode characters can be used.
+ addDataSourceProperty("characterEncoding", "utf8")
+ addDataSourceProperty("useUnicode", "true")
+ } else {
+
+ }*/
+}
diff --git a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt index d718f20..f76aad6 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt @@ -1,114 +1,114 @@ -@file:Suppress("NOTHING_TO_INLINE") - -package io.dico.parcels2.storage - -import io.dico.parcels2.* -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.Job -import kotlinx.coroutines.channels.ReceiveChannel -import kotlinx.coroutines.channels.SendChannel -import kotlinx.coroutines.launch -import org.joda.time.DateTime -import java.util.UUID -import kotlin.coroutines.CoroutineContext - -typealias DataPair = Pair<ParcelId, ParcelDataHolder?> -typealias PrivilegePair<TAttach> = Pair<TAttach, PrivilegesHolder> - -interface Storage { - val name: String - val isConnected: Boolean - - fun init(): Job - - fun shutdown(): Job - - - fun getWorldCreationTime(worldId: ParcelWorldId): Deferred<DateTime?> - - fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime): Job - - fun getPlayerUuidForName(name: String): Deferred<UUID?> - - fun updatePlayerName(uuid: UUID, name: String): Job - - fun readParcelData(parcel: ParcelId): Deferred<ParcelDataHolder?> - - fun transmitParcelData(parcels: Sequence<ParcelId>): ReceiveChannel<DataPair> - - fun transmitAllParcelData(): ReceiveChannel<DataPair> - - fun getOwnedParcels(user: PlayerProfile): Deferred<List<ParcelId>> - - fun getNumParcels(user: PlayerProfile): Deferred<Int> - - - fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?): Job - - fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?): Job - - fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean): Job - - fun setLocalPrivilege(parcel: ParcelId, player: PlayerProfile, privilege: Privilege): Job - - fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration): Job - - - fun transmitAllGlobalPrivileges(): ReceiveChannel<PrivilegePair<PlayerProfile>> - - fun readGlobalPrivileges(owner: PlayerProfile): Deferred<PrivilegesHolder?> - - fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege): Job - - - fun getChannelToUpdateParcelData(): SendChannel<Pair<ParcelId, ParcelDataHolder>> -} - -class BackedStorage internal constructor(val b: Backing) : Storage, CoroutineScope { - override val name get() = b.name - override val isConnected get() = b.isConnected - override val coroutineContext: CoroutineContext get() = b.coroutineContext - - override fun init() = launch { b.init() } - - override fun shutdown() = launch { b.shutdown() } - - - override fun getWorldCreationTime(worldId: ParcelWorldId): Deferred<DateTime?> = b.launchFuture { b.getWorldCreationTime(worldId) } - - override fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime): Job = b.launchJob { b.setWorldCreationTime(worldId, time) } - - override fun getPlayerUuidForName(name: String): Deferred<UUID?> = b.launchFuture { b.getPlayerUuidForName(name) } - - override fun updatePlayerName(uuid: UUID, name: String): Job = b.launchJob { b.updatePlayerName(uuid, name) } - - override fun readParcelData(parcel: ParcelId) = b.launchFuture { b.readParcelData(parcel) } - - override fun transmitParcelData(parcels: Sequence<ParcelId>) = b.openChannel<DataPair> { b.transmitParcelData(it, parcels) } - - override fun transmitAllParcelData() = b.openChannel<DataPair> { b.transmitAllParcelData(it) } - - override fun getOwnedParcels(user: PlayerProfile) = b.launchFuture { b.getOwnedParcels(user) } - - override fun getNumParcels(user: PlayerProfile) = b.launchFuture { b.getNumParcels(user) } - - override fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?) = b.launchJob { b.setParcelData(parcel, data) } - - override fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) = b.launchJob { b.setParcelOwner(parcel, owner) } - - override fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean): Job = b.launchJob { b.setParcelOwnerSignOutdated(parcel, outdated) } - - override fun setLocalPrivilege(parcel: ParcelId, player: PlayerProfile, privilege: Privilege) = b.launchJob { b.setLocalPrivilege(parcel, player, privilege) } - - override fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration) = b.launchJob { b.setParcelOptionsInteractConfig(parcel, config) } - - - override fun transmitAllGlobalPrivileges(): ReceiveChannel<PrivilegePair<PlayerProfile>> = b.openChannel { b.transmitAllGlobalPrivileges(it) } - - override fun readGlobalPrivileges(owner: PlayerProfile): Deferred<PrivilegesHolder?> = b.launchFuture { b.readGlobalPrivileges(owner) } - - override fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege) = b.launchJob { b.setGlobalPrivilege(owner, player, privilege) } - - override fun getChannelToUpdateParcelData(): SendChannel<Pair<ParcelId, ParcelDataHolder>> = b.openChannelForWriting { b.setParcelData(it.first, it.second) } -} +@file:Suppress("NOTHING_TO_INLINE")
+
+package io.dico.parcels2.storage
+
+import io.dico.parcels2.*
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.channels.ReceiveChannel
+import kotlinx.coroutines.channels.SendChannel
+import kotlinx.coroutines.launch
+import org.joda.time.DateTime
+import java.util.UUID
+import kotlin.coroutines.CoroutineContext
+
+typealias DataPair = Pair<ParcelId, ParcelDataHolder?>
+typealias PrivilegePair<TAttach> = Pair<TAttach, PrivilegesHolder>
+
+interface Storage {
+ val name: String
+ val isConnected: Boolean
+
+ fun init(): Job
+
+ fun shutdown(): Job
+
+
+ fun getWorldCreationTime(worldId: ParcelWorldId): Deferred<DateTime?>
+
+ fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime): Job
+
+ fun getPlayerUuidForName(name: String): Deferred<UUID?>
+
+ fun updatePlayerName(uuid: UUID, name: String): Job
+
+ fun readParcelData(parcel: ParcelId): Deferred<ParcelDataHolder?>
+
+ fun transmitParcelData(parcels: Sequence<ParcelId>): ReceiveChannel<DataPair>
+
+ fun transmitAllParcelData(): ReceiveChannel<DataPair>
+
+ fun getOwnedParcels(user: PlayerProfile): Deferred<List<ParcelId>>
+
+ fun getNumParcels(user: PlayerProfile): Deferred<Int>
+
+
+ fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?): Job
+
+ fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?): Job
+
+ fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean): Job
+
+ fun setLocalPrivilege(parcel: ParcelId, player: PlayerProfile, privilege: Privilege): Job
+
+ fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration): Job
+
+
+ fun transmitAllGlobalPrivileges(): ReceiveChannel<PrivilegePair<PlayerProfile>>
+
+ fun readGlobalPrivileges(owner: PlayerProfile): Deferred<PrivilegesHolder?>
+
+ fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege): Job
+
+
+ fun getChannelToUpdateParcelData(): SendChannel<Pair<ParcelId, ParcelDataHolder>>
+}
+
+class BackedStorage internal constructor(val b: Backing) : Storage, CoroutineScope {
+ override val name get() = b.name
+ override val isConnected get() = b.isConnected
+ override val coroutineContext: CoroutineContext get() = b.coroutineContext
+
+ override fun init() = launch { b.init() }
+
+ override fun shutdown() = launch { b.shutdown() }
+
+
+ override fun getWorldCreationTime(worldId: ParcelWorldId): Deferred<DateTime?> = b.launchFuture { b.getWorldCreationTime(worldId) }
+
+ override fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime): Job = b.launchJob { b.setWorldCreationTime(worldId, time) }
+
+ override fun getPlayerUuidForName(name: String): Deferred<UUID?> = b.launchFuture { b.getPlayerUuidForName(name) }
+
+ override fun updatePlayerName(uuid: UUID, name: String): Job = b.launchJob { b.updatePlayerName(uuid, name) }
+
+ override fun readParcelData(parcel: ParcelId) = b.launchFuture { b.readParcelData(parcel) }
+
+ override fun transmitParcelData(parcels: Sequence<ParcelId>) = b.openChannel<DataPair> { b.transmitParcelData(it, parcels) }
+
+ override fun transmitAllParcelData() = b.openChannel<DataPair> { b.transmitAllParcelData(it) }
+
+ override fun getOwnedParcels(user: PlayerProfile) = b.launchFuture { b.getOwnedParcels(user) }
+
+ override fun getNumParcels(user: PlayerProfile) = b.launchFuture { b.getNumParcels(user) }
+
+ override fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?) = b.launchJob { b.setParcelData(parcel, data) }
+
+ override fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) = b.launchJob { b.setParcelOwner(parcel, owner) }
+
+ override fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean): Job = b.launchJob { b.setParcelOwnerSignOutdated(parcel, outdated) }
+
+ override fun setLocalPrivilege(parcel: ParcelId, player: PlayerProfile, privilege: Privilege) = b.launchJob { b.setLocalPrivilege(parcel, player, privilege) }
+
+ override fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration) = b.launchJob { b.setParcelOptionsInteractConfig(parcel, config) }
+
+
+ override fun transmitAllGlobalPrivileges(): ReceiveChannel<PrivilegePair<PlayerProfile>> = b.openChannel { b.transmitAllGlobalPrivileges(it) }
+
+ override fun readGlobalPrivileges(owner: PlayerProfile): Deferred<PrivilegesHolder?> = b.launchFuture { b.readGlobalPrivileges(owner) }
+
+ override fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege) = b.launchJob { b.setGlobalPrivilege(owner, player, privilege) }
+
+ override fun getChannelToUpdateParcelData(): SendChannel<Pair<ParcelId, ParcelDataHolder>> = b.openChannelForWriting { b.setParcelData(it.first, it.second) }
+}
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 e49be79..32065bc 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt @@ -1,282 +1,282 @@ -@file:Suppress("NOTHING_TO_INLINE", "PARAMETER_NAME_CHANGED_ON_OVERRIDE", "LocalVariableName", "UNUSED_EXPRESSION") - -package io.dico.parcels2.storage.exposed - -import com.zaxxer.hikari.HikariDataSource -import io.dico.parcels2.* -import io.dico.parcels2.PlayerProfile.Star.name -import io.dico.parcels2.storage.* -import io.dico.parcels2.util.math.clampMax -import io.dico.parcels2.util.ext.synchronized -import kotlinx.coroutines.* -import kotlinx.coroutines.Job -import kotlinx.coroutines.channels.ArrayChannel -import kotlinx.coroutines.channels.LinkedListChannel -import kotlinx.coroutines.channels.ReceiveChannel -import kotlinx.coroutines.channels.SendChannel -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.UUID -import javax.sql.DataSource - -class ExposedDatabaseException(message: String? = null) : Exception(message) - -class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSize: Int) : Backing, CoroutineScope { - override val name get() = "Exposed" - override val coroutineContext = Job() + newFixedThreadPoolContext(poolSize, "Parcels StorageThread") - private var dataSource: DataSource? = null - private var database: Database? = null - private var isShutdown: Boolean = false - override val isConnected get() = database != null - - override fun launchJob(job: Backing.() -> Unit): Job = launch { transaction { job() } } - override fun <T> launchFuture(future: Backing.() -> T): Deferred<T> = async { transaction { future() } } - - override fun <T> openChannel(future: Backing.(SendChannel<T>) -> Unit): ReceiveChannel<T> { - val channel = LinkedListChannel<T>() - launchJob { future(channel) } - return channel - } - - override fun <T> openChannelForWriting(action: Backing.(T) -> Unit): SendChannel<T> { - val channel = ArrayChannel<T>(poolSize * 2) - - repeat(poolSize.clampMax(3)) { - launch { - try { - while (true) { - action(channel.receive()) - } - } catch (ex: Exception) { - // channel closed - } - } - } - - return channel - } - - private fun <T> transaction(statement: Transaction.() -> T) = transaction(database!!, statement) - - companion object { - init { - Database.registerDialect("mariadb") { - Class.forName("org.jetbrains.exposed.sql.vendors.MysqlDialect").newInstance() as DatabaseDialect - } - } - } - - override fun init() { - synchronized { - if (isShutdown || isConnected) throw IllegalStateException() - dataSource = dataSourceFactory() - database = Database.connect(dataSource!!) - transaction(database!!) { - create(WorldsT, ProfilesT, ParcelsT, ParcelOptionsT, PrivilegesLocalT, PrivilegesGlobalT) - } - } - } - - override fun shutdown() { - synchronized { - if (isShutdown) throw IllegalStateException() - isShutdown = true - coroutineContext[Job]!!.cancel(CancellationException("ExposedBacking shutdown")) - dataSource?.let { - (it as? HikariDataSource)?.close() - } - database = null - } - } - - @Suppress("RedundantObjectTypeCheck") - private fun PlayerProfile.toOwnerProfile(): PlayerProfile { - if (this is PlayerProfile.Star) return PlayerProfile.Fake(name) - return this - } - - private fun PlayerProfile.Unresolved.toResolvedProfile(): PlayerProfile.Real { - return resolve(getPlayerUuidForName(name) ?: throwException()) - } - - private fun PlayerProfile.toResolvedProfile(): PlayerProfile { - if (this is PlayerProfile.Unresolved) return toResolvedProfile() - return this - } - - private fun PlayerProfile.toRealProfile(): PlayerProfile.Real = when (this) { - is PlayerProfile.Real -> this - is PlayerProfile.Fake -> throw IllegalArgumentException("Fake profiles are not accepted") - is PlayerProfile.Unresolved -> toResolvedProfile() - else -> throw InternalError("Case should not be reached") - } - - - override fun getWorldCreationTime(worldId: ParcelWorldId): DateTime? { - return WorldsT.getWorldCreationTime(worldId) - } - - override fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime) { - WorldsT.setWorldCreationTime(worldId, time) - } - - override fun getPlayerUuidForName(name: String): UUID? { - return ProfilesT.slice(ProfilesT.uuid).select { ProfilesT.name.upperCase() eq name.toUpperCase() } - .firstOrNull()?.let { it[ProfilesT.uuid]?.toUUID() } - } - - override fun updatePlayerName(uuid: UUID, name: String) { - val binaryUuid = uuid.toByteArray() - ProfilesT.upsert(ProfilesT.uuid) { - it[ProfilesT.uuid] = binaryUuid - it[ProfilesT.name] = name - } - } - - override fun transmitParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>) { - for (parcel in parcels) { - val data = readParcelData(parcel) - channel.offer(parcel to data) - } - channel.close() - } - - override fun transmitAllParcelData(channel: SendChannel<DataPair>) { - ParcelsT.selectAll().forEach { row -> - val parcel = ParcelsT.getItem(row) ?: return@forEach - val data = rowToParcelData(row) - channel.offer(parcel to data) - } - channel.close() - } - - override fun readParcelData(parcel: ParcelId): ParcelDataHolder? { - val row = ParcelsT.getRow(parcel) ?: return null - return rowToParcelData(row) - } - - override fun getOwnedParcels(user: PlayerProfile): List<ParcelId> { - val user_id = ProfilesT.getId(user.toOwnerProfile()) ?: return emptyList() - return ParcelsT.select { ParcelsT.owner_id eq user_id } - .orderBy(ParcelsT.claim_time, isAsc = true) - .mapNotNull(ParcelsT::getItem) - .toList() - } - - override fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?) { - if (data == null) { - transaction { - ParcelsT.getId(parcel)?.let { id -> - ParcelsT.deleteIgnoreWhere { ParcelsT.id eq id } - - // Below should cascade automatically - /* - PrivilegesLocalT.deleteIgnoreWhere { PrivilegesLocalT.parcel_id eq id } - ParcelOptionsT.deleteIgnoreWhere(limit = 1) { ParcelOptionsT.parcel_id eq id } - */ - } - - } - return - } - - transaction { - val id = ParcelsT.getOrInitId(parcel) - PrivilegesLocalT.deleteIgnoreWhere { PrivilegesLocalT.attach_id eq id } - } - - setParcelOwner(parcel, data.owner) - - for ((profile, privilege) in data.privilegeMap) { - PrivilegesLocalT.setPrivilege(parcel, profile, privilege) - } - - data.privilegeOfStar.takeIf { it != Privilege.DEFAULT }?.let { privilege -> - PrivilegesLocalT.setPrivilege(parcel, PlayerProfile.Star, privilege) - } - - setParcelOptionsInteractConfig(parcel, data.interactableConfig) - } - - override fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) { - val id = if (owner == null) - ParcelsT.getId(parcel) ?: return - else - ParcelsT.getOrInitId(parcel) - - val owner_id = owner?.let { ProfilesT.getOrInitId(it.toOwnerProfile()) } - val time = owner?.let { DateTime.now() } - - ParcelsT.update({ ParcelsT.id eq id }) { - it[ParcelsT.owner_id] = owner_id - it[claim_time] = time - it[sign_oudated] = false - } - } - - override fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean) { - val id = ParcelsT.getId(parcel) ?: return - ParcelsT.update({ ParcelsT.id eq id }) { - it[sign_oudated] = outdated - } - } - - override fun setLocalPrivilege(parcel: ParcelId, player: PlayerProfile, privilege: Privilege) { - PrivilegesLocalT.setPrivilege(parcel, player.toRealProfile(), privilege) - } - - override fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration) { - val bitmaskArray = (config as? BitmaskInteractableConfiguration ?: return).bitmaskArray - val isAllZero = !bitmaskArray.fold(false) { cur, elem -> cur || elem != 0 } - - if (isAllZero) { - val id = ParcelsT.getId(parcel) ?: return - ParcelOptionsT.deleteWhere { ParcelOptionsT.parcel_id eq id } - return - } - - if (bitmaskArray.size != 1) throw IllegalArgumentException() - val array = bitmaskArray.toByteArray() - val id = ParcelsT.getOrInitId(parcel) - ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { - it[parcel_id] = id - it[interact_bitmask] = array - } - } - - override fun transmitAllGlobalPrivileges(channel: SendChannel<PrivilegePair<PlayerProfile>>) { - PrivilegesGlobalT.sendAllPrivilegesH(channel) - channel.close() - } - - override fun readGlobalPrivileges(owner: PlayerProfile): PrivilegesHolder? { - return PrivilegesGlobalT.readPrivileges(ProfilesT.getId(owner.toOwnerProfile()) ?: return null) - } - - override fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege) { - PrivilegesGlobalT.setPrivilege(owner, player.toRealProfile(), privilege) - } - - private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply { - owner = row[ParcelsT.owner_id]?.let { ProfilesT.getItem(it) } - lastClaimTime = row[ParcelsT.claim_time] - isOwnerSignOutdated = row[ParcelsT.sign_oudated] - - val id = row[ParcelsT.id] - ParcelOptionsT.select { ParcelOptionsT.parcel_id eq id }.firstOrNull()?.let { optrow -> - val source = optrow[ParcelOptionsT.interact_bitmask].toIntArray() - val target = (interactableConfig as? BitmaskInteractableConfiguration ?: return@let).bitmaskArray - System.arraycopy(source, 0, target, 0, source.size.clampMax(target.size)) - } - - val privileges = PrivilegesLocalT.readPrivileges(id) - if (privileges != null) { - copyPrivilegesFrom(privileges) - } - } - -} - +@file:Suppress("NOTHING_TO_INLINE", "PARAMETER_NAME_CHANGED_ON_OVERRIDE", "LocalVariableName", "UNUSED_EXPRESSION")
+
+package io.dico.parcels2.storage.exposed
+
+import com.zaxxer.hikari.HikariDataSource
+import io.dico.parcels2.*
+import io.dico.parcels2.PlayerProfile.Star.name
+import io.dico.parcels2.storage.*
+import io.dico.parcels2.util.math.clampMax
+import io.dico.parcels2.util.ext.synchronized
+import kotlinx.coroutines.*
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.channels.ArrayChannel
+import kotlinx.coroutines.channels.LinkedListChannel
+import kotlinx.coroutines.channels.ReceiveChannel
+import kotlinx.coroutines.channels.SendChannel
+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.UUID
+import javax.sql.DataSource
+
+class ExposedDatabaseException(message: String? = null) : Exception(message)
+
+class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSize: Int) : Backing, CoroutineScope {
+ override val name get() = "Exposed"
+ override val coroutineContext = Job() + newFixedThreadPoolContext(poolSize, "Parcels StorageThread")
+ private var dataSource: DataSource? = null
+ private var database: Database? = null
+ private var isShutdown: Boolean = false
+ override val isConnected get() = database != null
+
+ override fun launchJob(job: Backing.() -> Unit): Job = launch { transaction { job() } }
+ override fun <T> launchFuture(future: Backing.() -> T): Deferred<T> = async { transaction { future() } }
+
+ override fun <T> openChannel(future: Backing.(SendChannel<T>) -> Unit): ReceiveChannel<T> {
+ val channel = LinkedListChannel<T>()
+ launchJob { future(channel) }
+ return channel
+ }
+
+ override fun <T> openChannelForWriting(action: Backing.(T) -> Unit): SendChannel<T> {
+ val channel = ArrayChannel<T>(poolSize * 2)
+
+ repeat(poolSize.clampMax(3)) {
+ launch {
+ try {
+ while (true) {
+ action(channel.receive())
+ }
+ } catch (ex: Exception) {
+ // channel closed
+ }
+ }
+ }
+
+ return channel
+ }
+
+ private fun <T> transaction(statement: Transaction.() -> T) = transaction(database!!, statement)
+
+ companion object {
+ init {
+ Database.registerDialect("mariadb") {
+ Class.forName("org.jetbrains.exposed.sql.vendors.MysqlDialect").newInstance() as DatabaseDialect
+ }
+ }
+ }
+
+ override fun init() {
+ synchronized {
+ if (isShutdown || isConnected) throw IllegalStateException()
+ dataSource = dataSourceFactory()
+ database = Database.connect(dataSource!!)
+ transaction(database!!) {
+ create(WorldsT, ProfilesT, ParcelsT, ParcelOptionsT, PrivilegesLocalT, PrivilegesGlobalT)
+ }
+ }
+ }
+
+ override fun shutdown() {
+ synchronized {
+ if (isShutdown) throw IllegalStateException()
+ isShutdown = true
+ coroutineContext[Job]!!.cancel(CancellationException("ExposedBacking shutdown"))
+ dataSource?.let {
+ (it as? HikariDataSource)?.close()
+ }
+ database = null
+ }
+ }
+
+ @Suppress("RedundantObjectTypeCheck")
+ private fun PlayerProfile.toOwnerProfile(): PlayerProfile {
+ if (this is PlayerProfile.Star) return PlayerProfile.Fake(name)
+ return this
+ }
+
+ private fun PlayerProfile.Unresolved.toResolvedProfile(): PlayerProfile.Real {
+ return resolve(getPlayerUuidForName(name) ?: throwException())
+ }
+
+ private fun PlayerProfile.toResolvedProfile(): PlayerProfile {
+ if (this is PlayerProfile.Unresolved) return toResolvedProfile()
+ return this
+ }
+
+ private fun PlayerProfile.toRealProfile(): PlayerProfile.Real = when (this) {
+ is PlayerProfile.Real -> this
+ is PlayerProfile.Fake -> throw IllegalArgumentException("Fake profiles are not accepted")
+ is PlayerProfile.Unresolved -> toResolvedProfile()
+ else -> throw InternalError("Case should not be reached")
+ }
+
+
+ override fun getWorldCreationTime(worldId: ParcelWorldId): DateTime? {
+ return WorldsT.getWorldCreationTime(worldId)
+ }
+
+ override fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime) {
+ WorldsT.setWorldCreationTime(worldId, time)
+ }
+
+ override fun getPlayerUuidForName(name: String): UUID? {
+ return ProfilesT.slice(ProfilesT.uuid).select { ProfilesT.name.upperCase() eq name.toUpperCase() }
+ .firstOrNull()?.let { it[ProfilesT.uuid]?.toUUID() }
+ }
+
+ override fun updatePlayerName(uuid: UUID, name: String) {
+ val binaryUuid = uuid.toByteArray()
+ ProfilesT.upsert(ProfilesT.uuid) {
+ it[ProfilesT.uuid] = binaryUuid
+ it[ProfilesT.name] = name
+ }
+ }
+
+ override fun transmitParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>) {
+ for (parcel in parcels) {
+ val data = readParcelData(parcel)
+ channel.offer(parcel to data)
+ }
+ channel.close()
+ }
+
+ override fun transmitAllParcelData(channel: SendChannel<DataPair>) {
+ ParcelsT.selectAll().forEach { row ->
+ val parcel = ParcelsT.getItem(row) ?: return@forEach
+ val data = rowToParcelData(row)
+ channel.offer(parcel to data)
+ }
+ channel.close()
+ }
+
+ override fun readParcelData(parcel: ParcelId): ParcelDataHolder? {
+ val row = ParcelsT.getRow(parcel) ?: return null
+ return rowToParcelData(row)
+ }
+
+ override fun getOwnedParcels(user: PlayerProfile): List<ParcelId> {
+ val user_id = ProfilesT.getId(user.toOwnerProfile()) ?: return emptyList()
+ return ParcelsT.select { ParcelsT.owner_id eq user_id }
+ .orderBy(ParcelsT.claim_time, isAsc = true)
+ .mapNotNull(ParcelsT::getItem)
+ .toList()
+ }
+
+ override fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?) {
+ if (data == null) {
+ transaction {
+ ParcelsT.getId(parcel)?.let { id ->
+ ParcelsT.deleteIgnoreWhere { ParcelsT.id eq id }
+
+ // Below should cascade automatically
+ /*
+ PrivilegesLocalT.deleteIgnoreWhere { PrivilegesLocalT.parcel_id eq id }
+ ParcelOptionsT.deleteIgnoreWhere(limit = 1) { ParcelOptionsT.parcel_id eq id }
+ */
+ }
+
+ }
+ return
+ }
+
+ transaction {
+ val id = ParcelsT.getOrInitId(parcel)
+ PrivilegesLocalT.deleteIgnoreWhere { PrivilegesLocalT.attach_id eq id }
+ }
+
+ setParcelOwner(parcel, data.owner)
+
+ for ((profile, privilege) in data.privilegeMap) {
+ PrivilegesLocalT.setPrivilege(parcel, profile, privilege)
+ }
+
+ data.privilegeOfStar.takeIf { it != Privilege.DEFAULT }?.let { privilege ->
+ PrivilegesLocalT.setPrivilege(parcel, PlayerProfile.Star, privilege)
+ }
+
+ setParcelOptionsInteractConfig(parcel, data.interactableConfig)
+ }
+
+ override fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) {
+ val id = if (owner == null)
+ ParcelsT.getId(parcel) ?: return
+ else
+ ParcelsT.getOrInitId(parcel)
+
+ val owner_id = owner?.let { ProfilesT.getOrInitId(it.toOwnerProfile()) }
+ val time = owner?.let { DateTime.now() }
+
+ ParcelsT.update({ ParcelsT.id eq id }) {
+ it[ParcelsT.owner_id] = owner_id
+ it[claim_time] = time
+ it[sign_oudated] = false
+ }
+ }
+
+ override fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean) {
+ val id = ParcelsT.getId(parcel) ?: return
+ ParcelsT.update({ ParcelsT.id eq id }) {
+ it[sign_oudated] = outdated
+ }
+ }
+
+ override fun setLocalPrivilege(parcel: ParcelId, player: PlayerProfile, privilege: Privilege) {
+ PrivilegesLocalT.setPrivilege(parcel, player.toRealProfile(), privilege)
+ }
+
+ override fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration) {
+ val bitmaskArray = (config as? BitmaskInteractableConfiguration ?: return).bitmaskArray
+ val isAllZero = !bitmaskArray.fold(false) { cur, elem -> cur || elem != 0 }
+
+ if (isAllZero) {
+ val id = ParcelsT.getId(parcel) ?: return
+ ParcelOptionsT.deleteWhere { ParcelOptionsT.parcel_id eq id }
+ return
+ }
+
+ if (bitmaskArray.size != 1) throw IllegalArgumentException()
+ val array = bitmaskArray.toByteArray()
+ val id = ParcelsT.getOrInitId(parcel)
+ ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
+ it[parcel_id] = id
+ it[interact_bitmask] = array
+ }
+ }
+
+ override fun transmitAllGlobalPrivileges(channel: SendChannel<PrivilegePair<PlayerProfile>>) {
+ PrivilegesGlobalT.sendAllPrivilegesH(channel)
+ channel.close()
+ }
+
+ override fun readGlobalPrivileges(owner: PlayerProfile): PrivilegesHolder? {
+ return PrivilegesGlobalT.readPrivileges(ProfilesT.getId(owner.toOwnerProfile()) ?: return null)
+ }
+
+ override fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege) {
+ PrivilegesGlobalT.setPrivilege(owner, player.toRealProfile(), privilege)
+ }
+
+ private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply {
+ owner = row[ParcelsT.owner_id]?.let { ProfilesT.getItem(it) }
+ lastClaimTime = row[ParcelsT.claim_time]
+ isOwnerSignOutdated = row[ParcelsT.sign_oudated]
+
+ val id = row[ParcelsT.id]
+ ParcelOptionsT.select { ParcelOptionsT.parcel_id eq id }.firstOrNull()?.let { optrow ->
+ val source = optrow[ParcelOptionsT.interact_bitmask].toIntArray()
+ val target = (interactableConfig as? BitmaskInteractableConfiguration ?: return@let).bitmaskArray
+ System.arraycopy(source, 0, target, 0, source.size.clampMax(target.size))
+ }
+
+ val privileges = PrivilegesLocalT.readPrivileges(id)
+ if (privileges != null) {
+ copyPrivilegesFrom(privileges)
+ }
+ }
+
+}
+
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 0245625..7640c0c 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedExtensions.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedExtensions.kt @@ -1,74 +1,74 @@ -package io.dico.parcels2.storage.exposed - -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.Function -import org.jetbrains.exposed.sql.statements.InsertStatement -import org.jetbrains.exposed.sql.transactions.TransactionManager - -class UpsertStatement<Key : Any>(table: Table, conflictColumn: Column<*>? = null, conflictIndex: Index? = null) : InsertStatement<Key>(table, false) { - val indexName: String - val indexColumns: List<Column<*>> - - 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 : Table> T.upsert(conflictColumn: Column<*>? = null, conflictIndex: Index? = null, body: T.(UpsertStatement<Number>) -> Unit) = - UpsertStatement<Number>(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) - -fun <T : Int?> ExpressionWithColumnType<T>.abs(): Function<T> = Abs(this) - -class Abs<T : Int?>(val expr: Expression<T>) : Function<T>(IntegerColumnType()) { - override fun toSQL(queryBuilder: QueryBuilder): String = "ABS(${expr.toSQL(queryBuilder)})" -} - -fun <T : Comparable<T>> greaterOf(col1: ExpressionWithColumnType<T>, col2: ExpressionWithColumnType<T>): Expression<T> = - with(SqlExpressionBuilder) { - case(col1) - .When(col1.greater(col2), col1) - .Else(col2) - } - +package io.dico.parcels2.storage.exposed
+
+import org.jetbrains.exposed.sql.*
+import org.jetbrains.exposed.sql.Function
+import org.jetbrains.exposed.sql.statements.InsertStatement
+import org.jetbrains.exposed.sql.transactions.TransactionManager
+
+class UpsertStatement<Key : Any>(table: Table, conflictColumn: Column<*>? = null, conflictIndex: Index? = null) : InsertStatement<Key>(table, false) {
+ val indexName: String
+ val indexColumns: List<Column<*>>
+
+ 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 : Table> T.upsert(conflictColumn: Column<*>? = null, conflictIndex: Index? = null, body: T.(UpsertStatement<Number>) -> Unit) =
+ UpsertStatement<Number>(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)
+
+fun <T : Int?> ExpressionWithColumnType<T>.abs(): Function<T> = Abs(this)
+
+class Abs<T : Int?>(val expr: Expression<T>) : Function<T>(IntegerColumnType()) {
+ override fun toSQL(queryBuilder: QueryBuilder): String = "ABS(${expr.toSQL(queryBuilder)})"
+}
+
+fun <T : Comparable<T>> greaterOf(col1: ExpressionWithColumnType<T>, col2: ExpressionWithColumnType<T>): Expression<T> =
+ with(SqlExpressionBuilder) {
+ case(col1)
+ .When(col1.greater(col2), col1)
+ .Else(col2)
+ }
+
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 6f6ad6b..8428b3a 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt @@ -1,165 +1,165 @@ -@file:Suppress("NOTHING_TO_INLINE", "PARAMETER_NAME_CHANGED_ON_OVERRIDE", "unused", "MemberVisibilityCanBePrivate") - -package io.dico.parcels2.storage.exposed - -import io.dico.parcels2.ParcelId -import io.dico.parcels2.ParcelWorldId -import io.dico.parcels2.PlayerProfile -import io.dico.parcels2.storage.toByteArray -import io.dico.parcels2.storage.toUUID -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.statements.UpdateBuilder -import org.joda.time.DateTime -import java.util.UUID - -abstract class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj>, QueryObj>(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<Boolean>): Int? { - return select { where(table) }.firstOrNull()?.let { it[id] } - } - - internal inline fun getOrInitId(getId: () -> Int?, noinline body: TableT.(UpdateBuilder<*>) -> Unit, objName: () -> String): Int { - return getId() ?: table.insertIgnore(body)[id] ?: getId() - ?: throw ExposedDatabaseException("This should not happen - failed to insert ${objName()} and get its number") - } - - abstract fun getId(obj: QueryObj): Int? - abstract fun getOrInitId(obj: QueryObj): Int - fun getItem(id: Int): QueryObj? = select { this@IdTransactionsTable.id eq id }.firstOrNull()?.let { getItem(it) } - abstract fun getItem(row: ResultRow): QueryObj? - - fun getId(obj: QueryObj, init: Boolean): Int? = if (init) getOrInitId(obj) else getId(obj) -} - -object WorldsT : IdTransactionsTable<WorldsT, ParcelWorldId>("parcels_worlds", "world_id") { - val name = varchar("name", 50) - val uid = binary("uid", 16).nullable() - val creation_time = datetime("creation_time").nullable() - val index_name = uniqueIndexR("index_name", name) - val index_uid = uniqueIndexR("index_uid", uid) - - 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 -> - return getOrInitId( - { getId(worldName, binaryUid) }, - { it[name] = worldName; it[uid] = binaryUid }, - { "world named $worldName" }) - } - - override fun getId(world: ParcelWorldId): Int? = getId(world.name, world.uid) - override fun getOrInitId(world: ParcelWorldId): Int = getOrInitId(world.name, world.uid) - - override fun getItem(row: ResultRow): ParcelWorldId { - return ParcelWorldId(row[name], row[uid]?.toUUID()) - } - - fun getWorldCreationTime(worldId: ParcelWorldId): DateTime? { - val id = getId(worldId) ?: return null - return select { WorldsT.id eq id }.firstOrNull()?.let { it[WorldsT.creation_time] } - } - - fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime) { - val id = getOrInitId(worldId) - update({ WorldsT.id eq id }) { - it[WorldsT.creation_time] = time - } - } -} - -object ParcelsT : IdTransactionsTable<ParcelsT, ParcelId>("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(ProfilesT.id).nullable() - val sign_oudated = bool("sign_outdated").default(false) - 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(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 getOrInitId( - { getId(worldId, parcelX, parcelZ) }, - { it[world_id] = worldId; it[px] = parcelX; it[pz] = parcelZ }, - { "parcel at $worldName($parcelX, $parcelZ)" }) - } - - 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: ParcelId): ResultRow? = getId(parcel)?.let { getRow(it) } - - override fun getItem(row: ResultRow): ParcelId? { - val worldId = row[world_id] - val world = WorldsT.getItem(worldId) ?: return null - return ParcelId(world, row[px], row[pz]) - } -} - -object ProfilesT : IdTransactionsTable<ProfilesT, PlayerProfile>("parcels_profiles", "owner_id") { - val uuid = binary("uuid", 16).nullable() - val name = varchar("name", 32).nullable() - - // MySQL dialect MUST permit multiple null values for this to work. Server SQL does not allow this. That dialect is shit anyway. - val uuid_constraint = uniqueIndexR("uuid_constraint", uuid) - 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(nameIn: String) = getId { uuid.isNull() and (name.lowerCase() eq nameIn.toLowerCase()) } - private inline fun getRealId(nameIn: String) = getId { uuid.isNotNull() and (name.lowerCase() eq nameIn.toLowerCase()) } - - private inline fun getOrInitId(uuid: UUID, name: String?) = uuid.toByteArray().let { binaryUuid -> - getOrInitId( - { getId(binaryUuid) }, - { it[this@ProfilesT.uuid] = binaryUuid; it[this@ProfilesT.name] = name }, - { "profile(uuid = $uuid, name = $name)" }) - } - - private inline fun getOrInitId(name: String) = getOrInitId( - { getId(name) }, - { it[ProfilesT.name] = name }, - { "owner(name = $name)" }) - - - override fun getId(profile: PlayerProfile): Int? = when (profile) { - is PlayerProfile.Real -> getId(profile.uuid) - is PlayerProfile.Fake -> getId(profile.name) - is PlayerProfile.Unresolved -> getRealId(profile.name) - else -> throw IllegalArgumentException() - } - - override fun getOrInitId(profile: PlayerProfile): Int = when (profile) { - is PlayerProfile.Real -> getOrInitId(profile.uuid, profile.nameOrBukkitName) - is PlayerProfile.Fake -> getOrInitId(profile.name) - else -> throw IllegalArgumentException() // Unresolved profiles cannot be added to the database - } - - override fun getItem(row: ResultRow): PlayerProfile { - return PlayerProfile(row[uuid]?.toUUID(), row[name]) - } - - fun getRealItem(id: Int): PlayerProfile.Real? { - return getItem(id) as? PlayerProfile.Real - } - - /* - fun updatePlayerProfile(profile: PlayerProfile.Real) { - update({ uuid eq profile.uuid.toByteArray() }) { - it[name] = profile.nameOrBukkitName - } - }*/ - -} - +@file:Suppress("NOTHING_TO_INLINE", "PARAMETER_NAME_CHANGED_ON_OVERRIDE", "unused", "MemberVisibilityCanBePrivate")
+
+package io.dico.parcels2.storage.exposed
+
+import io.dico.parcels2.ParcelId
+import io.dico.parcels2.ParcelWorldId
+import io.dico.parcels2.PlayerProfile
+import io.dico.parcels2.storage.toByteArray
+import io.dico.parcels2.storage.toUUID
+import org.jetbrains.exposed.sql.*
+import org.jetbrains.exposed.sql.statements.UpdateBuilder
+import org.joda.time.DateTime
+import java.util.UUID
+
+abstract class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj>, QueryObj>(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<Boolean>): Int? {
+ return select { where(table) }.firstOrNull()?.let { it[id] }
+ }
+
+ internal inline fun getOrInitId(getId: () -> Int?, noinline body: TableT.(UpdateBuilder<*>) -> Unit, objName: () -> String): Int {
+ return getId() ?: table.insertIgnore(body)[id] ?: getId()
+ ?: throw ExposedDatabaseException("This should not happen - failed to insert ${objName()} and get its number")
+ }
+
+ abstract fun getId(obj: QueryObj): Int?
+ abstract fun getOrInitId(obj: QueryObj): Int
+ fun getItem(id: Int): QueryObj? = select { this@IdTransactionsTable.id eq id }.firstOrNull()?.let { getItem(it) }
+ abstract fun getItem(row: ResultRow): QueryObj?
+
+ fun getId(obj: QueryObj, init: Boolean): Int? = if (init) getOrInitId(obj) else getId(obj)
+}
+
+object WorldsT : IdTransactionsTable<WorldsT, ParcelWorldId>("parcels_worlds", "world_id") {
+ val name = varchar("name", 50)
+ val uid = binary("uid", 16).nullable()
+ val creation_time = datetime("creation_time").nullable()
+ val index_name = uniqueIndexR("index_name", name)
+ val index_uid = uniqueIndexR("index_uid", uid)
+
+ 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 ->
+ return getOrInitId(
+ { getId(worldName, binaryUid) },
+ { it[name] = worldName; it[uid] = binaryUid },
+ { "world named $worldName" })
+ }
+
+ override fun getId(world: ParcelWorldId): Int? = getId(world.name, world.uid)
+ override fun getOrInitId(world: ParcelWorldId): Int = getOrInitId(world.name, world.uid)
+
+ override fun getItem(row: ResultRow): ParcelWorldId {
+ return ParcelWorldId(row[name], row[uid]?.toUUID())
+ }
+
+ fun getWorldCreationTime(worldId: ParcelWorldId): DateTime? {
+ val id = getId(worldId) ?: return null
+ return select { WorldsT.id eq id }.firstOrNull()?.let { it[WorldsT.creation_time] }
+ }
+
+ fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime) {
+ val id = getOrInitId(worldId)
+ update({ WorldsT.id eq id }) {
+ it[WorldsT.creation_time] = time
+ }
+ }
+}
+
+object ParcelsT : IdTransactionsTable<ParcelsT, ParcelId>("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(ProfilesT.id).nullable()
+ val sign_oudated = bool("sign_outdated").default(false)
+ 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(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 getOrInitId(
+ { getId(worldId, parcelX, parcelZ) },
+ { it[world_id] = worldId; it[px] = parcelX; it[pz] = parcelZ },
+ { "parcel at $worldName($parcelX, $parcelZ)" })
+ }
+
+ 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: ParcelId): ResultRow? = getId(parcel)?.let { getRow(it) }
+
+ override fun getItem(row: ResultRow): ParcelId? {
+ val worldId = row[world_id]
+ val world = WorldsT.getItem(worldId) ?: return null
+ return ParcelId(world, row[px], row[pz])
+ }
+}
+
+object ProfilesT : IdTransactionsTable<ProfilesT, PlayerProfile>("parcels_profiles", "owner_id") {
+ val uuid = binary("uuid", 16).nullable()
+ val name = varchar("name", 32).nullable()
+
+ // MySQL dialect MUST permit multiple null values for this to work. Server SQL does not allow this. That dialect is shit anyway.
+ val uuid_constraint = uniqueIndexR("uuid_constraint", uuid)
+ 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(nameIn: String) = getId { uuid.isNull() and (name.lowerCase() eq nameIn.toLowerCase()) }
+ private inline fun getRealId(nameIn: String) = getId { uuid.isNotNull() and (name.lowerCase() eq nameIn.toLowerCase()) }
+
+ private inline fun getOrInitId(uuid: UUID, name: String?) = uuid.toByteArray().let { binaryUuid ->
+ getOrInitId(
+ { getId(binaryUuid) },
+ { it[this@ProfilesT.uuid] = binaryUuid; it[this@ProfilesT.name] = name },
+ { "profile(uuid = $uuid, name = $name)" })
+ }
+
+ private inline fun getOrInitId(name: String) = getOrInitId(
+ { getId(name) },
+ { it[ProfilesT.name] = name },
+ { "owner(name = $name)" })
+
+
+ override fun getId(profile: PlayerProfile): Int? = when (profile) {
+ is PlayerProfile.Real -> getId(profile.uuid)
+ is PlayerProfile.Fake -> getId(profile.name)
+ is PlayerProfile.Unresolved -> getRealId(profile.name)
+ else -> throw IllegalArgumentException()
+ }
+
+ override fun getOrInitId(profile: PlayerProfile): Int = when (profile) {
+ is PlayerProfile.Real -> getOrInitId(profile.uuid, profile.nameOrBukkitName)
+ is PlayerProfile.Fake -> getOrInitId(profile.name)
+ else -> throw IllegalArgumentException() // Unresolved profiles cannot be added to the database
+ }
+
+ override fun getItem(row: ResultRow): PlayerProfile {
+ return PlayerProfile(row[uuid]?.toUUID(), row[name])
+ }
+
+ fun getRealItem(id: Int): PlayerProfile.Real? {
+ return getItem(id) as? PlayerProfile.Real
+ }
+
+ /*
+ fun updatePlayerProfile(profile: PlayerProfile.Real) {
+ update({ uuid eq profile.uuid.toByteArray() }) {
+ it[name] = profile.nameOrBukkitName
+ }
+ }*/
+
+}
+
// val ParcelsWithOptionsT = ParcelsT.join(ParcelOptionsT, JoinType.INNER, onColumn = ParcelsT.id, otherColumn = ParcelOptionsT.parcel_id)
\ No newline at end of file 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 b9d16fc..26cfc7a 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt @@ -1,111 +1,111 @@ -@file:Suppress("PropertyName", "LocalVariableName", "NOTHING_TO_INLINE") - -package io.dico.parcels2.storage.exposed - -import io.dico.parcels2.* -import io.dico.parcels2.Privilege.DEFAULT -import io.dico.parcels2.util.ext.alsoIfTrue -import kotlinx.coroutines.channels.SendChannel -import org.jetbrains.exposed.sql.* - -object PrivilegesLocalT : PrivilegesTable<ParcelId>("parcels_privilege_local", ParcelsT) -object PrivilegesGlobalT : PrivilegesTable<PlayerProfile>("parcels_privilege_global", ProfilesT) - -object ParcelOptionsT : Table("parcels_options") { - val parcel_id = integer("parcel_id").primaryKey().references(ParcelsT.id, ReferenceOption.CASCADE) - val interact_bitmask = binary("interact_bitmask", 4) -} - -typealias PrivilegesSendChannel<AttachT> = SendChannel<Pair<AttachT, PrivilegesHolder>> - -sealed class PrivilegesTable<AttachT>(name: String, val idTable: IdTransactionsTable<*, AttachT>) : Table(name) { - val attach_id = integer("attach_id").references(idTable.id, ReferenceOption.CASCADE) - val profile_id = integer("profile_id").references(ProfilesT.id, ReferenceOption.CASCADE) - val privilege = integer("privilege") - val index_pair = uniqueIndexR("index_pair", attach_id, profile_id) - - fun setPrivilege(attachedOn: AttachT, player: PlayerProfile.Real, privilege: Privilege) { - privilege.requireNonTransient() - - if (privilege == DEFAULT) { - val player_id = ProfilesT.getId(player) ?: return - idTable.getId(attachedOn)?.let { holder -> - deleteWhere { (attach_id eq holder) and (profile_id eq player_id) } - } - return - } - - val holder = idTable.getOrInitId(attachedOn) - val player_id = ProfilesT.getOrInitId(player) - upsert(conflictIndex = index_pair) { - it[attach_id] = holder - it[profile_id] = player_id - it[this.privilege] = privilege.number - } - } - - fun readPrivileges(id: Int): PrivilegesHolder? { - val list = slice(profile_id, privilege).select { attach_id eq id } - val result = PrivilegesHolder() - for (row in list) { - val profile = ProfilesT.getRealItem(row[profile_id]) ?: continue - result.setRawStoredPrivilege(profile, Privilege.getByNumber(row[privilege]) ?: continue) - } - return result - } - - fun sendAllPrivilegesH(channel: PrivilegesSendChannel<AttachT>) { - val iterator = selectAll().orderBy(attach_id).iterator() - - if (iterator.hasNext()) { - var row = iterator.next() - var id: Int = row[attach_id] - var attach: AttachT? = null - var map: PrivilegesHolder? = null - - fun initAttachAndMap() { - attach = idTable.getItem(id) - map = attach?.let { PrivilegesHolder() } - } - - fun sendIfPresent() { - if (attach != null && map != null) { - channel.offer(attach!! to map!!) - } - attach = null - map = null - } - - initAttachAndMap() - - do { - val rowId = row[attach_id] - if (rowId != id) { - sendIfPresent() - id = rowId - initAttachAndMap() - } - - if (attach == null) { - continue // owner not found for this owner id - } - - val profile = ProfilesT.getRealItem(row[profile_id]) - if (profile == null) { - logger.error("Privilege from database is null, id ${row[profile_id]}") - continue - } - val privilege = Privilege.getByNumber(row[privilege]) - if (privilege == null) { - logger.error("Privilege from database is null, number ${row[this.privilege]}") - continue - } - map!!.setRawStoredPrivilege(profile, privilege) - - } while (iterator.hasNext().alsoIfTrue { row = iterator.next() }) - - sendIfPresent() - } - } - -} +@file:Suppress("PropertyName", "LocalVariableName", "NOTHING_TO_INLINE")
+
+package io.dico.parcels2.storage.exposed
+
+import io.dico.parcels2.*
+import io.dico.parcels2.Privilege.DEFAULT
+import io.dico.parcels2.util.ext.alsoIfTrue
+import kotlinx.coroutines.channels.SendChannel
+import org.jetbrains.exposed.sql.*
+
+object PrivilegesLocalT : PrivilegesTable<ParcelId>("parcels_privilege_local", ParcelsT)
+object PrivilegesGlobalT : PrivilegesTable<PlayerProfile>("parcels_privilege_global", ProfilesT)
+
+object ParcelOptionsT : Table("parcels_options") {
+ val parcel_id = integer("parcel_id").primaryKey().references(ParcelsT.id, ReferenceOption.CASCADE)
+ val interact_bitmask = binary("interact_bitmask", 4)
+}
+
+typealias PrivilegesSendChannel<AttachT> = SendChannel<Pair<AttachT, PrivilegesHolder>>
+
+sealed class PrivilegesTable<AttachT>(name: String, val idTable: IdTransactionsTable<*, AttachT>) : Table(name) {
+ val attach_id = integer("attach_id").references(idTable.id, ReferenceOption.CASCADE)
+ val profile_id = integer("profile_id").references(ProfilesT.id, ReferenceOption.CASCADE)
+ val privilege = integer("privilege")
+ val index_pair = uniqueIndexR("index_pair", attach_id, profile_id)
+
+ fun setPrivilege(attachedOn: AttachT, player: PlayerProfile.Real, privilege: Privilege) {
+ privilege.requireNonTransient()
+
+ if (privilege == DEFAULT) {
+ val player_id = ProfilesT.getId(player) ?: return
+ idTable.getId(attachedOn)?.let { holder ->
+ deleteWhere { (attach_id eq holder) and (profile_id eq player_id) }
+ }
+ return
+ }
+
+ val holder = idTable.getOrInitId(attachedOn)
+ val player_id = ProfilesT.getOrInitId(player)
+ upsert(conflictIndex = index_pair) {
+ it[attach_id] = holder
+ it[profile_id] = player_id
+ it[this.privilege] = privilege.number
+ }
+ }
+
+ fun readPrivileges(id: Int): PrivilegesHolder? {
+ val list = slice(profile_id, privilege).select { attach_id eq id }
+ val result = PrivilegesHolder()
+ for (row in list) {
+ val profile = ProfilesT.getRealItem(row[profile_id]) ?: continue
+ result.setRawStoredPrivilege(profile, Privilege.getByNumber(row[privilege]) ?: continue)
+ }
+ return result
+ }
+
+ fun sendAllPrivilegesH(channel: PrivilegesSendChannel<AttachT>) {
+ val iterator = selectAll().orderBy(attach_id).iterator()
+
+ if (iterator.hasNext()) {
+ var row = iterator.next()
+ var id: Int = row[attach_id]
+ var attach: AttachT? = null
+ var map: PrivilegesHolder? = null
+
+ fun initAttachAndMap() {
+ attach = idTable.getItem(id)
+ map = attach?.let { PrivilegesHolder() }
+ }
+
+ fun sendIfPresent() {
+ if (attach != null && map != null) {
+ channel.offer(attach!! to map!!)
+ }
+ attach = null
+ map = null
+ }
+
+ initAttachAndMap()
+
+ do {
+ val rowId = row[attach_id]
+ if (rowId != id) {
+ sendIfPresent()
+ id = rowId
+ initAttachAndMap()
+ }
+
+ if (attach == null) {
+ continue // owner not found for this owner id
+ }
+
+ val profile = ProfilesT.getRealItem(row[profile_id])
+ if (profile == null) {
+ logger.error("Privilege from database is null, id ${row[profile_id]}")
+ continue
+ }
+ val privilege = Privilege.getByNumber(row[privilege])
+ if (privilege == null) {
+ logger.error("Privilege from database is null, number ${row[this.privilege]}")
+ continue
+ }
+ map!!.setRawStoredPrivilege(profile, privilege)
+
+ } while (iterator.hasNext().alsoIfTrue { row = iterator.next() })
+
+ sendIfPresent()
+ }
+ }
+
+}
diff --git a/src/main/kotlin/io/dico/parcels2/storage/migration/Migration.kt b/src/main/kotlin/io/dico/parcels2/storage/migration/Migration.kt index acc7c5e..a512f2a 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/migration/Migration.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/migration/Migration.kt @@ -1,9 +1,9 @@ -package io.dico.parcels2.storage.migration - -import io.dico.parcels2.storage.Storage -import kotlinx.coroutines.Job - -interface Migration { - fun migrateTo(storage: Storage): Job -} - +package io.dico.parcels2.storage.migration
+
+import io.dico.parcels2.storage.Storage
+import kotlinx.coroutines.Job
+
+interface Migration {
+ fun migrateTo(storage: Storage): Job
+}
+
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 index 831fe42..954da5d 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt @@ -1,118 +1,118 @@ -@file:Suppress("RedundantSuspendModifier", "DEPRECATION") - -package io.dico.parcels2.storage.migration.plotme - -import com.zaxxer.hikari.HikariDataSource -import io.dico.parcels2.* -import io.dico.parcels2.options.PlotmeMigrationOptions -import io.dico.parcels2.storage.Storage -import io.dico.parcels2.storage.exposed.abs -import io.dico.parcels2.storage.exposed.greaterOf -import io.dico.parcels2.storage.migration.Migration -import io.dico.parcels2.storage.migration.plotme.PlotmeTables.PlotmePlotPlayerMap -import io.dico.parcels2.storage.migration.plotme.PlotmeTables.PlotmeTable -import io.dico.parcels2.storage.toUUID -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import kotlinx.coroutines.newFixedThreadPoolContext -import org.jetbrains.exposed.sql.* -import org.slf4j.LoggerFactory -import java.sql.Blob -import java.util.UUID -import javax.sql.DataSource - -class PlotmeMigration(val options: PlotmeMigrationOptions) : Migration { - private var dataSource: DataSource? = null - private var database: Database? = null - private var isShutdown: Boolean = false - private val mlogger = LoggerFactory.getLogger("PlotMe Migrator") - private val tables = PlotmeTables(options.tableNamesUppercase) - val dispatcher = newFixedThreadPoolContext(1, "PlotMe Migration Thread") - - private fun <T> transaction(statement: Transaction.() -> T) = org.jetbrains.exposed.sql.transactions.transaction(database!!, statement) - - override fun migrateTo(storage: Storage): Job { - return launch(dispatcher) { - init() - doWork(storage) - shutdown() - } - } - - fun init() { - if (isShutdown || database != null) throw IllegalStateException() - dataSource = options.storage.getDataSourceFactory()!!() - database = Database.connect(dataSource!!) - } - - fun shutdown() { - if (isShutdown) throw IllegalStateException() - dataSource?.let { - (it as? HikariDataSource)?.close() - } - database = null - isShutdown = true - } - - suspend fun doWork(target: Storage) = with (tables) { - val exit = transaction { - (!PlotmePlots.exists()).also { - if (it) mlogger.warn("Plotme tables don't appear to exist. Exiting.") - } - } - if (exit) return - - val worldCache = options.worldsFromTo.mapValues { ParcelWorldId(it.value) } - - fun getParcelId(table: PlotmeTable, row: ResultRow): ParcelId? { - val world = worldCache[row[table.world_name]] ?: return null - return ParcelId(world, row[table.px], row[table.pz]) - } - - fun PlotmePlotPlayerMap.transmitPlotmeAddedTable(kind: Privilege) { - selectAll().forEach { row -> - val parcel = getParcelId(this, row) ?: return@forEach - val profile = PrivilegeKey.safe(row[player_uuid]?.toUUID(), row[player_name]) ?: return@forEach - target.setLocalPrivilege(parcel, profile, kind) - } - } - - mlogger.info("Transmitting data from plotmeplots table") - var count = 0 - transaction { - - PlotmePlots.selectAll() - .orderBy(PlotmePlots.world_name) - .orderBy(greaterOf(PlotmePlots.px.abs(), PlotmePlots.pz.abs())) - .forEach { row -> - val parcel = getParcelId(PlotmePlots, row) ?: return@forEach - val owner = PlayerProfile.safe(row[PlotmePlots.owner_uuid]?.toUUID(), row[PlotmePlots.owner_name]) - target.setParcelOwner(parcel, owner) - target.setParcelOwnerSignOutdated(parcel, true) - ++count - } - } - - mlogger.info("Transmitting data from plotmeallowed table") - transaction { - PlotmeAllowed.transmitPlotmeAddedTable(Privilege.CAN_BUILD) - } - - mlogger.info("Transmitting data from plotmedenied table") - transaction { - PlotmeDenied.transmitPlotmeAddedTable(Privilege.BANNED) - } - - mlogger.warn("Data has been **transmitted**. $count plots were migrated to the parcels database.") - mlogger.warn("Loading parcel data might take a while as enqueued transactions from this migration are completed.") - } - - private fun Blob.toUUID(): UUID? { - val ba = ByteArray(16) - val count = binaryStream.read(ba, 0, 16) - if (count < 16) return null - return ba.toUUID() - } - - +@file:Suppress("RedundantSuspendModifier", "DEPRECATION")
+
+package io.dico.parcels2.storage.migration.plotme
+
+import com.zaxxer.hikari.HikariDataSource
+import io.dico.parcels2.*
+import io.dico.parcels2.options.PlotmeMigrationOptions
+import io.dico.parcels2.storage.Storage
+import io.dico.parcels2.storage.exposed.abs
+import io.dico.parcels2.storage.exposed.greaterOf
+import io.dico.parcels2.storage.migration.Migration
+import io.dico.parcels2.storage.migration.plotme.PlotmeTables.PlotmePlotPlayerMap
+import io.dico.parcels2.storage.migration.plotme.PlotmeTables.PlotmeTable
+import io.dico.parcels2.storage.toUUID
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.newFixedThreadPoolContext
+import org.jetbrains.exposed.sql.*
+import org.slf4j.LoggerFactory
+import java.sql.Blob
+import java.util.UUID
+import javax.sql.DataSource
+
+class PlotmeMigration(val options: PlotmeMigrationOptions) : Migration {
+ private var dataSource: DataSource? = null
+ private var database: Database? = null
+ private var isShutdown: Boolean = false
+ private val mlogger = LoggerFactory.getLogger("PlotMe Migrator")
+ private val tables = PlotmeTables(options.tableNamesUppercase)
+ val dispatcher = newFixedThreadPoolContext(1, "PlotMe Migration Thread")
+
+ private fun <T> transaction(statement: Transaction.() -> T) = org.jetbrains.exposed.sql.transactions.transaction(database!!, statement)
+
+ override fun migrateTo(storage: Storage): Job {
+ return launch(dispatcher) {
+ init()
+ doWork(storage)
+ shutdown()
+ }
+ }
+
+ fun init() {
+ if (isShutdown || database != null) throw IllegalStateException()
+ dataSource = options.storage.getDataSourceFactory()!!()
+ database = Database.connect(dataSource!!)
+ }
+
+ fun shutdown() {
+ if (isShutdown) throw IllegalStateException()
+ dataSource?.let {
+ (it as? HikariDataSource)?.close()
+ }
+ database = null
+ isShutdown = true
+ }
+
+ suspend fun doWork(target: Storage) = with (tables) {
+ val exit = transaction {
+ (!PlotmePlots.exists()).also {
+ if (it) mlogger.warn("Plotme tables don't appear to exist. Exiting.")
+ }
+ }
+ if (exit) return
+
+ val worldCache = options.worldsFromTo.mapValues { ParcelWorldId(it.value) }
+
+ fun getParcelId(table: PlotmeTable, row: ResultRow): ParcelId? {
+ val world = worldCache[row[table.world_name]] ?: return null
+ return ParcelId(world, row[table.px], row[table.pz])
+ }
+
+ fun PlotmePlotPlayerMap.transmitPlotmeAddedTable(kind: Privilege) {
+ selectAll().forEach { row ->
+ val parcel = getParcelId(this, row) ?: return@forEach
+ val profile = PrivilegeKey.safe(row[player_uuid]?.toUUID(), row[player_name]) ?: return@forEach
+ target.setLocalPrivilege(parcel, profile, kind)
+ }
+ }
+
+ mlogger.info("Transmitting data from plotmeplots table")
+ var count = 0
+ transaction {
+
+ PlotmePlots.selectAll()
+ .orderBy(PlotmePlots.world_name)
+ .orderBy(greaterOf(PlotmePlots.px.abs(), PlotmePlots.pz.abs()))
+ .forEach { row ->
+ val parcel = getParcelId(PlotmePlots, row) ?: return@forEach
+ val owner = PlayerProfile.safe(row[PlotmePlots.owner_uuid]?.toUUID(), row[PlotmePlots.owner_name])
+ target.setParcelOwner(parcel, owner)
+ target.setParcelOwnerSignOutdated(parcel, true)
+ ++count
+ }
+ }
+
+ mlogger.info("Transmitting data from plotmeallowed table")
+ transaction {
+ PlotmeAllowed.transmitPlotmeAddedTable(Privilege.CAN_BUILD)
+ }
+
+ mlogger.info("Transmitting data from plotmedenied table")
+ transaction {
+ PlotmeDenied.transmitPlotmeAddedTable(Privilege.BANNED)
+ }
+
+ mlogger.warn("Data has been **transmitted**. $count plots were migrated to the parcels database.")
+ mlogger.warn("Loading parcel data might take a while as enqueued transactions from this migration are completed.")
+ }
+
+ private fun Blob.toUUID(): UUID? {
+ val ba = ByteArray(16)
+ val count = binaryStream.read(ba, 0, 16)
+ if (count < 16) return null
+ return ba.toUUID()
+ }
+
+
}
\ 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 index dc788c8..b276e11 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeTables.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeTables.kt @@ -1,31 +1,31 @@ -package io.dico.parcels2.storage.migration.plotme - -import org.jetbrains.exposed.sql.Table - -class PlotmeTables(val uppercase: Boolean) { - fun String.toCorrectCase() = if (uppercase) this else toLowerCase() - - val PlotmePlots = PlotmePlotsT() - val PlotmeAllowed = PlotmeAllowedT() - val PlotmeDenied = PlotmeDeniedT() - - inner abstract class PlotmeTable(name: String) : Table(name) { - val px = integer("idX").primaryKey() - val pz = integer("idZ").primaryKey() - val world_name = varchar("world", 32).primaryKey() - } - - inner abstract class PlotmePlotPlayerMap(name: String) : PlotmeTable(name) { - val player_name = varchar("player", 32) - val player_uuid = blob("playerid").nullable() - } - - inner class PlotmePlotsT : PlotmeTable("plotmePlots".toCorrectCase()) { - val owner_name = varchar("owner", 32) - val owner_uuid = blob("ownerid").nullable() - } - - inner class PlotmeAllowedT : PlotmePlotPlayerMap("plotmeAllowed".toCorrectCase()) - inner class PlotmeDeniedT : PlotmePlotPlayerMap("plotmeDenied".toCorrectCase()) -} - +package io.dico.parcels2.storage.migration.plotme
+
+import org.jetbrains.exposed.sql.Table
+
+class PlotmeTables(val uppercase: Boolean) {
+ fun String.toCorrectCase() = if (uppercase) this else toLowerCase()
+
+ val PlotmePlots = PlotmePlotsT()
+ val PlotmeAllowed = PlotmeAllowedT()
+ val PlotmeDenied = PlotmeDeniedT()
+
+ inner abstract class PlotmeTable(name: String) : Table(name) {
+ val px = integer("idX").primaryKey()
+ val pz = integer("idZ").primaryKey()
+ val world_name = varchar("world", 32).primaryKey()
+ }
+
+ inner abstract class PlotmePlotPlayerMap(name: String) : PlotmeTable(name) {
+ val player_name = varchar("player", 32)
+ val player_uuid = blob("playerid").nullable()
+ }
+
+ inner class PlotmePlotsT : PlotmeTable("plotmePlots".toCorrectCase()) {
+ val owner_name = varchar("owner", 32)
+ val owner_uuid = blob("ownerid").nullable()
+ }
+
+ inner class PlotmeAllowedT : PlotmePlotPlayerMap("plotmeAllowed".toCorrectCase())
+ inner class PlotmeDeniedT : PlotmePlotPlayerMap("plotmeDenied".toCorrectCase())
+}
+
diff --git a/src/main/kotlin/io/dico/parcels2/util/BukkitUtil.kt b/src/main/kotlin/io/dico/parcels2/util/BukkitUtil.kt index 618eaed..a4a6da9 100644 --- a/src/main/kotlin/io/dico/parcels2/util/BukkitUtil.kt +++ b/src/main/kotlin/io/dico/parcels2/util/BukkitUtil.kt @@ -1,14 +1,14 @@ -package io.dico.parcels2.util - -import io.dico.parcels2.util.ext.isValid -import org.bukkit.Bukkit -import org.bukkit.OfflinePlayer -import java.util.UUID - -fun getPlayerName(uuid: UUID): String? = getOfflinePlayer(uuid)?.name - -fun getOfflinePlayer(uuid: UUID): OfflinePlayer? = Bukkit.getOfflinePlayer(uuid).takeIf { it.isValid } - -fun getOfflinePlayer(name: String): OfflinePlayer? = Bukkit.getOfflinePlayer(name).takeIf { it.isValid } - -fun isServerThread(): Boolean = Thread.currentThread().name == "Server thread" +package io.dico.parcels2.util
+
+import io.dico.parcels2.util.ext.isValid
+import org.bukkit.Bukkit
+import org.bukkit.OfflinePlayer
+import java.util.UUID
+
+fun getPlayerName(uuid: UUID): String? = getOfflinePlayer(uuid)?.name
+
+fun getOfflinePlayer(uuid: UUID): OfflinePlayer? = Bukkit.getOfflinePlayer(uuid).takeIf { it.isValid }
+
+fun getOfflinePlayer(name: String): OfflinePlayer? = Bukkit.getOfflinePlayer(name).takeIf { it.isValid }
+
+fun isServerThread(): Boolean = Thread.currentThread().name == "Server thread"
diff --git a/src/main/kotlin/io/dico/parcels2/util/MainThreadDispatcher.kt b/src/main/kotlin/io/dico/parcels2/util/MainThreadDispatcher.kt index 3eb2e81..3904026 100644 --- a/src/main/kotlin/io/dico/parcels2/util/MainThreadDispatcher.kt +++ b/src/main/kotlin/io/dico/parcels2/util/MainThreadDispatcher.kt @@ -1,43 +1,43 @@ -package io.dico.parcels2.util - -import kotlinx.coroutines.CancellableContinuation -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Delay -import kotlinx.coroutines.Runnable -import kotlinx.coroutines.timeunit.TimeUnit -import org.bukkit.plugin.Plugin -import kotlin.coroutines.CoroutineContext - -abstract class MainThreadDispatcher : CoroutineDispatcher(), Delay { - abstract val mainThread: Thread - abstract fun runOnMainThread(task: Runnable) -} - -@Suppress("FunctionName") -fun MainThreadDispatcher(plugin: Plugin): MainThreadDispatcher { - return object : MainThreadDispatcher() { - override val mainThread: Thread = Thread.currentThread() - - override fun dispatch(context: CoroutineContext, block: Runnable) { - doDispatch(block) - } - - override fun runOnMainThread(task: Runnable) { - doDispatch(task) - } - - private fun doDispatch(task: Runnable) { - if (Thread.currentThread() === mainThread) task.run() - else plugin.server.scheduler.runTaskLater(plugin, task, 0) - } - - override fun scheduleResumeAfterDelay(time: Long, unit: TimeUnit, continuation: CancellableContinuation<Unit>) { - val task = Runnable { - with (continuation) { resumeUndispatched(Unit) } - } - - val millis = unit.toMillis(time) - plugin.server.scheduler.runTaskLater(plugin, task, (millis + 25) / 50 - 1) - } - } +package io.dico.parcels2.util
+
+import kotlinx.coroutines.CancellableContinuation
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Delay
+import kotlinx.coroutines.Runnable
+import kotlinx.coroutines.timeunit.TimeUnit
+import org.bukkit.plugin.Plugin
+import kotlin.coroutines.CoroutineContext
+
+abstract class MainThreadDispatcher : CoroutineDispatcher(), Delay {
+ abstract val mainThread: Thread
+ abstract fun runOnMainThread(task: Runnable)
+}
+
+@Suppress("FunctionName")
+fun MainThreadDispatcher(plugin: Plugin): MainThreadDispatcher {
+ return object : MainThreadDispatcher() {
+ override val mainThread: Thread = Thread.currentThread()
+
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ doDispatch(block)
+ }
+
+ override fun runOnMainThread(task: Runnable) {
+ doDispatch(task)
+ }
+
+ private fun doDispatch(task: Runnable) {
+ if (Thread.currentThread() === mainThread) task.run()
+ else plugin.server.scheduler.runTaskLater(plugin, task, 0)
+ }
+
+ override fun scheduleResumeAfterDelay(time: Long, unit: TimeUnit, continuation: CancellableContinuation<Unit>) {
+ val task = Runnable {
+ with (continuation) { resumeUndispatched(Unit) }
+ }
+
+ val millis = unit.toMillis(time)
+ plugin.server.scheduler.runTaskLater(plugin, task, (millis + 25) / 50 - 1)
+ }
+ }
}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/util/PluginScheduler.kt b/src/main/kotlin/io/dico/parcels2/util/PluginScheduler.kt index f29ba2b..268a083 100644 --- a/src/main/kotlin/io/dico/parcels2/util/PluginScheduler.kt +++ b/src/main/kotlin/io/dico/parcels2/util/PluginScheduler.kt @@ -1,20 +1,20 @@ -package io.dico.parcels2.util - -import org.bukkit.plugin.Plugin -import org.bukkit.scheduler.BukkitTask - -interface PluginScheduler { - val plugin: Plugin - - 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()) - } -} - -@Suppress("NOTHING_TO_INLINE") -inline fun PluginScheduler.schedule(noinline task: () -> Unit) = schedule(0, task) - +package io.dico.parcels2.util
+
+import org.bukkit.plugin.Plugin
+import org.bukkit.scheduler.BukkitTask
+
+interface PluginScheduler {
+ val plugin: Plugin
+
+ 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())
+ }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline fun PluginScheduler.schedule(noinline task: () -> Unit) = schedule(0, task)
+
diff --git a/src/main/kotlin/io/dico/parcels2/util/ext/Material.kt b/src/main/kotlin/io/dico/parcels2/util/ext/Material.kt index e160e55..1351b5d 100644 --- a/src/main/kotlin/io/dico/parcels2/util/ext/Material.kt +++ b/src/main/kotlin/io/dico/parcels2/util/ext/Material.kt @@ -1,108 +1,108 @@ -package io.dico.parcels2.util.ext - -import org.bukkit.Material -import org.bukkit.Material.* - -/* -colors: -WHITE_$, ORANGE_$, MAGENTA_$, LIGHT_BLUE_$, YELLOW_$, LIME_$, PINK_$, GRAY_$, LIGHT_GRAY_$, CYAN_$, PURPLE_$, BLUE_$, BROWN_$, GREEN_$, RED_$, BLACK_$, -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.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.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 - } - -private fun getMaterialPrefixed(prefix: String, name: String): Material { - return Material.getMaterial("${prefix}_$name") ?: throw IllegalArgumentException("Material ${prefix}_$name doesn't exist") -} - -fun getMaterialsWithWoodTypePrefix(name: String) = arrayOf( - getMaterialPrefixed("OAK", name), - getMaterialPrefixed("BIRCH", name), - getMaterialPrefixed("SPRUCE", name), - getMaterialPrefixed("JUNGLE", name), - getMaterialPrefixed("ACACIA", name), - getMaterialPrefixed("DARK_OAK", name) -) - -fun getMaterialsWithWoolColorPrefix(name: String) = arrayOf( - getMaterialPrefixed("WHITE", name), - getMaterialPrefixed("ORANGE", name), - getMaterialPrefixed("MAGENTA", name), - getMaterialPrefixed("LIGHT_BLUE", name), - getMaterialPrefixed("YELLOW", name), - getMaterialPrefixed("LIME", name), - getMaterialPrefixed("PINK", name), - getMaterialPrefixed("GRAY", name), - getMaterialPrefixed("LIGHT_GRAY", name), - getMaterialPrefixed("CYAN", name), - getMaterialPrefixed("PURPLE", name), - getMaterialPrefixed("BLUE", name), - getMaterialPrefixed("BROWN", name), - getMaterialPrefixed("GREEN", name), - getMaterialPrefixed("RED", name), - getMaterialPrefixed("BLACK", name) +package io.dico.parcels2.util.ext
+
+import org.bukkit.Material
+import org.bukkit.Material.*
+
+/*
+colors:
+WHITE_$, ORANGE_$, MAGENTA_$, LIGHT_BLUE_$, YELLOW_$, LIME_$, PINK_$, GRAY_$, LIGHT_GRAY_$, CYAN_$, PURPLE_$, BLUE_$, BROWN_$, GREEN_$, RED_$, BLACK_$,
+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.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.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
+ }
+
+private fun getMaterialPrefixed(prefix: String, name: String): Material {
+ return Material.getMaterial("${prefix}_$name") ?: throw IllegalArgumentException("Material ${prefix}_$name doesn't exist")
+}
+
+fun getMaterialsWithWoodTypePrefix(name: String) = arrayOf(
+ getMaterialPrefixed("OAK", name),
+ getMaterialPrefixed("BIRCH", name),
+ getMaterialPrefixed("SPRUCE", name),
+ getMaterialPrefixed("JUNGLE", name),
+ getMaterialPrefixed("ACACIA", name),
+ getMaterialPrefixed("DARK_OAK", name)
+)
+
+fun getMaterialsWithWoolColorPrefix(name: String) = arrayOf(
+ getMaterialPrefixed("WHITE", name),
+ getMaterialPrefixed("ORANGE", name),
+ getMaterialPrefixed("MAGENTA", name),
+ getMaterialPrefixed("LIGHT_BLUE", name),
+ getMaterialPrefixed("YELLOW", name),
+ getMaterialPrefixed("LIME", name),
+ getMaterialPrefixed("PINK", name),
+ getMaterialPrefixed("GRAY", name),
+ getMaterialPrefixed("LIGHT_GRAY", name),
+ getMaterialPrefixed("CYAN", name),
+ getMaterialPrefixed("PURPLE", name),
+ getMaterialPrefixed("BLUE", name),
+ getMaterialPrefixed("BROWN", name),
+ getMaterialPrefixed("GREEN", name),
+ getMaterialPrefixed("RED", name),
+ getMaterialPrefixed("BLACK", name)
)
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/util/ext/Misc.kt b/src/main/kotlin/io/dico/parcels2/util/ext/Misc.kt index 75aba35..e5e8aa9 100644 --- a/src/main/kotlin/io/dico/parcels2/util/ext/Misc.kt +++ b/src/main/kotlin/io/dico/parcels2/util/ext/Misc.kt @@ -1,81 +1,81 @@ -package io.dico.parcels2.util.ext - -import io.dico.dicore.Formatting -import io.dico.parcels2.logger -import java.io.File - -fun File.tryCreate(): Boolean { - if (exists()) { - return !isDirectory - } - val parent = parentFile - if (parent == null || !(parent.exists() || parent.mkdirs()) || !createNewFile()) { - logger.warn("Failed to create file $canonicalPath") - return false - } - return true -} - -inline fun Boolean.alsoIfTrue(block: () -> Unit): Boolean = also { if (it) block() } -inline fun Boolean.alsoIfFalse(block: () -> Unit): Boolean = also { if (!it) block() } - -inline fun <R> Any.synchronized(block: () -> R): R = synchronized(this, block) - -//inline fun <T> T?.isNullOr(condition: T.() -> Boolean): Boolean = this == null || condition() -//inline fun <T> T?.isPresentAnd(condition: T.() -> Boolean): Boolean = this != null && condition() -inline fun <T> T?.ifNullRun(block: () -> Unit): T? { - if (this == null) block() - return this -} - -inline fun <T, U> MutableMap<T, U>.editLoop(block: EditLoopScope<T, U>.(T, U) -> Unit) { - return EditLoopScope(this).doEditLoop(block) -} - -inline fun <T, U> MutableMap<T, U>.editLoop(block: EditLoopScope<T, U>.() -> Unit) { - return EditLoopScope(this).doEditLoop(block) -} - -class EditLoopScope<T, U>(val _map: MutableMap<T, U>) { - private var iterator: MutableIterator<MutableMap.MutableEntry<T, U>>? = null - lateinit var _entry: MutableMap.MutableEntry<T, U> - - inline val key get() = _entry.key - inline var value - get() = _entry.value - set(target) = run { _entry.setValue(target) } - - inline fun doEditLoop(block: EditLoopScope<T, U>.() -> Unit) { - val it = _initIterator() - while (it.hasNext()) { - _entry = it.next() - block() - } - } - - inline fun doEditLoop(block: EditLoopScope<T, U>.(T, U) -> Unit) { - val it = _initIterator() - while (it.hasNext()) { - val entry = it.next().also { _entry = it } - block(entry.key, entry.value) - } - } - - fun remove() { - iterator!!.remove() - } - - fun _initIterator(): MutableIterator<MutableMap.MutableEntry<T, U>> { - iterator?.let { throw IllegalStateException() } - return _map.entries.iterator().also { iterator = it } - } - -} - -operator fun Formatting.plus(other: Formatting) = toString() + other -operator fun Formatting.plus(other: String) = toString() + other - -inline fun <T> Pair<T, T>.forEach(block: (T) -> Unit) { - block(first) - block(second) -} +package io.dico.parcels2.util.ext
+
+import io.dico.dicore.Formatting
+import io.dico.parcels2.logger
+import java.io.File
+
+fun File.tryCreate(): Boolean {
+ if (exists()) {
+ return !isDirectory
+ }
+ val parent = parentFile
+ if (parent == null || !(parent.exists() || parent.mkdirs()) || !createNewFile()) {
+ logger.warn("Failed to create file $canonicalPath")
+ return false
+ }
+ return true
+}
+
+inline fun Boolean.alsoIfTrue(block: () -> Unit): Boolean = also { if (it) block() }
+inline fun Boolean.alsoIfFalse(block: () -> Unit): Boolean = also { if (!it) block() }
+
+inline fun <R> Any.synchronized(block: () -> R): R = synchronized(this, block)
+
+//inline fun <T> T?.isNullOr(condition: T.() -> Boolean): Boolean = this == null || condition()
+//inline fun <T> T?.isPresentAnd(condition: T.() -> Boolean): Boolean = this != null && condition()
+inline fun <T> T?.ifNullRun(block: () -> Unit): T? {
+ if (this == null) block()
+ return this
+}
+
+inline fun <T, U> MutableMap<T, U>.editLoop(block: EditLoopScope<T, U>.(T, U) -> Unit) {
+ return EditLoopScope(this).doEditLoop(block)
+}
+
+inline fun <T, U> MutableMap<T, U>.editLoop(block: EditLoopScope<T, U>.() -> Unit) {
+ return EditLoopScope(this).doEditLoop(block)
+}
+
+class EditLoopScope<T, U>(val _map: MutableMap<T, U>) {
+ private var iterator: MutableIterator<MutableMap.MutableEntry<T, U>>? = null
+ lateinit var _entry: MutableMap.MutableEntry<T, U>
+
+ inline val key get() = _entry.key
+ inline var value
+ get() = _entry.value
+ set(target) = run { _entry.setValue(target) }
+
+ inline fun doEditLoop(block: EditLoopScope<T, U>.() -> Unit) {
+ val it = _initIterator()
+ while (it.hasNext()) {
+ _entry = it.next()
+ block()
+ }
+ }
+
+ inline fun doEditLoop(block: EditLoopScope<T, U>.(T, U) -> Unit) {
+ val it = _initIterator()
+ while (it.hasNext()) {
+ val entry = it.next().also { _entry = it }
+ block(entry.key, entry.value)
+ }
+ }
+
+ fun remove() {
+ iterator!!.remove()
+ }
+
+ fun _initIterator(): MutableIterator<MutableMap.MutableEntry<T, U>> {
+ iterator?.let { throw IllegalStateException() }
+ return _map.entries.iterator().also { iterator = it }
+ }
+
+}
+
+operator fun Formatting.plus(other: Formatting) = toString() + other
+operator fun Formatting.plus(other: String) = toString() + other
+
+inline fun <T> Pair<T, T>.forEach(block: (T) -> Unit) {
+ block(first)
+ block(second)
+}
diff --git a/src/main/kotlin/io/dico/parcels2/util/ext/Player.kt b/src/main/kotlin/io/dico/parcels2/util/ext/Player.kt index a7f21c5..4c502a0 100644 --- a/src/main/kotlin/io/dico/parcels2/util/ext/Player.kt +++ b/src/main/kotlin/io/dico/parcels2/util/ext/Player.kt @@ -1,57 +1,57 @@ -package io.dico.parcels2.util.ext - -import io.dico.dicore.Formatting -import io.dico.parcels2.ParcelsPlugin -import io.dico.parcels2.logger -import org.bukkit.OfflinePlayer -import org.bukkit.entity.Player -import org.bukkit.permissions.Permissible -import org.bukkit.plugin.java.JavaPlugin - -inline val OfflinePlayer.uuid get() = uniqueId - -@Suppress("UsePropertyAccessSyntax") -inline val OfflinePlayer.isValid - get() = isOnline() || hasPlayedBefore() - -const val PERM_BAN_BYPASS = "parcels.admin.bypass.ban" -const val PERM_BUILD_ANYWHERE = "parcels.admin.bypass.build" -const val PERM_ADMIN_MANAGE = "parcels.admin.manage" - -inline val Permissible.hasPermBanBypass get() = hasPermission(PERM_BAN_BYPASS) -inline val Permissible.hasPermGamemodeBypass get() = hasPermission("parcels.admin.bypass.gamemode") -inline val Permissible.hasPermBuildAnywhere get() = hasPermission(PERM_BUILD_ANYWHERE) -inline val Permissible.hasPermAdminManage get() = hasPermission(PERM_ADMIN_MANAGE) -inline val Permissible.hasParcelHomeOthers get() = hasPermission("parcels.command.home.others") -inline val Permissible.hasPermRandomSpecific get() = hasPermission("parcels.command.random.specific") -val Player.parcelLimit: Int - get() { - for (info in effectivePermissions) { - val perm = info.permission - if (perm.startsWith("parcels.limit.")) { - val limitString = perm.substring("parcels.limit.".length) - if (limitString == "*") { - return Int.MAX_VALUE - } - return limitString.toIntOrNull() ?: DEFAULT_LIMIT.also { - logger.warn("$name has permission '$perm'. The suffix can not be parsed to an integer (or *).") - } - } - } - return DEFAULT_LIMIT - } - -private const val DEFAULT_LIMIT = 1 -private val prefix = Formatting.translateChars('&', "&4[&c${JavaPlugin.getPlugin(ParcelsPlugin::class.java).name}&4] &a") - -fun Player.sendParcelMessage(except: Boolean = false, nopermit: Boolean = false, message: String) { - if (except) { - sendMessage(prefix + Formatting.YELLOW + Formatting.translateChars('&', message)) - } else if (nopermit) { - sendMessage(prefix + Formatting.RED + Formatting.translateChars('&', message)) - } else { - sendMessage(prefix + Formatting.translateChars('&', message)) - } -} - -const val PLAYER_NAME_PLACEHOLDER = ":unknown_name:" +package io.dico.parcels2.util.ext
+
+import io.dico.dicore.Formatting
+import io.dico.parcels2.ParcelsPlugin
+import io.dico.parcels2.logger
+import org.bukkit.OfflinePlayer
+import org.bukkit.entity.Player
+import org.bukkit.permissions.Permissible
+import org.bukkit.plugin.java.JavaPlugin
+
+inline val OfflinePlayer.uuid get() = uniqueId
+
+@Suppress("UsePropertyAccessSyntax")
+inline val OfflinePlayer.isValid
+ get() = isOnline() || hasPlayedBefore()
+
+const val PERM_BAN_BYPASS = "parcels.admin.bypass.ban"
+const val PERM_BUILD_ANYWHERE = "parcels.admin.bypass.build"
+const val PERM_ADMIN_MANAGE = "parcels.admin.manage"
+
+inline val Permissible.hasPermBanBypass get() = hasPermission(PERM_BAN_BYPASS)
+inline val Permissible.hasPermGamemodeBypass get() = hasPermission("parcels.admin.bypass.gamemode")
+inline val Permissible.hasPermBuildAnywhere get() = hasPermission(PERM_BUILD_ANYWHERE)
+inline val Permissible.hasPermAdminManage get() = hasPermission(PERM_ADMIN_MANAGE)
+inline val Permissible.hasParcelHomeOthers get() = hasPermission("parcels.command.home.others")
+inline val Permissible.hasPermRandomSpecific get() = hasPermission("parcels.command.random.specific")
+val Player.parcelLimit: Int
+ get() {
+ for (info in effectivePermissions) {
+ val perm = info.permission
+ if (perm.startsWith("parcels.limit.")) {
+ val limitString = perm.substring("parcels.limit.".length)
+ if (limitString == "*") {
+ return Int.MAX_VALUE
+ }
+ return limitString.toIntOrNull() ?: DEFAULT_LIMIT.also {
+ logger.warn("$name has permission '$perm'. The suffix can not be parsed to an integer (or *).")
+ }
+ }
+ }
+ return DEFAULT_LIMIT
+ }
+
+private const val DEFAULT_LIMIT = 1
+private val prefix = Formatting.translateChars('&', "&4[&c${JavaPlugin.getPlugin(ParcelsPlugin::class.java).name}&4] &a")
+
+fun Player.sendParcelMessage(except: Boolean = false, nopermit: Boolean = false, message: String) {
+ if (except) {
+ sendMessage(prefix + Formatting.YELLOW + Formatting.translateChars('&', message))
+ } else if (nopermit) {
+ sendMessage(prefix + Formatting.RED + Formatting.translateChars('&', message))
+ } else {
+ sendMessage(prefix + Formatting.translateChars('&', message))
+ }
+}
+
+const val PLAYER_NAME_PLACEHOLDER = ":unknown_name:"
diff --git a/src/main/kotlin/io/dico/parcels2/util/math/Dimension.kt b/src/main/kotlin/io/dico/parcels2/util/math/Dimension.kt index cf67148..5b16860 100644 --- a/src/main/kotlin/io/dico/parcels2/util/math/Dimension.kt +++ b/src/main/kotlin/io/dico/parcels2/util/math/Dimension.kt @@ -1,19 +1,19 @@ -package io.dico.parcels2.util.math - -enum class Dimension { - X, - Y, - Z; - - val otherDimensions - get() = when (this) { - X -> Y to Z - Y -> X to Z - Z -> X to Y - } - - companion object { - private val values = values() - operator fun get(ordinal: Int) = values[ordinal] - } +package io.dico.parcels2.util.math
+
+enum class Dimension {
+ X,
+ Y,
+ Z;
+
+ val otherDimensions
+ get() = when (this) {
+ X -> Y to Z
+ Y -> X to Z
+ Z -> X to Y
+ }
+
+ companion object {
+ private val values = values()
+ operator fun get(ordinal: Int) = values[ordinal]
+ }
}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/util/math/Math.kt b/src/main/kotlin/io/dico/parcels2/util/math/Math.kt index 12c3e9f..5f8deef 100644 --- a/src/main/kotlin/io/dico/parcels2/util/math/Math.kt +++ b/src/main/kotlin/io/dico/parcels2/util/math/Math.kt @@ -1,42 +1,42 @@ -package io.dico.parcels2.util.math - -fun Double.floor(): Int { - val down = toInt() - if (down.toDouble() != this && (java.lang.Double.doubleToRawLongBits(this).ushr(63).toInt()) == 1) { - return down - 1 - } - return down -} - -infix fun Int.umod(divisor: Int): Int { - val out = this % divisor - if (out < 0) { - return out + divisor - } - return out -} - -val Int.even: Boolean get() = and(1) == 0 - -fun IntRange.clamp(min: Int, max: Int): IntRange { - if (first < min) { - if (last > max) { - return IntRange(min, max) - } - return IntRange(min, last) - } - if (last > max) { - return IntRange(first, max) - } - return this -} - -// the name coerceAtMost is bad -fun Int.clampMax(max: Int) = coerceAtMost(max) -fun Double.clampMin(min: Double) = coerceAtLeast(min) -fun Double.clampMax(max: Double) = coerceAtMost(max) - -// Why does this not exist? -infix fun Int.ceilDiv(divisor: Int): Int { - return -Math.floorDiv(-this, divisor) +package io.dico.parcels2.util.math
+
+fun Double.floor(): Int {
+ val down = toInt()
+ if (down.toDouble() != this && (java.lang.Double.doubleToRawLongBits(this).ushr(63).toInt()) == 1) {
+ return down - 1
+ }
+ return down
+}
+
+infix fun Int.umod(divisor: Int): Int {
+ val out = this % divisor
+ if (out < 0) {
+ return out + divisor
+ }
+ return out
+}
+
+val Int.even: Boolean get() = and(1) == 0
+
+fun IntRange.clamp(min: Int, max: Int): IntRange {
+ if (first < min) {
+ if (last > max) {
+ return IntRange(min, max)
+ }
+ return IntRange(min, last)
+ }
+ if (last > max) {
+ return IntRange(first, max)
+ }
+ return this
+}
+
+// the name coerceAtMost is bad
+fun Int.clampMax(max: Int) = coerceAtMost(max)
+fun Double.clampMin(min: Double) = coerceAtLeast(min)
+fun Double.clampMax(max: Double) = coerceAtMost(max)
+
+// Why does this not exist?
+infix fun Int.ceilDiv(divisor: Int): Int {
+ return -Math.floorDiv(-this, divisor)
}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/util/math/Region.kt b/src/main/kotlin/io/dico/parcels2/util/math/Region.kt index cdbd497..cdcbe0e 100644 --- a/src/main/kotlin/io/dico/parcels2/util/math/Region.kt +++ b/src/main/kotlin/io/dico/parcels2/util/math/Region.kt @@ -1,37 +1,37 @@ -package io.dico.parcels2.util.math - -data class Region(val origin: Vec3i, val size: Vec3i) { - val blockCount: Int get() = size.x * size.y * size.z - - val center: Vec3d - get() { - val x = (origin.x + size.x) / 2.0 - val y = (origin.y + size.y) / 2.0 - val z = (origin.z + size.z) / 2.0 - return Vec3d(x, y, z) - } - - val end: Vec3i - get() = origin + size - - val max: Vec3i - get() = Vec3i(origin.x + size.x - 1, origin.y + size.y - 1, origin.z + size.z - 1) - - fun withSize(size: Vec3i): Region { - if (size == this.size) return this - return Region(origin, size) - } - - operator fun contains(loc: Vec3i): Boolean = getFirstUncontainedDimensionOf(loc) == null - - fun getFirstUncontainedDimensionOf(loc: Vec3i): Dimension? { - val max = max - return when { - loc.x !in origin.x..max.x -> Dimension.X - loc.z !in origin.z..max.z -> Dimension.Z - loc.y !in origin.y..max.y -> Dimension.Y - else -> null - } - } - +package io.dico.parcels2.util.math
+
+data class Region(val origin: Vec3i, val size: Vec3i) {
+ val blockCount: Int get() = size.x * size.y * size.z
+
+ val center: Vec3d
+ get() {
+ val x = (origin.x + size.x) / 2.0
+ val y = (origin.y + size.y) / 2.0
+ val z = (origin.z + size.z) / 2.0
+ return Vec3d(x, y, z)
+ }
+
+ val end: Vec3i
+ get() = origin + size
+
+ val max: Vec3i
+ get() = Vec3i(origin.x + size.x - 1, origin.y + size.y - 1, origin.z + size.z - 1)
+
+ fun withSize(size: Vec3i): Region {
+ if (size == this.size) return this
+ return Region(origin, size)
+ }
+
+ operator fun contains(loc: Vec3i): Boolean = getFirstUncontainedDimensionOf(loc) == null
+
+ fun getFirstUncontainedDimensionOf(loc: Vec3i): Dimension? {
+ val max = max
+ return when {
+ loc.x !in origin.x..max.x -> Dimension.X
+ loc.z !in origin.z..max.z -> Dimension.Z
+ loc.y !in origin.y..max.y -> Dimension.Y
+ else -> null
+ }
+ }
+
}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/util/math/Vec2i.kt b/src/main/kotlin/io/dico/parcels2/util/math/Vec2i.kt index 5945120..3b25526 100644 --- a/src/main/kotlin/io/dico/parcels2/util/math/Vec2i.kt +++ b/src/main/kotlin/io/dico/parcels2/util/math/Vec2i.kt @@ -1,9 +1,9 @@ -package io.dico.parcels2.util.math - -data class Vec2i( - val x: Int, - val z: Int -) { - fun add(ox: Int, oz: Int) = Vec2i(x + ox, z + oz) - fun toChunk() = Vec2i(x shr 4, z shr 4) -} +package io.dico.parcels2.util.math
+
+data class Vec2i(
+ val x: Int,
+ val z: Int
+) {
+ fun add(ox: Int, oz: Int) = Vec2i(x + ox, z + oz)
+ fun toChunk() = Vec2i(x shr 4, z shr 4)
+}
diff --git a/src/main/kotlin/io/dico/parcels2/util/math/Vec3d.kt b/src/main/kotlin/io/dico/parcels2/util/math/Vec3d.kt index 787f46c..72b6dcd 100644 --- a/src/main/kotlin/io/dico/parcels2/util/math/Vec3d.kt +++ b/src/main/kotlin/io/dico/parcels2/util/math/Vec3d.kt @@ -1,53 +1,53 @@ -package io.dico.parcels2.util.math - -import org.bukkit.Location -import kotlin.math.sqrt - -data class Vec3d( - val x: Double, - val y: Double, - val z: Double -) { - constructor(loc: Location) : this(loc.x, loc.y, loc.z) - - operator fun plus(o: Vec3d) = Vec3d(x + o.x, y + o.y, z + o.z) - operator fun minus(o: Vec3i) = Vec3d(x - o.x, y - o.y, z - o.z) - infix fun addX(o: Double) = Vec3d(x + o, y, z) - infix fun addY(o: Double) = Vec3d(x, y + o, z) - infix fun addZ(o: Double) = Vec3d(x, y, z + o) - infix fun withX(o: Double) = Vec3d(o, y, z) - infix fun withY(o: Double) = Vec3d(x, o, z) - infix fun withZ(o: Double) = Vec3d(x, y, o) - fun add(ox: Double, oy: Double, oz: Double) = Vec3d(x + ox, y + oy, z + oz) - fun toVec3i() = Vec3i(x.floor(), y.floor(), z.floor()) - - fun distanceSquared(o: Vec3d): Double { - val dx = o.x - x - val dy = o.y - y - val dz = o.z - z - return dx * dx + dy * dy + dz * dz - } - - fun distance(o: Vec3d) = sqrt(distanceSquared(o)) - - operator fun get(dimension: Dimension) = - when (dimension) { - Dimension.X -> x - Dimension.Y -> y - Dimension.Z -> z - } - - fun with(dimension: Dimension, value: Double) = - when (dimension) { - Dimension.X -> withX(value) - Dimension.Y -> withY(value) - Dimension.Z -> withZ(value) - } - - fun add(dimension: Dimension, value: Double) = - when (dimension) { - Dimension.X -> addX(value) - Dimension.Y -> addY(value) - Dimension.Z -> addZ(value) - } +package io.dico.parcels2.util.math
+
+import org.bukkit.Location
+import kotlin.math.sqrt
+
+data class Vec3d(
+ val x: Double,
+ val y: Double,
+ val z: Double
+) {
+ constructor(loc: Location) : this(loc.x, loc.y, loc.z)
+
+ operator fun plus(o: Vec3d) = Vec3d(x + o.x, y + o.y, z + o.z)
+ operator fun minus(o: Vec3i) = Vec3d(x - o.x, y - o.y, z - o.z)
+ infix fun addX(o: Double) = Vec3d(x + o, y, z)
+ infix fun addY(o: Double) = Vec3d(x, y + o, z)
+ infix fun addZ(o: Double) = Vec3d(x, y, z + o)
+ infix fun withX(o: Double) = Vec3d(o, y, z)
+ infix fun withY(o: Double) = Vec3d(x, o, z)
+ infix fun withZ(o: Double) = Vec3d(x, y, o)
+ fun add(ox: Double, oy: Double, oz: Double) = Vec3d(x + ox, y + oy, z + oz)
+ fun toVec3i() = Vec3i(x.floor(), y.floor(), z.floor())
+
+ fun distanceSquared(o: Vec3d): Double {
+ val dx = o.x - x
+ val dy = o.y - y
+ val dz = o.z - z
+ return dx * dx + dy * dy + dz * dz
+ }
+
+ fun distance(o: Vec3d) = sqrt(distanceSquared(o))
+
+ operator fun get(dimension: Dimension) =
+ when (dimension) {
+ Dimension.X -> x
+ Dimension.Y -> y
+ Dimension.Z -> z
+ }
+
+ fun with(dimension: Dimension, value: Double) =
+ when (dimension) {
+ Dimension.X -> withX(value)
+ Dimension.Y -> withY(value)
+ Dimension.Z -> withZ(value)
+ }
+
+ fun add(dimension: Dimension, value: Double) =
+ when (dimension) {
+ Dimension.X -> addX(value)
+ Dimension.Y -> addY(value)
+ Dimension.Z -> addZ(value)
+ }
}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/util/math/Vec3i.kt b/src/main/kotlin/io/dico/parcels2/util/math/Vec3i.kt index b25764e..484ad13 100644 --- a/src/main/kotlin/io/dico/parcels2/util/math/Vec3i.kt +++ b/src/main/kotlin/io/dico/parcels2/util/math/Vec3i.kt @@ -1,105 +1,105 @@ -package io.dico.parcels2.util.math - -import org.bukkit.Location -import org.bukkit.World -import org.bukkit.block.Block -import org.bukkit.block.BlockFace - -data class Vec3i( - val x: Int, - val y: Int, - val z: Int -) { - constructor(loc: Location) : this(loc.blockX, loc.blockY, loc.blockZ) - constructor(block: Block) : this(block.x, block.y, block.z) - - fun toVec2i() = Vec2i(x, z) - operator fun plus(o: Vec3i) = Vec3i(x + o.x, y + o.y, z + o.z) - operator fun minus(o: Vec3i) = Vec3i(x - o.x, y - o.y, z - o.z) - infix fun addX(o: Int) = Vec3i(x + o, y, z) - infix fun addY(o: Int) = Vec3i(x, y + o, z) - infix fun addZ(o: Int) = Vec3i(x, y, z + o) - infix fun withX(o: Int) = Vec3i(o, y, z) - infix fun withY(o: Int) = Vec3i(x, o, z) - infix fun withZ(o: Int) = Vec3i(x, y, o) - fun add(ox: Int, oy: Int, oz: Int) = Vec3i(x + ox, y + oy, z + oz) - fun neg() = Vec3i(-x, -y, -z) - fun clampMax(o: Vec3i) = Vec3i(x.clampMax(o.x), y.clampMax(o.y), z.clampMax(o.z)) - - operator fun get(dimension: Dimension) = - when (dimension) { - Dimension.X -> x - Dimension.Y -> y - Dimension.Z -> z - } - - fun with(dimension: Dimension, value: Int) = - when (dimension) { - Dimension.X -> withX(value) - Dimension.Y -> withY(value) - Dimension.Z -> withZ(value) - } - - fun add(dimension: Dimension, value: Int) = - when (dimension) { - Dimension.X -> addX(value) - Dimension.Y -> addY(value) - Dimension.Z -> addZ(value) - } - - companion object { - private operator fun invoke(face: BlockFace) = Vec3i(face.modX, face.modY, face.modZ) - val down = Vec3i(BlockFace.DOWN) - val up = Vec3i(BlockFace.UP) - val north = Vec3i(BlockFace.NORTH) - val east = Vec3i(BlockFace.EAST) - val south = Vec3i(BlockFace.SOUTH) - val west = Vec3i(BlockFace.WEST) - - fun convert(face: BlockFace) = when (face) { - BlockFace.DOWN -> down - BlockFace.UP -> up - BlockFace.NORTH -> north - BlockFace.EAST -> east - BlockFace.SOUTH -> south - BlockFace.WEST -> west - else -> Vec3i(face) - } - } -} - -@Suppress("NOTHING_TO_INLINE") -inline operator fun World.get(vec: Vec3i): Block = getBlockAt(vec.x, vec.y, vec.z) - -/* -private /*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 { - val result = ushr(offset).toInt().and(mask) - return if (result > max) result or mask.inv() else result - } - } - - 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) - -} -*/ +package io.dico.parcels2.util.math
+
+import org.bukkit.Location
+import org.bukkit.World
+import org.bukkit.block.Block
+import org.bukkit.block.BlockFace
+
+data class Vec3i(
+ val x: Int,
+ val y: Int,
+ val z: Int
+) {
+ constructor(loc: Location) : this(loc.blockX, loc.blockY, loc.blockZ)
+ constructor(block: Block) : this(block.x, block.y, block.z)
+
+ fun toVec2i() = Vec2i(x, z)
+ operator fun plus(o: Vec3i) = Vec3i(x + o.x, y + o.y, z + o.z)
+ operator fun minus(o: Vec3i) = Vec3i(x - o.x, y - o.y, z - o.z)
+ infix fun addX(o: Int) = Vec3i(x + o, y, z)
+ infix fun addY(o: Int) = Vec3i(x, y + o, z)
+ infix fun addZ(o: Int) = Vec3i(x, y, z + o)
+ infix fun withX(o: Int) = Vec3i(o, y, z)
+ infix fun withY(o: Int) = Vec3i(x, o, z)
+ infix fun withZ(o: Int) = Vec3i(x, y, o)
+ fun add(ox: Int, oy: Int, oz: Int) = Vec3i(x + ox, y + oy, z + oz)
+ fun neg() = Vec3i(-x, -y, -z)
+ fun clampMax(o: Vec3i) = Vec3i(x.clampMax(o.x), y.clampMax(o.y), z.clampMax(o.z))
+
+ operator fun get(dimension: Dimension) =
+ when (dimension) {
+ Dimension.X -> x
+ Dimension.Y -> y
+ Dimension.Z -> z
+ }
+
+ fun with(dimension: Dimension, value: Int) =
+ when (dimension) {
+ Dimension.X -> withX(value)
+ Dimension.Y -> withY(value)
+ Dimension.Z -> withZ(value)
+ }
+
+ fun add(dimension: Dimension, value: Int) =
+ when (dimension) {
+ Dimension.X -> addX(value)
+ Dimension.Y -> addY(value)
+ Dimension.Z -> addZ(value)
+ }
+
+ companion object {
+ private operator fun invoke(face: BlockFace) = Vec3i(face.modX, face.modY, face.modZ)
+ val down = Vec3i(BlockFace.DOWN)
+ val up = Vec3i(BlockFace.UP)
+ val north = Vec3i(BlockFace.NORTH)
+ val east = Vec3i(BlockFace.EAST)
+ val south = Vec3i(BlockFace.SOUTH)
+ val west = Vec3i(BlockFace.WEST)
+
+ fun convert(face: BlockFace) = when (face) {
+ BlockFace.DOWN -> down
+ BlockFace.UP -> up
+ BlockFace.NORTH -> north
+ BlockFace.EAST -> east
+ BlockFace.SOUTH -> south
+ BlockFace.WEST -> west
+ else -> Vec3i(face)
+ }
+ }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun World.get(vec: Vec3i): Block = getBlockAt(vec.x, vec.y, vec.z)
+
+/*
+private /*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 {
+ val result = ushr(offset).toInt().and(mask)
+ return if (result > max) result or mask.inv() else result
+ }
+ }
+
+ 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)
+
+}
+*/
diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index eff524c..c2e272e 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -1,17 +1,17 @@ -<configuration> - <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> - <encoder> - <!-- old pattern <pattern>%d{HH:mm:ss.SSS} %magenta(%-8.-8(%thread)) %highlight(%-5level) %boldCyan(%8.-32logger{32}) - %msg\n</pattern>--> - <pattern>%magenta(%-16.-16(%thread)) %highlight(%-5level) %boldCyan(%6.-32logger{32}) - %msg</pattern> - </encoder> - </appender> - - <root level="debug"> - <appender-ref ref="STDOUT" /> - </root> - - <logger name="com.zaxxer.hikari.pool.HikariPool" level="info"/> - <logger name="com.zaxxer.hikari.pool.PoolBase" level="info"/> - <logger name="com.zaxxer.hikari.HikariConfig" level="info"/> - <logger name="Exposed" level="info"/> +<configuration>
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder>
+ <!-- old pattern <pattern>%d{HH:mm:ss.SSS} %magenta(%-8.-8(%thread)) %highlight(%-5level) %boldCyan(%8.-32logger{32}) - %msg\n</pattern>-->
+ <pattern>%magenta(%-16.-16(%thread)) %highlight(%-5level) %boldCyan(%6.-32logger{32}) - %msg</pattern>
+ </encoder>
+ </appender>
+
+ <root level="debug">
+ <appender-ref ref="STDOUT" />
+ </root>
+
+ <logger name="com.zaxxer.hikari.pool.HikariPool" level="info"/>
+ <logger name="com.zaxxer.hikari.pool.PoolBase" level="info"/>
+ <logger name="com.zaxxer.hikari.HikariConfig" level="info"/>
+ <logger name="Exposed" level="info"/>
</configuration>
\ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 19c68da..8830377 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ -name: Parcels -author: Dico -main: io.dico.parcels2.ParcelsPlugin -version: 0.1 -api-version: 1.13 +name: Parcels
+author: Dico
+main: io.dico.parcels2.ParcelsPlugin
+version: 0.1
+api-version: 1.13
load: STARTUP
\ No newline at end of file |