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 // not on sk89q maven repo yet
compileClasspath(files("$rootDir/debug/plugins/worldedit-bukkit-7.0.0-beta-01.jar")) 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("org.jetbrains.exposed:exposed:0.10.5") { isTransitive = false }
compile("joda-time:joda-time:2.10") 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 io.dico.parcels2.util.math.clampMin
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -23,9 +21,9 @@ data class TickJobtimeOptions(var jobTime: Int, var tickInterval: Int)
interface JobDispatcher { 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 * Get a list of all jobs
@@ -55,7 +53,7 @@ interface Job : JobAndScopeMembersUnion {
/** /**
* The coroutine associated with this job * The coroutine associated with this job
*/ */
val job: CoroutineJob val coroutine: CoroutineJob
/** /**
* true if this job has completed * true if this job has completed
@@ -147,8 +145,8 @@ class BukkitJobDispatcher(private val plugin: ParcelsPlugin, var options: TickJo
private val _jobs = LinkedList<JobInternal>() private val _jobs = LinkedList<JobInternal>()
override val jobs: List<Job> = _jobs override val jobs: List<Job> = _jobs
override fun dispatch(task: JobFunction): Job { override fun dispatch(function: JobFunction): Job {
val job: JobInternal = JobImpl(plugin, task) val job: JobInternal = JobImpl(plugin, function)
if (bukkitTask == null) { if (bukkitTask == null) {
val completed = job.resume(options.jobTime.toLong()) 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 { 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 continuation: Continuation<Unit>? = null
private var nextSuspensionTime: Long = 0L private var nextSuspensionTime: Long = 0L
@@ -207,10 +205,10 @@ private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal {
override val elapsedTime override val elapsedTime
get() = get() =
if (job.isCompleted) startTimeOrElapsedTime if (coroutine.isCompleted) startTimeOrElapsedTime
else currentTimeMillis() - startTimeOrElapsedTime else currentTimeMillis() - startTimeOrElapsedTime
override val isComplete get() = job.isCompleted override val isComplete get() = coroutine.isCompleted
private var _progress = 0.0 private var _progress = 0.0
override val progress get() = _progress override val progress get() = _progress
@@ -223,7 +221,7 @@ private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal {
private var onCompleted: JobUpdateLister? = null private var onCompleted: JobUpdateLister? = null
init { init {
job.invokeOnCompletion { exception -> coroutine.invokeOnCompletion { exception ->
// report any error that occurred // report any error that occurred
completionException = exception?.also { completionException = exception?.also {
if (it !is CancellationException) if (it !is CancellationException)
@@ -306,13 +304,13 @@ private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal {
isStarted = true isStarted = true
startTimeOrElapsedTime = System.currentTimeMillis() startTimeOrElapsedTime = System.currentTimeMillis()
job.start() coroutine.start()
return continuation == null return continuation == null
} }
override suspend fun awaitCompletion() { override suspend fun awaitCompletion() {
job.join() coroutine.join()
} }
private fun delegateProgress(curPortion: Double, portion: Double): JobScope = private fun delegateProgress(curPortion: Double, portion: Double): JobScope =

View File

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

View File

@@ -1,15 +1,18 @@
package io.dico.parcels2 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.Region
import io.dico.parcels2.util.math.Vec2i import io.dico.parcels2.util.math.Vec2i
import io.dico.parcels2.util.math.Vec3i
import io.dico.parcels2.util.math.get import io.dico.parcels2.util.math.get
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import org.bukkit.Chunk import org.bukkit.Chunk
import org.bukkit.Location import org.bukkit.Location
import org.bukkit.Material
import org.bukkit.World import org.bukkit.World
import org.bukkit.block.Biome import org.bukkit.block.Biome
import org.bukkit.block.Block import org.bukkit.block.Block
import org.bukkit.block.BlockFace
import org.bukkit.entity.Entity import org.bukkit.entity.Entity
import org.bukkit.generator.BlockPopulator import org.bukkit.generator.BlockPopulator
import org.bukkit.generator.ChunkGenerator import org.bukkit.generator.ChunkGenerator
@@ -34,10 +37,12 @@ abstract class ParcelGenerator : ChunkGenerator() {
}) })
} }
abstract fun makeParcelLocatorAndBlockManager(worldId: ParcelWorldId, abstract fun makeParcelLocatorAndBlockManager(
container: ParcelContainer, parcelProvider: ParcelProvider,
coroutineScope: CoroutineScope, container: ParcelContainer,
jobDispatcher: JobDispatcher): Pair<ParcelLocator, ParcelBlockManager> coroutineScope: CoroutineScope,
jobDispatcher: JobDispatcher
): Pair<ParcelLocator, ParcelBlockManager>
} }
interface ParcelBlockManager { interface ParcelBlockManager {
@@ -45,7 +50,7 @@ interface ParcelBlockManager {
val jobDispatcher: JobDispatcher val jobDispatcher: JobDispatcher
val parcelTraverser: RegionTraverser val parcelTraverser: RegionTraverser
// fun getBottomBlock(parcel: ParcelId): Vec2i fun getRegionOrigin(parcel: ParcelId) = getRegion(parcel).origin.toVec2i()
fun getHomeLocation(parcel: ParcelId): Location fun getHomeLocation(parcel: ParcelId): Location
@@ -53,15 +58,15 @@ interface ParcelBlockManager {
fun getEntities(parcel: ParcelId): Collection<Entity> 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 * Used to update owner blocks in the corner of the parcel
@@ -69,9 +74,12 @@ interface ParcelBlockManager {
fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i> fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i>
} }
inline fun ParcelBlockManager.doBlockOperation(parcel: ParcelId, inline fun ParcelBlockManager.tryDoBlockOperation(
traverser: RegionTraverser, parcelProvider: ParcelProvider,
crossinline operation: suspend JobScope.(Block) -> Unit) = submitBlockVisitor(parcel) { parcel: ParcelId,
traverser: RegionTraverser,
crossinline operation: suspend JobScope.(Block) -> Unit
) = parcelProvider.trySubmitBlockVisitor(Permit(), arrayOf(parcel)) {
val region = getRegion(parcel) val region = getRegion(parcel)
val blockCount = region.blockCount.toDouble() val blockCount = region.blockCount.toDouble()
val blocks = traverser.traverseRegion(region) val blocks = traverser.traverseRegion(region)

View File

@@ -1,3 +1,5 @@
@file:Suppress("FunctionName")
package io.dico.parcels2 package io.dico.parcels2
import io.dico.parcels2.util.math.Vec2i 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) 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) } 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 * 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 pos: Vec2i get() = Vec2i(x, z)
val idString get() = "$x,$z" val idString get() = "$x,$z"
fun equals(id: ParcelId): Boolean = x == id.x && z == id.z && worldId.equals(id.worldId) 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, private class ParcelWorldIdImpl(override val name: String,
override val uid: UUID?) : ParcelWorldId { override val uid: UUID?) : ParcelWorldId {
override fun toString() = toStringExt() override fun toString() = parcelWorldIdToString()
} }
private class ParcelIdImpl(override val worldId: ParcelWorldId, private class ParcelIdImpl(override val worldId: ParcelWorldId,
override val x: Int, override val x: Int,
override val z: Int) : ParcelId { 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.block.Block
import org.bukkit.entity.Entity import org.bukkit.entity.Entity
import org.joda.time.DateTime import org.joda.time.DateTime
import java.lang.IllegalStateException
import java.util.UUID import java.util.UUID
class Permit
interface ParcelProvider { interface ParcelProvider {
val worlds: Map<String, ParcelWorld> val worlds: Map<String, ParcelWorld>
@@ -43,6 +46,15 @@ interface ParcelProvider {
fun getWorldGenerator(worldName: String): ParcelGenerator? fun getWorldGenerator(worldName: String): ParcelGenerator?
fun loadWorlds() 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 { interface ParcelLocator {
@@ -69,7 +81,7 @@ interface ParcelContainer {
fun getParcelById(id: Vec2i): Parcel? = getParcelById(id.x, id.z) 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? fun nextEmptyParcel(): Parcel?
@@ -88,5 +100,4 @@ interface ParcelWorld : ParcelLocator, ParcelContainer {
val globalPrivileges: GlobalPrivilegesManager val globalPrivileges: GlobalPrivilegesManager
val creationTime: DateTime? val creationTime: DateTime?
} }

View File

@@ -3,8 +3,6 @@ package io.dico.parcels2
import io.dico.dicore.Registrator import io.dico.dicore.Registrator
import io.dico.dicore.command.EOverridePolicy import io.dico.dicore.command.EOverridePolicy
import io.dico.dicore.command.ICommandDispatcher 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.command.getParcelCommands
import io.dico.parcels2.defaultimpl.GlobalPrivilegesManagerImpl import io.dico.parcels2.defaultimpl.GlobalPrivilegesManagerImpl
import io.dico.parcels2.defaultimpl.ParcelProviderImpl 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.MainThreadDispatcher
import io.dico.parcels2.util.PluginScheduler import io.dico.parcels2.util.PluginScheduler
import io.dico.parcels2.util.ext.tryCreate import io.dico.parcels2.util.ext.tryCreate
import io.dico.parcels2.util.isServerThread
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.generator.ChunkGenerator import org.bukkit.generator.ChunkGenerator
@@ -47,6 +46,7 @@ class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginScheduler {
val jobDispatcher: JobDispatcher by lazy { BukkitJobDispatcher(this, options.tickJobtime) } val jobDispatcher: JobDispatcher by lazy { BukkitJobDispatcher(this, options.tickJobtime) }
override fun onEnable() { override fun onEnable() {
plogger.info("Is server thread: ${isServerThread()}")
plogger.info("Debug enabled: ${plogger.isDebugEnabled}") plogger.info("Debug enabled: ${plogger.isDebugEnabled}")
plogger.debug(System.getProperty("user.dir")) plogger.debug(System.getProperty("user.dir"))
if (!init()) { if (!init()) {

View File

@@ -6,27 +6,89 @@ import io.dico.parcels2.util.math.Vec3i
import io.dico.parcels2.util.math.clampMax import io.dico.parcels2.util.math.clampMax
private typealias Scope = SequenceScope<Vec3i> private typealias Scope = SequenceScope<Vec3i>
/*
class ParcelTraverser(
val parcelProvider: ParcelProvider,
val delegate: RegionTraverser,
scope: CoroutineScope
) : RegionTraverser(), CoroutineScope by scope {
sealed class RegionTraverser { class OccupiedException(parcelId: ParcelId) : Exception("Parcel $parcelId is occupied")
fun traverseRegion(region: Region, worldHeight: Int = 256): Iterable<Vec3i> =
Iterable { iterator<Vec3i> { build(validify(region, worldHeight)) } }
private fun validify(region: Region, worldHeight: Int): Region { /**
if (region.origin.y < 0) { * Traverse the blocks of parcel's land
val origin = region.origin withY 0 * The iterator must be exhausted, else the permit to traverse it will not be reclaimed.
val size = region.size.withY((region.size.y + region.origin.y).clampMax(worldHeight)) *
return Region(origin, size) * @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 region = world.blockManager.getRegion(parcelId)
val size = region.size.withY(worldHeight - region.origin.y) return traverseRegion(region, world.world.maxHeight, medium)
return Region(region.origin, size)
}
return region
} }
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 { companion object {
val upward = Directional(TraverseDirection(1, 1, 1), TraverseOrderFactory.createWith(Dimension.Y, Dimension.X)) 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 toClear get() = downward
val toFill get() = upward 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) 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) 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( 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 order = order
val (primary, secondary, tertiary) = order.toArray() val (primary, secondary, tertiary) = order.toArray()
val (origin, size) = region val (origin, size) = region
@@ -71,6 +159,7 @@ sealed class RegionTraverser {
} }
} }
/*medium.iterationCompleted()*/
} }
} }
@@ -91,7 +180,7 @@ sealed class RegionTraverser {
return region to null 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) val (bottom, top) = slice(region, bottomSectionMaxY)
if (bottomFirst) { if (bottomFirst) {
@@ -101,15 +190,22 @@ sealed class RegionTraverser {
top?.let { with(topTraverser) { build(it) } } top?.let { with(topTraverser) { build(it) } }
with(bottomTraverser) { build(bottom) } 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 { fun childForPosition(position: Vec3i): Directional {
var cur = this var cur = this
while (true) { while (true) {
when (cur) { when (cur) {
is Directional -> /*is ParcelTraverser -> cur = cur.delegate*/
return cur is Directional -> return cur
is Slicing -> is Slicing ->
cur = cur =
if (position.y <= cur.bottomSectionMaxY) cur.bottomTraverser 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 { fun comesFirst(current: Vec3i, block: Vec3i): Boolean {
var cur = this var cur = this
while (true) { while (true) {
when (cur) { when (cur) {
/*is ParcelTraverser -> cur = cur.delegate*/
is Directional -> return cur.direction.comesFirst(current, block) is Directional -> return cur.direction.comesFirst(current, block)
is Slicing -> { is Slicing -> {
val border = cur.bottomSectionMaxY val border = cur.bottomSectionMaxY

View File

@@ -1,5 +1,7 @@
package io.dico.parcels2.blockvisitor 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.Region
import io.dico.parcels2.util.math.Vec3i import io.dico.parcels2.util.math.Vec3i
import io.dico.parcels2.util.math.get import io.dico.parcels2.util.math.get

View File

@@ -1,12 +1,12 @@
package io.dico.parcels2.command package io.dico.parcels2.command
import io.dico.dicore.command.* import io.dico.dicore.command.CommandException
import io.dico.parcels2.ParcelWorld import io.dico.dicore.command.EMessageType
import io.dico.parcels2.ParcelsPlugin import io.dico.dicore.command.ExecutionContext
import io.dico.parcels2.PlayerProfile import io.dico.dicore.command.ICommandReceiver
import io.dico.parcels2.PlayerProfile.* import io.dico.parcels2.*
import io.dico.parcels2.PrivilegeKey import io.dico.parcels2.PlayerProfile.Real
import io.dico.parcels2.blockvisitor.Job import io.dico.parcels2.PlayerProfile.Unresolved
import io.dico.parcels2.util.ext.hasPermAdminManage import io.dico.parcels2.util.ext.hasPermAdminManage
import io.dico.parcels2.util.ext.parcelLimit import io.dico.parcels2.util.ext.parcelLimit
import org.bukkit.entity.Player import org.bukkit.entity.Player
@@ -42,15 +42,13 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei
else -> throw CommandException() else -> throw CommandException()
} }
protected fun areYouSureMessage(context: ExecutionContext) = "Are you sure? You cannot undo this action!\n" + protected fun areYouSureMessage(context: ExecutionContext): String {
"Run \"/${context.route.joinToString(" ")} -sure\" if you want to go through with this." val command = (context.route + context.original).joinToString(" ") + " -sure"
return "Are you sure? You cannot undo this action!\n" +
protected fun ParcelScope.clearWithProgressUpdates(context: ExecutionContext, action: String) { "Run \"/$command\" if you want to go through with this."
Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel")
world.blockManager.clearParcel(parcel.id)
} }
protected fun Job.reportProgressUpdates(context: ExecutionContext, action: String) { protected fun Job.reportProgressUpdates(context: ExecutionContext, action: String): Job =
onProgressUpdate(1000, 1000) { progress, elapsedTime -> onProgressUpdate(1000, 1000) { progress, elapsedTime ->
val alt = context.getFormat(EMessageType.NUMBER) val alt = context.getFormat(EMessageType.NUMBER)
val main = context.getFormat(EMessageType.INFORMATIVE) val main = context.getFormat(EMessageType.INFORMATIVE)
@@ -59,7 +57,6 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei
.format(progress * 100, elapsedTime / 1000.0) .format(progress * 100, elapsedTime / 1000.0)
) )
} }
}
override fun getCoroutineContext() = plugin.coroutineContext 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.Validate
import io.dico.dicore.command.annotation.Cmd import io.dico.dicore.command.annotation.Cmd
import io.dico.dicore.command.annotation.Flag import io.dico.dicore.command.annotation.Flag
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.*
import io.dico.parcels2.PlayerProfile
import io.dico.parcels2.Privilege
import io.dico.parcels2.command.ParcelTarget.TargetKind 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) { 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})" 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") @Cmd("dispose")
@RequireParcelPrivilege(Privilege.ADMIN) @RequireParcelPrivilege(Privilege.ADMIN)
fun ParcelScope.cmdDispose(): Any? { fun ParcelScope.cmdDispose(): Any? {
@@ -37,15 +63,17 @@ class CommandsAdmin(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
if (!sure) return areYouSureMessage(context) if (!sure) return areYouSureMessage(context)
parcel.dispose() 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" return "Data of (${parcel.id.idString}) has been disposed"
} }
@Cmd("swap") @Cmd("swap")
@RequireParcelPrivilege(Privilege.ADMIN) @RequireParcelPrivilege(Privilege.ADMIN)
fun ParcelScope.cmdSwap(context: ExecutionContext, fun ParcelScope.cmdSwap(
@TargetKind(TargetKind.ID) target: ParcelTarget, context: ExecutionContext,
@Flag sure: Boolean): Any? { @TargetKind(TargetKind.ID) target: ParcelTarget,
@Flag sure: Boolean
): Any? {
Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel") Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel")
if (!sure) return areYouSureMessage(context) if (!sure) return areYouSureMessage(context)
@@ -53,13 +81,14 @@ class CommandsAdmin(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
?: throw CommandException("Invalid parcel target") ?: throw CommandException("Invalid parcel target")
// Validate.isTrue(parcel2.world == world, "Parcel must be in the same world") // 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 val data = parcel.data
parcel.copyData(parcel2.data) parcel.copyData(parcel2.data)
parcel2.copyData(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 return null
} }

View File

@@ -1,5 +1,6 @@
package io.dico.parcels2.command package io.dico.parcels2.command
import io.dico.dicore.Formatting
import io.dico.dicore.command.* import io.dico.dicore.command.*
import io.dico.dicore.command.IContextFilter.Priority.PERMISSION import io.dico.dicore.command.IContextFilter.Priority.PERMISSION
import io.dico.dicore.command.annotation.Cmd import io.dico.dicore.command.annotation.Cmd
@@ -54,9 +55,9 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
) )
val random = Random() 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)] block.blockData = blockDatas[random.nextInt(7)]
}.onProgressUpdate(1000, 1000) { progress, elapsedTime -> }?.onProgressUpdate(1000, 1000) { progress, elapsedTime ->
context.sendMessage( context.sendMessage(
EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed" EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed"
.format(progress * 100, elapsedTime / 1000.0) .format(progress * 100, elapsedTime / 1000.0)
@@ -82,7 +83,7 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@Cmd("jobs") @Cmd("jobs")
fun cmdJobs(): Any? { fun cmdJobs(): Any? {
val workers = plugin.jobDispatcher.jobs 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}" return "Task count: ${workers.size}"
} }
@@ -95,7 +96,7 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@PreprocessArgs @PreprocessArgs
fun cmdMessage(sender: CommandSender, message: String): Any? { fun cmdMessage(sender: CommandSender, message: String): Any? {
// testing @PreprocessArgs which merges "hello there" into a single argument // testing @PreprocessArgs which merges "hello there" into a single argument
sender.sendMessage(message) sender.sendMessage(Formatting.translate(message))
return null return null
} }

View File

@@ -127,7 +127,7 @@ class CommandsGeneral(plugin: ParcelsPlugin, parent: SpecialCommandAddress) : Ab
fun ParcelScope.cmdClear(context: ExecutionContext, @Flag sure: Boolean): Any? { fun ParcelScope.cmdClear(context: ExecutionContext, @Flag sure: Boolean): Any? {
Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel") Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel")
if (!sure) return areYouSureMessage(context) if (!sure) return areYouSureMessage(context)
world.blockManager.clearParcel(parcel.id).reportProgressUpdates(context, "Clear") world.blockManager.clearParcel(parcel.id)?.reportProgressUpdates(context, "Clear")
return null return null
} }
@@ -135,7 +135,7 @@ class CommandsGeneral(plugin: ParcelsPlugin, parent: SpecialCommandAddress) : Ab
@RequireParcelPrivilege(Privilege.OWNER) @RequireParcelPrivilege(Privilege.OWNER)
fun ParcelScope.cmdSetbiome(context: ExecutionContext, biome: Biome): Any? { fun ParcelScope.cmdSetbiome(context: ExecutionContext, biome: Biome): Any? {
Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel") 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 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 { override fun parse(parameter: Parameter<ParcelTarget, Int>, sender: CommandSender, buffer: ArgumentBuffer): ParcelTarget {
var input = buffer.next() var input = buffer.next()
val worldString = input.substringBefore("->", missingDelimiterValue = "") val worldString = input.substringBefore("/", missingDelimiterValue = "")
input = input.substringAfter("->") input = input.substringAfter("/")
val world = if (worldString.isEmpty()) { val world = if (worldString.isEmpty()) {
val player = requirePlayer(sender, parameter, "the world") 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) { for (array in parcels) {
yieldAll(array.iterator()) yieldAll(array.iterator())
} }

View File

@@ -1,22 +1,14 @@
package io.dico.parcels2.defaultimpl package io.dico.parcels2.defaultimpl
import io.dico.parcels2.* 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.options.DefaultGeneratorOptions
import io.dico.parcels2.util.math.Region import io.dico.parcels2.util.math.*
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 kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart.UNDISPATCHED
import kotlinx.coroutines.launch
import org.bukkit.* import org.bukkit.*
import org.bukkit.block.Biome import org.bukkit.block.Biome
import org.bukkit.block.BlockFace import org.bukkit.block.BlockFace
import org.bukkit.block.Skull import org.bukkit.block.Skull
import org.bukkit.block.data.BlockData
import org.bukkit.block.data.type.Slab import org.bukkit.block.data.type.Slab
import org.bukkit.block.data.type.WallSign import org.bukkit.block.data.type.WallSign
import java.util.Random import java.util.Random
@@ -118,12 +110,13 @@ class DefaultParcelGenerator(
} }
override fun makeParcelLocatorAndBlockManager( override fun makeParcelLocatorAndBlockManager(
worldId: ParcelWorldId, parcelProvider: ParcelProvider,
container: ParcelContainer, container: ParcelContainer,
coroutineScope: CoroutineScope, coroutineScope: CoroutineScope,
jobDispatcher: JobDispatcher jobDispatcher: JobDispatcher
): Pair<ParcelLocator, ParcelBlockManager> { ): 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? { private inline fun <T> convertBlockLocationToId(x: Int, z: Int, mapper: (Int, Int) -> T): T? {
@@ -139,11 +132,25 @@ class DefaultParcelGenerator(
return null return null
} }
private inner class ParcelLocatorImpl( @Suppress("DEPRECATION")
val worldId: ParcelWorldId, private inner class ParcelLocatorAndBlockManagerImpl(
val container: ParcelContainer val parcelProvider: ParcelProvider,
) : ParcelLocator { val container: ParcelContainer,
override val world: World = this@DefaultParcelGenerator.world 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? { override fun getParcelAt(x: Int, z: Int): Parcel? {
return convertBlockLocationToId(x, z, container::getParcelById) return convertBlockLocationToId(x, z, container::getParcelById)
@@ -152,112 +159,113 @@ class DefaultParcelGenerator(
override fun getParcelIdAt(x: Int, z: Int): ParcelId? { override fun getParcelIdAt(x: Int, z: Int): ParcelId? {
return convertBlockLocationToId(x, z) { idx, idz -> ParcelId(worldId, idx, idz) } 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( private fun checkParcelId(parcel: ParcelId): ParcelId {
sectionSize * (parcel.x - 1) + pathOffset + o.offsetX, if (!parcel.worldId.equals(worldId)) {
sectionSize * (parcel.z - 1) + pathOffset + o.offsetZ throw IllegalArgumentException()
) }
return parcel
}
override fun getHomeLocation(parcel: ParcelId): Location { override fun getRegionOrigin(parcel: ParcelId): Vec2i {
val bottom = getBottomBlock(parcel) checkParcelId(parcel)
val x = bottom.x + (o.parcelSize - 1) / 2.0 return Vec2i(
val z = bottom.z - 2 sectionSize * (parcel.x - 1) + pathOffset + o.offsetX,
return Location(world, x + 0.5, o.floorHeight + 1.0, z + 0.5, 0F, 0F) sectionSize * (parcel.z - 1) + pathOffset + o.offsetZ
)
} }
override fun getRegion(parcel: ParcelId): Region { override fun getRegion(parcel: ParcelId): Region {
val bottom = getBottomBlock(parcel) val origin = getRegionOrigin(parcel)
return Region( return Region(
Vec3i(bottom.x, 0, bottom.z), Vec3i(origin.x, 0, origin.z),
Vec3i(o.parcelSize, maxHeight, o.parcelSize) Vec3i(o.parcelSize, maxHeight, o.parcelSize)
) )
} }
private fun getRegionConsideringWorld(parcel: ParcelId): Region { override fun getHomeLocation(parcel: ParcelId): Location {
if (parcel.worldId != worldId) { val origin = getRegionOrigin(parcel)
(parcel.worldId as? ParcelWorld)?.let { val x = origin.x + (o.parcelSize - 1) / 2.0
return it.blockManager.getRegion(parcel) val z = origin.z - 2
} return Location(world, x + 0.5, o.floorHeight + 1.0, z + 0.5, 0F, 0F)
throw IllegalArgumentException()
}
return getRegion(parcel)
} }
override fun setOwnerBlock(parcel: ParcelId, owner: PlayerProfile?) { override fun getParcelForInfoBlockInteraction(block: Vec3i, type: Material, face: BlockFace): Parcel? {
val b = getBottomBlock(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 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) val skullBlock = world.getBlockAt(b.x - 1, o.floorHeight + 2, b.z - 1)
if (owner == null) { if (owner == null) {
wallBlock.blockData = o.wallType wallBlock.blockData = o.wallType
signBlock.type = Material.AIR signBlock.type = Material.AIR
skullBlock.type = Material.AIR skullBlock.type = Material.AIR
} else { } else {
cornerWallType?.let { wallBlock.blockData = it }
val wallBlockType: BlockData = if (o.wallType is Slab)
(o.wallType.clone() as Slab).apply { type = Slab.Type.DOUBLE }
else
o.wallType
wallBlock.blockData = wallBlockType
signBlock.blockData = (Bukkit.createBlockData(Material.WALL_SIGN) as WallSign).apply { facing = BlockFace.NORTH } signBlock.blockData = (Bukkit.createBlockData(Material.WALL_SIGN) as WallSign).apply { facing = BlockFace.NORTH }
val sign = signBlock.state as org.bukkit.block.Sign val sign = signBlock.state as org.bukkit.block.Sign
sign.setLine(0, "${parcel.x},${parcel.z}") sign.setLine(0, "${parcel.x},${parcel.z}")
sign.setLine(2, owner.name) sign.setLine(2, owner.name ?: "")
sign.update() sign.update()
skullBlock.type = Material.AIR
skullBlock.type = Material.PLAYER_HEAD skullBlock.type = Material.PLAYER_HEAD
val skull = skullBlock.state as Skull val skull = skullBlock.state as Skull
if (owner is PlayerProfile.Real) { if (owner is PlayerProfile.Real) {
skull.owningPlayer = Bukkit.getOfflinePlayer(owner.uuid) 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() skull.update()
} }
} }
private fun getParcel(parcelId: ParcelId): Parcel? { private fun trySubmitBlockVisitor(vararg parcels: ParcelId, function: JobFunction): Job? {
// todo dont rely on this cast parcels.forEach { checkParcelId(it) }
val world = worldId as? ParcelWorld ?: return null return parcelProvider.trySubmitBlockVisitor(Permit(), parcels, function)
return world.getParcelById(parcelId)
} }
override fun submitBlockVisitor(vararg parcelIds: ParcelId, task: JobFunction): Job { override fun setBiome(parcel: ParcelId, biome: Biome) = trySubmitBlockVisitor(checkParcelId(parcel)) {
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) {
val world = world val world = world
val b = getBottomBlock(parcel) val b = getRegionOrigin(parcel)
val parcelSize = o.parcelSize val parcelSize = o.parcelSize
for (x in b.x until b.x + parcelSize) { for (x in b.x until b.x + parcelSize) {
for (z in b.z until b.z + 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 region = getRegion(parcel)
val blocks = parcelTraverser.traverseRegion(region) val blocks = parcelTraverser.traverseRegion(region)
val blockCount = region.blockCount.toDouble() 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> { override fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i> {
/* /*
* Get the offsets for the world out of the way * 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.*
import io.dico.parcels2.Privilege.* import io.dico.parcels2.Privilege.*
import io.dico.parcels2.util.ext.alsoIfTrue import io.dico.parcels2.util.ext.alsoIfTrue
import io.dico.parcels2.util.isServerThread
import io.dico.parcels2.util.math.Vec2i import io.dico.parcels2.util.math.Vec2i
import org.bukkit.Material import org.bukkit.Material
import org.joda.time.DateTime 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 world: ParcelWorld,
override val x: Int, override val x: Int,
override val z: Int override val z: Int
) : Parcel, ParcelId { ) : Parcel, ParcelId {
override val id: ParcelId = this override val id: ParcelId get() = this
override val pos get() = Vec2i(x, z) override val pos get() = Vec2i(x, z)
override var data: ParcelDataHolder = ParcelDataHolder(); private set override var data = ParcelDataHolder(); private set
override val hasBlockVisitors get() = blockVisitors.get() > 0
override val worldId: ParcelWorldId get() = world.id override val worldId: ParcelWorldId get() = world.id
override fun copyDataIgnoringDatabase(data: ParcelData) { override fun copyData(newData: ParcelDataHolder, callerIsDatabase: Boolean) {
this.data = ((data as? Parcel)?.data ?: data) as ParcelDataHolder 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) world.storage.setParcelData(this, data)
} }
override fun dispose() {
copyDataIgnoringDatabase(ParcelDataHolder())
world.storage.setParcelData(this, null)
}
override var owner: PlayerProfile? override var owner: PlayerProfile?
get() = data.owner get() = data.owner
set(value) { set(value) {
if (data.owner != value) { if (data.owner != value) {
world.storage.setParcelOwner(this, value) world.storage.setParcelOwner(this, value)
world.blockManager.setOwnerBlock(this, value)
data.owner = value data.owner = value
updateOwnerSign(true, false, true)
} }
} }
override val lastClaimTime: DateTime? override val lastClaimTime: DateTime?
get() = data.lastClaimTime get() = data.lastClaimTime
override var ownerSignOutdated: Boolean override var isOwnerSignOutdated: Boolean
get() = data.ownerSignOutdated get() = data.isOwnerSignOutdated
set(value) { set(value) {
if (data.ownerSignOutdated != value) { if (data.isOwnerSignOutdated != value) {
world.storage.setParcelOwnerSignOutdated(this, 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 override val privilegeMap: PrivilegeMap
get() = data.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) private var blockVisitors = AtomicInteger(0)
override suspend fun withBlockVisitorPermit(block: suspend () -> Unit) { override suspend fun withBlockVisitorPermit(block: suspend () -> Unit) {
@@ -116,9 +158,9 @@ class ParcelImpl(
} finally { } finally {
blockVisitors.getAndDecrement() blockVisitors.getAndDecrement()
} }
} }*/
override fun toString() = toStringExt() override fun toString() = parcelIdToString()
override val infoString: String override val infoString: String
get() = getInfoString() get() = getInfoString()

View File

@@ -1,8 +1,10 @@
package io.dico.parcels2.defaultimpl package io.dico.parcels2.defaultimpl
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.blockvisitor.Schematic
import io.dico.parcels2.util.schedule import io.dico.parcels2.util.schedule
import kotlinx.coroutines.Unconfined import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.WorldCreator import org.bukkit.WorldCreator
@@ -44,10 +46,11 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
private fun loadWorlds0() { private fun loadWorlds0() {
if (Bukkit.getWorlds().isEmpty()) { if (Bukkit.getWorlds().isEmpty()) {
plugin.schedule(::loadWorlds0) 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 return
} }
val newlyCreatedWorlds = mutableListOf<ParcelWorld>()
for ((worldName, worldOptions) in options.worlds.entries) { for ((worldName, worldOptions) in options.worlds.entries) {
var parcelWorld = _worlds[worldName] var parcelWorld = _worlds[worldName]
if (parcelWorld != null) continue if (parcelWorld != null) continue
@@ -56,19 +59,20 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
val worldExists = Bukkit.getWorld(worldName) != null val worldExists = Bukkit.getWorld(worldName) != null
val bukkitWorld = val bukkitWorld =
if (worldExists) Bukkit.getWorld(worldName)!! 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( parcelWorld = ParcelWorldImpl(plugin, bukkitWorld, generator, worldOptions.runtime,::DefaultParcelContainer)
bukkitWorld, generator, worldOptions.runtime, plugin.storage,
plugin.globalPrivileges, ::DefaultParcelContainer, plugin, plugin.jobDispatcher
)
if (!worldExists) { if (!worldExists) {
val time = DateTime.now() val time = DateTime.now()
plugin.storage.setWorldCreationTime(parcelWorld.id, time) plugin.storage.setWorldCreationTime(parcelWorld.id, time)
parcelWorld.creationTime = time parcelWorld.creationTime = time
newlyCreatedWorlds.add(parcelWorld)
} else { } else {
launch(context = Unconfined) { GlobalScope.launch(context = Dispatchers.Unconfined) {
parcelWorld.creationTime = plugin.storage.getWorldCreationTime(parcelWorld.id).await() ?: DateTime.now() parcelWorld.creationTime = plugin.storage.getWorldCreationTime(parcelWorld.id).await() ?: DateTime.now()
} }
} }
@@ -76,11 +80,11 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
_worlds[worldName] = parcelWorld _worlds[worldName] = parcelWorld
} }
loadStoredData() loadStoredData(newlyCreatedWorlds.toSet())
} }
private fun loadStoredData() { private fun loadStoredData(newlyCreatedWorlds: Collection<ParcelWorld> = emptyList()) {
plugin.launch { plugin.launch(Dispatchers.Default) {
val migration = plugin.options.migration val migration = plugin.options.migration
if (migration.enabled) { if (migration.enabled) {
migration.instance?.newInstance()?.apply { migration.instance?.newInstance()?.apply {
@@ -96,11 +100,14 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
} }
logger.info("Loading all parcel data...") logger.info("Loading all parcel data...")
val channel = plugin.storage.transmitAllParcelData()
while (true) { val job1 = launch {
val (id, data) = channel.receiveOrNull() ?: break val channel = plugin.storage.transmitAllParcelData()
val parcel = getParcelById(id) ?: continue while (true) {
data?.let { parcel.copyDataIgnoringDatabase(it) } val (id, data) = channel.receiveOrNull() ?: break
val parcel = getParcelById(id) ?: continue
data?.let { parcel.copyData(it, callerIsDatabase = true) }
}
} }
val channel2 = plugin.storage.transmitAllGlobalPrivileges() val channel2 = plugin.storage.transmitAllGlobalPrivileges()
@@ -113,11 +120,61 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
(plugin.globalPrivileges[profile] as PrivilegesHolder).copyPrivilegesFrom(data) (plugin.globalPrivileges[profile] as PrivilegesHolder).copyPrivilegesFrom(data)
} }
job1.join()
logger.info("Loading data completed") logger.info("Loading data completed")
_dataIsLoaded = true _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) { fun loadWorlds(options: Options) {
for ((worldName, worldOptions) in options.worlds.entries) { for ((worldName, worldOptions) in options.worlds.entries) {

View File

@@ -3,30 +3,27 @@
package io.dico.parcels2.defaultimpl package io.dico.parcels2.defaultimpl
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.blockvisitor.JobDispatcher
import io.dico.parcels2.options.RuntimeWorldOptions import io.dico.parcels2.options.RuntimeWorldOptions
import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.Storage
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import org.bukkit.GameRule
import org.bukkit.World import org.bukkit.World
import org.joda.time.DateTime import org.joda.time.DateTime
import java.util.UUID import java.util.UUID
class ParcelWorldImpl(override val world: World, class ParcelWorldImpl(
override val generator: ParcelGenerator, val plugin: ParcelsPlugin,
override var options: RuntimeWorldOptions, override val world: World,
override val storage: Storage, override val generator: ParcelGenerator,
override val globalPrivileges: GlobalPrivilegesManager, override var options: RuntimeWorldOptions,
containerFactory: ParcelContainerFactory, containerFactory: ParcelContainerFactory
coroutineScope: CoroutineScope, ) : ParcelWorld, ParcelWorldId, ParcelContainer, ParcelLocator {
jobDispatcher: JobDispatcher)
: ParcelWorld,
ParcelWorldId,
ParcelContainer, /* missing delegation */
ParcelLocator /* missing delegation */ {
override val id: ParcelWorldId get() = this override val id: ParcelWorldId get() = this
override val uid: UUID? get() = world.uid override val uid: UUID? get() = world.uid
override val storage get() = plugin.storage
override val globalPrivileges get() = plugin.globalPrivileges
init { init {
if (generator.world != world) { if (generator.world != world) {
throw IllegalArgumentException() throw IllegalArgumentException()
@@ -39,52 +36,40 @@ class ParcelWorldImpl(override val world: World,
override val blockManager: ParcelBlockManager override val blockManager: ParcelBlockManager
init { init {
val pair = generator.makeParcelLocatorAndBlockManager(id, container, coroutineScope, jobDispatcher) val (locator, blockManager) = generator.makeParcelLocatorAndBlockManager(plugin.parcelProvider, container, plugin, plugin.jobDispatcher)
locator = pair.first this.locator = locator
blockManager = pair.second this.blockManager = blockManager
enforceOptions() enforceOptions()
} }
fun enforceOptions() { fun enforceOptions() {
if (options.dayTime) { if (options.dayTime) {
world.setGameRuleValue("doDaylightCycle", "false") world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false)
world.setTime(6000) world.setTime(6000)
} }
if (options.noWeather) { if (options.noWeather) {
world.setStorm(false) world.setStorm(false)
world.setThundering(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 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? = locator.getParcelAt(x, z)
override fun getParcelAt(x: Int, z: Int): Parcel? {
return locator.getParcelAt(x, z)
}
override fun getParcelIdAt(x: Int, z: Int): ParcelId? { override fun getParcelIdAt(x: Int, z: Int): ParcelId? = locator.getParcelIdAt(x, z)
return locator.getParcelIdAt(x, z)
}
// ParcelContainer interface override fun getParcelById(x: Int, z: Int): Parcel? = container.getParcelById(x, z)
override fun getParcelById(x: Int, z: Int): Parcel? {
return container.getParcelById(x, z)
}
override fun nextEmptyParcel(): Parcel? { override fun getParcelById(id: ParcelId): Parcel? = container.getParcelById(id)
return container.nextEmptyParcel()
}
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.TLongCollection
import gnu.trove.set.hash.TLongHashSet import gnu.trove.set.hash.TLongHashSet
import io.dico.dicore.Formatting
import io.dico.dicore.ListenerMarker import io.dico.dicore.ListenerMarker
import io.dico.dicore.RegistratorListener import io.dico.dicore.RegistratorListener
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.Storage
import io.dico.parcels2.util.ext.* import io.dico.parcels2.util.ext.*
import io.dico.parcels2.util.math.Dimension import io.dico.parcels2.util.math.*
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 org.bukkit.Location import org.bukkit.Location
import org.bukkit.Material.* import org.bukkit.Material.*
import org.bukkit.World import org.bukkit.World
@@ -128,6 +125,8 @@ class ParcelListeners(
if (!canBuildOnArea(event.player, area)) { if (!canBuildOnArea(event.player, area)) {
event.isCancelled = true 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"))) { Action.PHYSICAL -> if (!canBuildOnArea(user, parcel) && !(parcel != null && parcel.interactableConfig("pressure_plates"))) {
user.sendParcelMessage(nopermit = true, message = "You cannot use inputs in this parcel") user.sendParcelMessage(nopermit = true, message = "You cannot use inputs in this parcel")
event.isCancelled = true; return@l event.isCancelled = true; return@l
@@ -265,7 +269,7 @@ class ParcelListeners(
} }
@Suppress("NON_EXHAUSTIVE_WHEN") @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()) { if (event.hasItem()) {
val item = event.item.type val item = event.item.type
if (world.options.blockedItems.contains(item)) { if (world.options.blockedItems.contains(item)) {
@@ -275,7 +279,9 @@ class ParcelListeners(
if (!canBuildOnArea(event.player, parcel)) { if (!canBuildOnArea(event.player, parcel)) {
when (item) { 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 if (parcels.isEmpty()) return@l
parcels.forEach { id -> parcels.forEach { id ->
val parcel = world.getParcelById(id)?.takeIf { it.ownerSignOutdated } ?: return@forEach val parcel = world.getParcelById(id)?.takeIf { it.isOwnerSignOutdated } ?: return@forEach
world.blockManager.setOwnerBlock(parcel.id, parcel.owner) world.blockManager.updateParcelInfo(parcel.id, parcel.owner)
parcel.ownerSignOutdated = false 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"), 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 package io.dico.parcels2.options
import io.dico.parcels2.blockvisitor.TickJobtimeOptions import io.dico.parcels2.TickJobtimeOptions
import org.bukkit.GameMode import org.bukkit.GameMode
import org.bukkit.Material import org.bukkit.Material
import java.io.Reader import java.io.Reader

View File

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

View File

@@ -13,7 +13,7 @@ import org.joda.time.DateTime
import java.util.UUID import java.util.UUID
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
typealias DataPair = Pair<ParcelId, ParcelData?> typealias DataPair = Pair<ParcelId, ParcelDataHolder?>
typealias PrivilegePair<TAttach> = Pair<TAttach, PrivilegesHolder> typealias PrivilegePair<TAttach> = Pair<TAttach, PrivilegesHolder>
interface Storage { interface Storage {
@@ -33,7 +33,7 @@ interface Storage {
fun updatePlayerName(uuid: UUID, name: String): Job 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> fun transmitParcelData(parcels: Sequence<ParcelId>): ReceiveChannel<DataPair>
@@ -44,7 +44,7 @@ interface Storage {
fun getNumParcels(user: PlayerProfile): Deferred<Int> 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 fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?): Job
@@ -62,7 +62,7 @@ interface Storage {
fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege): Job 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 { 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 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) } 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 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.math.clampMax
import io.dico.parcels2.util.ext.synchronized import io.dico.parcels2.util.ext.synchronized
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.ArrayChannel import kotlinx.coroutines.channels.ArrayChannel
import kotlinx.coroutines.channels.LinkedListChannel import kotlinx.coroutines.channels.LinkedListChannel
import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.channels.ReceiveChannel
@@ -152,7 +153,7 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
channel.close() channel.close()
} }
override fun readParcelData(parcel: ParcelId): ParcelData? { override fun readParcelData(parcel: ParcelId): ParcelDataHolder? {
val row = ParcelsT.getRow(parcel) ?: return null val row = ParcelsT.getRow(parcel) ?: return null
return rowToParcelData(row) return rowToParcelData(row)
} }
@@ -165,7 +166,7 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
.toList() .toList()
} }
override fun setParcelData(parcel: ParcelId, data: ParcelData?) { override fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?) {
if (data == null) { if (data == null) {
transaction { transaction {
ParcelsT.getId(parcel)?.let { id -> ParcelsT.getId(parcel)?.let { id ->
@@ -262,7 +263,7 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply { private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply {
owner = row[ParcelsT.owner_id]?.let { ProfilesT.getItem(it) } owner = row[ParcelsT.owner_id]?.let { ProfilesT.getItem(it) }
lastClaimTime = row[ParcelsT.claim_time] lastClaimTime = row[ParcelsT.claim_time]
ownerSignOutdated = row[ParcelsT.sign_oudated] isOwnerSignOutdated = row[ParcelsT.sign_oudated]
val id = row[ParcelsT.id] val id = row[ParcelsT.id]
ParcelOptionsT.select { ParcelOptionsT.parcel_id eq id }.firstOrNull()?.let { optrow -> 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.statements.InsertStatement
import org.jetbrains.exposed.sql.transactions.TransactionManager import org.jetbrains.exposed.sql.transactions.TransactionManager
class UpsertStatement<Key : Any>(table: Table, conflictColumn: Column<*>? = null, conflictIndex: Index? = null) class UpsertStatement<Key : Any>(table: Table, conflictColumn: Column<*>? = null, conflictIndex: Index? = null) : InsertStatement<Key>(table, false) {
: InsertStatement<Key>(table, false) {
val indexName: String val indexName: String
val indexColumns: List<Column<*>> 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)})" override fun toSQL(queryBuilder: QueryBuilder): String = "ABS(${expr.toSQL(queryBuilder)})"
} }
fun <T : Comparable<T>> SqlExpressionBuilder.greater(col1: ExpressionWithColumnType<T>, col2: ExpressionWithColumnType<T>): Expression<T> { fun <T : Comparable<T>> greaterOf(col1: ExpressionWithColumnType<T>, col2: ExpressionWithColumnType<T>): Expression<T> =
return case(col1) with(SqlExpressionBuilder) {
.When(col1.greater(col2), col1) case(col1)
.Else(col2) .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.options.PlotmeMigrationOptions
import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.Storage
import io.dico.parcels2.storage.exposed.abs 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.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 io.dico.parcels2.storage.toUUID
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -24,6 +26,7 @@ class PlotmeMigration(val options: PlotmeMigrationOptions) : Migration {
private var database: Database? = null private var database: Database? = null
private var isShutdown: Boolean = false private var isShutdown: Boolean = false
private val mlogger = LoggerFactory.getLogger("PlotMe Migrator") private val mlogger = LoggerFactory.getLogger("PlotMe Migrator")
private val tables = PlotmeTables(options.tableNamesUppercase)
val dispatcher = newFixedThreadPoolContext(1, "PlotMe Migration Thread") val dispatcher = newFixedThreadPoolContext(1, "PlotMe Migration Thread")
private fun <T> transaction(statement: Transaction.() -> T) = org.jetbrains.exposed.sql.transactions.transaction(database!!, statement) 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 isShutdown = true
} }
suspend fun doWork(target: Storage) { suspend fun doWork(target: Storage) = with (tables) {
val exit = transaction { val exit = transaction {
(!PlotmePlotsT.exists()).also { (!PlotmePlots.exists()).also {
if (it) mlogger.warn("Plotme tables don't appear to exist. Exiting.") 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") mlogger.info("Transmitting data from plotmeplots table")
var count = 0
transaction { transaction {
PlotmePlotsT.selectAll()
.orderBy(PlotmePlotsT.world_name) PlotmePlots.selectAll()
.orderBy(with(SqlExpressionBuilder) { greater(PlotmePlotsT.px.abs(), PlotmePlotsT.pz.abs()) }) .orderBy(PlotmePlots.world_name)
.orderBy(greaterOf(PlotmePlots.px.abs(), PlotmePlots.pz.abs()))
.forEach { row -> .forEach { row ->
val parcel = getParcelId(PlotmePlotsT, row) ?: return@forEach val parcel = getParcelId(PlotmePlots, row) ?: return@forEach
val owner = PlayerProfile.safe(row[PlotmePlotsT.owner_uuid]?.toUUID(), row[PlotmePlotsT.owner_name]) val owner = PlayerProfile.safe(row[PlotmePlots.owner_uuid]?.toUUID(), row[PlotmePlots.owner_name])
target.setParcelOwner(parcel, owner) target.setParcelOwner(parcel, owner)
target.setParcelOwnerSignOutdated(parcel, true) target.setParcelOwnerSignOutdated(parcel, true)
++count
} }
} }
mlogger.info("Transmitting data from plotmeallowed table") mlogger.info("Transmitting data from plotmeallowed table")
transaction { transaction {
PlotmeAllowedT.transmitPlotmeAddedTable(Privilege.CAN_BUILD) PlotmeAllowed.transmitPlotmeAddedTable(Privilege.CAN_BUILD)
} }
mlogger.info("Transmitting data from plotmedenied table") mlogger.info("Transmitting data from plotmedenied table")
transaction { 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.") 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 import org.jetbrains.exposed.sql.Table
const val uppercase: Boolean = false class PlotmeTables(val uppercase: Boolean) {
@Suppress("ConstantConditionIf") fun String.toCorrectCase() = if (uppercase) this else toLowerCase()
fun String.toCorrectCase() = if (uppercase) this else toLowerCase()
sealed class PlotmeTable(name: String) : Table(name) { val PlotmePlots = PlotmePlotsT()
val px = integer("idX").primaryKey() val PlotmeAllowed = PlotmeAllowedT()
val pz = integer("idZ").primaryKey() val PlotmeDenied = PlotmeDeniedT()
val world_name = varchar("world", 32).primaryKey()
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(uuid: UUID): OfflinePlayer? = Bukkit.getOfflinePlayer(uuid).takeIf { it.isValid }
fun getOfflinePlayer(name: String): OfflinePlayer? = Bukkit.getOfflinePlayer(name).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( data class Vec2i(
val x: Int, val x: Int,
val z: 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 val z: Int
) { ) {
constructor(loc: Location) : this(loc.blockX, loc.blockY, loc.blockZ) 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 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) 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 addX(o: Int) = Vec3i(x + o, y, z)