diff options
Diffstat (limited to 'src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt')
-rw-r--r-- | src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt | 767 |
1 files changed, 390 insertions, 377 deletions
diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt index caa3f1f..73b6b4d 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt @@ -1,378 +1,391 @@ -package io.dico.parcels2.defaultimpl
-
-import io.dico.parcels2.*
-import io.dico.parcels2.blockvisitor.RegionTraverser
-import io.dico.parcels2.options.DefaultGeneratorOptions
-import io.dico.parcels2.util.math.*
-import kotlinx.coroutines.CoroutineScope
-import org.bukkit.*
-import org.bukkit.block.Biome
-import org.bukkit.block.BlockFace
-import org.bukkit.block.Skull
-import org.bukkit.block.data.type.Slab
-import org.bukkit.block.data.type.WallSign
-import java.util.Random
-
-private val airType = Bukkit.createBlockData(Material.AIR)
-
-private const val chunkSize = 16
-
-class DefaultParcelGenerator(
- override val worldName: String,
- private val o: DefaultGeneratorOptions
-) : ParcelGenerator() {
- private var _world: World? = null
- override val world: World
- get() {
- if (_world == null) {
- val world = Bukkit.getWorld(worldName)
- maxHeight = world.maxHeight
- _world = world
- return world
- }
- return _world!!
- }
-
- private var maxHeight = 0
- val sectionSize = o.parcelSize + o.pathSize
- val pathOffset = (if (o.pathSize % 2 == 0) o.pathSize + 2 else o.pathSize + 1) / 2
- val makePathMain = o.pathSize > 2
- val makePathAlt = o.pathSize > 4
-
- private inline fun <T> generate(
- chunkX: Int,
- chunkZ: Int,
- floor: T, wall:
- T, pathMain: T,
- pathAlt: T,
- fill: T,
- setter: (Int, Int, Int, T) -> Unit
- ) {
-
- val floorHeight = o.floorHeight
- val parcelSize = o.parcelSize
- val sectionSize = sectionSize
- val pathOffset = pathOffset
- val makePathMain = makePathMain
- val makePathAlt = makePathAlt
-
- // parcel bottom x and z
- // umod is unsigned %: the result is always >= 0
- val pbx = ((chunkX shl 4) - o.offsetX) umod sectionSize
- val pbz = ((chunkZ shl 4) - o.offsetZ) umod sectionSize
-
- var curHeight: Int
- var x: Int
- var z: Int
- for (cx in 0..15) {
- for (cz in 0..15) {
- x = (pbx + cx) % sectionSize - pathOffset
- z = (pbz + cz) % sectionSize - pathOffset
- curHeight = floorHeight
-
- val type = when {
- (x in 0 until parcelSize && z in 0 until parcelSize) -> floor
- (x in -1..parcelSize && z in -1..parcelSize) -> {
- curHeight++
- wall
- }
- (makePathAlt && x in -2 until parcelSize + 2 && z in -2 until parcelSize + 2) -> pathAlt
- (makePathMain) -> pathMain
- else -> {
- curHeight++
- wall
- }
- }
-
- for (y in 0 until curHeight) {
- setter(cx, y, cz, fill)
- }
- setter(cx, curHeight, cz, type)
- }
- }
- }
-
- override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData {
- val out = Bukkit.createChunkData(world)
- generate(chunkX, chunkZ, o.floorType, o.wallType, o.pathMainType, o.pathAltType, o.fillType) { x, y, z, type ->
- out.setBlock(x, y, z, type)
- }
- return out
- }
-
- override fun populate(world: World?, random: Random?, chunk: Chunk?) {
- // do nothing
- }
-
- override fun getFixedSpawnLocation(world: World?, random: Random?): Location {
- val fix = if (o.parcelSize.even) 0.5 else 0.0
- return Location(world, o.offsetX + fix, o.floorHeight + 1.0, o.offsetZ + fix)
- }
-
- override fun makeParcelLocatorAndBlockManager(
- parcelProvider: ParcelProvider,
- container: ParcelContainer,
- coroutineScope: CoroutineScope,
- jobDispatcher: JobDispatcher
- ): Pair<ParcelLocator, ParcelBlockManager> {
- val impl = ParcelLocatorAndBlockManagerImpl(parcelProvider, container, coroutineScope, jobDispatcher)
- return impl to impl
- }
-
- private inline fun <T> convertBlockLocationToId(x: Int, z: Int, mapper: (Int, Int) -> T): T? {
- val sectionSize = sectionSize
- val parcelSize = o.parcelSize
- val absX = x - o.offsetX - pathOffset
- val absZ = z - o.offsetZ - pathOffset
- val modX = absX umod sectionSize
- val modZ = absZ umod sectionSize
- if (modX in 0 until parcelSize && modZ in 0 until parcelSize) {
- return mapper((absX - modX) / sectionSize + 1, (absZ - modZ) / sectionSize + 1)
- }
- return null
- }
-
- @Suppress("DEPRECATION")
- private inner class ParcelLocatorAndBlockManagerImpl(
- val parcelProvider: ParcelProvider,
- val container: ParcelContainer,
- coroutineScope: CoroutineScope,
- override val jobDispatcher: JobDispatcher
- ) : ParcelBlockManagerBase(), ParcelLocator, CoroutineScope by coroutineScope {
-
- override val world: World get() = this@DefaultParcelGenerator.world
- val worldId = parcelProvider.getWorld(world)?.id ?: ParcelWorldId(world)
- override val parcelTraverser: RegionTraverser = RegionTraverser.convergingTo(o.floorHeight)
-
- private val cornerWallType = when {
- o.wallType is Slab -> (o.wallType.clone() as Slab).apply { type = Slab.Type.DOUBLE }
- o.wallType.material.name.endsWith("CARPET") -> {
- Bukkit.createBlockData(Material.getMaterial(o.wallType.material.name.substringBefore("CARPET") + "WOOL"))
- }
- else -> null
- }
-
- override fun getParcelAt(x: Int, z: Int): Parcel? {
- return convertBlockLocationToId(x, z, container::getParcelById)
- }
-
- override fun getParcelIdAt(x: Int, z: Int): ParcelId? {
- return convertBlockLocationToId(x, z) { idx, idz -> ParcelId(worldId, idx, idz) }
- }
-
-
- private fun checkParcelId(parcel: ParcelId): ParcelId {
- if (!parcel.worldId.equals(worldId)) {
- throw IllegalArgumentException()
- }
- return parcel
- }
-
- override fun getRegionOrigin(parcel: ParcelId): Vec2i {
- checkParcelId(parcel)
- return Vec2i(
- sectionSize * (parcel.x - 1) + pathOffset + o.offsetX,
- sectionSize * (parcel.z - 1) + pathOffset + o.offsetZ
- )
- }
-
- override fun getRegion(parcel: ParcelId): Region {
- val origin = getRegionOrigin(parcel)
- return Region(
- Vec3i(origin.x, 0, origin.z),
- Vec3i(o.parcelSize, maxHeight, o.parcelSize)
- )
- }
-
- override fun getHomeLocation(parcel: ParcelId): Location {
- val origin = getRegionOrigin(parcel)
- val x = origin.x + (o.parcelSize - 1) / 2.0
- val z = origin.z - 2
- return Location(world, x + 0.5, o.floorHeight + 1.0, z + 0.5, 0F, 0F)
- }
-
- override fun getParcelForInfoBlockInteraction(block: Vec3i, type: Material, face: BlockFace): Parcel? {
- if (block.y != o.floorHeight + 1) return null
-
- val expectedParcelOrigin = when (type) {
- Material.WALL_SIGN -> Vec2i(block.x + 1, block.z + 2)
- o.wallType.material, cornerWallType?.material -> {
- if (face != BlockFace.NORTH || world[block + Vec3i.convert(BlockFace.NORTH)].type == Material.WALL_SIGN) {
- return null
- }
-
- Vec2i(block.x + 1, block.z + 1)
- }
- else -> return null
- }
-
- return getParcelAt(expectedParcelOrigin.x, expectedParcelOrigin.z)
- ?.takeIf { expectedParcelOrigin == getRegionOrigin(it.id) }
- ?.also { parcel ->
- if (type != Material.WALL_SIGN && parcel.owner != null) {
- updateParcelInfo(parcel.id, parcel.owner)
- parcel.isOwnerSignOutdated = false
- }
- }
- }
-
- override fun isParcelInfoSectionLoaded(parcel: ParcelId): Boolean {
- val wallBlockChunk = getRegionOrigin(parcel).add(-1, -1).toChunk()
- return world.isChunkLoaded(wallBlockChunk.x, wallBlockChunk.z)
- }
-
- override fun updateParcelInfo(parcel: ParcelId, owner: PlayerProfile?) {
- val b = getRegionOrigin(parcel)
-
- val wallBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 1)
- val signBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 2)
- val skullBlock = world.getBlockAt(b.x - 1, o.floorHeight + 2, b.z - 1)
-
- if (owner == null) {
- wallBlock.blockData = o.wallType
- signBlock.type = Material.AIR
- skullBlock.type = Material.AIR
-
- } else {
- cornerWallType?.let { wallBlock.blockData = it }
- signBlock.blockData = (Bukkit.createBlockData(Material.WALL_SIGN) as WallSign).apply { facing = BlockFace.NORTH }
-
- val sign = signBlock.state as org.bukkit.block.Sign
- sign.setLine(0, "${parcel.x},${parcel.z}")
- sign.setLine(2, owner.name ?: "")
- sign.update()
-
- skullBlock.type = Material.AIR
- skullBlock.type = Material.PLAYER_HEAD
- val skull = skullBlock.state as Skull
- if (owner is PlayerProfile.Real) {
- skull.owningPlayer = Bukkit.getOfflinePlayer(owner.uuid)
-
- } else if (!skull.setOwner(owner.name)) {
- skullBlock.type = Material.AIR
- return
- }
-
- skull.rotation = BlockFace.SOUTH
- skull.update()
- }
- }
-
- private fun trySubmitBlockVisitor(vararg parcels: ParcelId, function: JobFunction): Job? {
- parcels.forEach { checkParcelId(it) }
- return parcelProvider.trySubmitBlockVisitor(Permit(), parcels, function)
- }
-
- override fun setBiome(parcel: ParcelId, biome: Biome) = trySubmitBlockVisitor(checkParcelId(parcel)) {
- val world = world
- val b = getRegionOrigin(parcel)
- val parcelSize = o.parcelSize
- for (x in b.x until b.x + parcelSize) {
- for (z in b.z until b.z + parcelSize) {
- markSuspensionPoint()
- world.setBiome(x, z, biome)
- }
- }
- }
-
- override fun clearParcel(parcel: ParcelId) = trySubmitBlockVisitor(checkParcelId(parcel)) {
- val region = getRegion(parcel)
- val blocks = parcelTraverser.traverseRegion(region)
- val blockCount = region.blockCount.toDouble()
- val world = world
- val floorHeight = o.floorHeight
- val airType = airType
- val floorType = o.floorType
- val fillType = o.fillType
-
- for ((index, vec) in blocks.withIndex()) {
- markSuspensionPoint()
- val y = vec.y
- val blockType = when {
- y > floorHeight -> airType
- y == floorHeight -> floorType
- else -> fillType
- }
- world[vec].blockData = blockType
- setProgress((index + 1) / blockCount)
- }
- }
-
- override fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i> {
- /*
- * Get the offsets for the world out of the way
- * to simplify the calculation that follows.
- */
-
- val x = chunk.x.shl(4) - (o.offsetX + pathOffset)
- val z = chunk.z.shl(4) - (o.offsetZ + pathOffset)
-
- /* Locations of wall corners (where owner blocks are placed) are defined as:
- *
- * x umod sectionSize == sectionSize-1
- *
- * This check needs to be made for all 16 slices of the chunk in 2 dimensions
- * How to optimize this?
- * Let's take the expression
- *
- * x umod sectionSize
- *
- * And call it modX
- * x can be shifted (chunkSize -1) times to attempt to get a modX of 0.
- * This means that if the modX is 1, and sectionSize == (chunkSize-1), there would be a match at the last shift.
- * To check that there are any matches, we can see if the following holds:
- *
- * modX >= ((sectionSize-1) - (chunkSize-1))
- *
- * Which can be simplified to:
- * modX >= sectionSize - chunkSize
- *
- * if sectionSize == chunkSize, this expression can be simplified to
- * modX >= 0
- * which is always true. This is expected.
- * To get the total number of matches on a dimension, we can evaluate the following:
- *
- * (modX - (sectionSize - chunkSize) + sectionSize) / sectionSize
- *
- * We add sectionSize to the lhs because, if the other part of the lhs is 0, we need at least 1.
- * This can be simplified to:
- *
- * (modX + chunkSize) / sectionSize
- */
-
- val sectionSize = sectionSize
-
- val modX = x umod sectionSize
- val matchesOnDimensionX = (modX + chunkSize) / sectionSize
- if (matchesOnDimensionX <= 0) return emptyList()
-
- val modZ = z umod sectionSize
- val matchesOnDimensionZ = (modZ + chunkSize) / sectionSize
- if (matchesOnDimensionZ <= 0) return emptyList()
-
- /*
- * Now we need to find the first id within the matches,
- * and then return the subsequent matches in a rectangle following it.
- *
- * On each dimension, get the distance to the first match, which is equal to (sectionSize-1 - modX)
- * and add it to the coordinate value
- */
- val firstX = x + (sectionSize - 1 - modX)
- val firstZ = z + (sectionSize - 1 - modZ)
-
- val firstIdX = (firstX + 1) / sectionSize + 1
- val firstIdZ = (firstZ + 1) / sectionSize + 1
-
- if (matchesOnDimensionX == 1 && matchesOnDimensionZ == 1) {
- // fast-path optimization
- return listOf(Vec2i(firstIdX, firstIdZ))
- }
-
- return (0 until matchesOnDimensionX).flatMap { idOffsetX ->
- (0 until matchesOnDimensionZ).map { idOffsetZ -> Vec2i(firstIdX + idOffsetX, firstIdZ + idOffsetZ) }
- }
- }
-
- }
-
+package io.dico.parcels2.defaultimpl + +import io.dico.parcels2.* +import io.dico.parcels2.blockvisitor.RegionTraverser +import io.dico.parcels2.options.DefaultGeneratorOptions +import io.dico.parcels2.util.math.* +import kotlinx.coroutines.CoroutineScope +import org.bukkit.* +import org.bukkit.block.Biome +import org.bukkit.block.BlockFace +import org.bukkit.block.Skull +import org.bukkit.block.data.type.Slab +import org.bukkit.block.data.type.WallSign +import org.bukkit.entity.Player +import java.util.Random + +private val airType = Bukkit.createBlockData(Material.AIR) + +private const val chunkSize = 16 + +class DefaultParcelGenerator( + override val worldName: String, + private val o: DefaultGeneratorOptions +) : ParcelGenerator() { + private var _world: World? = null + override val world: World + get() { + if (_world == null) { + val world = Bukkit.getWorld(worldName) + maxHeight = world.maxHeight + _world = world + return world + } + return _world!! + } + + private var maxHeight = 0 + val sectionSize = o.parcelSize + o.pathSize + val pathOffset = (if (o.pathSize % 2 == 0) o.pathSize + 2 else o.pathSize + 1) / 2 + val makePathMain = o.pathSize > 2 + val makePathAlt = o.pathSize > 4 + + private inline fun <T> generate( + chunkX: Int, + chunkZ: Int, + floor: T, wall: + T, pathMain: T, + pathAlt: T, + fill: T, + setter: (Int, Int, Int, T) -> Unit + ) { + + val floorHeight = o.floorHeight + val parcelSize = o.parcelSize + val sectionSize = sectionSize + val pathOffset = pathOffset + val makePathMain = makePathMain + val makePathAlt = makePathAlt + + // parcel bottom x and z + // umod is unsigned %: the result is always >= 0 + val pbx = ((chunkX shl 4) - o.offsetX) umod sectionSize + val pbz = ((chunkZ shl 4) - o.offsetZ) umod sectionSize + + var curHeight: Int + var x: Int + var z: Int + for (cx in 0..15) { + for (cz in 0..15) { + x = (pbx + cx) % sectionSize - pathOffset + z = (pbz + cz) % sectionSize - pathOffset + curHeight = floorHeight + + val type = when { + (x in 0 until parcelSize && z in 0 until parcelSize) -> floor + (x in -1..parcelSize && z in -1..parcelSize) -> { + curHeight++ + wall + } + (makePathAlt && x in -2 until parcelSize + 2 && z in -2 until parcelSize + 2) -> pathAlt + (makePathMain) -> pathMain + else -> { + curHeight++ + wall + } + } + + for (y in 0 until curHeight) { + setter(cx, y, cz, fill) + } + setter(cx, curHeight, cz, type) + } + } + } + + override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData { + val out = Bukkit.createChunkData(world) + generate(chunkX, chunkZ, o.floorType, o.wallType, o.pathMainType, o.pathAltType, o.fillType) { x, y, z, type -> + out.setBlock(x, y, z, type) + } + return out + } + + override fun populate(world: World?, random: Random?, chunk: Chunk?) { + // do nothing + } + + override fun getFixedSpawnLocation(world: World?, random: Random?): Location { + val fix = if (o.parcelSize.even) 0.5 else 0.0 + return Location(world, o.offsetX + fix, o.floorHeight + 1.0, o.offsetZ + fix) + } + + override fun makeParcelLocatorAndBlockManager( + parcelProvider: ParcelProvider, + container: ParcelContainer, + coroutineScope: CoroutineScope, + jobDispatcher: JobDispatcher + ): Pair<ParcelLocator, ParcelBlockManager> { + val impl = ParcelLocatorAndBlockManagerImpl(parcelProvider, container, coroutineScope, jobDispatcher) + return impl to impl + } + + private inline fun <T> convertBlockLocationToId(x: Int, z: Int, mapper: (Int, Int) -> T): T? { + val sectionSize = sectionSize + val parcelSize = o.parcelSize + val absX = x - o.offsetX - pathOffset + val absZ = z - o.offsetZ - pathOffset + val modX = absX umod sectionSize + val modZ = absZ umod sectionSize + if (modX in 0 until parcelSize && modZ in 0 until parcelSize) { + return mapper((absX - modX) / sectionSize + 1, (absZ - modZ) / sectionSize + 1) + } + return null + } + + @Suppress("DEPRECATION") + private inner class ParcelLocatorAndBlockManagerImpl( + val parcelProvider: ParcelProvider, + val container: ParcelContainer, + coroutineScope: CoroutineScope, + override val jobDispatcher: JobDispatcher + ) : ParcelBlockManagerBase(), ParcelLocator, CoroutineScope by coroutineScope { + + override val world: World get() = this@DefaultParcelGenerator.world + val worldId = parcelProvider.getWorld(world)?.id ?: ParcelWorldId(world) + override val parcelTraverser: RegionTraverser = RegionTraverser.convergingTo(o.floorHeight) + + private val cornerWallType = when { + o.wallType is Slab -> (o.wallType.clone() as Slab).apply { type = Slab.Type.DOUBLE } + o.wallType.material.name.endsWith("CARPET") -> { + Bukkit.createBlockData(Material.getMaterial(o.wallType.material.name.substringBefore("CARPET") + "WOOL")) + } + else -> null + } + + override fun getParcelAt(x: Int, z: Int): Parcel? { + return convertBlockLocationToId(x, z, container::getParcelById) + } + + override fun getParcelIdAt(x: Int, z: Int): ParcelId? { + return convertBlockLocationToId(x, z) { idx, idz -> ParcelId(worldId, idx, idz) } + } + + + private fun checkParcelId(parcel: ParcelId): ParcelId { + if (!parcel.worldId.equals(worldId)) { + throw IllegalArgumentException() + } + return parcel + } + + override fun getRegionOrigin(parcel: ParcelId): Vec2i { + checkParcelId(parcel) + return Vec2i( + sectionSize * (parcel.x - 1) + pathOffset + o.offsetX, + sectionSize * (parcel.z - 1) + pathOffset + o.offsetZ + ) + } + + override fun getRegion(parcel: ParcelId): Region { + val origin = getRegionOrigin(parcel) + return Region( + Vec3i(origin.x, 0, origin.z), + Vec3i(o.parcelSize, maxHeight, o.parcelSize) + ) + } + + override fun getHomeLocation(parcel: ParcelId): Location { + val origin = getRegionOrigin(parcel) + val x = origin.x + (o.parcelSize - 1) / 2.0 + val z = origin.z - 2 + return Location(world, x + 0.5, o.floorHeight + 1.0, z + 0.5, 0F, 0F) + } + + override fun getParcelForInfoBlockInteraction(block: Vec3i, type: Material, face: BlockFace): Parcel? { + if (block.y != o.floorHeight + 1) return null + + val expectedParcelOrigin = when (type) { + Material.WALL_SIGN -> Vec2i(block.x + 1, block.z + 2) + o.wallType.material, cornerWallType?.material -> { + if (face != BlockFace.NORTH || world[block + Vec3i.convert(BlockFace.NORTH)].type == Material.WALL_SIGN) { + return null + } + + Vec2i(block.x + 1, block.z + 1) + } + else -> return null + } + + return getParcelAt(expectedParcelOrigin.x, expectedParcelOrigin.z) + ?.takeIf { expectedParcelOrigin == getRegionOrigin(it.id) } + ?.also { parcel -> + if (type != Material.WALL_SIGN && parcel.owner != null) { + updateParcelInfo(parcel.id, parcel.owner) + parcel.isOwnerSignOutdated = false + } + } + } + + override fun isParcelInfoSectionLoaded(parcel: ParcelId): Boolean { + val wallBlockChunk = getRegionOrigin(parcel).add(-1, -1).toChunk() + return world.isChunkLoaded(wallBlockChunk.x, wallBlockChunk.z) + } + + override fun updateParcelInfo(parcel: ParcelId, owner: PlayerProfile?) { + val b = getRegionOrigin(parcel) + + val wallBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 1) + val signBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 2) + val skullBlock = world.getBlockAt(b.x - 1, o.floorHeight + 2, b.z - 1) + + if (owner == null) { + wallBlock.blockData = o.wallType + signBlock.type = Material.AIR + skullBlock.type = Material.AIR + + } else { + cornerWallType?.let { wallBlock.blockData = it } + signBlock.blockData = (Bukkit.createBlockData(Material.WALL_SIGN) as WallSign).apply { facing = BlockFace.NORTH } + + val sign = signBlock.state as org.bukkit.block.Sign + sign.setLine(0, "${parcel.x},${parcel.z}") + sign.setLine(2, owner.name ?: "") + sign.update() + + skullBlock.type = Material.AIR + skullBlock.type = Material.PLAYER_HEAD + val skull = skullBlock.state as Skull + if (owner is PlayerProfile.Real) { + skull.owningPlayer = Bukkit.getOfflinePlayer(owner.uuid) + + } else if (!skull.setOwner(owner.name)) { + skullBlock.type = Material.AIR + return + } + + skull.rotation = BlockFace.SOUTH + skull.update() + } + } + + private fun trySubmitBlockVisitor(vararg parcels: ParcelId, function: JobFunction): Job? { + parcels.forEach { checkParcelId(it) } + return parcelProvider.trySubmitBlockVisitor(Permit(), parcels, function) + } + + override fun setBiome(parcel: ParcelId, biome: Biome) = trySubmitBlockVisitor(checkParcelId(parcel)) { + val world = world + val b = getRegionOrigin(parcel) + val parcelSize = o.parcelSize + for (x in b.x until b.x + parcelSize) { + for (z in b.z until b.z + parcelSize) { + markSuspensionPoint() + world.setBiome(x, z, biome) + } + } + } + + override fun clearParcel(parcel: ParcelId) = trySubmitBlockVisitor(checkParcelId(parcel)) { + val region = getRegion(parcel) + val blocks = parcelTraverser.traverseRegion(region) + val blockCount = region.blockCount.toDouble() + val world = world + val floorHeight = o.floorHeight + val airType = airType + val floorType = o.floorType + val fillType = o.fillType + + delegateWork(0.95) { + for ((index, vec) in blocks.withIndex()) { + markSuspensionPoint() + val y = vec.y + val blockType = when { + y > floorHeight -> airType + y == floorHeight -> floorType + else -> fillType + } + world[vec].blockData = blockType + setProgress((index + 1) / blockCount) + } + } + + delegateWork { + val entities = getEntities(region) + for ((index, entity) in entities.withIndex()) { + if (entity is Player) continue + entity.remove() + setProgress((index + 1) / entities.size.toDouble()) + } + } + + } + + override fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i> { + /* + * Get the offsets for the world out of the way + * to simplify the calculation that follows. + */ + + val x = chunk.x.shl(4) - (o.offsetX + pathOffset) + val z = chunk.z.shl(4) - (o.offsetZ + pathOffset) + + /* Locations of wall corners (where owner blocks are placed) are defined as: + * + * x umod sectionSize == sectionSize-1 + * + * This check needs to be made for all 16 slices of the chunk in 2 dimensions + * How to optimize this? + * Let's take the expression + * + * x umod sectionSize + * + * And call it modX + * x can be shifted (chunkSize -1) times to attempt to get a modX of 0. + * This means that if the modX is 1, and sectionSize == (chunkSize-1), there would be a match at the last shift. + * To check that there are any matches, we can see if the following holds: + * + * modX >= ((sectionSize-1) - (chunkSize-1)) + * + * Which can be simplified to: + * modX >= sectionSize - chunkSize + * + * if sectionSize == chunkSize, this expression can be simplified to + * modX >= 0 + * which is always true. This is expected. + * To get the total number of matches on a dimension, we can evaluate the following: + * + * (modX - (sectionSize - chunkSize) + sectionSize) / sectionSize + * + * We add sectionSize to the lhs because, if the other part of the lhs is 0, we need at least 1. + * This can be simplified to: + * + * (modX + chunkSize) / sectionSize + */ + + val sectionSize = sectionSize + + val modX = x umod sectionSize + val matchesOnDimensionX = (modX + chunkSize) / sectionSize + if (matchesOnDimensionX <= 0) return emptyList() + + val modZ = z umod sectionSize + val matchesOnDimensionZ = (modZ + chunkSize) / sectionSize + if (matchesOnDimensionZ <= 0) return emptyList() + + /* + * Now we need to find the first id within the matches, + * and then return the subsequent matches in a rectangle following it. + * + * On each dimension, get the distance to the first match, which is equal to (sectionSize-1 - modX) + * and add it to the coordinate value + */ + val firstX = x + (sectionSize - 1 - modX) + val firstZ = z + (sectionSize - 1 - modZ) + + val firstIdX = (firstX + 1) / sectionSize + 1 + val firstIdZ = (firstZ + 1) / sectionSize + 1 + + if (matchesOnDimensionX == 1 && matchesOnDimensionZ == 1) { + // fast-path optimization + return listOf(Vec2i(firstIdX, firstIdZ)) + } + + return (0 until matchesOnDimensionX).flatMap { idOffsetX -> + (0 until matchesOnDimensionZ).map { idOffsetZ -> Vec2i(firstIdX + idOffsetX, firstIdZ + idOffsetZ) } + } + } + + } + }
\ No newline at end of file |