Archived
0

10/10 commit messages btw

This commit is contained in:
Dico
2018-09-28 21:16:14 +01:00
parent bb6ae7d370
commit 67cb73e4c7
31 changed files with 602 additions and 352 deletions

View File

@@ -77,7 +77,7 @@ dependencies {
// not on sk89q maven repo yet
compileClasspath(files("$rootDir/debug/plugins/worldedit-bukkit-7.0.0-beta-01.jar"))
//compileClasspath(files("$rootDir/debug/lib/spigot-1.13.1.jar"))
compileClasspath(files("$rootDir/debug/lib/spigot-1.13.1.jar"))
compile("org.jetbrains.exposed:exposed:0.10.5") { isTransitive = false }
compile("joda-time:joda-time:2.10")

View File

@@ -1,7 +1,5 @@
package io.dico.parcels2.blockvisitor
package io.dico.parcels2
import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.logger
import io.dico.parcels2.util.math.clampMin
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
@@ -23,9 +21,9 @@ data class TickJobtimeOptions(var jobTime: Int, var tickInterval: Int)
interface JobDispatcher {
/**
* Submit a [task] that should be run synchronously, but limited such that it does not stall the server
* Submit a [function] that should be run synchronously, but limited such that it does not stall the server
*/
fun dispatch(task: JobFunction): Job
fun dispatch(function: JobFunction): Job
/**
* Get a list of all jobs
@@ -55,7 +53,7 @@ interface Job : JobAndScopeMembersUnion {
/**
* The coroutine associated with this job
*/
val job: CoroutineJob
val coroutine: CoroutineJob
/**
* true if this job has completed
@@ -147,8 +145,8 @@ class BukkitJobDispatcher(private val plugin: ParcelsPlugin, var options: TickJo
private val _jobs = LinkedList<JobInternal>()
override val jobs: List<Job> = _jobs
override fun dispatch(task: JobFunction): Job {
val job: JobInternal = JobImpl(plugin, task)
override fun dispatch(function: JobFunction): Job {
val job: JobInternal = JobImpl(plugin, function)
if (bukkitTask == null) {
val completed = job.resume(options.jobTime.toLong())
@@ -198,7 +196,7 @@ class BukkitJobDispatcher(private val plugin: ParcelsPlugin, var options: TickJo
}
private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal {
override val job: CoroutineJob = scope.launch(start = LAZY) { task() }
override val coroutine: CoroutineJob = scope.launch(start = LAZY) { task() }
private var continuation: Continuation<Unit>? = null
private var nextSuspensionTime: Long = 0L
@@ -207,10 +205,10 @@ private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal {
override val elapsedTime
get() =
if (job.isCompleted) startTimeOrElapsedTime
if (coroutine.isCompleted) startTimeOrElapsedTime
else currentTimeMillis() - startTimeOrElapsedTime
override val isComplete get() = job.isCompleted
override val isComplete get() = coroutine.isCompleted
private var _progress = 0.0
override val progress get() = _progress
@@ -223,7 +221,7 @@ private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal {
private var onCompleted: JobUpdateLister? = null
init {
job.invokeOnCompletion { exception ->
coroutine.invokeOnCompletion { exception ->
// report any error that occurred
completionException = exception?.also {
if (it !is CancellationException)
@@ -306,13 +304,13 @@ private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal {
isStarted = true
startTimeOrElapsedTime = System.currentTimeMillis()
job.start()
coroutine.start()
return continuation == null
}
override suspend fun awaitCompletion() {
job.join()
coroutine.join()
}
private fun delegateProgress(curPortion: Double, portion: Double): JobScope =

View File

@@ -1,6 +1,7 @@
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
@@ -19,7 +20,7 @@ interface Parcel : ParcelData, Privileges {
val pos: Vec2i
val x: Int
val z: Int
val data: ParcelData
val data: ParcelDataHolder
val infoString: String
val hasBlockVisitors: Boolean
val globalPrivileges: GlobalPrivileges?
@@ -27,21 +28,21 @@ interface Parcel : ParcelData, Privileges {
override val keyOfOwner: PlayerProfile.Real?
get() = owner as? PlayerProfile.Real
fun copyDataIgnoringDatabase(data: ParcelData)
fun copyData(newData: ParcelDataHolder, callerIsDatabase: Boolean = false)
fun copyData(data: ParcelData)
fun dispose() = copyData(ParcelDataHolder())
fun dispose()
suspend fun withBlockVisitorPermit(block: suspend () -> Unit)
fun updateOwnerSign(force: Boolean = false)
val homeLocation: Location get() = world.blockManager.getHomeLocation(id)
}
interface ParcelData : RawPrivileges {
var owner: PlayerProfile?
val lastClaimTime: DateTime?
var ownerSignOutdated: Boolean
var isOwnerSignOutdated: Boolean
var interactableConfig: InteractableConfiguration
//fun canBuild(player: OfflinePlayer, checkAdmin: Boolean = true, checkGlobal: Boolean = true): Boolean
@@ -59,7 +60,7 @@ class ParcelDataHolder(addedMap: MutablePrivilegeMap = mutableMapOf())
: ParcelData, PrivilegesHolder(addedMap) {
override var owner: PlayerProfile? = null
override var lastClaimTime: DateTime? = null
override var ownerSignOutdated = false
override var isOwnerSignOutdated = false
override var interactableConfig: InteractableConfiguration = BitmaskInteractableConfiguration()
}

View File

@@ -1,15 +1,18 @@
package io.dico.parcels2
import io.dico.parcels2.blockvisitor.*
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
@@ -34,10 +37,12 @@ abstract class ParcelGenerator : ChunkGenerator() {
})
}
abstract fun makeParcelLocatorAndBlockManager(worldId: ParcelWorldId,
container: ParcelContainer,
coroutineScope: CoroutineScope,
jobDispatcher: JobDispatcher): Pair<ParcelLocator, ParcelBlockManager>
abstract fun makeParcelLocatorAndBlockManager(
parcelProvider: ParcelProvider,
container: ParcelContainer,
coroutineScope: CoroutineScope,
jobDispatcher: JobDispatcher
): Pair<ParcelLocator, ParcelBlockManager>
}
interface ParcelBlockManager {
@@ -45,7 +50,7 @@ interface ParcelBlockManager {
val jobDispatcher: JobDispatcher
val parcelTraverser: RegionTraverser
// fun getBottomBlock(parcel: ParcelId): Vec2i
fun getRegionOrigin(parcel: ParcelId) = getRegion(parcel).origin.toVec2i()
fun getHomeLocation(parcel: ParcelId): Location
@@ -53,15 +58,15 @@ interface ParcelBlockManager {
fun getEntities(parcel: ParcelId): Collection<Entity>
fun setOwnerBlock(parcel: ParcelId, owner: PlayerProfile?)
fun isParcelInfoSectionLoaded(parcel: ParcelId): Boolean
fun setBiome(parcel: ParcelId, biome: Biome): Job
fun updateParcelInfo(parcel: ParcelId, owner: PlayerProfile?)
fun clearParcel(parcel: ParcelId): Job
fun getParcelForInfoBlockInteraction(block: Vec3i, type: Material, face: BlockFace): Parcel?
fun swapParcels(parcel1: ParcelId, parcel2: ParcelId): Job
fun setBiome(parcel: ParcelId, biome: Biome): Job?
fun submitBlockVisitor(vararg parcelIds: ParcelId, task: JobFunction): Job
fun clearParcel(parcel: ParcelId): Job?
/**
* Used to update owner blocks in the corner of the parcel
@@ -69,9 +74,12 @@ interface ParcelBlockManager {
fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i>
}
inline fun ParcelBlockManager.doBlockOperation(parcel: ParcelId,
traverser: RegionTraverser,
crossinline operation: suspend JobScope.(Block) -> Unit) = submitBlockVisitor(parcel) {
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)

View File

@@ -1,3 +1,5 @@
@file:Suppress("FunctionName")
package io.dico.parcels2
import io.dico.parcels2.util.math.Vec2i
@@ -15,14 +17,12 @@ interface ParcelWorldId {
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) }
companion object {
operator fun invoke(worldName: String, worldUid: UUID?): ParcelWorldId = ParcelWorldIdImpl(worldName, worldUid)
operator fun invoke(worldName: String): ParcelWorldId = ParcelWorldIdImpl(worldName, null)
}
}
fun ParcelWorldId.toStringExt() = "ParcelWorld($name)"
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
@@ -35,24 +35,22 @@ interface ParcelId {
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)
companion object {
operator fun invoke(worldId: ParcelWorldId, pos: Vec2i) = invoke(worldId, pos.x, pos.z)
operator fun invoke(worldName: String, worldUid: UUID?, pos: Vec2i) = invoke(worldName, worldUid, pos.x, pos.z)
operator fun invoke(worldName: String, worldUid: UUID?, x: Int, z: Int) = invoke(ParcelWorldId(worldName, worldUid), x, z)
operator fun invoke(worldId: ParcelWorldId, x: Int, z: Int): ParcelId = ParcelIdImpl(worldId, x, z)
}
}
fun ParcelId.toStringExt() = "Parcel(${worldId.name},$idString)"
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() = toStringExt()
override fun toString() = parcelWorldIdToString()
}
private class ParcelIdImpl(override val worldId: ParcelWorldId,
override val x: Int,
override val z: Int) : ParcelId {
override fun toString() = toStringExt()
override fun toString() = parcelIdToString()
}

View File

@@ -9,8 +9,11 @@ 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>
@@ -43,6 +46,15 @@ interface ParcelProvider {
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 {
@@ -69,7 +81,7 @@ interface ParcelContainer {
fun getParcelById(id: Vec2i): Parcel? = getParcelById(id.x, id.z)
fun getParcelById(id: ParcelId): Parcel? = getParcelById(id.x, id.z)
fun getParcelById(id: ParcelId): Parcel?
fun nextEmptyParcel(): Parcel?
@@ -88,5 +100,4 @@ interface ParcelWorld : ParcelLocator, ParcelContainer {
val globalPrivileges: GlobalPrivilegesManager
val creationTime: DateTime?
}

View File

@@ -3,8 +3,6 @@ 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.blockvisitor.BukkitJobDispatcher
import io.dico.parcels2.blockvisitor.JobDispatcher
import io.dico.parcels2.command.getParcelCommands
import io.dico.parcels2.defaultimpl.GlobalPrivilegesManagerImpl
import io.dico.parcels2.defaultimpl.ParcelProviderImpl
@@ -17,6 +15,7 @@ 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
@@ -47,6 +46,7 @@ class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginScheduler {
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()) {

View File

@@ -6,27 +6,89 @@ 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 {
sealed class RegionTraverser {
fun traverseRegion(region: Region, worldHeight: Int = 256): Iterable<Vec3i> =
Iterable { iterator<Vec3i> { build(validify(region, worldHeight)) } }
class OccupiedException(parcelId: ParcelId) : Exception("Parcel $parcelId is occupied")
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)
/**
* 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
}
if (region.origin.y + region.size.y > worldHeight) {
val size = region.size.withY(worldHeight - region.origin.y)
return Region(region.origin, size)
}
return region
val region = world.blockManager.getRegion(parcelId)
return traverseRegion(region, world.world.maxHeight, medium)
}
protected abstract suspend fun Scope.build(region: Region)
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))
@@ -34,9 +96,35 @@ sealed class RegionTraverser {
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(
@@ -50,7 +138,7 @@ sealed class RegionTraverser {
}
}
override suspend fun Scope.build(region: Region) {
override suspend fun Scope.build(region: Region/*, medium: TraverserMedium*/) {
val order = order
val (primary, secondary, tertiary) = order.toArray()
val (origin, size) = region
@@ -71,6 +159,7 @@ sealed class RegionTraverser {
}
}
/*medium.iterationCompleted()*/
}
}
@@ -91,7 +180,7 @@ sealed class RegionTraverser {
return region to null
}
override suspend fun Scope.build(region: Region) {
override suspend fun Scope.build(region: Region/*, medium: TraverserMedium*/) {
val (bottom, top) = slice(region, bottomSectionMaxY)
if (bottomFirst) {
@@ -101,15 +190,22 @@ sealed class RegionTraverser {
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 Directional ->
return cur
/*is ParcelTraverser -> cur = cur.delegate*/
is Directional -> return cur
is Slicing ->
cur =
if (position.y <= cur.bottomSectionMaxY) cur.bottomTraverser
@@ -118,10 +214,17 @@ sealed class RegionTraverser {
}
}
/**
* 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

View File

@@ -1,5 +1,7 @@
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

View File

@@ -1,12 +1,12 @@
package io.dico.parcels2.command
import io.dico.dicore.command.*
import io.dico.parcels2.ParcelWorld
import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.PlayerProfile
import io.dico.parcels2.PlayerProfile.*
import io.dico.parcels2.PrivilegeKey
import io.dico.parcels2.blockvisitor.Job
import io.dico.dicore.command.CommandException
import io.dico.dicore.command.EMessageType
import io.dico.dicore.command.ExecutionContext
import io.dico.dicore.command.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
@@ -42,15 +42,13 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei
else -> throw CommandException()
}
protected fun areYouSureMessage(context: ExecutionContext) = "Are you sure? You cannot undo this action!\n" +
"Run \"/${context.route.joinToString(" ")} -sure\" if you want to go through with this."
protected fun ParcelScope.clearWithProgressUpdates(context: ExecutionContext, action: String) {
Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel")
world.blockManager.clearParcel(parcel.id)
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) {
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)
@@ -59,7 +57,6 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei
.format(progress * 100, elapsedTime / 1000.0)
)
}
}
override fun getCoroutineContext() = plugin.coroutineContext
}

View File

@@ -5,11 +5,10 @@ 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.ParcelsPlugin
import io.dico.parcels2.PlayerProfile
import io.dico.parcels2.Privilege
import io.dico.parcels2.*
import io.dico.parcels2.command.ParcelTarget.TargetKind
import io.dico.parcels2.resolved
import io.dico.parcels2.defaultimpl.DefaultParcelContainer
import io.dico.parcels2.util.ext.PERM_ADMIN_MANAGE
class CommandsAdmin(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@@ -23,6 +22,33 @@ class CommandsAdmin(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
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? {
@@ -37,15 +63,17 @@ class CommandsAdmin(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
if (!sure) return areYouSureMessage(context)
parcel.dispose()
world.blockManager.clearParcel(parcel.id).reportProgressUpdates(context, "Reset")
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? {
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)
@@ -53,13 +81,14 @@ class CommandsAdmin(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
?: 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 this parcel")
Validate.isTrue(!parcel2.hasBlockVisitors, "A process is already running in that parcel")
val data = parcel.data
parcel.copyData(parcel2.data)
parcel2.copyData(data)
world.blockManager.swapParcels(parcel.id, parcel2.id).reportProgressUpdates(context, "Swap")
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
}

View File

@@ -1,5 +1,6 @@
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
@@ -54,9 +55,9 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
)
val random = Random()
world.blockManager.doBlockOperation(parcel.id, traverser = RegionTraverser.upward) { block ->
world.blockManager.tryDoBlockOperation(plugin.parcelProvider, parcel.id, traverser = RegionTraverser.upward) { block ->
block.blockData = blockDatas[random.nextInt(7)]
}.onProgressUpdate(1000, 1000) { progress, elapsedTime ->
}?.onProgressUpdate(1000, 1000) { progress, elapsedTime ->
context.sendMessage(
EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed"
.format(progress * 100, elapsedTime / 1000.0)
@@ -82,7 +83,7 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@Cmd("jobs")
fun cmdJobs(): Any? {
val workers = plugin.jobDispatcher.jobs
println(workers.map { it.job }.joinToString(separator = "\n"))
println(workers.map { it.coroutine }.joinToString(separator = "\n"))
return "Task count: ${workers.size}"
}
@@ -95,7 +96,7 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@PreprocessArgs
fun cmdMessage(sender: CommandSender, message: String): Any? {
// testing @PreprocessArgs which merges "hello there" into a single argument
sender.sendMessage(message)
sender.sendMessage(Formatting.translate(message))
return null
}

View File

@@ -127,7 +127,7 @@ class CommandsGeneral(plugin: ParcelsPlugin, parent: SpecialCommandAddress) : Ab
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")
world.blockManager.clearParcel(parcel.id)?.reportProgressUpdates(context, "Clear")
return null
}
@@ -135,7 +135,7 @@ class CommandsGeneral(plugin: ParcelsPlugin, parent: SpecialCommandAddress) : Ab
@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")
world.blockManager.setBiome(parcel.id, biome)?.reportProgressUpdates(context, "Biome change")
return null
}

View File

@@ -92,8 +92,8 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
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 worldString = input.substringBefore("/", missingDelimiterValue = "")
input = input.substringAfter("/")
val world = if (worldString.isEmpty()) {
val player = requirePlayer(sender, parameter, "the world")

View File

@@ -64,7 +64,7 @@ class DefaultParcelContainer(val world: ParcelWorld) : ParcelContainer {
}
}
fun allParcels(): Sequence<Parcel> = sequence {
fun getAllParcels(): Iterator<Parcel> = iterator {
for (array in parcels) {
yieldAll(array.iterator())
}

View File

@@ -1,22 +1,14 @@
package io.dico.parcels2.defaultimpl
import io.dico.parcels2.*
import io.dico.parcels2.blockvisitor.*
import io.dico.parcels2.blockvisitor.RegionTraverser
import io.dico.parcels2.options.DefaultGeneratorOptions
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.even
import io.dico.parcels2.util.math.umod
import io.dico.parcels2.util.math.get
import io.dico.parcels2.util.math.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart.UNDISPATCHED
import kotlinx.coroutines.launch
import org.bukkit.*
import org.bukkit.block.Biome
import org.bukkit.block.BlockFace
import org.bukkit.block.Skull
import org.bukkit.block.data.BlockData
import org.bukkit.block.data.type.Slab
import org.bukkit.block.data.type.WallSign
import java.util.Random
@@ -118,12 +110,13 @@ class DefaultParcelGenerator(
}
override fun makeParcelLocatorAndBlockManager(
worldId: ParcelWorldId,
parcelProvider: ParcelProvider,
container: ParcelContainer,
coroutineScope: CoroutineScope,
jobDispatcher: JobDispatcher
): Pair<ParcelLocator, ParcelBlockManager> {
return ParcelLocatorImpl(worldId, container) to ParcelBlockManagerImpl(worldId, coroutineScope, jobDispatcher)
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? {
@@ -139,11 +132,25 @@ class DefaultParcelGenerator(
return null
}
private inner class ParcelLocatorImpl(
val worldId: ParcelWorldId,
val container: ParcelContainer
) : ParcelLocator {
override val world: World = this@DefaultParcelGenerator.world
@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)
@@ -152,112 +159,113 @@ class DefaultParcelGenerator(
override fun getParcelIdAt(x: Int, z: Int): ParcelId? {
return convertBlockLocationToId(x, z) { idx, idz -> ParcelId(worldId, idx, idz) }
}
}
@Suppress("DEPRECATION")
private inner class ParcelBlockManagerImpl(
val worldId: ParcelWorldId,
coroutineScope: CoroutineScope,
override val jobDispatcher: JobDispatcher
) : ParcelBlockManagerBase(), CoroutineScope by coroutineScope {
override val world: World = this@DefaultParcelGenerator.world
override val parcelTraverser: RegionTraverser = RegionTraverser.convergingTo(o.floorHeight)
/*override*/ fun getBottomBlock(parcel: ParcelId): Vec2i = Vec2i(
sectionSize * (parcel.x - 1) + pathOffset + o.offsetX,
sectionSize * (parcel.z - 1) + pathOffset + o.offsetZ
)
private fun checkParcelId(parcel: ParcelId): ParcelId {
if (!parcel.worldId.equals(worldId)) {
throw IllegalArgumentException()
}
return parcel
}
override fun getHomeLocation(parcel: ParcelId): Location {
val bottom = getBottomBlock(parcel)
val x = bottom.x + (o.parcelSize - 1) / 2.0
val z = bottom.z - 2
return Location(world, x + 0.5, o.floorHeight + 1.0, z + 0.5, 0F, 0F)
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 bottom = getBottomBlock(parcel)
val origin = getRegionOrigin(parcel)
return Region(
Vec3i(bottom.x, 0, bottom.z),
Vec3i(origin.x, 0, origin.z),
Vec3i(o.parcelSize, maxHeight, o.parcelSize)
)
}
private fun getRegionConsideringWorld(parcel: ParcelId): Region {
if (parcel.worldId != worldId) {
(parcel.worldId as? ParcelWorld)?.let {
return it.blockManager.getRegion(parcel)
}
throw IllegalArgumentException()
}
return getRegion(parcel)
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 setOwnerBlock(parcel: ParcelId, owner: PlayerProfile?) {
val b = getBottomBlock(parcel)
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 - 2, 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 {
val wallBlockType: BlockData = if (o.wallType is Slab)
(o.wallType.clone() as Slab).apply { type = Slab.Type.DOUBLE }
else
o.wallType
wallBlock.blockData = wallBlockType
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.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 {
skull.owner = owner.name
} else if (!skull.setOwner(owner.name)) {
skullBlock.type = Material.AIR
return
}
skull.rotation = BlockFace.WEST
skull.rotation = BlockFace.SOUTH
skull.update()
}
}
private fun getParcel(parcelId: ParcelId): Parcel? {
// todo dont rely on this cast
val world = worldId as? ParcelWorld ?: return null
return world.getParcelById(parcelId)
private fun trySubmitBlockVisitor(vararg parcels: ParcelId, function: JobFunction): Job? {
parcels.forEach { checkParcelId(it) }
return parcelProvider.trySubmitBlockVisitor(Permit(), parcels, function)
}
override fun submitBlockVisitor(vararg parcelIds: ParcelId, task: JobFunction): Job {
val parcels = parcelIds.mapNotNull { getParcel(it) }
if (parcels.isEmpty()) return jobDispatcher.dispatch(task)
if (parcels.any { it.hasBlockVisitors }) throw IllegalArgumentException("This parcel already has a block visitor")
val worker = jobDispatcher.dispatch(task)
for (parcel in parcels) {
launch(start = UNDISPATCHED) {
parcel.withBlockVisitorPermit {
worker.awaitCompletion()
}
}
}
return worker
}
override fun setBiome(parcel: ParcelId, biome: Biome): Job = submitBlockVisitor(parcel) {
override fun setBiome(parcel: ParcelId, biome: Biome) = trySubmitBlockVisitor(checkParcelId(parcel)) {
val world = world
val b = getBottomBlock(parcel)
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) {
@@ -267,7 +275,7 @@ class DefaultParcelGenerator(
}
}
override fun clearParcel(parcel: ParcelId): Job = submitBlockVisitor(parcel) {
override fun clearParcel(parcel: ParcelId) = trySubmitBlockVisitor(checkParcelId(parcel)) {
val region = getRegion(parcel)
val blocks = parcelTraverser.traverseRegion(region)
val blockCount = region.blockCount.toDouble()
@@ -291,22 +299,6 @@ class DefaultParcelGenerator(
}
}
override fun swapParcels(parcel1: ParcelId, parcel2: ParcelId): Job = submitBlockVisitor(parcel1, parcel2) {
var region1 = getRegionConsideringWorld(parcel1)
var region2 = getRegionConsideringWorld(parcel2)
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(world, region1) } }
val schematicOf2 = delegateWork(0.25) { Schematic().apply { load(world, region2) } }
delegateWork(0.25) { with(schematicOf1) { paste(world, region2.origin) } }
delegateWork(0.25) { with(schematicOf2) { paste(world, region1.origin) } }
}
override fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i> {
/*
* Get the offsets for the world out of the way

View File

@@ -3,59 +3,84 @@ package io.dico.parcels2.defaultimpl
import io.dico.parcels2.*
import io.dico.parcels2.Privilege.*
import io.dico.parcels2.util.ext.alsoIfTrue
import io.dico.parcels2.util.isServerThread
import io.dico.parcels2.util.math.Vec2i
import org.bukkit.Material
import org.joda.time.DateTime
import java.util.concurrent.atomic.AtomicInteger
import java.lang.IllegalStateException
class ParcelImpl(
class ParcelImpl (
override val world: ParcelWorld,
override val x: Int,
override val z: Int
) : Parcel, ParcelId {
override val id: ParcelId = this
override val id: ParcelId get() = this
override val pos get() = Vec2i(x, z)
override var data: ParcelDataHolder = ParcelDataHolder(); private set
override val hasBlockVisitors get() = blockVisitors.get() > 0
override var data = ParcelDataHolder(); private set
override val worldId: ParcelWorldId get() = world.id
override fun copyDataIgnoringDatabase(data: ParcelData) {
this.data = ((data as? Parcel)?.data ?: data) as ParcelDataHolder
}
override fun copyData(newData: ParcelDataHolder, callerIsDatabase: Boolean) {
if (callerIsDatabase) {
data = newData
return
}
val ownerChanged = owner != newData.owner
if (ownerChanged) {
updateOwnerSign(true, false, true)
}
val ownerSignWasOutdated = if (callerIsDatabase) newData.isOwnerSignOutdated else isOwnerSignOutdated
val ownerChanged = owner != newData.owner
data = newData
if (ownerChanged && isServerThread()) {
updateOwnerSign(true, false, updateDatabase = callerIsDatabase)
} else {
newData.isOwnerSignOutdated = ownerChanged || ownerSignWasOutdated
}
override fun copyData(data: ParcelData) {
copyDataIgnoringDatabase(data)
world.storage.setParcelData(this, data)
}
override fun dispose() {
copyDataIgnoringDatabase(ParcelDataHolder())
world.storage.setParcelData(this, null)
}
override var owner: PlayerProfile?
get() = data.owner
set(value) {
if (data.owner != value) {
world.storage.setParcelOwner(this, value)
world.blockManager.setOwnerBlock(this, value)
data.owner = value
updateOwnerSign(true, false, true)
}
}
override val lastClaimTime: DateTime?
get() = data.lastClaimTime
override var ownerSignOutdated: Boolean
get() = data.ownerSignOutdated
override var isOwnerSignOutdated: Boolean
get() = data.isOwnerSignOutdated
set(value) {
if (data.ownerSignOutdated != value) {
if (data.isOwnerSignOutdated != value) {
world.storage.setParcelOwnerSignOutdated(this, value)
data.ownerSignOutdated = value
data.isOwnerSignOutdated = value
}
}
override fun updateOwnerSign(force: Boolean) {
updateOwnerSign(false, force, true)
}
private fun updateOwnerSign(ownerChanged: Boolean, force: Boolean, updateDatabase: Boolean) {
if (!ownerChanged && !isOwnerSignOutdated && !force) return
val update = force || world.blockManager.isParcelInfoSectionLoaded(this)
if (update) world.blockManager.updateParcelInfo(this, owner)
if (updateDatabase) isOwnerSignOutdated = !update
else data.isOwnerSignOutdated = !update
}
override val privilegeMap: PrivilegeMap
get() = data.privilegeMap
@@ -106,7 +131,24 @@ class ParcelImpl(
}
}
override val hasBlockVisitors: Boolean
get() = permit != null
private var permit: Permit? = null
fun acquireBlockVisitorPermit(with: Permit): Boolean {
if (permit === with) return true
if (permit != null) return false
permit = with
return true
}
fun releaseBlockVisitorPermit(with: Permit) {
if (permit !== with) throw IllegalStateException()
permit = null
}
/*
private var blockVisitors = AtomicInteger(0)
override suspend fun withBlockVisitorPermit(block: suspend () -> Unit) {
@@ -116,9 +158,9 @@ class ParcelImpl(
} finally {
blockVisitors.getAndDecrement()
}
}
}*/
override fun toString() = toStringExt()
override fun toString() = parcelIdToString()
override val infoString: String
get() = getInfoString()

View File

@@ -1,8 +1,10 @@
package io.dico.parcels2.defaultimpl
import io.dico.parcels2.*
import io.dico.parcels2.blockvisitor.Schematic
import io.dico.parcels2.util.schedule
import kotlinx.coroutines.Unconfined
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.bukkit.Bukkit
import org.bukkit.WorldCreator
@@ -44,10 +46,11 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
private fun loadWorlds0() {
if (Bukkit.getWorlds().isEmpty()) {
plugin.schedule(::loadWorlds0)
plugin.logger.warning("Scheduling to load worlds in the next tick, \nbecause no bukkit worlds are loaded yet")
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
@@ -56,19 +59,20 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
val worldExists = Bukkit.getWorld(worldName) != null
val bukkitWorld =
if (worldExists) Bukkit.getWorld(worldName)!!
else WorldCreator(worldName).generator(generator).createWorld().also { logger.info("Creating world $worldName") }
else {
logger.info("Creating world $worldName")
WorldCreator(worldName).generator(generator).createWorld()
}
parcelWorld = ParcelWorldImpl(
bukkitWorld, generator, worldOptions.runtime, plugin.storage,
plugin.globalPrivileges, ::DefaultParcelContainer, plugin, plugin.jobDispatcher
)
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 {
launch(context = Unconfined) {
GlobalScope.launch(context = Dispatchers.Unconfined) {
parcelWorld.creationTime = plugin.storage.getWorldCreationTime(parcelWorld.id).await() ?: DateTime.now()
}
}
@@ -76,11 +80,11 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
_worlds[worldName] = parcelWorld
}
loadStoredData()
loadStoredData(newlyCreatedWorlds.toSet())
}
private fun loadStoredData() {
plugin.launch {
private fun loadStoredData(newlyCreatedWorlds: Collection<ParcelWorld> = emptyList()) {
plugin.launch(Dispatchers.Default) {
val migration = plugin.options.migration
if (migration.enabled) {
migration.instance?.newInstance()?.apply {
@@ -96,11 +100,14 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
}
logger.info("Loading all parcel data...")
val channel = plugin.storage.transmitAllParcelData()
while (true) {
val (id, data) = channel.receiveOrNull() ?: break
val parcel = getParcelById(id) ?: continue
data?.let { parcel.copyDataIgnoringDatabase(it) }
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()
@@ -113,11 +120,61 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
(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) {

View File

@@ -3,30 +3,27 @@
package io.dico.parcels2.defaultimpl
import io.dico.parcels2.*
import io.dico.parcels2.blockvisitor.JobDispatcher
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(override val world: World,
override val generator: ParcelGenerator,
override var options: RuntimeWorldOptions,
override val storage: Storage,
override val globalPrivileges: GlobalPrivilegesManager,
containerFactory: ParcelContainerFactory,
coroutineScope: CoroutineScope,
jobDispatcher: JobDispatcher)
: ParcelWorld,
ParcelWorldId,
ParcelContainer, /* missing delegation */
ParcelLocator /* missing delegation */ {
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()
@@ -39,52 +36,40 @@ class ParcelWorldImpl(override val world: World,
override val blockManager: ParcelBlockManager
init {
val pair = generator.makeParcelLocatorAndBlockManager(id, container, coroutineScope, jobDispatcher)
locator = pair.first
blockManager = pair.second
val (locator, blockManager) = generator.makeParcelLocatorAndBlockManager(plugin.parcelProvider, container, plugin, plugin.jobDispatcher)
this.locator = locator
this.blockManager = blockManager
enforceOptions()
}
fun enforceOptions() {
if (options.dayTime) {
world.setGameRuleValue("doDaylightCycle", "false")
world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false)
world.setTime(6000)
}
if (options.noWeather) {
world.setStorm(false)
world.setThundering(false)
world.weatherDuration = Integer.MAX_VALUE
world.weatherDuration = Int.MAX_VALUE
}
world.setGameRuleValue("doTileDrops", "${options.doTileDrops}")
world.setGameRule(GameRule.DO_TILE_DROPS, options.doTileDrops)
}
// Updated by ParcelProviderImpl
// Accessed by ParcelProviderImpl
override var creationTime: DateTime? = null
/*
Interface delegation needs to be implemented manually because JetBrains has yet to fix it.
*/
// ParcelLocator interface
override fun getParcelAt(x: Int, z: Int): Parcel? {
return locator.getParcelAt(x, z)
}
override fun getParcelAt(x: Int, z: Int): Parcel? = locator.getParcelAt(x, z)
override fun getParcelIdAt(x: Int, z: Int): ParcelId? {
return locator.getParcelIdAt(x, z)
}
override fun getParcelIdAt(x: Int, z: Int): ParcelId? = locator.getParcelIdAt(x, z)
// ParcelContainer interface
override fun getParcelById(x: Int, z: Int): Parcel? {
return container.getParcelById(x, z)
}
override fun getParcelById(x: Int, z: Int): Parcel? = container.getParcelById(x, z)
override fun nextEmptyParcel(): Parcel? {
return container.nextEmptyParcel()
}
override fun getParcelById(id: ParcelId): Parcel? = container.getParcelById(id)
override fun toString() = toStringExt()
override fun nextEmptyParcel(): Parcel? = container.nextEmptyParcel()
override fun toString() = parcelWorldIdToString()
}

View File

@@ -2,16 +2,13 @@ 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.Dimension
import io.dico.parcels2.util.math.Vec3d
import io.dico.parcels2.util.math.Vec3i
import io.dico.parcels2.util.math.clampMax
import io.dico.parcels2.util.math.clampMin
import io.dico.parcels2.util.math.*
import org.bukkit.Location
import org.bukkit.Material.*
import org.bukkit.World
@@ -128,6 +125,8 @@ class ParcelListeners(
if (!canBuildOnArea(event.player, area)) {
event.isCancelled = true
}
area?.updateOwnerSign()
}
/*
@@ -253,10 +252,15 @@ class ParcelListeners(
}
}
onPlayerInteractEvent_RightClick(event, world, parcel)
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 -> onPlayerInteractEvent_RightClick(event, world, parcel)
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
@@ -265,7 +269,7 @@ class ParcelListeners(
}
@Suppress("NON_EXHAUSTIVE_WHEN")
private fun onPlayerInteractEvent_RightClick(event: PlayerInteractEvent, world: ParcelWorld, parcel: Parcel?) {
private fun onPlayerRightClick(event: PlayerInteractEvent, world: ParcelWorld, parcel: Parcel?) {
if (event.hasItem()) {
val item = event.item.type
if (world.options.blockedItems.contains(item)) {
@@ -275,7 +279,9 @@ class ParcelListeners(
if (!canBuildOnArea(event.player, parcel)) {
when (item) {
LAVA_BUCKET, WATER_BUCKET, BUCKET, FLINT_AND_STEEL -> event.isCancelled = true
LAVA_BUCKET, WATER_BUCKET, BUCKET, FLINT_AND_STEEL -> {
event.isCancelled = true
}
}
}
}
@@ -614,9 +620,9 @@ class ParcelListeners(
if (parcels.isEmpty()) return@l
parcels.forEach { id ->
val parcel = world.getParcelById(id)?.takeIf { it.ownerSignOutdated } ?: return@forEach
world.blockManager.setOwnerBlock(parcel.id, parcel.owner)
parcel.ownerSignOutdated = false
val parcel = world.getParcelById(id)?.takeIf { it.isOwnerSignOutdated } ?: return@forEach
world.blockManager.updateParcelInfo(parcel.id, parcel.owner)
parcel.isOwnerSignOutdated = false
}
}

View File

@@ -18,4 +18,5 @@ private class PlotmeMigrationFactory : PolymorphicOptionsFactory<Migration> {
}
class PlotmeMigrationOptions(val worldsFromTo: Map<String, String> = mapOf("plotworld" to "parcels"),
val storage: StorageOptions = StorageOptions(options = DataConnectionOptions(database = "plotme")))
val storage: StorageOptions = StorageOptions(options = DataConnectionOptions(database = "plotme")),
val tableNamesUppercase: Boolean = false)

View File

@@ -1,6 +1,6 @@
package io.dico.parcels2.options
import io.dico.parcels2.blockvisitor.TickJobtimeOptions
import io.dico.parcels2.TickJobtimeOptions
import org.bukkit.GameMode
import org.bukkit.Material
import java.io.Reader

View File

@@ -43,14 +43,14 @@ interface Backing {
fun transmitAllParcelData(channel: SendChannel<DataPair>)
fun readParcelData(parcel: ParcelId): ParcelData?
fun readParcelData(parcel: ParcelId): ParcelDataHolder?
fun getOwnedParcels(user: PlayerProfile): List<ParcelId>
fun getNumParcels(user: PlayerProfile): Int = getOwnedParcels(user).size
fun setParcelData(parcel: ParcelId, data: ParcelData?)
fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?)
fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?)

View File

@@ -13,7 +13,7 @@ import org.joda.time.DateTime
import java.util.UUID
import kotlin.coroutines.CoroutineContext
typealias DataPair = Pair<ParcelId, ParcelData?>
typealias DataPair = Pair<ParcelId, ParcelDataHolder?>
typealias PrivilegePair<TAttach> = Pair<TAttach, PrivilegesHolder>
interface Storage {
@@ -33,7 +33,7 @@ interface Storage {
fun updatePlayerName(uuid: UUID, name: String): Job
fun readParcelData(parcel: ParcelId): Deferred<ParcelData?>
fun readParcelData(parcel: ParcelId): Deferred<ParcelDataHolder?>
fun transmitParcelData(parcels: Sequence<ParcelId>): ReceiveChannel<DataPair>
@@ -44,7 +44,7 @@ interface Storage {
fun getNumParcels(user: PlayerProfile): Deferred<Int>
fun setParcelData(parcel: ParcelId, data: ParcelData?): Job
fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?): Job
fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?): Job
@@ -62,7 +62,7 @@ interface Storage {
fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege): Job
fun getChannelToUpdateParcelData(): SendChannel<Pair<ParcelId, ParcelData>>
fun getChannelToUpdateParcelData(): SendChannel<Pair<ParcelId, ParcelDataHolder>>
}
class BackedStorage internal constructor(val b: Backing) : Storage, CoroutineScope {
@@ -93,7 +93,7 @@ class BackedStorage internal constructor(val b: Backing) : Storage, CoroutineSco
override fun getNumParcels(user: PlayerProfile) = b.launchFuture { b.getNumParcels(user) }
override fun setParcelData(parcel: ParcelId, data: ParcelData?) = b.launchJob { b.setParcelData(parcel, data) }
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) }
@@ -110,5 +110,5 @@ class BackedStorage internal constructor(val b: Backing) : Storage, CoroutineSco
override fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege) = b.launchJob { b.setGlobalPrivilege(owner, player, privilege) }
override fun getChannelToUpdateParcelData(): SendChannel<Pair<ParcelId, ParcelData>> = b.openChannelForWriting { b.setParcelData(it.first, it.second) }
override fun getChannelToUpdateParcelData(): SendChannel<Pair<ParcelId, ParcelDataHolder>> = b.openChannelForWriting { b.setParcelData(it.first, it.second) }
}

View File

@@ -9,6 +9,7 @@ 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
@@ -152,7 +153,7 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
channel.close()
}
override fun readParcelData(parcel: ParcelId): ParcelData? {
override fun readParcelData(parcel: ParcelId): ParcelDataHolder? {
val row = ParcelsT.getRow(parcel) ?: return null
return rowToParcelData(row)
}
@@ -165,7 +166,7 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
.toList()
}
override fun setParcelData(parcel: ParcelId, data: ParcelData?) {
override fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?) {
if (data == null) {
transaction {
ParcelsT.getId(parcel)?.let { id ->
@@ -262,7 +263,7 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply {
owner = row[ParcelsT.owner_id]?.let { ProfilesT.getItem(it) }
lastClaimTime = row[ParcelsT.claim_time]
ownerSignOutdated = row[ParcelsT.sign_oudated]
isOwnerSignOutdated = row[ParcelsT.sign_oudated]
val id = row[ParcelsT.id]
ParcelOptionsT.select { ParcelOptionsT.parcel_id eq id }.firstOrNull()?.let { optrow ->

View File

@@ -5,8 +5,7 @@ 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) {
class UpsertStatement<Key : Any>(table: Table, conflictColumn: Column<*>? = null, conflictIndex: Index? = null) : InsertStatement<Key>(table, false) {
val indexName: String
val indexColumns: List<Column<*>>
@@ -66,9 +65,10 @@ 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>> SqlExpressionBuilder.greater(col1: ExpressionWithColumnType<T>, col2: ExpressionWithColumnType<T>): Expression<T> {
return case(col1)
.When(col1.greater(col2), col1)
.Else(col2)
}
fun <T : Comparable<T>> greaterOf(col1: ExpressionWithColumnType<T>, col2: ExpressionWithColumnType<T>): Expression<T> =
with(SqlExpressionBuilder) {
case(col1)
.When(col1.greater(col2), col1)
.Else(col2)
}

View File

@@ -7,8 +7,10 @@ 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.greater
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
@@ -24,6 +26,7 @@ class PlotmeMigration(val options: PlotmeMigrationOptions) : Migration {
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)
@@ -51,9 +54,9 @@ class PlotmeMigration(val options: PlotmeMigrationOptions) : Migration {
isShutdown = true
}
suspend fun doWork(target: Storage) {
suspend fun doWork(target: Storage) = with (tables) {
val exit = transaction {
(!PlotmePlotsT.exists()).also {
(!PlotmePlots.exists()).also {
if (it) mlogger.warn("Plotme tables don't appear to exist. Exiting.")
}
}
@@ -75,29 +78,32 @@ class PlotmeMigration(val options: PlotmeMigrationOptions) : Migration {
}
mlogger.info("Transmitting data from plotmeplots table")
var count = 0
transaction {
PlotmePlotsT.selectAll()
.orderBy(PlotmePlotsT.world_name)
.orderBy(with(SqlExpressionBuilder) { greater(PlotmePlotsT.px.abs(), PlotmePlotsT.pz.abs()) })
PlotmePlots.selectAll()
.orderBy(PlotmePlots.world_name)
.orderBy(greaterOf(PlotmePlots.px.abs(), PlotmePlots.pz.abs()))
.forEach { row ->
val parcel = getParcelId(PlotmePlotsT, row) ?: return@forEach
val owner = PlayerProfile.safe(row[PlotmePlotsT.owner_uuid]?.toUUID(), row[PlotmePlotsT.owner_name])
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 {
PlotmeAllowedT.transmitPlotmeAddedTable(Privilege.CAN_BUILD)
PlotmeAllowed.transmitPlotmeAddedTable(Privilege.CAN_BUILD)
}
mlogger.info("Transmitting data from plotmedenied table")
transaction {
PlotmeDeniedT.transmitPlotmeAddedTable(Privilege.BANNED)
PlotmeDenied.transmitPlotmeAddedTable(Privilege.BANNED)
}
mlogger.warn("Data has been **transmitted**.")
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.")
}

View File

@@ -2,25 +2,30 @@ package io.dico.parcels2.storage.migration.plotme
import org.jetbrains.exposed.sql.Table
const val uppercase: Boolean = false
@Suppress("ConstantConditionIf")
fun String.toCorrectCase() = if (uppercase) this else toLowerCase()
class PlotmeTables(val uppercase: Boolean) {
fun String.toCorrectCase() = if (uppercase) this else toLowerCase()
sealed class PlotmeTable(name: String) : Table(name) {
val px = integer("idX").primaryKey()
val pz = integer("idZ").primaryKey()
val world_name = varchar("world", 32).primaryKey()
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())
}
object PlotmePlotsT : PlotmeTable("plotmePlots".toCorrectCase()) {
val owner_name = varchar("owner", 32)
val owner_uuid = blob("ownerid").nullable()
}
sealed class PlotmePlotPlayerMap(name: String) : PlotmeTable(name) {
val player_name = varchar("player", 32)
val player_uuid = blob("playerid").nullable()
}
object PlotmeAllowedT : PlotmePlotPlayerMap("plotmeAllowed".toCorrectCase())
object PlotmeDeniedT : PlotmePlotPlayerMap("plotmeDenied".toCorrectCase())

View File

@@ -10,3 +10,5 @@ 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"

View File

@@ -3,4 +3,7 @@ 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)
}

View File

@@ -11,7 +11,9 @@ data class Vec3i(
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)