summaryrefslogtreecommitdiff
path: root/src/main/kotlin/io/dico/parcels2/blockvisitor
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin/io/dico/parcels2/blockvisitor')
-rw-r--r--src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt122
-rw-r--r--src/main/kotlin/io/dico/parcels2/blockvisitor/Entities.kt38
-rw-r--r--src/main/kotlin/io/dico/parcels2/blockvisitor/ExtraBlockChange.kt76
-rw-r--r--src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt646
-rw-r--r--src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt242
5 files changed, 581 insertions, 543 deletions
diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt
index 8f2c565..83567c5 100644
--- a/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt
+++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt
@@ -1,61 +1,61 @@
-package io.dico.parcels2.blockvisitor
-
-import io.dico.parcels2.util.math.Vec3i
-import io.dico.parcels2.util.ext.getMaterialsWithWoodTypePrefix
-import io.dico.parcels2.util.ext.getMaterialsWithWoolColorPrefix
-import org.bukkit.Material
-import org.bukkit.Material.*
-import org.bukkit.block.BlockFace
-import org.bukkit.block.data.BlockData
-import org.bukkit.block.data.Directional
-import java.util.EnumSet
-
-private val attachables = EnumSet.of(
- REPEATER, COMPARATOR,
- *getMaterialsWithWoodTypePrefix("PRESSURE_PLATE"),
- STONE_PRESSURE_PLATE, LIGHT_WEIGHTED_PRESSURE_PLATE, HEAVY_WEIGHTED_PRESSURE_PLATE,
- *getMaterialsWithWoodTypePrefix("BUTTON"),
- STONE_BUTTON, LEVER,
- *getMaterialsWithWoodTypePrefix("DOOR"), IRON_DOOR,
- ACTIVATOR_RAIL, POWERED_RAIL, DETECTOR_RAIL, RAIL,
- PISTON, STICKY_PISTON,
- REDSTONE_TORCH, REDSTONE_WALL_TORCH, REDSTONE_WIRE,
- TRIPWIRE, TRIPWIRE_HOOK,
-
- BROWN_MUSHROOM, RED_MUSHROOM, CACTUS, CARROT, COCOA,
- WHEAT, DEAD_BUSH, CHORUS_FLOWER, DANDELION, SUGAR_CANE,
- TALL_GRASS, TALL_SEAGRASS, NETHER_WART, MELON_STEM,
- PUMPKIN_STEM, SUNFLOWER, POTATO, LILY_PAD, VINE,
- *getMaterialsWithWoodTypePrefix("SAPLING"),
-
- SAND, RED_SAND, DRAGON_EGG, ANVIL,
- *getMaterialsWithWoolColorPrefix("CONCRETE_POWDER"),
-
- *getMaterialsWithWoolColorPrefix("CARPET"),
- CAKE, FIRE,
- FLOWER_POT,
- LADDER,
- // NETHER_PORTAL, fuck nether portals
- FLOWER_POT,
- SNOW,
- TORCH, WALL_TORCH,
- *getMaterialsWithWoolColorPrefix("BANNER"),
- *getMaterialsWithWoolColorPrefix("WALL_BANNER"),
- SIGN, WALL_SIGN
-)
-
-fun isAttachable(type: Material) = attachables.contains(type)
-
-fun getSupportingBlock(data: BlockData): Vec3i = when (data) {
- //is MultipleFacing -> // fuck it xD this is good enough
-
- is Directional -> Vec3i.convert(when (data.material) {
- // exceptions
- COCOA -> data.facing
- OAK_DOOR, BIRCH_DOOR, SPRUCE_DOOR, JUNGLE_DOOR, ACACIA_DOOR, DARK_OAK_DOOR, IRON_DOOR -> BlockFace.DOWN
-
- else -> data.facing.oppositeFace
- })
-
- else -> Vec3i.down
-}
+package io.dico.parcels2.blockvisitor
+
+import io.dico.parcels2.util.math.Vec3i
+import io.dico.parcels2.util.ext.getMaterialsWithWoodTypePrefix
+import io.dico.parcels2.util.ext.getMaterialsWithWoolColorPrefix
+import org.bukkit.Material
+import org.bukkit.Material.*
+import org.bukkit.block.BlockFace
+import org.bukkit.block.data.BlockData
+import org.bukkit.block.data.Directional
+import java.util.EnumSet
+
+private val attachables = EnumSet.of(
+ REPEATER, COMPARATOR,
+ *getMaterialsWithWoodTypePrefix("PRESSURE_PLATE"),
+ STONE_PRESSURE_PLATE, LIGHT_WEIGHTED_PRESSURE_PLATE, HEAVY_WEIGHTED_PRESSURE_PLATE,
+ *getMaterialsWithWoodTypePrefix("BUTTON"),
+ STONE_BUTTON, LEVER,
+ *getMaterialsWithWoodTypePrefix("DOOR"), IRON_DOOR,
+ ACTIVATOR_RAIL, POWERED_RAIL, DETECTOR_RAIL, RAIL,
+ PISTON, STICKY_PISTON,
+ REDSTONE_TORCH, REDSTONE_WALL_TORCH, REDSTONE_WIRE,
+ TRIPWIRE, TRIPWIRE_HOOK,
+
+ BROWN_MUSHROOM, RED_MUSHROOM, CACTUS, CARROT, COCOA,
+ WHEAT, DEAD_BUSH, CHORUS_FLOWER, DANDELION, SUGAR_CANE,
+ TALL_GRASS, TALL_SEAGRASS, NETHER_WART, MELON_STEM,
+ PUMPKIN_STEM, SUNFLOWER, POTATO, LILY_PAD, VINE,
+ *getMaterialsWithWoodTypePrefix("SAPLING"),
+
+ SAND, RED_SAND, DRAGON_EGG, ANVIL,
+ *getMaterialsWithWoolColorPrefix("CONCRETE_POWDER"),
+
+ *getMaterialsWithWoolColorPrefix("CARPET"),
+ CAKE, FIRE,
+ FLOWER_POT,
+ LADDER,
+ // NETHER_PORTAL, fuck nether portals
+ FLOWER_POT,
+ SNOW,
+ TORCH, WALL_TORCH,
+ *getMaterialsWithWoolColorPrefix("BANNER"),
+ *getMaterialsWithWoolColorPrefix("WALL_BANNER"),
+ SIGN, WALL_SIGN
+)
+
+fun isAttachable(type: Material) = attachables.contains(type)
+
+fun getSupportingBlock(data: BlockData): Vec3i = when (data) {
+ //is MultipleFacing -> // fuck it xD this is good enough
+
+ is Directional -> Vec3i.convert(when (data.material) {
+ // exceptions
+ COCOA -> data.facing
+ OAK_DOOR, BIRCH_DOOR, SPRUCE_DOOR, JUNGLE_DOOR, ACACIA_DOOR, DARK_OAK_DOOR, IRON_DOOR -> BlockFace.DOWN
+
+ else -> data.facing.oppositeFace
+ })
+
+ else -> Vec3i.down
+}
diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/Entities.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/Entities.kt
new file mode 100644
index 0000000..d9ea09f
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/Entities.kt
@@ -0,0 +1,38 @@
+package io.dico.parcels2.blockvisitor
+
+import io.dico.parcels2.util.math.Vec3d
+import org.bukkit.Location
+import org.bukkit.World
+import org.bukkit.entity.Entity
+import org.bukkit.entity.Minecart
+
+/*
+open class EntityCopy<T : Entity>(entity: T) {
+ val type = entity.type
+
+ @Suppress("UNCHECKED_CAST")
+ fun spawn(world: World, position: Vec3d): T {
+ val entity = world.spawnEntity(Location(null, position.x, position.y, position.z), type) as T
+ setAttributes(entity)
+ return entity
+ }
+
+ open fun setAttributes(entity: T) {}
+}
+
+open class MinecartCopy<T : Minecart>(entity: T) : EntityCopy<T>(entity) {
+ val damage = entity.damage
+ val maxSpeed = entity.maxSpeed
+ val isSlowWhenEmpty = entity.isSlowWhenEmpty
+ val flyingVelocityMod = entity.flyingVelocityMod
+ val derailedVelocityMod = entity.derailedVelocityMod
+ val displayBlockData = entity.displayBlockData
+ val displayBlockOffset = entity.displayBlockOffset
+
+ override fun setAttributes(entity: T) {
+ super.setAttributes(entity)
+ entity.damage = damage
+ entity.displayBlockData = displayBlockData
+ entity.displayBlockOffset = displayBlockOffset
+ }
+}*/ \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/ExtraBlockChange.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/ExtraBlockChange.kt
index 3f7e070..ddfec27 100644
--- a/src/main/kotlin/io/dico/parcels2/blockvisitor/ExtraBlockChange.kt
+++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/ExtraBlockChange.kt
@@ -1,38 +1,38 @@
-package io.dico.parcels2.blockvisitor
-
-import org.bukkit.block.Block
-import org.bukkit.block.BlockState
-import org.bukkit.block.Sign
-import kotlin.reflect.KClass
-
-interface ExtraBlockChange {
- fun update(block: Block)
-}
-
-abstract class BlockStateChange<T : BlockState> : ExtraBlockChange {
- abstract val stateClass: KClass<T>
-
- abstract fun update(state: T)
-
- override fun update(block: Block) {
- val state = block.state
- if (stateClass.isInstance(state)) {
- @Suppress("UNCHECKED_CAST")
- update(state as T)
- }
- }
-}
-
-class SignStateChange(state: Sign) : BlockStateChange<Sign>() {
- val lines = state.lines
-
- override val stateClass: KClass<Sign>
- get() = Sign::class
-
- override fun update(state: Sign) {
- for (i in lines.indices) {
- val line = lines[i]
- state.setLine(i, line)
- }
- }
-}
+package io.dico.parcels2.blockvisitor
+
+import org.bukkit.block.Block
+import org.bukkit.block.BlockState
+import org.bukkit.block.Sign
+import kotlin.reflect.KClass
+
+interface ExtraBlockChange {
+ fun update(block: Block)
+}
+
+abstract class BlockStateChange<T : BlockState> : ExtraBlockChange {
+ abstract val stateClass: KClass<T>
+
+ abstract fun update(state: T)
+
+ override fun update(block: Block) {
+ val state = block.state
+ if (stateClass.isInstance(state)) {
+ @Suppress("UNCHECKED_CAST")
+ update(state as T)
+ }
+ }
+}
+
+class SignStateChange(state: Sign) : BlockStateChange<Sign>() {
+ val lines = state.lines
+
+ override val stateClass: KClass<Sign>
+ get() = Sign::class
+
+ override fun update(state: Sign) {
+ for (i in lines.indices) {
+ val line = lines[i]
+ state.setLine(i, line)
+ }
+ }
+}
diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt
index b749b36..6525655 100644
--- a/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt
+++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt
@@ -1,323 +1,323 @@
-package io.dico.parcels2.blockvisitor
-
-import io.dico.parcels2.util.math.Dimension
-import io.dico.parcels2.util.math.Region
-import io.dico.parcels2.util.math.Vec3i
-import io.dico.parcels2.util.math.clampMax
-
-private typealias Scope = SequenceScope<Vec3i>
-/*
-class ParcelTraverser(
- val parcelProvider: ParcelProvider,
- val delegate: RegionTraverser,
- scope: CoroutineScope
-) : RegionTraverser(), CoroutineScope by scope {
-
- class OccupiedException(parcelId: ParcelId) : Exception("Parcel $parcelId is occupied")
-
- /**
- * Traverse the blocks of parcel's land
- * The iterator must be exhausted, else the permit to traverse it will not be reclaimed.
- *
- * @throws OccupiedException if a parcel is maintained with the given parcel id and an
- * iterator exists for it that has not been exhausted
- */
- fun traverseParcel(parcelId: ParcelId): Iterator<Vec3i> {
- val world = parcelProvider.getWorldById(parcelId.worldId)
- ?: throw IllegalArgumentException()
- val parcel = parcelProvider.getParcelById(parcelId)
-
- val medium = if (parcel != null) {
- if (parcel.hasBlockVisitors || parcel !is ParcelImpl) {
- throw OccupiedException(parcelId)
- }
- parcel.hasBlockVisitors = true
- TraverserMedium { parcel.hasBlockVisitors = false }
- } else {
- TraverserMedium.DoNothing
- }
-
- val region = world.blockManager.getRegion(parcelId)
- return traverseRegion(region, world.world.maxHeight, medium)
- }
-
- override suspend fun Scope.build(region: Region, medium: TraverserMedium) {
- with(delegate) {
- return build(region, medium)
- }
- }
-
-}
-
-@Suppress("FunctionName")
-inline fun TraverserMedium(crossinline whenComplete: () -> Unit) =
- object : TraverserMedium {
- override fun iterationCompleted() {
- whenComplete()
- }
- }
-
-/**
- * An object that is able to communicate with an iterator returned by [RegionTraverser]
- *
- */
-interface TraverserMedium {
-
- /**
- * Called by the traverser during first [Iterator.hasNext] call that returns false
- */
- fun iterationCompleted()
-
- /**
- * The default [TraverserMedium], which does nothing.
- */
- object DoNothing : TraverserMedium {
- override fun iterationCompleted() {}
- }
-}*/
-
-sealed class RegionTraverser {
-
- /**
- * Get an iterator traversing [region] using this traverser.
- * Depending on the implementation, [region] might be traversed in a specific order and direction.
- */
- fun traverseRegion(
- region: Region,
- worldHeight: Int = 256/*,
- medium: TraverserMedium = TraverserMedium.DoNothing*/
- ): Iterator<Vec3i> = iterator { build(validify(region, worldHeight)/*, medium*/) }
-
- abstract suspend fun Scope.build(region: Region/*, medium: TraverserMedium = TraverserMedium.DoNothing*/)
-
- companion object {
- val upward = Directional(TraverseDirection(1, 1, 1), TraverseOrderFactory.createWith(Dimension.Y, Dimension.X))
- val downward = Directional(TraverseDirection(1, -1, 1), TraverseOrderFactory.createWith(Dimension.Y, Dimension.X))
- val toClear get() = downward
- val toFill get() = upward
-
- /**
- * The returned [RegionTraverser] will traverse the regions
- * * below and including absolute level [y] first, in [upward] direction.
- * * above absolute level [y] last, in [downward] direction.
- */
- fun convergingTo(y: Int) = Slicing(y, upward, downward, true)
-
- /**
- * The returned [RegionTraverser] will traverse the regions
- * * above absolute level [y] first, in [upward] direction.
- * * below and including absolute level [y] second, in [downward] direction.
- */
- fun separatingFrom(y: Int) = Slicing(y, downward, upward, false)
-
- private fun validify(region: Region, worldHeight: Int): Region {
- if (region.origin.y < 0) {
- val origin = region.origin withY 0
- val size = region.size.withY((region.size.y + region.origin.y).clampMax(worldHeight))
- return Region(origin, size)
- }
-
- if (region.origin.y + region.size.y > worldHeight) {
- val size = region.size.withY(worldHeight - region.origin.y)
- return Region(region.origin, size)
- }
-
- return region
- }
-
- }
-
- class Directional(
- val direction: TraverseDirection,
- val order: TraverseOrder
- ) : RegionTraverser() {
-
- private inline fun iterate(max: Int, increasing: Boolean, action: (Int) -> Unit) {
- for (i in 0..max) {
- action(if (increasing) i else max - i)
- }
- }
-
- override suspend fun Scope.build(region: Region/*, medium: TraverserMedium*/) {
- val order = order
- val (primary, secondary, tertiary) = order.toArray()
- val (origin, size) = region
-
- val maxOfPrimary = size[primary] - 1
- val maxOfSecondary = size[secondary] - 1
- val maxOfTertiary = size[tertiary] - 1
-
- val isPrimaryIncreasing = direction.isIncreasing(primary)
- val isSecondaryIncreasing = direction.isIncreasing(secondary)
- val isTertiaryIncreasing = direction.isIncreasing(tertiary)
-
- iterate(maxOfPrimary, isPrimaryIncreasing) { p ->
- iterate(maxOfSecondary, isSecondaryIncreasing) { s ->
- iterate(maxOfTertiary, isTertiaryIncreasing) { t ->
- yield(order.add(origin, p, s, t))
- }
- }
- }
-
- /*medium.iterationCompleted()*/
- }
-
- }
-
- class Slicing(
- val bottomSectionMaxY: Int,
- val bottomTraverser: RegionTraverser,
- val topTraverser: RegionTraverser,
- val bottomFirst: Boolean = true
- ) : RegionTraverser() {
-
- private fun slice(region: Region, atY: Int): Pair<Region, Region?> {
- if (atY < region.size.y + 1) {
- val bottom = Region(region.origin, region.size.withY(atY + 1))
- val top = Region(region.origin.withY(atY + 1), region.size.addY(-atY - 1))
- return bottom to top
- }
- return region to null
- }
-
- override suspend fun Scope.build(region: Region/*, medium: TraverserMedium*/) {
- val (bottom, top) = slice(region, bottomSectionMaxY)
-
- if (bottomFirst) {
- with(bottomTraverser) { build(bottom) }
- top?.let { with(topTraverser) { build(it) } }
- } else {
- top?.let { with(topTraverser) { build(it) } }
- with(bottomTraverser) { build(bottom) }
- }
-
- /*medium.iterationCompleted()*/
- }
- }
-
- /**
- * Returns [Directional] instance that would be responsible for
- * emitting the given position if it is contained in a region.
- * [Directional] instance has a set order and direction
- */
- fun childForPosition(position: Vec3i): Directional {
- var cur = this
- while (true) {
- when (cur) {
- /*is ParcelTraverser -> cur = cur.delegate*/
- is Directional -> return cur
- is Slicing ->
- cur =
- if (position.y <= cur.bottomSectionMaxY) cur.bottomTraverser
- else cur.topTraverser
- }
- }
- }
-
- /**
- * Returns true if and only if this traverser would visit the given
- * [block] position before the given [current] position.
- * If at least one of [block] and [current] is not contained in a
- * region being traversed the result is undefined.
- */
- fun comesFirst(current: Vec3i, block: Vec3i): Boolean {
- var cur = this
- while (true) {
- when (cur) {
- /*is ParcelTraverser -> cur = cur.delegate*/
- is Directional -> return cur.direction.comesFirst(current, block)
- is Slicing -> {
- val border = cur.bottomSectionMaxY
- cur = when {
- current.y <= border && block.y <= border -> cur.bottomTraverser
- current.y <= border -> return !cur.bottomFirst
- block.y <= border -> return cur.bottomFirst
- else -> cur.topTraverser
- }
- }
- }
- }
- }
-
-}
-
-object TraverseOrderFactory {
- private fun isSwap(primary: Dimension, secondary: Dimension) = secondary.ordinal != (primary.ordinal + 1) % 3
-
- fun createWith(primary: Dimension, secondary: Dimension): TraverseOrder {
- // tertiary is implicit
- if (primary == secondary) throw IllegalArgumentException()
- return TraverseOrder(primary, isSwap(primary, secondary))
- }
-}
-
-inline class TraverseOrder(val orderNum: Int) {
- constructor(first: Dimension, swap: Boolean)
- : this(if (swap) first.ordinal + 3 else first.ordinal)
-
- @Suppress("NOTHING_TO_INLINE")
- private inline fun element(index: Int) = Dimension[(orderNum + index) % 3]
-
- private val swap inline get() = orderNum >= 3
-
- /**
- * The slowest changing dimension
- */
- val primary: Dimension get() = element(0)
-
- /**
- * Second slowest changing dimension
- */
- val secondary: Dimension get() = element(if (swap) 2 else 1)
-
- /**
- * Dimension that changes every block
- */
- val tertiary: Dimension get() = element(if (swap) 1 else 2)
-
- /**
- * All 3 dimensions in this order
- */
- fun toArray() = arrayOf(primary, secondary, tertiary)
-
- fun add(vec: Vec3i, p: Int, s: Int, t: Int): Vec3i =
- // optimize this, will be called lots
- when (orderNum) {
- 0 -> vec.add(p, s, t) // xyz
- 1 -> vec.add(t, p, s) // yzx
- 2 -> vec.add(s, t, p) // zxy
- 3 -> vec.add(p, t, s) // xzy
- 4 -> vec.add(s, p, t) // yxz
- 5 -> vec.add(t, s, p) // zyx
- else -> error("Invalid orderNum $orderNum")
- }
-}
-
-inline class TraverseDirection(val bits: Int) {
- fun isIncreasing(dimension: Dimension) = (1 shl dimension.ordinal) and bits != 0
-
- fun comesFirst(current: Vec3i, block: Vec3i, dimension: Dimension): Boolean =
- if (isIncreasing(dimension))
- block[dimension] <= current[dimension]
- else
- block[dimension] >= current[dimension]
-
- fun comesFirst(current: Vec3i, block: Vec3i) =
- comesFirst(current, block, Dimension.X)
- && comesFirst(current, block, Dimension.Y)
- && comesFirst(current, block, Dimension.Z)
-
- companion object {
- operator fun invoke(x: Int, y: Int, z: Int) = invoke(Vec3i(x, y, z))
-
- operator fun invoke(block: Vec3i): TraverseDirection {
- if (block.x == 0 || block.y == 0 || block.z == 0) throw IllegalArgumentException()
- var bits = 0
- if (block.x > 0) bits = bits or 1
- if (block.y > 0) bits = bits or 2
- if (block.z > 0) bits = bits or 4
- return TraverseDirection(bits)
- }
- }
-
-}
+package io.dico.parcels2.blockvisitor
+
+import io.dico.parcels2.util.math.Dimension
+import io.dico.parcels2.util.math.Region
+import io.dico.parcels2.util.math.Vec3i
+import io.dico.parcels2.util.math.clampMax
+
+private typealias Scope = SequenceScope<Vec3i>
+/*
+class ParcelTraverser(
+ val parcelProvider: ParcelProvider,
+ val delegate: RegionTraverser,
+ scope: CoroutineScope
+) : RegionTraverser(), CoroutineScope by scope {
+
+ class OccupiedException(parcelId: ParcelId) : Exception("Parcel $parcelId is occupied")
+
+ /**
+ * Traverse the blocks of parcel's land
+ * The iterator must be exhausted, else the permit to traverse it will not be reclaimed.
+ *
+ * @throws OccupiedException if a parcel is maintained with the given parcel id and an
+ * iterator exists for it that has not been exhausted
+ */
+ fun traverseParcel(parcelId: ParcelId): Iterator<Vec3i> {
+ val world = parcelProvider.getWorldById(parcelId.worldId)
+ ?: throw IllegalArgumentException()
+ val parcel = parcelProvider.getParcelById(parcelId)
+
+ val medium = if (parcel != null) {
+ if (parcel.hasBlockVisitors || parcel !is ParcelImpl) {
+ throw OccupiedException(parcelId)
+ }
+ parcel.hasBlockVisitors = true
+ TraverserMedium { parcel.hasBlockVisitors = false }
+ } else {
+ TraverserMedium.DoNothing
+ }
+
+ val region = world.blockManager.getRegion(parcelId)
+ return traverseRegion(region, world.world.maxHeight, medium)
+ }
+
+ override suspend fun Scope.build(region: Region, medium: TraverserMedium) {
+ with(delegate) {
+ return build(region, medium)
+ }
+ }
+
+}
+
+@Suppress("FunctionName")
+inline fun TraverserMedium(crossinline whenComplete: () -> Unit) =
+ object : TraverserMedium {
+ override fun iterationCompleted() {
+ whenComplete()
+ }
+ }
+
+/**
+ * An object that is able to communicate with an iterator returned by [RegionTraverser]
+ *
+ */
+interface TraverserMedium {
+
+ /**
+ * Called by the traverser during first [Iterator.hasNext] call that returns false
+ */
+ fun iterationCompleted()
+
+ /**
+ * The default [TraverserMedium], which does nothing.
+ */
+ object DoNothing : TraverserMedium {
+ override fun iterationCompleted() {}
+ }
+}*/
+
+sealed class RegionTraverser {
+
+ /**
+ * Get an iterator traversing [region] using this traverser.
+ * Depending on the implementation, [region] might be traversed in a specific order and direction.
+ */
+ fun traverseRegion(
+ region: Region,
+ worldHeight: Int = 256/*,
+ medium: TraverserMedium = TraverserMedium.DoNothing*/
+ ): Iterator<Vec3i> = iterator { build(validify(region, worldHeight)/*, medium*/) }
+
+ abstract suspend fun Scope.build(region: Region/*, medium: TraverserMedium = TraverserMedium.DoNothing*/)
+
+ companion object {
+ val upward = Directional(TraverseDirection(1, 1, 1), TraverseOrderFactory.createWith(Dimension.Y, Dimension.X))
+ val downward = Directional(TraverseDirection(1, -1, 1), TraverseOrderFactory.createWith(Dimension.Y, Dimension.X))
+ val toClear get() = downward
+ val toFill get() = upward
+
+ /**
+ * The returned [RegionTraverser] will traverse the regions
+ * * below and including absolute level [y] first, in [upward] direction.
+ * * above absolute level [y] last, in [downward] direction.
+ */
+ fun convergingTo(y: Int) = Slicing(y, upward, downward, true)
+
+ /**
+ * The returned [RegionTraverser] will traverse the regions
+ * * above absolute level [y] first, in [upward] direction.
+ * * below and including absolute level [y] second, in [downward] direction.
+ */
+ fun separatingFrom(y: Int) = Slicing(y, downward, upward, false)
+
+ private fun validify(region: Region, worldHeight: Int): Region {
+ if (region.origin.y < 0) {
+ val origin = region.origin withY 0
+ val size = region.size.withY((region.size.y + region.origin.y).clampMax(worldHeight))
+ return Region(origin, size)
+ }
+
+ if (region.origin.y + region.size.y > worldHeight) {
+ val size = region.size.withY(worldHeight - region.origin.y)
+ return Region(region.origin, size)
+ }
+
+ return region
+ }
+
+ }
+
+ class Directional(
+ val direction: TraverseDirection,
+ val order: TraverseOrder
+ ) : RegionTraverser() {
+
+ private inline fun iterate(max: Int, increasing: Boolean, action: (Int) -> Unit) {
+ for (i in 0..max) {
+ action(if (increasing) i else max - i)
+ }
+ }
+
+ override suspend fun Scope.build(region: Region/*, medium: TraverserMedium*/) {
+ val order = order
+ val (primary, secondary, tertiary) = order.toArray()
+ val (origin, size) = region
+
+ val maxOfPrimary = size[primary] - 1
+ val maxOfSecondary = size[secondary] - 1
+ val maxOfTertiary = size[tertiary] - 1
+
+ val isPrimaryIncreasing = direction.isIncreasing(primary)
+ val isSecondaryIncreasing = direction.isIncreasing(secondary)
+ val isTertiaryIncreasing = direction.isIncreasing(tertiary)
+
+ iterate(maxOfPrimary, isPrimaryIncreasing) { p ->
+ iterate(maxOfSecondary, isSecondaryIncreasing) { s ->
+ iterate(maxOfTertiary, isTertiaryIncreasing) { t ->
+ yield(order.add(origin, p, s, t))
+ }
+ }
+ }
+
+ /*medium.iterationCompleted()*/
+ }
+
+ }
+
+ class Slicing(
+ val bottomSectionMaxY: Int,
+ val bottomTraverser: RegionTraverser,
+ val topTraverser: RegionTraverser,
+ val bottomFirst: Boolean = true
+ ) : RegionTraverser() {
+
+ private fun slice(region: Region, atY: Int): Pair<Region, Region?> {
+ if (atY < region.size.y + 1) {
+ val bottom = Region(region.origin, region.size.withY(atY + 1))
+ val top = Region(region.origin.withY(atY + 1), region.size.addY(-atY - 1))
+ return bottom to top
+ }
+ return region to null
+ }
+
+ override suspend fun Scope.build(region: Region/*, medium: TraverserMedium*/) {
+ val (bottom, top) = slice(region, bottomSectionMaxY)
+
+ if (bottomFirst) {
+ with(bottomTraverser) { build(bottom) }
+ top?.let { with(topTraverser) { build(it) } }
+ } else {
+ top?.let { with(topTraverser) { build(it) } }
+ with(bottomTraverser) { build(bottom) }
+ }
+
+ /*medium.iterationCompleted()*/
+ }
+ }
+
+ /**
+ * Returns [Directional] instance that would be responsible for
+ * emitting the given position if it is contained in a region.
+ * [Directional] instance has a set order and direction
+ */
+ fun childForPosition(position: Vec3i): Directional {
+ var cur = this
+ while (true) {
+ when (cur) {
+ /*is ParcelTraverser -> cur = cur.delegate*/
+ is Directional -> return cur
+ is Slicing ->
+ cur =
+ if (position.y <= cur.bottomSectionMaxY) cur.bottomTraverser
+ else cur.topTraverser
+ }
+ }
+ }
+
+ /**
+ * Returns true if and only if this traverser would visit the given
+ * [block] position before the given [current] position.
+ * If at least one of [block] and [current] is not contained in a
+ * region being traversed the result is undefined.
+ */
+ fun comesFirst(current: Vec3i, block: Vec3i): Boolean {
+ var cur = this
+ while (true) {
+ when (cur) {
+ /*is ParcelTraverser -> cur = cur.delegate*/
+ is Directional -> return cur.direction.comesFirst(current, block)
+ is Slicing -> {
+ val border = cur.bottomSectionMaxY
+ cur = when {
+ current.y <= border && block.y <= border -> cur.bottomTraverser
+ current.y <= border -> return !cur.bottomFirst
+ block.y <= border -> return cur.bottomFirst
+ else -> cur.topTraverser
+ }
+ }
+ }
+ }
+ }
+
+}
+
+object TraverseOrderFactory {
+ private fun isSwap(primary: Dimension, secondary: Dimension) = secondary.ordinal != (primary.ordinal + 1) % 3
+
+ fun createWith(primary: Dimension, secondary: Dimension): TraverseOrder {
+ // tertiary is implicit
+ if (primary == secondary) throw IllegalArgumentException()
+ return TraverseOrder(primary, isSwap(primary, secondary))
+ }
+}
+
+inline class TraverseOrder(val orderNum: Int) {
+ constructor(first: Dimension, swap: Boolean)
+ : this(if (swap) first.ordinal + 3 else first.ordinal)
+
+ @Suppress("NOTHING_TO_INLINE")
+ private inline fun element(index: Int) = Dimension[(orderNum + index) % 3]
+
+ private val swap inline get() = orderNum >= 3
+
+ /**
+ * The slowest changing dimension
+ */
+ val primary: Dimension get() = element(0)
+
+ /**
+ * Second slowest changing dimension
+ */
+ val secondary: Dimension get() = element(if (swap) 2 else 1)
+
+ /**
+ * Dimension that changes every block
+ */
+ val tertiary: Dimension get() = element(if (swap) 1 else 2)
+
+ /**
+ * All 3 dimensions in this order
+ */
+ fun toArray() = arrayOf(primary, secondary, tertiary)
+
+ fun add(vec: Vec3i, p: Int, s: Int, t: Int): Vec3i =
+ // optimize this, will be called lots
+ when (orderNum) {
+ 0 -> vec.add(p, s, t) // xyz
+ 1 -> vec.add(t, p, s) // yzx
+ 2 -> vec.add(s, t, p) // zxy
+ 3 -> vec.add(p, t, s) // xzy
+ 4 -> vec.add(s, p, t) // yxz
+ 5 -> vec.add(t, s, p) // zyx
+ else -> error("Invalid orderNum $orderNum")
+ }
+}
+
+inline class TraverseDirection(val bits: Int) {
+ fun isIncreasing(dimension: Dimension) = (1 shl dimension.ordinal) and bits != 0
+
+ fun comesFirst(current: Vec3i, block: Vec3i, dimension: Dimension): Boolean =
+ if (isIncreasing(dimension))
+ block[dimension] <= current[dimension]
+ else
+ block[dimension] >= current[dimension]
+
+ fun comesFirst(current: Vec3i, block: Vec3i) =
+ comesFirst(current, block, Dimension.X)
+ && comesFirst(current, block, Dimension.Y)
+ && comesFirst(current, block, Dimension.Z)
+
+ companion object {
+ operator fun invoke(x: Int, y: Int, z: Int) = invoke(Vec3i(x, y, z))
+
+ operator fun invoke(block: Vec3i): TraverseDirection {
+ if (block.x == 0 || block.y == 0 || block.z == 0) throw IllegalArgumentException()
+ var bits = 0
+ if (block.x > 0) bits = bits or 1
+ if (block.y > 0) bits = bits or 2
+ if (block.z > 0) bits = bits or 4
+ return TraverseDirection(bits)
+ }
+ }
+
+}
diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt
index df3cfab..39b4345 100644
--- a/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt
+++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt
@@ -1,121 +1,121 @@
-package io.dico.parcels2.blockvisitor
-
-import io.dico.parcels2.JobFunction
-import io.dico.parcels2.JobScope
-import io.dico.parcels2.util.math.Region
-import io.dico.parcels2.util.math.Vec3i
-import io.dico.parcels2.util.math.get
-import org.bukkit.Bukkit
-import org.bukkit.Material
-import org.bukkit.World
-import org.bukkit.block.Sign
-import org.bukkit.block.data.BlockData
-
-private val air = Bukkit.createBlockData(Material.AIR)
-
-class Schematic {
- val size: Vec3i get() = _size!!
- private var _size: Vec3i? = null
- set(value) {
- field?.let { throw IllegalStateException() }
- field = value
- }
-
- private var blockDatas: Array<BlockData?>? = null
- private val extra = mutableListOf<Pair<Vec3i, ExtraBlockChange>>()
- private var isLoaded = false; private set
- private val traverser: RegionTraverser = RegionTraverser.upward
-
- suspend fun JobScope.load(world: World, region: Region) {
- _size = region.size
-
- val data = arrayOfNulls<BlockData>(region.blockCount).also { blockDatas = it }
- val blocks = traverser.traverseRegion(region)
- val total = region.blockCount.toDouble()
-
- loop@ for ((index, vec) in blocks.withIndex()) {
- markSuspensionPoint()
- setProgress(index / total)
-
- val block = world[vec]
- if (block.y > 255) continue
- val blockData = block.blockData
- data[index] = blockData
-
- val extraChange = when (blockData.material) {
- Material.SIGN,
- Material.WALL_SIGN -> SignStateChange(block.state as Sign)
- else -> continue@loop
- }
-
- extra += (vec - region.origin) to extraChange
- }
-
- isLoaded = true
- }
-
- suspend fun JobScope.paste(world: World, position: Vec3i) {
- if (!isLoaded) throw IllegalStateException()
-
- val region = Region(position, _size!!)
- val blocks = traverser.traverseRegion(region, worldHeight = world.maxHeight)
- val blockDatas = blockDatas!!
- var postponed = hashMapOf<Vec3i, BlockData>()
-
- val total = region.blockCount.toDouble()
- var processed = 0
-
- for ((index, vec) in blocks.withIndex()) {
- markSuspensionPoint()
- setProgress(index / total)
-
- val block = world[vec]
- val type = blockDatas[index] ?: air
- if (type !== air && isAttachable(type.material)) {
- val supportingBlock = vec + getSupportingBlock(type)
-
- if (!postponed.containsKey(supportingBlock) && traverser.comesFirst(vec, supportingBlock)) {
- block.blockData = type
- setProgress(++processed / total)
- } else {
- postponed[vec] = type
- }
-
- } else {
- block.blockData = type
- setProgress(++processed / total)
- }
- }
-
- while (!postponed.isEmpty()) {
- markSuspensionPoint()
- val newMap = hashMapOf<Vec3i, BlockData>()
- for ((vec, type) in postponed) {
- val supportingBlock = vec + getSupportingBlock(type)
- if (supportingBlock in postponed && supportingBlock != vec) {
- newMap[vec] = type
- } else {
- world[vec].blockData = type
- setProgress(++processed / total)
- }
- }
- postponed = newMap
- }
-
- // Should be negligible so we don't track progress
- for ((vec, extraChange) in extra) {
- markSuspensionPoint()
- val block = world[position + vec]
- extraChange.update(block)
- }
- }
-
- fun getLoadTask(world: World, region: Region): JobFunction = {
- load(world, region)
- }
-
- fun getPasteTask(world: World, position: Vec3i): JobFunction = {
- paste(world, position)
- }
-
-}
+package io.dico.parcels2.blockvisitor
+
+import io.dico.parcels2.JobFunction
+import io.dico.parcels2.JobScope
+import io.dico.parcels2.util.math.Region
+import io.dico.parcels2.util.math.Vec3i
+import io.dico.parcels2.util.math.get
+import org.bukkit.Bukkit
+import org.bukkit.Material
+import org.bukkit.World
+import org.bukkit.block.Sign
+import org.bukkit.block.data.BlockData
+
+private val air = Bukkit.createBlockData(Material.AIR)
+
+class Schematic {
+ val size: Vec3i get() = _size!!
+ private var _size: Vec3i? = null
+ set(value) {
+ field?.let { throw IllegalStateException() }
+ field = value
+ }
+
+ private var blockDatas: Array<BlockData?>? = null
+ private val extra = mutableListOf<Pair<Vec3i, ExtraBlockChange>>()
+ private var isLoaded = false; private set
+ private val traverser: RegionTraverser = RegionTraverser.upward
+
+ suspend fun JobScope.load(world: World, region: Region) {
+ _size = region.size
+
+ val data = arrayOfNulls<BlockData>(region.blockCount).also { blockDatas = it }
+ val blocks = traverser.traverseRegion(region)
+ val total = region.blockCount.toDouble()
+
+ loop@ for ((index, vec) in blocks.withIndex()) {
+ markSuspensionPoint()
+ setProgress(index / total)
+
+ val block = world[vec]
+ if (block.y > 255) continue
+ val blockData = block.blockData
+ data[index] = blockData
+
+ val extraChange = when (blockData.material) {
+ Material.SIGN,
+ Material.WALL_SIGN -> SignStateChange(block.state as Sign)
+ else -> continue@loop
+ }
+
+ extra += (vec - region.origin) to extraChange
+ }
+
+ isLoaded = true
+ }
+
+ suspend fun JobScope.paste(world: World, position: Vec3i) {
+ if (!isLoaded) throw IllegalStateException()
+
+ val region = Region(position, _size!!)
+ val blocks = traverser.traverseRegion(region, worldHeight = world.maxHeight)
+ val blockDatas = blockDatas!!
+ var postponed = hashMapOf<Vec3i, BlockData>()
+
+ val total = region.blockCount.toDouble()
+ var processed = 0
+
+ for ((index, vec) in blocks.withIndex()) {
+ markSuspensionPoint()
+ setProgress(index / total)
+
+ val block = world[vec]
+ val type = blockDatas[index] ?: air
+ if (type !== air && isAttachable(type.material)) {
+ val supportingBlock = vec + getSupportingBlock(type)
+
+ if (!postponed.containsKey(supportingBlock) && traverser.comesFirst(vec, supportingBlock)) {
+ block.blockData = type
+ setProgress(++processed / total)
+ } else {
+ postponed[vec] = type
+ }
+
+ } else {
+ block.blockData = type
+ setProgress(++processed / total)
+ }
+ }
+
+ while (!postponed.isEmpty()) {
+ markSuspensionPoint()
+ val newMap = hashMapOf<Vec3i, BlockData>()
+ for ((vec, type) in postponed) {
+ val supportingBlock = vec + getSupportingBlock(type)
+ if (supportingBlock in postponed && supportingBlock != vec) {
+ newMap[vec] = type
+ } else {
+ world[vec].blockData = type
+ setProgress(++processed / total)
+ }
+ }
+ postponed = newMap
+ }
+
+ // Should be negligible so we don't track progress
+ for ((vec, extraChange) in extra) {
+ markSuspensionPoint()
+ val block = world[position + vec]
+ extraChange.update(block)
+ }
+ }
+
+ fun getLoadTask(world: World, region: Region): JobFunction = {
+ load(world, region)
+ }
+
+ fun getPasteTask(world: World, position: Vec3i): JobFunction = {
+ paste(world, position)
+ }
+
+}