Archived
0

Refactor and improve a lot of the API. Move default implementations into a package. Reformatting.

This commit is contained in:
Dico
2018-08-02 18:22:36 +01:00
parent 6513ad9237
commit 0af2e615d3
47 changed files with 1420 additions and 1091 deletions

View File

@@ -0,0 +1,65 @@
package io.dico.parcels2.defaultimpl
import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelContainer
import io.dico.parcels2.ParcelWorld
import kotlin.coroutines.experimental.buildIterator
import kotlin.coroutines.experimental.buildSequence
class DefaultParcelContainer(val world: ParcelWorld) : ParcelContainer {
private var parcels: Array<Array<Parcel>>
init {
parcels = initArray(world.options.axisLimit, world)
}
fun resizeIfSizeChanged() {
if (parcels.size != world.options.axisLimit * 2 + 1) {
resize(world.options.axisLimit)
}
}
fun resize(axisLimit: Int) {
parcels = initArray(axisLimit, world, this)
}
fun initArray(axisLimit: Int, world: ParcelWorld, cur: DefaultParcelContainer? = null): Array<Array<Parcel>> {
val arraySize = 2 * axisLimit + 1
return Array(arraySize) {
val x = it - axisLimit
Array(arraySize) {
val z = it - axisLimit
cur?.getParcelById(x, z) ?: ParcelImpl(world, x, z)
}
}
}
override fun getParcelById(x: Int, z: Int): Parcel? {
return parcels.getOrNull(x + world.options.axisLimit)?.getOrNull(z + world.options.axisLimit)
}
override fun nextEmptyParcel(): Parcel? {
return walkInCircle().find { it.owner == null }
}
private fun walkInCircle(): Iterable<Parcel> = Iterable {
buildIterator {
val center = world.options.axisLimit
for (radius in 0..center) {
var x = center - radius;
var z = center - radius
repeat(radius * 2) { yield(parcels[x++][z]) }
repeat(radius * 2) { yield(parcels[x][z++]) }
repeat(radius * 2) { yield(parcels[x--][z]) }
repeat(radius * 2) { yield(parcels[x][z--]) }
}
}
}
fun allParcels(): Sequence<Parcel> = buildSequence {
for (array in parcels) {
yieldAll(array.iterator())
}
}
}

View File

@@ -0,0 +1,267 @@
package io.dico.parcels2.defaultimpl
import io.dico.parcels2.*
import io.dico.parcels2.blockvisitor.RegionTraversal
import io.dico.parcels2.blockvisitor.Worker
import io.dico.parcels2.blockvisitor.WorktimeLimiter
import io.dico.parcels2.util.*
import org.bukkit.*
import org.bukkit.block.Biome
import org.bukkit.block.Block
import org.bukkit.block.BlockFace
import org.bukkit.block.Skull
import org.bukkit.block.data.BlockData
import org.bukkit.block.data.type.Sign
import org.bukkit.block.data.type.Slab
import java.util.Random
private val airType = Bukkit.createBlockData(Material.AIR)
data class DefaultGeneratorOptions(var defaultBiome: Biome = Biome.JUNGLE,
var wallType: BlockData = Bukkit.createBlockData(Material.STONE_SLAB),
var floorType: BlockData = Bukkit.createBlockData(Material.QUARTZ_BLOCK),
var fillType: BlockData = Bukkit.createBlockData(Material.QUARTZ_BLOCK),
var pathMainType: BlockData = Bukkit.createBlockData(Material.SANDSTONE),
var pathAltType: BlockData = Bukkit.createBlockData(Material.REDSTONE_BLOCK),
var parcelSize: Int = 101,
var pathSize: Int = 9,
var floorHeight: Int = 64,
var offsetX: Int = 0,
var offsetZ: Int = 0) : GeneratorOptions() {
override fun generatorFactory(): GeneratorFactory = DefaultParcelGenerator.Factory
}
class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOptions) : ParcelGenerator() {
private var _world: World? = null
override val world: World
get() {
if (_world == null) _world = Bukkit.getWorld(name)!!.also {
maxHeight = it.maxHeight
return it
}
return _world!!
}
private var maxHeight = 0
companion object Factory : GeneratorFactory {
override val name get() = "default"
override val optionsClass get() = DefaultGeneratorOptions::class
override fun newParcelGenerator(worldName: String, options: GeneratorOptions): ParcelGenerator {
return DefaultParcelGenerator(worldName, options as DefaultGeneratorOptions)
}
}
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 makeParcelBlockManager(worktimeLimiter: WorktimeLimiter): ParcelBlockManager {
return ParcelBlockManagerImpl(worktimeLimiter)
}
override fun makeParcelLocator(container: ParcelContainer): ParcelLocator {
return ParcelLocatorImpl(container)
}
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, (absZ - modZ) / sectionSize)
}
return null
}
private inner class ParcelLocatorImpl(val container: ParcelContainer) : ParcelLocator {
override val world: World = this@DefaultParcelGenerator.world
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(world.name, world.uid, idx, idz) }
}
}
@Suppress("DEPRECATION")
private inner class ParcelBlockManagerImpl(override val worktimeLimiter: WorktimeLimiter) : ParcelBlockManager {
override val world: World = this@DefaultParcelGenerator.world
override fun getBottomBlock(parcel: ParcelId): Vec2i = Vec2i(
sectionSize * parcel.pos.x + pathOffset + o.offsetX,
sectionSize * parcel.pos.z + pathOffset + o.offsetZ
)
override fun getHomeLocation(parcel: ParcelId): Location {
val bottom = getBottomBlock(parcel)
return Location(world, bottom.x.toDouble(), o.floorHeight + 1.0, bottom.z + (o.parcelSize - 1) / 2.0, -90F, 0F)
}
override fun setOwnerBlock(parcel: ParcelId, owner: ParcelOwner?) {
val b = getBottomBlock(parcel)
val wallBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 1)
val signBlock = world.getBlockAt(b.x - 2, o.floorHeight + 1, b.z - 1)
val skullBlock = world.getBlockAt(b.x - 1, o.floorHeight + 2, b.z - 1)
if (owner == null) {
wallBlock.blockData = o.wallType
signBlock.type = Material.AIR
skullBlock.type = Material.AIR
} else {
val wallBlockType: BlockData = if (o.wallType is Slab)
(o.wallType.clone() as Slab).apply { type = Slab.Type.DOUBLE }
else
o.wallType
wallBlock.blockData = wallBlockType
signBlock.blockData = (Bukkit.createBlockData(Material.WALL_SIGN) as Sign).apply { rotation = 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.PLAYER_HEAD
val skull = skullBlock.state as Skull
if (owner.uuid != null) {
skull.owningPlayer = owner.offlinePlayer
} else {
skull.owner = owner.name
}
skull.rotation = BlockFace.WEST
skull.update()
}
}
override fun setBiome(parcel: ParcelId, biome: Biome): Worker = worktimeLimiter.submit {
val world = world
val b = getBottomBlock(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): Worker = worktimeLimiter.submit {
val bottom = getBottomBlock(parcel)
val region = Region(Vec3i(bottom.x, 0, bottom.z), Vec3i(o.parcelSize, maxHeight + 1, o.parcelSize))
val blocks = RegionTraversal.DOWNWARD.regionTraverser(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 doBlockOperation(parcel: ParcelId, direction: RegionTraversal, operation: (Block) -> Unit): Worker = worktimeLimiter.submit {
val bottom = getBottomBlock(parcel)
val region = Region(Vec3i(bottom.x, 0, bottom.z), Vec3i(o.parcelSize, maxHeight + 1, o.parcelSize))
val blocks = direction.regionTraverser(region)
val blockCount = region.blockCount.toDouble()
val world = world
for ((index, vec) in blocks.withIndex()) {
markSuspensionPoint()
operation(world[vec])
setProgress((index + 1) / blockCount)
}
}
}
}

View File

@@ -0,0 +1,39 @@
@file:Suppress("UNCHECKED_CAST")
package io.dico.parcels2.defaultimpl
import io.dico.parcels2.*
import java.util.Collections
import java.util.UUID
class GlobalAddedDataManagerImpl(val plugin: ParcelsPlugin) : GlobalAddedDataManager {
private val map = mutableMapOf<ParcelOwner, GlobalAddedData>()
override fun get(owner: ParcelOwner): GlobalAddedData {
return map[owner] ?: GlobalAddedDataImpl(owner).also { map[owner] = it }
}
private inner class GlobalAddedDataImpl(override val owner: ParcelOwner,
data: MutableAddedDataMap = emptyData)
: AddedDataHolder(data), GlobalAddedData {
private inline var data get() = addedMap; set(value) = run { addedMap = value }
private inline val isEmpty get() = data === emptyData
override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean {
if (isEmpty) {
if (status == AddedStatus.DEFAULT) return false
data = mutableMapOf()
}
return super.setAddedStatus(uuid, status).also {
if (it) plugin.storage.setGlobalAddedStatus(owner, uuid, status)
}
}
}
private companion object {
val emptyData = Collections.emptyMap<Any, Any>() as MutableAddedDataMap
}
}

View File

@@ -0,0 +1,138 @@
package io.dico.parcels2.defaultimpl
import io.dico.dicore.Formatting
import io.dico.parcels2.*
import io.dico.parcels2.util.Vec2i
import io.dico.parcels2.util.getPlayerName
import org.bukkit.OfflinePlayer
import org.joda.time.DateTime
import java.util.UUID
import kotlin.reflect.KProperty
class ParcelImpl(override val world: ParcelWorld,
override val x: Int,
override val z: Int) : Parcel, ParcelId {
override val id: ParcelId = this
override val pos get() = Vec2i(x, z)
override var data: ParcelDataHolder = ParcelDataHolder(); private set
override val infoString by ParcelInfoStringComputer
override var hasBlockVisitors: Boolean = false; private set
override val worldId: ParcelWorldId get() = world.id
override fun copyDataIgnoringDatabase(data: ParcelData) {
this.data = ((data as? Parcel)?.data ?: data) as ParcelDataHolder
}
override fun copyData(data: ParcelData) {
copyDataIgnoringDatabase(data)
world.storage.setParcelData(this, data)
}
override fun dispose() {
copyDataIgnoringDatabase(ParcelDataHolder())
world.storage.setParcelData(this, null)
}
override val addedMap: Map<UUID, AddedStatus> get() = data.addedMap
override fun getAddedStatus(uuid: UUID) = data.getAddedStatus(uuid)
override fun isBanned(uuid: UUID) = data.isBanned(uuid)
override fun isAllowed(uuid: UUID) = data.isAllowed(uuid)
override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean): Boolean {
return (data.canBuild(player, checkAdmin, false))
|| checkGlobal && world.globalAddedData[owner ?: return false].isAllowed(player)
}
val globalAddedMap: Map<UUID, AddedStatus>? get() = owner?.let { world.globalAddedData[it].addedMap }
override val since: DateTime? get() = data.since
override var owner: ParcelOwner?
get() = data.owner
set(value) {
if (data.owner != value) {
world.storage.setParcelOwner(this, value)
data.owner = value
}
}
override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean {
return data.setAddedStatus(uuid, status).also {
if (it) world.storage.setParcelPlayerStatus(this, uuid, status)
}
}
override var allowInteractInputs: Boolean
get() = data.allowInteractInputs
set(value) {
if (data.allowInteractInputs == value) return
world.storage.setParcelAllowsInteractInputs(this, value)
data.allowInteractInputs = value
}
override var allowInteractInventory: Boolean
get() = data.allowInteractInventory
set(value) {
if (data.allowInteractInventory == value) return
world.storage.setParcelAllowsInteractInventory(this, value)
data.allowInteractInventory = value
}
}
private object ParcelInfoStringComputer {
val infoStringColor1 = Formatting.GREEN
val infoStringColor2 = Formatting.AQUA
private inline fun StringBuilder.appendField(name: String, value: StringBuilder.() -> Unit) {
append(infoStringColor1)
append(name)
append(": ")
append(infoStringColor2)
value()
append(' ')
}
operator fun getValue(parcel: Parcel, property: KProperty<*>): String = buildString {
appendField("ID") {
append(parcel.x)
append(',')
append(parcel.z)
}
appendField("Owner") {
val owner = parcel.owner
if (owner == null) {
append(infoStringColor1)
append("none")
} else {
append(owner.notNullName)
}
}
// plotme appends biome here
append('\n')
val allowedMap = parcel.addedMap.filterValues { it.isAllowed }
if (allowedMap.isNotEmpty()) appendField("Allowed") {
allowedMap.keys.map(::getPlayerName).joinTo(this)
}
val bannedMap = parcel.addedMap.filterValues { it.isBanned }
if (bannedMap.isNotEmpty()) appendField("Banned") {
bannedMap.keys.map(::getPlayerName).joinTo(this)
}
if (!parcel.allowInteractInputs || !parcel.allowInteractInventory) {
appendField("Options") {
append("(")
appendField("inputs") { append(parcel.allowInteractInputs) }
append(", ")
appendField("inventory") { append(parcel.allowInteractInventory) }
append(")")
}
}
}
}

View File

@@ -0,0 +1,119 @@
package io.dico.parcels2.defaultimpl
import io.dico.parcels2.*
import org.bukkit.Bukkit
import org.bukkit.WorldCreator
class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
inline val options get() = plugin.options
override val worlds: Map<String, ParcelWorld> get() = _worlds
private val _worlds: MutableMap<String, ParcelWorld> = hashMapOf()
private val _generators: MutableMap<String, ParcelGenerator> = hashMapOf()
private var _worldsLoaded = false
private var _dataIsLoaded = false
// disabled while !_dataIsLoaded. getParcelById() will work though for data loading.
override fun getWorld(name: String): ParcelWorld? = _worlds[name]?.takeIf { _dataIsLoaded }
override fun getWorldById(id: ParcelWorldId): ParcelWorld? {
if (id is ParcelWorld) return id
return _worlds[id.name] ?: id.bukkitWorld?.let { getWorld(it) }
}
override fun getParcelById(id: ParcelId): Parcel? {
if (id is Parcel) return id
return getWorldById(id.worldId)?.container?.getParcelById(id.x, id.z)
}
override fun getWorldGenerator(worldName: String): ParcelGenerator? {
return _worlds[worldName]?.generator
?: _generators[worldName]
?: options.worlds[worldName]?.generator?.newGenerator(worldName)?.also { _generators[worldName] = it }
}
override fun loadWorlds() {
if (_worldsLoaded) throw IllegalStateException()
_worldsLoaded = true
loadWorlds0()
}
private fun loadWorlds0() {
if (Bukkit.getWorlds().isEmpty()) {
plugin.functionHelper.schedule(::loadWorlds0)
plugin.logger.warning("Scheduling to load worlds in the next tick, \nbecause no bukkit worlds are loaded yet")
return
}
for ((worldName, worldOptions) in options.worlds.entries) {
var parcelWorld = _worlds[worldName]
if (parcelWorld != null) continue
val generator: ParcelGenerator = getWorldGenerator(worldName)!!
val bukkitWorld = Bukkit.getWorld(worldName) ?: WorldCreator(worldName).generator(generator).createWorld()
parcelWorld = ParcelWorldImpl(bukkitWorld, generator, worldOptions.runtime, plugin.storage,
plugin.globalAddedData, ::DefaultParcelContainer, plugin.worktimeLimiter)
_worlds[worldName] = parcelWorld
}
loadStoredData()
}
private fun loadStoredData() {
plugin.functionHelper.launchLazilyOnMainThread {
val channel = plugin.storage.readAllParcelData()
do {
val pair = channel.receiveOrNull() ?: break
val parcel = getParcelById(pair.first) ?: continue
pair.second?.let { parcel.copyDataIgnoringDatabase(it) }
} while (true)
_dataIsLoaded = true
}.start()
}
/*
fun loadWorlds(options: Options) {
for ((worldName, worldOptions) in options.worlds.entries) {
val world: ParcelWorld
try {
world = ParcelWorldImpl(
worldName,
worldOptions,
worldOptions.generator.newGenerator(this, worldName),
plugin.storage,
plugin.globalAddedData,
::DefaultParcelContainer)
} catch (ex: Exception) {
ex.printStackTrace()
continue
}
_worlds[worldName] = world
}
plugin.functionHelper.schedule(10) {
println("Parcels generating parcelProvider now")
for ((name, world) in _worlds) {
if (Bukkit.getWorld(name) == null) {
val bworld = WorldCreator(name).generator(world.generator).createWorld()
val spawn = world.generator.getFixedSpawnLocation(bworld, null)
bworld.setSpawnLocation(spawn.x.floor(), spawn.y.floor(), spawn.z.floor())
}
}
val channel = plugin.storage.readAllParcelData()
val job = plugin.functionHelper.launchLazilyOnMainThread {
do {
val pair = channel.receiveOrNull() ?: break
val parcel = getParcelById(pair.first) ?: continue
pair.second?.let { parcel.copyDataIgnoringDatabase(it) }
} while (true)
}
job.start()
}
}
*/
}

View File

@@ -0,0 +1,94 @@
@file:Suppress("CanBePrimaryConstructorProperty", "UsePropertyAccessSyntax")
package io.dico.parcels2.defaultimpl
import io.dico.parcels2.*
import io.dico.parcels2.blockvisitor.WorktimeLimiter
import io.dico.parcels2.storage.Storage
import org.bukkit.World
import java.util.UUID
class ParcelWorldImpl private
constructor(override val world: World,
override val generator: ParcelGenerator,
override var options: WorldOptions,
override val storage: Storage,
override val globalAddedData: GlobalAddedDataManager,
containerFactory: ParcelContainerFactory,
blockManager: ParcelBlockManager)
: ParcelWorld,
ParcelWorldId,
ParcelContainer, // missing delegation
ParcelLocator, // missing delegation
ParcelBlockManager by blockManager {
override val id: ParcelWorldId get() = this
override val uid: UUID? get() = world.uid
init {
if (generator.world != world) {
throw IllegalArgumentException()
}
}
override val name: String = world.name!!
override val container: ParcelContainer = containerFactory(this)
override val locator: ParcelLocator = generator.makeParcelLocator(container)
override val blockManager: ParcelBlockManager = blockManager
init {
enforceOptions()
}
fun enforceOptions() {
if (options.dayTime) {
world.setGameRuleValue("doDaylightCycle", "false")
world.setTime(6000)
}
if (options.noWeather) {
world.setStorm(false)
world.setThundering(false)
world.weatherDuration = Integer.MAX_VALUE
}
world.setGameRuleValue("doTileDrops", "${options.doTileDrops}")
}
/*
Interface delegation needs to be implemented manually because JetBrains has yet to fix it.
*/
companion object {
// Use this to be able to delegate blockManager and assign it to a property too, at least.
operator fun invoke(world: World,
generator: ParcelGenerator,
options: WorldOptions,
storage: Storage,
globalAddedData: GlobalAddedDataManager,
containerFactory: ParcelContainerFactory,
worktimeLimiter: WorktimeLimiter): ParcelWorldImpl {
val blockManager = generator.makeParcelBlockManager(worktimeLimiter)
return ParcelWorldImpl(world, generator, options, storage, globalAddedData, containerFactory, blockManager)
}
}
// ParcelLocator interface
override fun getParcelAt(x: Int, z: Int): Parcel? {
return locator.getParcelAt(x, z)
}
override fun getParcelIdAt(x: Int, z: Int): ParcelId? {
return locator.getParcelIdAt(x, z)
}
// ParcelContainer interface
override fun getParcelById(x: Int, z: Int): Parcel? {
return container.getParcelById(x, z)
}
override fun nextEmptyParcel(): Parcel? {
return container.nextEmptyParcel()
}
}