diff options
author | Dico <dico.karssiens@gmail.com> | 2018-08-02 18:22:36 +0100 |
---|---|---|
committer | Dico <dico.karssiens@gmail.com> | 2018-08-02 18:22:36 +0100 |
commit | 0af2e615d3fa1d8509be46e14f99d40dc9cdb342 (patch) | |
tree | 3864043218969a67b5df17784a05c6af424e3617 /src | |
parent | 6513ad9237dbda0244a52608ae639fee5822b3ee (diff) |
Refactor and improve a lot of the API. Move default implementations into a package. Reformatting.
Diffstat (limited to 'src')
46 files changed, 1424 insertions, 1096 deletions
diff --git a/src/main/kotlin/io/dico/parcels2/AddedData.kt b/src/main/kotlin/io/dico/parcels2/AddedData.kt index 5d2a68d..633fe72 100644 --- a/src/main/kotlin/io/dico/parcels2/AddedData.kt +++ b/src/main/kotlin/io/dico/parcels2/AddedData.kt @@ -2,10 +2,13 @@ package io.dico.parcels2 import io.dico.parcels2.util.uuid import org.bukkit.OfflinePlayer -import java.util.* +import java.util.UUID + +typealias MutableAddedDataMap = MutableMap<UUID, AddedStatus> +typealias AddedDataMap = Map<UUID, AddedStatus> interface AddedData { - val addedMap: Map<UUID, AddedStatus> + val addedMap: AddedDataMap fun getAddedStatus(uuid: UUID): AddedStatus fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean @@ -28,8 +31,7 @@ interface AddedData { fun unban(player: OfflinePlayer) = unban(player.uuid) } -open class AddedDataHolder(override var addedMap: MutableMap<UUID, AddedStatus> - = mutableMapOf<UUID, AddedStatus>()) : AddedData { +open class AddedDataHolder(override var addedMap: MutableAddedDataMap = mutableMapOf()) : AddedData { override fun getAddedStatus(uuid: UUID): AddedStatus = addedMap.getOrDefault(uuid, AddedStatus.DEFAULT) override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean = status.takeIf { it != AddedStatus.DEFAULT } ?.let { addedMap.put(uuid, it) != it } @@ -44,4 +46,12 @@ enum class AddedStatus { val isDefault get() = this == DEFAULT val isAllowed get() = this == ALLOWED val isBanned get() = this == BANNED -}
\ No newline at end of file +} + +interface GlobalAddedData : AddedData { + val owner: ParcelOwner +} + +interface GlobalAddedDataManager { + operator fun get(owner: ParcelOwner): GlobalAddedData +} diff --git a/src/main/kotlin/io/dico/parcels2/Options.kt b/src/main/kotlin/io/dico/parcels2/Options.kt index c349d8b..aefa9e1 100644 --- a/src/main/kotlin/io/dico/parcels2/Options.kt +++ b/src/main/kotlin/io/dico/parcels2/Options.kt @@ -1,27 +1,33 @@ package io.dico.parcels2 - import com.fasterxml.jackson.annotation.JsonIgnore import io.dico.parcels2.blockvisitor.TickWorktimeOptions +import io.dico.parcels2.defaultimpl.DefaultGeneratorOptions import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.StorageFactory import io.dico.parcels2.storage.yamlObjectMapper -import org.bukkit.Bukkit.createBlockData import org.bukkit.GameMode import org.bukkit.Material -import org.bukkit.block.Biome -import org.bukkit.block.data.BlockData import java.io.Reader import java.io.Writer -import java.util.* +import java.util.EnumSet class Options { - var worlds: Map<String, WorldOptions> = HashMap() + var worlds: Map<String, WorldOptionsHolder> = hashMapOf() private set var storage: StorageOptions = StorageOptions("postgresql", DataConnectionOptions()) var tickWorktime: TickWorktimeOptions = TickWorktimeOptions(20, 1) - fun addWorld(name: String, options: WorldOptions) = (worlds as MutableMap).put(name, options) + fun addWorld(name: String, + generatorOptions: GeneratorOptions? = null, + worldOptions: WorldOptions? = null) { + val optionsHolder = WorldOptionsHolder( + generatorOptions ?: DefaultGeneratorOptions(), + worldOptions ?: WorldOptions() + ) + + (worlds as MutableMap).put(name, optionsHolder) + } fun writeTo(writer: Writer) = yamlObjectMapper.writeValue(writer, this) @@ -31,6 +37,9 @@ class Options { } +class WorldOptionsHolder(var generator: GeneratorOptions = DefaultGeneratorOptions(), + var runtime: WorldOptions = WorldOptions()) + data class WorldOptions(var gameMode: GameMode? = GameMode.CREATIVE, var dayTime: Boolean = true, var noWeather: Boolean = true, @@ -42,8 +51,7 @@ data class WorldOptions(var gameMode: GameMode? = GameMode.CREATIVE, var blockPortalCreation: Boolean = true, var blockMobSpawning: Boolean = true, var blockedItems: Set<Material> = EnumSet.of(Material.FLINT_AND_STEEL, Material.SNOWBALL), - var axisLimit: Int = 10, - var generator: GeneratorOptions = DefaultGeneratorOptions()) { + var axisLimit: Int = 10) { } @@ -51,23 +59,7 @@ abstract class GeneratorOptions { abstract fun generatorFactory(): GeneratorFactory - fun getGenerator(worlds: Worlds, worldName: String) = generatorFactory().newParcelGenerator(worlds, worldName, this) - -} - -data class DefaultGeneratorOptions(var defaultBiome: Biome = Biome.JUNGLE, - var wallType: BlockData = createBlockData(Material.STONE_SLAB), - var floorType: BlockData = createBlockData(Material.QUARTZ_BLOCK), - var fillType: BlockData = createBlockData(Material.QUARTZ_BLOCK), - var pathMainType: BlockData = createBlockData(Material.SANDSTONE), - var pathAltType: BlockData = 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 + fun newGenerator(worldName: String) = generatorFactory().newParcelGenerator(worldName, this) } @@ -104,4 +96,9 @@ data class DataConnectionOptions(val address: String = "localhost", } -data class DataFileOptions(val location: String = "/flatfile-storage/")
\ No newline at end of file +data class DataFileOptions(val location: String = "/flatfile-storage/") + +class MigrationOptions() { + + +} diff --git a/src/main/kotlin/io/dico/parcels2/Parcel.kt b/src/main/kotlin/io/dico/parcels2/Parcel.kt index 335b415..a69116a 100644 --- a/src/main/kotlin/io/dico/parcels2/Parcel.kt +++ b/src/main/kotlin/io/dico/parcels2/Parcel.kt @@ -1,14 +1,11 @@ package io.dico.parcels2 -import io.dico.dicore.Formatting import io.dico.parcels2.util.Vec2i -import io.dico.parcels2.util.getPlayerName import io.dico.parcels2.util.hasBuildAnywhere import org.bukkit.OfflinePlayer import org.bukkit.entity.Player import org.joda.time.DateTime -import java.util.* -import kotlin.reflect.KProperty +import java.util.UUID /** * Parcel implementation of ParcelData will update the database when changes are made. @@ -18,73 +15,21 @@ import kotlin.reflect.KProperty * However, this implementation is intentionally not thread-safe. * Therefore, database query callbacks should schedule their updates using the bukkit scheduler. */ -class Parcel(val world: ParcelWorld, val pos: Vec2i) : ParcelData { - var data: ParcelData = ParcelDataHolder(); private set +interface Parcel : ParcelData { + val id: ParcelId + val world: ParcelWorld + val pos: Vec2i + val x: Int + val z: Int + val data: ParcelData + val infoString: String + val hasBlockVisitors: Boolean - val id get() = "${pos.x}:${pos.z}" - val homeLocation get() = world.generator.getHomeLocation(this) + fun copyDataIgnoringDatabase(data: ParcelData) - val infoString by ParcelInfoStringComputer + fun copyData(data: ParcelData) - fun copyDataIgnoringDatabase(data: ParcelData) { - this.data = data - } - - fun copyData(data: ParcelData) { - copyDataIgnoringDatabase(data) - world.storage.setParcelData(this, data) - } - - 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 - } - - var hasBlockVisitors: Boolean = false; private set + fun dispose() } interface ParcelData : AddedData { @@ -113,59 +58,3 @@ class ParcelDataHolder : AddedDataHolder(), ParcelData { override var allowInteractInventory = true } -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.pos.x) - append(':') - append(parcel.pos.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(")") - } - } - - } -}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt b/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt new file mode 100644 index 0000000..ff28537 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt @@ -0,0 +1,83 @@ +package 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.defaultimpl.DefaultParcelGenerator +import io.dico.parcels2.util.Vec2i +import org.bukkit.Chunk +import org.bukkit.Location +import org.bukkit.World +import org.bukkit.block.Biome +import org.bukkit.block.Block +import org.bukkit.entity.Entity +import org.bukkit.generator.BlockPopulator +import org.bukkit.generator.ChunkGenerator +import java.util.HashMap +import java.util.Random +import kotlin.reflect.KClass + +object GeneratorFactories { + private val map: MutableMap<String, GeneratorFactory> = HashMap() + + fun registerFactory(generator: GeneratorFactory): Boolean = map.putIfAbsent(generator.name, generator) == null + + fun getFactory(name: String): GeneratorFactory? = map.get(name) + + init { + registerFactory(DefaultParcelGenerator.Factory) + } +} + +interface GeneratorFactory { + val name: String + + val optionsClass: KClass<out GeneratorOptions> + + fun newParcelGenerator(worldName: String, options: GeneratorOptions): ParcelGenerator +} + +abstract class ParcelGenerator : ChunkGenerator() { + abstract val world: World + + abstract override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData + + abstract fun populate(world: World?, random: Random?, chunk: Chunk?) + + abstract override fun getFixedSpawnLocation(world: World?, random: Random?): Location + + override fun getDefaultPopulators(world: World?): MutableList<BlockPopulator> { + return mutableListOf(object : BlockPopulator() { + override fun populate(world: World?, random: Random?, chunk: Chunk?) { + this@ParcelGenerator.populate(world, random, chunk) + } + }) + } + + abstract fun makeParcelBlockManager(worktimeLimiter: WorktimeLimiter): ParcelBlockManager + + abstract fun makeParcelLocator(container: ParcelContainer): ParcelLocator +} + +interface ParcelBlockManager { + val world: World + val worktimeLimiter: WorktimeLimiter + + fun getBottomBlock(parcel: ParcelId): Vec2i + + fun getHomeLocation(parcel: ParcelId): Location + + fun setOwnerBlock(parcel: ParcelId, owner: ParcelOwner?) + + @Deprecated("") + fun getEntities(parcel: ParcelId): Collection<Entity> = TODO() + + @Deprecated("") + fun getBlocks(parcel: ParcelId, yRange: IntRange = 0..255): Iterator<Block> = TODO() + + fun setBiome(parcel: ParcelId, biome: Biome): Worker + + fun clearParcel(parcel: ParcelId): Worker + + fun doBlockOperation(parcel: ParcelId, direction: RegionTraversal = RegionTraversal.DOWNWARD, operation: (Block) -> Unit): Worker +} diff --git a/src/main/kotlin/io/dico/parcels2/ParcelId.kt b/src/main/kotlin/io/dico/parcels2/ParcelId.kt new file mode 100644 index 0000000..951a172 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/ParcelId.kt @@ -0,0 +1,49 @@ +package io.dico.parcels2 + +import io.dico.parcels2.util.Vec2i +import org.bukkit.Bukkit +import org.bukkit.World +import java.util.UUID + +/** + * Used by storage backing options to encompass the identity of a world + * Does NOT support equality operator. + */ +interface ParcelWorldId { + val name: String + val uid: UUID? + fun equals(id: ParcelWorldId): Boolean = name == name || (uid != null && uid == id.uid) + + val bukkitWorld: World? get() = Bukkit.getWorld(name) ?: uid?.let { Bukkit.getWorld(it) } + + companion object { + operator fun invoke(worldName: String, worldUid: UUID?): ParcelWorldId = ParcelWorldIdImpl(worldName, worldUid) + operator fun invoke(worldName: String): ParcelWorldId = ParcelWorldIdImpl(worldName, null) + } +} + +/** + * Used by storage backing options to encompass the location of a parcel + * Does NOT support equality operator. + */ +interface ParcelId { + val worldId: ParcelWorldId + val x: Int + val z: Int + val pos: Vec2i get() = Vec2i(x, z) + fun equals(id: ParcelId): Boolean = x == id.x && z == id.z && worldId.equals(id.worldId) + + companion object { + operator fun invoke(worldId: ParcelWorldId, pos: Vec2i) = invoke(worldId, pos.x, pos.z) + operator fun invoke(worldName: String, worldUid: UUID?, pos: Vec2i) = invoke(worldName, worldUid, pos.x, pos.z) + operator fun invoke(worldName: String, worldUid: UUID?, x: Int, z: Int) = invoke(ParcelWorldId(worldName, worldUid), x, z) + operator fun invoke(worldId: ParcelWorldId, x: Int, z: Int): ParcelId = ParcelIdImpl(worldId, x, z) + } +} + +private class ParcelWorldIdImpl(override val name: String, + override val uid: UUID?) : ParcelWorldId + +private class ParcelIdImpl(override val worldId: ParcelWorldId, + override val x: Int, + override val z: Int) : ParcelId diff --git a/src/main/kotlin/io/dico/parcels2/ParcelOwner.kt b/src/main/kotlin/io/dico/parcels2/ParcelOwner.kt index 07e7c09..5a36cac 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelOwner.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelOwner.kt @@ -1,4 +1,4 @@ -@file:Suppress("unused") +@file:Suppress("unused", "UsePropertyAccessSyntax", "DEPRECATION") package io.dico.parcels2 @@ -8,11 +8,10 @@ import io.dico.parcels2.util.uuid import org.bukkit.Bukkit import org.bukkit.OfflinePlayer import org.bukkit.entity.Player -import java.util.* +import java.util.UUID -@Suppress("UsePropertyAccessSyntax") -class ParcelOwner private constructor(val uuid: UUID?, - val name: String?) { +class ParcelOwner(val uuid: UUID?, + val name: String?) { val notNullName: String by lazy { name ?: getPlayerNameOrDefault(uuid!!) } constructor(name: String) : this(null, name) @@ -26,11 +25,11 @@ class ParcelOwner private constructor(val uuid: UUID?, inline val hasUUID: Boolean get() = uuid != null val onlinePlayer: Player? get() = uuid?.let { Bukkit.getPlayer(uuid) } - @Suppress("DEPRECATION") + val onlinePlayerAllowingNameMatch: Player? get() = onlinePlayer ?: name?.let { Bukkit.getPlayerExact(name) } val offlinePlayer: OfflinePlayer? get() = uuid?.let { Bukkit.getOfflinePlayer(it).takeIf { it.isValid } } - @Suppress("DEPRECATION") - val offlinePlayerAllowingNameMatch: OfflinePlayer? get() = offlinePlayer ?: Bukkit.getOfflinePlayer(name).takeIf { it.isValid } + val offlinePlayerAllowingNameMatch: OfflinePlayer? + get() = offlinePlayer ?: Bukkit.getOfflinePlayer(name).takeIf { it.isValid } fun matches(player: OfflinePlayer, allowNameMatch: Boolean = false): Boolean { return uuid?.let { it == player.uniqueId } ?: false diff --git a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt index 5e7187b..16f108f 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt @@ -1,223 +1,86 @@ package io.dico.parcels2 -import io.dico.parcels2.storage.SerializableParcel -import io.dico.parcels2.storage.SerializableWorld import io.dico.parcels2.storage.Storage -import io.dico.parcels2.storage.getParcelBySerializedValue import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.floor -import kotlinx.coroutines.experimental.launch -import org.bukkit.Bukkit import org.bukkit.Location import org.bukkit.World -import org.bukkit.WorldCreator import org.bukkit.block.Block import org.bukkit.entity.Entity -import org.bukkit.entity.Player -import java.util.* -import kotlin.coroutines.experimental.buildIterator -import kotlin.coroutines.experimental.buildSequence +import java.util.UUID -class Worlds(val plugin: ParcelsPlugin) { - val worlds: Map<String, ParcelWorld> get() = _worlds - private val _worlds: MutableMap<String, ParcelWorld> = HashMap() +interface ParcelProvider { + val worlds: Map<String, ParcelWorld> + + fun getWorldById(id: ParcelWorldId): ParcelWorld? + + fun getParcelById(id: ParcelId): Parcel? - fun getWorld(name: String): ParcelWorld? = _worlds[name] + fun getWorld(name: String): ParcelWorld? fun getWorld(world: World): ParcelWorld? = getWorld(world.name) - fun getParcelAt(block: Block): Parcel? = getParcelAt(block.world, block.x, block.z) + fun getWorld(block: Block): ParcelWorld? = getWorld(block.world) + + fun getWorld(loc: Location): ParcelWorld? = getWorld(loc.world) - fun getParcelAt(player: Player): Parcel? = getParcelAt(player.location) + fun getWorld(entity: Entity): ParcelWorld? = getWorld(entity.location) - fun getParcelAt(location: Location): Parcel? = getParcelAt(location.world, location.x.floor(), location.z.floor()) + fun getParcelAt(worldName: String, x: Int, z: Int): Parcel? = getWorld(worldName)?.locator?.getParcelAt(x, z) fun getParcelAt(world: World, x: Int, z: Int): Parcel? = getParcelAt(world.name, x, z) - fun getParcelAt(world: String, x: Int, z: Int): Parcel? { - with(getWorld(world) ?: return null) { - return generator.parcelAt(x, z) - } - } - - operator fun SerializableParcel.invoke(): Parcel? { - return world()?.parcelByID(pos) - } - - operator fun SerializableWorld.invoke(): ParcelWorld? { - return world?.let { getWorld(it) } - } - - fun loadWorlds(options: Options) { - for ((worldName, worldOptions) in options.worlds.entries) { - val world: ParcelWorld - try { - - world = ParcelWorld( - worldName, - worldOptions, - worldOptions.generator.getGenerator(this, worldName), - plugin.storage, - plugin.globalAddedData) - - } catch (ex: Exception) { - ex.printStackTrace() - continue - } - - _worlds[worldName] = world - } - - plugin.functionHelper.schedule(10) { - println("Parcels generating worlds 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 = getParcelBySerializedValue(pair.first) ?: continue - pair.second?.let { parcel.copyDataIgnoringDatabase(it) } - } while (true) - } - job.start() - } - - } + fun getParcelAt(world: World, vec: Vec2i): Parcel? = getParcelAt(world, vec.x, vec.z) + + fun getParcelAt(loc: Location): Parcel? = getParcelAt(loc.world, loc.x.floor(), loc.z.floor()) + + fun getParcelAt(entity: Entity): Parcel? = getParcelAt(entity.location) + + fun getParcelAt(block: Block): Parcel? = getParcelAt(block.world, block.x, block.z) + + fun getWorldGenerator(worldName: String): ParcelGenerator? + + fun loadWorlds() } -interface ParcelProvider { +interface ParcelLocator { + val world: World - fun parcelAt(x: Int, z: Int): Parcel? + fun getParcelIdAt(x: Int, z: Int): ParcelId? - fun parcelAt(vec: Vec2i): Parcel? = parcelAt(vec.x, vec.z) + fun getParcelAt(x: Int, z: Int): Parcel? - fun parcelAt(loc: Location): Parcel? = parcelAt(loc.x.floor(), loc.z.floor()) + fun getParcelAt(vec: Vec2i): Parcel? = getParcelAt(vec.x, vec.z) - fun parcelAt(entity: Entity): Parcel? = parcelAt(entity.location) + fun getParcelAt(loc: Location): Parcel? = getParcelAt(loc.x.floor(), loc.z.floor()).takeIf { loc.world == world } - fun parcelAt(block: Block): Parcel? = parcelAt(block.x, block.z) -} + fun getParcelAt(entity: Entity): Parcel? = getParcelAt(entity.location).takeIf { entity.world == world } -class ParcelWorld constructor(val name: String, - val options: WorldOptions, - val generator: ParcelGenerator, - val storage: Storage, - val globalAddedData: GlobalAddedDataManager) : ParcelProvider by generator, ParcelContainer { - val world: World by lazy { - Bukkit.getWorld(name) ?: throw NullPointerException("World $name does not appear to be loaded") - } - val container: ParcelContainer = DefaultParcelContainer(this, storage) - - override fun parcelByID(x: Int, z: Int): Parcel? { - return container.parcelByID(x, z) - } - - override fun nextEmptyParcel(): Parcel? { - return container.nextEmptyParcel() - } - - fun parcelByID(id: Vec2i): Parcel? = parcelByID(id.x, id.z) - - fun enforceOptionsIfApplicable() { - val world = world - val options = options - 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}") - } + fun getParcelAt(block: Block): Parcel? = getParcelAt(block.x, block.z).takeIf { block.world == world } } +typealias ParcelContainerFactory = (ParcelWorld) -> ParcelContainer + interface ParcelContainer { - fun parcelByID(x: Int, z: Int): Parcel? + fun getParcelById(x: Int, z: Int): Parcel? + + fun getParcelById(id: Vec2i): Parcel? = getParcelById(id.x, id.z) fun nextEmptyParcel(): Parcel? } -typealias ParcelContainerFactory = (ParcelWorld) -> ParcelContainer - -class DefaultParcelContainer(private val world: ParcelWorld, - private val storage: Storage) : ParcelContainer { - private var parcels: Array<Array<Parcel>> - - init { - parcels = initArray(world.options.axisLimit, world) - } - - fun resizeIfSizeChanged() { - if (parcels.size / 2 != world.options.axisLimit) { - 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?.parcelByID(x, z) ?: Parcel(world, Vec2i(x, z)) - } - } - } - - override fun parcelByID(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()) - } - } - - fun loadAllData() { - val channel = storage.readParcelData(allParcels()) - launch(storage.asyncDispatcher) { - for ((parcel, data) in channel) { - data?.let { parcel.copyDataIgnoringDatabase(it) } - } - } - } - -}
\ No newline at end of file +interface ParcelWorld : ParcelLocator, ParcelContainer, ParcelBlockManager { + val id: ParcelWorldId + val name: String + val uid: UUID? + val options: WorldOptions + val generator: ParcelGenerator + val storage: Storage + val container: ParcelContainer + val locator: ParcelLocator + val blockManager: ParcelBlockManager + val globalAddedData: GlobalAddedDataManager +} diff --git a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt index 2533277..3a74626 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt @@ -6,6 +6,8 @@ import io.dico.dicore.command.ICommandDispatcher import io.dico.parcels2.blockvisitor.TickWorktimeLimiter import io.dico.parcels2.blockvisitor.WorktimeLimiter import io.dico.parcels2.command.getParcelCommands +import io.dico.parcels2.defaultimpl.GlobalAddedDataManagerImpl +import io.dico.parcels2.defaultimpl.ParcelProviderImpl import io.dico.parcels2.listener.ParcelEntityTracker import io.dico.parcels2.listener.ParcelListeners import io.dico.parcels2.storage.Storage @@ -13,6 +15,7 @@ import io.dico.parcels2.storage.yamlObjectMapper import io.dico.parcels2.util.FunctionHelper import io.dico.parcels2.util.tryCreate import org.bukkit.Bukkit +import org.bukkit.generator.ChunkGenerator import org.bukkit.plugin.java.JavaPlugin import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -24,7 +27,7 @@ private inline val plogger get() = logger class ParcelsPlugin : JavaPlugin() { lateinit var optionsFile: File; private set lateinit var options: Options; private set - lateinit var worlds: Worlds; private set + lateinit var parcelProvider: ParcelProvider; private set lateinit var storage: Storage; private set lateinit var globalAddedData: GlobalAddedDataManager; private set @@ -50,7 +53,7 @@ class ParcelsPlugin : JavaPlugin() { private fun init(): Boolean { optionsFile = File(dataFolder, "options.yml") options = Options() - worlds = Worlds(this) + parcelProvider = ParcelProviderImpl(this) try { if (!loadOptions()) return false @@ -64,8 +67,7 @@ class ParcelsPlugin : JavaPlugin() { } globalAddedData = GlobalAddedDataManagerImpl(this) - worlds.loadWorlds(options) - entityTracker = ParcelEntityTracker(worlds) + entityTracker = ParcelEntityTracker(parcelProvider) } catch (ex: Exception) { plogger.error("Error loading options", ex) return false @@ -74,6 +76,7 @@ class ParcelsPlugin : JavaPlugin() { registerListeners() registerCommands() + parcelProvider.loadWorlds() return true } @@ -81,7 +84,7 @@ class ParcelsPlugin : JavaPlugin() { when { optionsFile.exists() -> yamlObjectMapper.readerForUpdating(options).readValue<Options>(optionsFile) optionsFile.tryCreate() -> { - options.addWorld("parcels", WorldOptions()) + options.addWorld("parcels") try { yamlObjectMapper.writeValue(optionsFile, options) } catch (ex: Throwable) { @@ -99,6 +102,10 @@ class ParcelsPlugin : JavaPlugin() { return true } + override fun getDefaultWorldGenerator(worldName: String, generatorId: String?): ChunkGenerator? { + return parcelProvider.getWorldGenerator(worldName) + } + private fun registerCommands() { cmdDispatcher = getParcelCommands(this).apply { registerToCommandMap("parcels:", EOverridePolicy.FALLBACK_ONLY) @@ -107,7 +114,7 @@ class ParcelsPlugin : JavaPlugin() { private fun registerListeners() { if (listeners == null) { - listeners = ParcelListeners(worlds, entityTracker) + listeners = ParcelListeners(parcelProvider, entityTracker) registrator.registerListeners(listeners!!) } } diff --git a/src/main/kotlin/io/dico/parcels2/WorldGenerator.kt b/src/main/kotlin/io/dico/parcels2/WorldGenerator.kt deleted file mode 100644 index ccbc7e9..0000000 --- a/src/main/kotlin/io/dico/parcels2/WorldGenerator.kt +++ /dev/null @@ -1,319 +0,0 @@ -package io.dico.parcels2 - -import io.dico.parcels2.blockvisitor.Worker -import io.dico.parcels2.blockvisitor.RegionTraversal -import io.dico.parcels2.util.* -import org.bukkit.* -import org.bukkit.Bukkit.createBlockData -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 org.bukkit.entity.Entity -import org.bukkit.generator.BlockPopulator -import org.bukkit.generator.ChunkGenerator -import java.util.* -import kotlin.coroutines.experimental.buildIterator -import kotlin.reflect.KClass - -abstract class ParcelGenerator : ChunkGenerator(), ParcelProvider { - abstract val world: ParcelWorld - - abstract val factory: GeneratorFactory - - abstract fun parcelIDAt(x: Int, z: Int): Vec2i? - - abstract override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData - - abstract fun populate(world: World?, random: Random?, chunk: Chunk?) - - abstract override fun getFixedSpawnLocation(world: World?, random: Random?): Location - - override fun getDefaultPopulators(world: World?): MutableList<BlockPopulator> { - return Collections.singletonList(object : BlockPopulator() { - override fun populate(world: World?, random: Random?, chunk: Chunk?) { - this@ParcelGenerator.populate(world, random, chunk) - } - }) - } - - abstract fun updateOwner(parcel: Parcel) - - abstract fun getBottomCoord(parcel: Parcel): Vec2i - - abstract fun getHomeLocation(parcel: Parcel): Location - - abstract fun setBiome(parcel: Parcel, biome: Biome) - - abstract fun getEntities(parcel: Parcel): Collection<Entity> - - abstract fun getBlocks(parcel: Parcel, yRange: IntRange = 0..255): Iterator<Block> - - abstract fun clearParcel(parcel: Parcel): Worker - - abstract fun doBlockOperation(parcel: Parcel, direction: RegionTraversal = RegionTraversal.DOWNWARD, operation: (Block) -> Unit): Worker - -} - -interface GeneratorFactory { - companion object GeneratorFactories { - private val map: MutableMap<String, GeneratorFactory> = HashMap() - - fun registerFactory(generator: GeneratorFactory): Boolean = map.putIfAbsent(generator.name, generator) == null - - fun getFactory(name: String): GeneratorFactory? = map.get(name) - - init { - registerFactory(DefaultParcelGenerator.Factory) - } - - } - - val name: String - - val optionsClass: KClass<out GeneratorOptions> - - fun newParcelGenerator(worlds: Worlds, worldName: String, options: GeneratorOptions): ParcelGenerator - -} - -class DefaultParcelGenerator(val worlds: Worlds, val name: String, private val o: DefaultGeneratorOptions) : ParcelGenerator() { - override val world: ParcelWorld by lazy { worlds.getWorld(name)!! } - override val factory = Factory - val worktimeLimiter = worlds.plugin.worktimeLimiter - val maxHeight by lazy { world.world.maxHeight } - val airType = worlds.plugin.server.createBlockData(Material.AIR) - - companion object Factory : GeneratorFactory { - override val name get() = "default" - override val optionsClass get() = DefaultGeneratorOptions::class - override fun newParcelGenerator(worlds: Worlds, worldName: String, options: GeneratorOptions): ParcelGenerator { - return DefaultParcelGenerator(worlds, 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?) { - /* - generate(chunk!!.x, chunk.z, o.floorType.data, o.wallType.data, o.pathMainType.data, o.pathAltType.data, o.fillType.data) { x, y, z, type -> - if (type == 0.toByte()) chunk.getBlock(x, y, z).setData(type, false) - } - */ - } - - 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) - } - - 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 - } - - override fun parcelIDAt(x: Int, z: Int): Vec2i? { - return convertBlockLocationToID(x, z) { idx, idz -> Vec2i(idx, idz) } - } - - override fun parcelAt(x: Int, z: Int): Parcel? { - return convertBlockLocationToID(x, z) { idx, idz -> - world.parcelByID(idx, idz) - } - } - - override fun getBottomCoord(parcel: Parcel): Vec2i = Vec2i(sectionSize * parcel.pos.x + pathOffset + o.offsetX, - sectionSize * parcel.pos.z + pathOffset + o.offsetZ) - - override fun getHomeLocation(parcel: Parcel): Location { - val bottom = getBottomCoord(parcel) - return Location(world.world, bottom.x.toDouble(), o.floorHeight + 1.0, bottom.z + (o.parcelSize - 1) / 2.0, -90F, 0F) - } - - override fun updateOwner(parcel: Parcel) { - val world = this.world.world - val b = getBottomCoord(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) - - val owner = parcel.owner - 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 = (createBlockData(Material.WALL_SIGN) as Sign).apply { rotation = BlockFace.NORTH } - - val sign = signBlock.state as org.bukkit.block.Sign - sign.setLine(0, parcel.id) - 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: Parcel, biome: Biome) { - val world = this.world.world - val b = getBottomCoord(parcel) - val parcelSize = o.parcelSize - for (x in b.x until b.x + parcelSize) { - for (z in b.z until b.z + parcelSize) { - world.setBiome(x, z, biome) - } - } - } - - override fun getEntities(parcel: Parcel): Collection<Entity> { - val world = this.world.world - val b = getBottomCoord(parcel) - val parcelSize = o.parcelSize - val center = Location(world, (b.x + parcelSize) / 2.0, 128.0, (b.z + parcelSize) / 2.0) - return world.getNearbyEntities(center, parcelSize / 2.0 + 0.2, 128.0, parcelSize / 2.0 + 0.2) - } - - override fun getBlocks(parcel: Parcel, yRange: IntRange): Iterator<Block> = buildIterator { - val range = yRange.clamp(0, 255) - val world = this@DefaultParcelGenerator.world.world - val b = getBottomCoord(parcel) - val parcelSize = o.parcelSize - for (x in b.x until b.x + parcelSize) { - for (z in b.z until b.z + parcelSize) { - for (y in range) { - yield(world.getBlockAt(x, y, z)) - } - } - } - } - - override fun clearParcel(parcel: Parcel) = worktimeLimiter.submit { - val bottom = getBottomCoord(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.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: Parcel, direction: RegionTraversal, operation: (Block) -> Unit) = worktimeLimiter.submit { - val bottom = getBottomCoord(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.world - - for ((index, vec) in blocks.withIndex()) { - markSuspensionPoint() - operation(world[vec]) - setProgress((index + 1) / blockCount) - } - } - -}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt index e753295..9403f34 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt @@ -2,7 +2,7 @@ package io.dico.parcels2.blockvisitor import org.bukkit.Material import org.bukkit.Material.* -import java.util.* +import java.util.EnumSet val attachables: Set<Material> = EnumSet.of( ACACIA_DOOR, diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt index 7c5f2c5..a18c63b 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt @@ -6,7 +6,7 @@ import kotlinx.coroutines.experimental.CancellationException import kotlinx.coroutines.experimental.Job import org.bukkit.scheduler.BukkitTask import java.lang.System.currentTimeMillis -import java.util.* +import java.util.LinkedList import java.util.logging.Level import kotlin.coroutines.experimental.Continuation import kotlin.coroutines.experimental.intrinsics.COROUTINE_SUSPENDED diff --git a/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt b/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt index f712d00..2fceb5b 100644 --- a/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt +++ b/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt @@ -16,10 +16,10 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei override fun getPlugin(): Plugin = plugin override fun getReceiver(context: ExecutionContext, target: Method, cmdName: String): ICommandReceiver { - return getParcelCommandReceiver(plugin.worlds, context, target, cmdName) + return getParcelCommandReceiver(plugin.parcelProvider, context, target, cmdName) } - protected inline val worlds get() = plugin.worlds + protected inline val worlds get() = plugin.parcelProvider protected fun error(message: String): Nothing { throw CommandException(message) @@ -32,7 +32,7 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei protected suspend fun checkParcelLimit(player: Player, world: ParcelWorld) { if (player.hasAdminManage) return val numOwnedParcels = plugin.storage.getOwnedParcels(ParcelOwner(player)).await() - .filter { it.world.world == world.world }.size + .filter { it.worldId.equals(world.id) }.size val limit = player.parcelLimit if (numOwnedParcels >= limit) { diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusGlobal.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusGlobal.kt new file mode 100644 index 0000000..d483126 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusGlobal.kt @@ -0,0 +1,65 @@ +package io.dico.parcels2.command + +import io.dico.dicore.command.Validate +import io.dico.dicore.command.annotation.Cmd +import io.dico.dicore.command.annotation.Desc +import io.dico.parcels2.GlobalAddedData +import io.dico.parcels2.GlobalAddedDataManager +import io.dico.parcels2.ParcelOwner +import io.dico.parcels2.ParcelsPlugin +import org.bukkit.OfflinePlayer +import org.bukkit.entity.Player + +class CommandsAddedStatusGlobal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { + private inline val data get() = plugin.globalAddedData + @Suppress("NOTHING_TO_INLINE") + private inline operator fun GlobalAddedDataManager.get(player: OfflinePlayer): GlobalAddedData = data[ParcelOwner(player)] + + @Cmd("allow", aliases = ["add", "permit"]) + @Desc("Globally allows a player to build on all", + "the parcels that you own.", + shortVersion = "globally allows a player to build on your parcels") + @ParcelRequire(owner = true) + fun cmdAllow(sender: Player, player: OfflinePlayer): Any? { + Validate.isTrue(player != sender, "The target cannot be yourself") + Validate.isTrue(data[sender].allow(player), "${player.name} is already allowed globally") + return "${player.name} is now allowed to build on all your parcels" + } + + @Cmd("disallow", aliases = ["remove", "forbid"]) + @Desc("Globally disallows a player to build on", + "the parcels that you own.", + "If the player is allowed to build on specific", + "parcels, they can still build there.", + shortVersion = "globally disallows a player to build on your parcels") + @ParcelRequire(owner = true) + fun cmdDisallow(sender: Player, player: OfflinePlayer): Any? { + Validate.isTrue(player != sender, "The target cannot be yourself") + Validate.isTrue(data[sender].disallow(player), "${player.name} is not currently allowed globally") + return "${player.name} is not allowed to build on all your parcels anymore" + } + + @Cmd("ban", aliases = ["deny"]) + @Desc("Globally bans a player from all the parcels", + "that you own, making them unable to enter.", + shortVersion = "globally bans a player from your parcels") + @ParcelRequire(owner = true) + fun cmdBan(sender: Player, player: OfflinePlayer): Any? { + Validate.isTrue(player != sender, "The target cannot be yourself") + Validate.isTrue(data[sender].ban(player), "${player.name} is already banned from all your parcels") + return "${player.name} is now banned from all your parcels" + } + + @Cmd("unban", aliases = ["undeny"]) + @Desc("Globally unbans a player from all the parcels", + "that you own, they can enter again.", + "If the player is banned from specific parcels,", + "they will still be banned there.", + shortVersion = "globally unbans a player from your parcels") + @ParcelRequire(owner = true) + fun cmdUnban(sender: Player, player: OfflinePlayer): Any? { + Validate.isTrue(data[sender].unban(player), "${player.name} is not currently banned from all your parcels") + return "${player.name} is not banned from all your parcels anymore" + } + +}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatus.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusLocal.kt index 997044a..69da341 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatus.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusLocal.kt @@ -8,7 +8,7 @@ import io.dico.parcels2.util.hasAdminManage import org.bukkit.OfflinePlayer import org.bukkit.entity.Player -class CommandsAddedStatus(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { +class CommandsAddedStatusLocal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { @Cmd("allow", aliases = ["add", "permit"]) @Desc("Allows a player to build on this parcel", diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt index 9493d26..bf5a870 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt @@ -9,7 +9,7 @@ import io.dico.parcels2.blockvisitor.RegionTraversal import org.bukkit.Bukkit import org.bukkit.Material import org.bukkit.entity.Player -import java.util.* +import java.util.Random class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { @@ -39,7 +39,8 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { server.createBlockData(Material.QUARTZ_BLOCK) ) val random = Random() - world.generator.doBlockOperation(parcel, direction = RegionTraversal.UPWARD) { block -> + + world.doBlockOperation(parcel.id, direction = RegionTraversal.UPWARD) { block -> block.blockData = blockDatas[random.nextInt(4)] }.onProgressUpdate(1000, 1000) { progress, elapsedTime -> context.sendMessage(EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed" diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt index bfa1f6c..769cf5f 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt @@ -8,7 +8,6 @@ import io.dico.dicore.command.annotation.Flag import io.dico.dicore.command.annotation.RequireParameters import io.dico.parcels2.ParcelOwner import io.dico.parcels2.ParcelsPlugin -import io.dico.parcels2.storage.getParcelBySerializedValue import io.dico.parcels2.util.hasAdminManage import io.dico.parcels2.util.hasParcelHomeOthers import io.dico.parcels2.util.uuid @@ -27,7 +26,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { val parcel = world.nextEmptyParcel() ?: error("This world is full, please ask an admin to upsize it") parcel.owner = ParcelOwner(uuid = player.uuid) - player.teleport(parcel.homeLocation) + player.teleport(parcel.world.getHomeLocation(parcel.id)) return "Enjoy your new parcel!" } @@ -53,13 +52,13 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { val ownedParcelsResult = plugin.storage.getOwnedParcels(ownerTarget.owner).await() val ownedParcels = ownedParcelsResult - .map { worlds.getParcelBySerializedValue(it) } + .map { worlds.getParcelById(it) } .filter { it != null && ownerTarget.world == it.world } val targetMatch = ownedParcels.getOrNull(target.index) ?: error("The specified parcel could not be matched") - player.teleport(targetMatch.homeLocation) + player.teleport(targetMatch.world.getHomeLocation(targetMatch.id)) return "" } @@ -91,7 +90,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { if (!sure) return "Are you sure? You cannot undo this action!\n" + "Type ${context.rawInput} -sure if you want to go through with this." - world.generator.clearParcel(parcel) + world.clearParcel(parcel.id) .onProgressUpdate(1000, 1000) { progress, elapsedTime -> context.sendMessage(EMessageType.INFORMATIVE, "Clear progress: %.02f%%, %.2fs elapsed" .format(progress * 100, elapsedTime / 1000.0)) diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt index f3bd63c..b633c3e 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt @@ -3,29 +3,32 @@ package io.dico.parcels2.command import io.dico.dicore.command.CommandBuilder import io.dico.dicore.command.ICommandAddress import io.dico.dicore.command.ICommandDispatcher -import io.dico.dicore.command.predef.PredefinedCommand import io.dico.dicore.command.registration.reflect.ReflectiveRegistration import io.dico.parcels2.ParcelsPlugin -import io.dico.parcels2.logger -import java.util.* +import java.util.LinkedList +import java.util.Queue @Suppress("UsePropertyAccessSyntax") fun getParcelCommands(plugin: ParcelsPlugin): ICommandDispatcher { //@formatter:off return CommandBuilder() .setChatController(ParcelsChatController()) - .addParameterType(false, ParcelParameterType(plugin.worlds)) - .addParameterType(true, ParcelTarget.PType(plugin.worlds)) + .addParameterType(false, ParcelParameterType(plugin.parcelProvider)) + .addParameterType(true, ParcelTarget.PType(plugin.parcelProvider)) .group("parcel", "plot", "plots", "p") .registerCommands(CommandsGeneral(plugin)) - .registerCommands(CommandsAddedStatus(plugin)) + .registerCommands(CommandsAddedStatusLocal(plugin)) - .group("option") + .group("option", "opt", "o") //.apply { CommandsParcelOptions.setGroupDescription(this) } .registerCommands(CommandsParcelOptions(plugin)) .parent() + .group("global", "g") + .registerCommands(CommandsAddedStatusGlobal(plugin)) + .parent() + .group("admin", "a") .registerCommands(CommandsAdmin(plugin)) .parent() diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt index 5dd8270..eab02c4 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt @@ -5,8 +5,8 @@ import io.dico.dicore.command.ExecutionContext import io.dico.dicore.command.ICommandReceiver import io.dico.dicore.command.Validate import io.dico.parcels2.Parcel +import io.dico.parcels2.ParcelProvider import io.dico.parcels2.ParcelWorld -import io.dico.parcels2.Worlds import io.dico.parcels2.util.hasAdminManage import io.dico.parcels2.util.uuid import org.bukkit.entity.Player @@ -30,7 +30,7 @@ open class ParcelScope(val parcel: Parcel) : WorldScope(parcel.world) { "You must own this parcel to $action") } -fun getParcelCommandReceiver(worlds: Worlds, context: ExecutionContext, method: Method, cmdName: String): ICommandReceiver { +fun getParcelCommandReceiver(parcelProvider: ParcelProvider, context: ExecutionContext, method: Method, cmdName: String): ICommandReceiver { val player = context.sender as Player val function = method.kotlinFunction!! val receiverType = function.extensionReceiverParameter!!.type @@ -39,20 +39,20 @@ fun getParcelCommandReceiver(worlds: Worlds, context: ExecutionContext, method: val owner = require?.owner == true return when (receiverType.jvmErasure) { - ParcelScope::class -> ParcelScope(worlds.getParcelRequired(player, admin, owner)) - WorldScope::class -> WorldScope(worlds.getWorldRequired(player, admin)) + ParcelScope::class -> ParcelScope(parcelProvider.getParcelRequired(player, admin, owner)) + WorldScope::class -> WorldScope(parcelProvider.getWorldRequired(player, admin)) else -> throw InternalError("Invalid command receiver type") } } -fun Worlds.getWorldRequired(player: Player, admin: Boolean = false): ParcelWorld { +fun ParcelProvider.getWorldRequired(player: Player, admin: Boolean = false): ParcelWorld { if (admin) Validate.isTrue(player.hasAdminManage, "You must have admin rights to use that command") return getWorld(player.world) ?: throw CommandException("You must be in a parcel world to use that command") } -fun Worlds.getParcelRequired(player: Player, admin: Boolean = false, own: Boolean = false): Parcel { - val parcel = getWorldRequired(player, admin = admin).parcelAt(player) +fun ParcelProvider.getParcelRequired(player: Player, admin: Boolean = false, own: Boolean = false): Parcel { + val parcel = getWorldRequired(player, admin = admin).getParcelAt(player) ?: throw CommandException("You must be in a parcel to use that command") if (own) Validate.isTrue(parcel.isOwner(player.uuid) || player.hasAdminManage, "You must own this parcel to use that command") diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt index b5a1abf..de3cf64 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt @@ -3,15 +3,10 @@ package io.dico.parcels2.command import io.dico.dicore.command.CommandException import io.dico.dicore.command.parameter.ArgumentBuffer import io.dico.dicore.command.parameter.Parameter -import io.dico.dicore.command.parameter.type.ParameterConfig import io.dico.dicore.command.parameter.type.ParameterType import io.dico.parcels2.Parcel +import io.dico.parcels2.ParcelProvider import io.dico.parcels2.ParcelWorld -import io.dico.parcels2.ParcelsPlugin -import io.dico.parcels2.Worlds -import io.dico.parcels2.util.isValid -import org.bukkit.Bukkit -import org.bukkit.OfflinePlayer import org.bukkit.command.CommandSender import org.bukkit.entity.Player @@ -19,7 +14,7 @@ fun invalidInput(parameter: Parameter<*, *>, message: String): Nothing { throw CommandException("invalid input for ${parameter.name}: $message") } -fun Worlds.getTargetWorld(input: String?, sender: CommandSender, parameter: Parameter<*, *>): ParcelWorld { +fun ParcelProvider.getTargetWorld(input: String?, sender: CommandSender, parameter: Parameter<*, *>): ParcelWorld { val worldName = input ?.takeUnless { it.isEmpty() } ?: (sender as? Player)?.world?.name @@ -29,14 +24,14 @@ fun Worlds.getTargetWorld(input: String?, sender: CommandSender, parameter: Para ?: invalidInput(parameter, "$worldName is not a parcel world") } -class ParcelParameterType(val worlds: Worlds) : ParameterType<Parcel, Void>(Parcel::class.java) { +class ParcelParameterType(val parcelProvider: ParcelProvider) : ParameterType<Parcel, Void>(Parcel::class.java) { val regex = Regex.fromLiteral("((.+)->)?([0-9]+):([0-9]+)") override fun parse(parameter: Parameter<Parcel, Void>, sender: CommandSender, buffer: ArgumentBuffer): Parcel { val matchResult = regex.matchEntire(buffer.next()) ?: invalidInput(parameter, "must match (w->)?a:b (/${regex.pattern}/)") - val world = worlds.getTargetWorld(matchResult.groupValues[2], sender, parameter) + val world = parcelProvider.getTargetWorld(matchResult.groupValues[2], sender, parameter) val x = matchResult.groupValues[3].toIntOrNull() ?: invalidInput(parameter, "couldn't parse int") @@ -44,7 +39,7 @@ class ParcelParameterType(val worlds: Worlds) : ParameterType<Parcel, Void>(Parc val z = matchResult.groupValues[4].toIntOrNull() ?: invalidInput(parameter, "couldn't parse int") - return world.parcelByID(x, z) + return world.getParcelById(x, z) ?: invalidInput(parameter, "parcel id is out of range") } diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt index 4dd2825..5504e6b 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt @@ -5,7 +5,6 @@ import io.dico.dicore.command.parameter.Parameter import io.dico.dicore.command.parameter.type.ParameterConfig import io.dico.dicore.command.parameter.type.ParameterType import io.dico.parcels2.* -import io.dico.parcels2.storage.getParcelBySerializedValue import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.floor import io.dico.parcels2.util.isValid @@ -20,7 +19,7 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) { class ByID(world: ParcelWorld, val id: Vec2i?, isDefault: Boolean) : ParcelTarget(world, isDefault) { override suspend fun ParcelsPlugin.getParcelSuspend(): Parcel? = getParcel() - fun getParcel() = id?.let { world.parcelByID(it) } + fun getParcel() = id?.let { world.getParcelById(it) } val isPath: Boolean get() = id == null } @@ -32,7 +31,7 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) { override suspend fun ParcelsPlugin.getParcelSuspend(): Parcel? { val ownedParcelsSerialized = storage.getOwnedParcels(owner).await() val ownedParcels = ownedParcelsSerialized - .map { worlds.getParcelBySerializedValue(it) } + .map { parcelProvider.getParcelById(it) } .filter { it != null && world == it.world && owner == it.owner } return ownedParcels.getOrNull(index) } @@ -59,7 +58,7 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) { // instead of parcel that the player is in } - class PType(val worlds: Worlds) : ParameterType<ParcelTarget, Int>(ParcelTarget::class.java, ParcelTarget.Config) { + class PType(val parcelProvider: ParcelProvider) : ParameterType<ParcelTarget, Int>(ParcelTarget::class.java, ParcelTarget.Config) { override fun parse(parameter: Parameter<ParcelTarget, Int>, sender: CommandSender, buffer: ArgumentBuffer): ParcelTarget { var input = buffer.next() @@ -68,19 +67,19 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) { val world = if (worldString.isEmpty()) { val player = requirePlayer(sender, parameter, "the world") - worlds.getWorld(player.world) + parcelProvider.getWorld(player.world) ?: invalidInput(parameter, "You cannot omit the world if you're not in a parcel world") } else { - worlds.getWorld(worldString) ?: invalidInput(parameter, "$worldString is not a parcel world") + parcelProvider.getWorld(worldString) ?: invalidInput(parameter, "$worldString is not a parcel world") } val kind = parameter.paramInfo ?: DEFAULT_KIND if (input.contains(',')) { - if (kind and ID == 0) invalidInput(parameter, "You must specify a parcel by ID, that is, the x and z component separated by a comma") + if (kind and ID == 0) invalidInput(parameter, "You must specify a parcel by OWNER, that is, an owner and index") return ByID(world, getId(parameter, input), false) } - if (kind and OWNER == 0) invalidInput(parameter, "You must specify a parcel by OWNER, that is, an owner and index") + if (kind and OWNER == 0) invalidInput(parameter, "You must specify a parcel by ID, that is, the x and z component separated by a comma") val (owner, index) = getHomeIndex(parameter, sender, input) return ByOwner(world, owner, index, false) } @@ -106,7 +105,7 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) { indexString = input } else { ownerString = input.substring(0, splitIdx) - indexString = input.substring(0, splitIdx + 1) + indexString = input.substring(splitIdx + 1) } val owner = if (ownerString.isEmpty()) @@ -151,9 +150,9 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) { } val player = requirePlayer(sender, parameter, "the parcel") - val world = worlds.getWorld(player.world) ?: invalidInput(parameter, "You must be in a parcel world to omit the parcel") + val world = parcelProvider.getWorld(player.world) ?: invalidInput(parameter, "You must be in a parcel world to omit the parcel") if (useLocation) { - val id = player.location.let { world.generator.parcelIDAt(it.x.floor(), it.z.floor()) } + val id = player.location.let { world.getParcelIdAt(it.x.floor(), it.z.floor())?.pos } return ByID(world, id, true) } diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt new file mode 100644 index 0000000..0597a9f --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt @@ -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()) + } + } + +}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt new file mode 100644 index 0000000..90eb631 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt @@ -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) + } + } + + } + +}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalAddedDataManagerImpl.kt index 1528d21..1ac053f 100644 --- a/src/main/kotlin/io/dico/parcels2/GlobalAddedData.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalAddedDataManagerImpl.kt @@ -1,16 +1,10 @@ @file:Suppress("UNCHECKED_CAST") -package io.dico.parcels2 +package io.dico.parcels2.defaultimpl -import java.util.* - -interface GlobalAddedData : AddedData { - val owner: ParcelOwner -} - -interface GlobalAddedDataManager { - operator fun get(owner: ParcelOwner): GlobalAddedData -} +import io.dico.parcels2.* +import java.util.Collections +import java.util.UUID class GlobalAddedDataManagerImpl(val plugin: ParcelsPlugin) : GlobalAddedDataManager { private val map = mutableMapOf<ParcelOwner, GlobalAddedData>() @@ -20,7 +14,7 @@ class GlobalAddedDataManagerImpl(val plugin: ParcelsPlugin) : GlobalAddedDataMan } private inner class GlobalAddedDataImpl(override val owner: ParcelOwner, - data: MutableMap<UUID, AddedStatus> = emptyData) + data: MutableAddedDataMap = emptyData) : AddedDataHolder(data), GlobalAddedData { private inline var data get() = addedMap; set(value) = run { addedMap = value } @@ -39,10 +33,7 @@ class GlobalAddedDataManagerImpl(val plugin: ParcelsPlugin) : GlobalAddedDataMan } private companion object { - val emptyData = Collections.emptyMap<UUID, AddedStatus>() as MutableMap<UUID, AddedStatus> + val emptyData = Collections.emptyMap<Any, Any>() as MutableAddedDataMap } -} - - - +}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt new file mode 100644 index 0000000..576fc48 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt @@ -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(")") + } + } + + } +}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt new file mode 100644 index 0000000..52e675d --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt @@ -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() + } + + } + */ +}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt new file mode 100644 index 0000000..590794d --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt @@ -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() + } + + +} diff --git a/src/main/kotlin/io/dico/parcels2/listener/ListenerHelper.kt b/src/main/kotlin/io/dico/parcels2/listener/ListenerHelper.kt index 97d045f..e8617fd 100644 --- a/src/main/kotlin/io/dico/parcels2/listener/ListenerHelper.kt +++ b/src/main/kotlin/io/dico/parcels2/listener/ListenerHelper.kt @@ -8,14 +8,7 @@ interface HasPlugin { val plugin: ParcelsPlugin } -inline fun <reified T: Event> HasPlugin.listener(crossinline block: suspend (T) -> Unit) = RegistratorListener<T> { event -> - - - - - - - +inline fun <reified T : Event> HasPlugin.listener(crossinline block: suspend (T) -> Unit) = RegistratorListener<T> { event -> } diff --git a/src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt b/src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt index 2f64ab1..285fd3e 100644 --- a/src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt +++ b/src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt @@ -1,12 +1,12 @@ package io.dico.parcels2.listener import io.dico.parcels2.Parcel -import io.dico.parcels2.Worlds +import io.dico.parcels2.ParcelProvider import io.dico.parcels2.util.editLoop import io.dico.parcels2.util.isPresentAnd import org.bukkit.entity.Entity -class ParcelEntityTracker(val worlds: Worlds) { +class ParcelEntityTracker(val parcelProvider: ParcelProvider) { val map = mutableMapOf<Entity, Parcel?>() fun untrack(entity: Entity) { @@ -32,7 +32,7 @@ class ParcelEntityTracker(val worlds: Worlds) { if (parcel.isPresentAnd { hasBlockVisitors }) { remove() } - val newParcel = worlds.getParcelAt(entity.location) + val newParcel = parcelProvider.getParcelAt(entity.location) if (newParcel !== parcel && !newParcel.isPresentAnd { hasBlockVisitors }) { remove() entity.remove() diff --git a/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt b/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt index f5eb5ca..d34f8bf 100644 --- a/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt +++ b/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt @@ -4,8 +4,8 @@ import gnu.trove.TLongCollection import io.dico.dicore.ListenerMarker import io.dico.dicore.RegistratorListener import io.dico.parcels2.Parcel +import io.dico.parcels2.ParcelProvider import io.dico.parcels2.ParcelWorld -import io.dico.parcels2.Worlds import io.dico.parcels2.util.* import org.bukkit.Material.* import org.bukkit.World @@ -26,11 +26,10 @@ import org.bukkit.event.player.* import org.bukkit.event.vehicle.VehicleMoveEvent import org.bukkit.event.weather.WeatherChangeEvent import org.bukkit.event.world.StructureGrowEvent -import org.bukkit.event.world.WorldLoadEvent import org.bukkit.inventory.InventoryHolder @Suppress("NOTHING_TO_INLINE") -class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker) { +class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: ParcelEntityTracker) { private inline fun Parcel?.canBuildN(user: Player) = isPresentAnd { canBuild(user) } || user.hasBuildAnywhere /** @@ -40,8 +39,8 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker * returns null if not in a registered parcel world */ private fun getWoAndPPa(block: Block): Pair<ParcelWorld, Parcel?>? { - val world = worlds.getWorld(block.world) ?: return null - return world to world.parcelAt(block) + val world = parcelProvider.getWorld(block.world) ?: return null + return world to world.getParcelAt(block) } /* @@ -51,10 +50,10 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker val onPlayerMoveEvent = RegistratorListener<PlayerMoveEvent> l@{ event -> val user = event.player if (user.hasBanBypass) return@l - val parcel = worlds.getParcelAt(event.to) ?: return@l + val parcel = parcelProvider.getParcelAt(event.to) ?: return@l if (parcel.isBanned(user.uuid)) { - worlds.getParcelAt(event.from)?.also { - user.teleport(it.homeLocation) + parcelProvider.getParcelAt(event.from)?.also { + user.teleport(it.world.getHomeLocation(it.id)) user.sendParcelMessage(nopermit = true, message = "You are banned from this parcel") } ?: run { event.to = event.from } } @@ -113,7 +112,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker private inline fun TLongCollection.troveForEach(block: (Long) -> Unit) = iterator().let { while (it.hasNext()) block(it.next()) } //@formatter:on private fun checkPistonMovement(event: BlockPistonEvent, blocks: List<Block>) { - val world = worlds.getWorld(event.block.world) ?: return + val world = parcelProvider.getWorld(event.block.world) ?: return val direction = event.direction val columns = gnu.trove.set.hash.TLongHashSet(blocks.size * 2) @@ -123,7 +122,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker } columns.troveForEach { - val ppa = world.parcelAt(it.columnX, it.columnZ) + val ppa = world.getParcelAt(it.columnX, it.columnZ) if (ppa.isNullOr { hasBlockVisitors }) { event.isCancelled = true return @@ -150,8 +149,8 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker @field:ListenerMarker(priority = NORMAL) val onEntityExplodeEvent = RegistratorListener<EntityExplodeEvent> l@{ event -> entityTracker.untrack(event.entity) - val world = worlds.getWorld(event.entity.world) ?: return@l - if (world.options.disableExplosions || world.parcelAt(event.entity).isPresentAnd { hasBlockVisitors }) { + val world = parcelProvider.getWorld(event.entity.world) ?: return@l + if (world.options.disableExplosions || world.getParcelAt(event.entity).isPresentAnd { hasBlockVisitors }) { event.isCancelled = true } } @@ -175,9 +174,9 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker @field:ListenerMarker(priority = NORMAL) val onPlayerInteractEvent = RegistratorListener<PlayerInteractEvent> l@{ event -> val user = event.player - val world = worlds.getWorld(user.world) ?: return@l + val world = parcelProvider.getWorld(user.world) ?: return@l val clickedBlock = event.clickedBlock - val parcel = clickedBlock?.let { world.parcelAt(it) } + val parcel = clickedBlock?.let { world.getParcelAt(it) } if (!user.hasBuildAnywhere && parcel.isPresentAnd { isBanned(user.uuid) }) { user.sendParcelMessage(nopermit = true, message = "You cannot interact with parcels you're banned from") @@ -300,7 +299,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker */ @field:ListenerMarker(priority = NORMAL) val onEntityCreatePortalEvent = RegistratorListener<EntityCreatePortalEvent> l@{ event -> - val world = worlds.getWorld(event.entity.world) ?: return@l + val world = parcelProvider.getWorld(event.entity.world) ?: return@l if (world.options.blockPortalCreation) event.isCancelled = true } @@ -341,7 +340,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker */ @field:ListenerMarker(priority = NORMAL) val onWeatherChangeEvent = RegistratorListener<WeatherChangeEvent> l@{ event -> - val world = worlds.getWorld(event.world) ?: return@l + val world = parcelProvider.getWorld(event.world) ?: return@l if (world.options.noWeather && event.toWeatherState()) { event.isCancelled = true } @@ -353,29 +352,6 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker world.weatherDuration = Int.MAX_VALUE } - /* - * Sets time to day and doDayLightCycle gamerule if requested by the config for that world - * Sets the weather to sunny if requested by the config for that world. - */ - @field:ListenerMarker(priority = NORMAL) - val onWorldLoadEvent = RegistratorListener<WorldLoadEvent> l@{ event -> - enforceWorldSettingsIfApplicable(event.world) - } - - fun enforceWorldSettingsIfApplicable(w: World) { - val world = worlds.getWorld(w) ?: return - if (world.options.dayTime) { - w.setGameRuleValue("doDaylightCycle", "false") - w.time = 6000 - } - - if (world.options.noWeather) { - resetWeather(w) - } - - w.setGameRuleValue("doTileDrops", world.options.doTileDrops.toString()) - } - // TODO: BlockFormEvent, BlockSpreadEvent, BlockFadeEvent /* @@ -396,10 +372,10 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker val cancel: Boolean = when (block.type) { - // prevent ice generation from Frost Walkers enchantment + // prevent ice generation from Frost Walkers enchantment ICE -> player != null && !ppa.canBuild(player) - // prevent snow generation from weather + // prevent snow generation from weather SNOW -> !hasEntity && wo.options.preventWeatherBlockChanges else -> false @@ -415,10 +391,10 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker */ @field:ListenerMarker(priority = NORMAL) val onEntitySpawnEvent = RegistratorListener<EntitySpawnEvent> l@{ event -> - val world = worlds.getWorld(event.entity.world) ?: return@l + val world = parcelProvider.getWorld(event.entity.world) ?: return@l if (event.entity is Creature && world.options.blockMobSpawning) { event.isCancelled = true - } else if (world.parcelAt(event.entity).isPresentAnd { hasBlockVisitors }) { + } else if (world.getParcelAt(event.entity).isPresentAnd { hasBlockVisitors }) { event.isCancelled = true } } @@ -448,7 +424,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker */ @field:ListenerMarker(priority = NORMAL) val onEntityDamageByEntityEvent = RegistratorListener<EntityDamageByEntityEvent> l@{ event -> - val world = worlds.getWorld(event.entity.world) ?: return@l + val world = parcelProvider.getWorld(event.entity.world) ?: return@l if (world.options.disableExplosions && event.damager is ExplosiveMinecart || event.damager is Creeper) { event.isCancelled = true; return@l } @@ -457,19 +433,19 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker ?: (event.damager as? Projectile)?.let { it.shooter as? Player } ?: return@l - if (!world.parcelAt(event.entity).canBuildN(user)) { + if (!world.getParcelAt(event.entity).canBuildN(user)) { event.isCancelled = true } } @field:ListenerMarker(priority = NORMAL) val onHangingBreakEvent = RegistratorListener<HangingBreakEvent> l@{ event -> - val world = worlds.getWorld(event.entity.world) ?: return@l + val world = parcelProvider.getWorld(event.entity.world) ?: return@l if (event.cause == HangingBreakEvent.RemoveCause.EXPLOSION && world.options.disableExplosions) { event.isCancelled = true; return@l } - if (world.parcelAt(event.entity).isPresentAnd { hasBlockVisitors }) { + if (world.getParcelAt(event.entity).isPresentAnd { hasBlockVisitors }) { event.isCancelled = true } } @@ -480,9 +456,9 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker */ @field:ListenerMarker(priority = NORMAL) val onHangingBreakByEntityEvent = RegistratorListener<HangingBreakByEntityEvent> l@{ event -> - val world = worlds.getWorld(event.entity.world) ?: return@l + val world = parcelProvider.getWorld(event.entity.world) ?: return@l val user = event.remover as? Player ?: return@l - if (!world.parcelAt(event.entity).canBuildN(user)) { + if (!world.getParcelAt(event.entity).canBuildN(user)) { event.isCancelled = true } } @@ -492,9 +468,9 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker */ @field:ListenerMarker(priority = NORMAL) val onHangingPlaceEvent = RegistratorListener<HangingPlaceEvent> l@{ event -> - val world = worlds.getWorld(event.entity.world) ?: return@l + val world = parcelProvider.getWorld(event.entity.world) ?: return@l val block = event.block.getRelative(event.blockFace) - if (!world.parcelAt(block).canBuildN(event.player)) { + if (!world.getParcelAt(block).canBuildN(event.player)) { event.isCancelled = true } } @@ -513,7 +489,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker event.isCancelled = true; return@l } - event.blocks.removeIf { wo.parcelAt(it.block) !== ppa } + event.blocks.removeIf { wo.getParcelAt(it.block) !== ppa } } /* @@ -523,10 +499,10 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker val onBlockDispenseEvent = RegistratorListener<BlockDispenseEvent> l@{ event -> val block = event.block if (!block.type.let { it == DISPENSER || it == DROPPER }) return@l - val world = worlds.getWorld(block.world) ?: return@l + val world = parcelProvider.getWorld(block.world) ?: return@l val data = block.blockData as Directional val targetBlock = block.getRelative(data.facing) - if (world.parcelAt(targetBlock) == null) { + if (world.getParcelAt(targetBlock) == null) { event.isCancelled = true } } @@ -547,7 +523,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker @field:ListenerMarker(priority = NORMAL) val onEntityTeleportEvent = RegistratorListener<EntityTeleportEvent> l@{ event -> val (wo, ppa) = getWoAndPPa(event.from.block) ?: return@l - if (ppa !== wo.parcelAt(event.to)) { + if (ppa !== wo.getParcelAt(event.to)) { event.isCancelled = true } } @@ -572,7 +548,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker @field:ListenerMarker(priority = NORMAL) val onEntityDeathEvent = RegistratorListener<EntityDeathEvent> l@{ event -> entityTracker.untrack(event.entity) - val world = worlds.getWorld(event.entity.world) ?: return@l + val world = parcelProvider.getWorld(event.entity.world) ?: return@l if (!world.options.dropEntityItems) { event.drops.clear() event.droppedExp = 0 @@ -584,7 +560,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker */ @field:ListenerMarker(priority = NORMAL) val onPlayerChangedWorldEvent = RegistratorListener<PlayerChangedWorldEvent> l@{ event -> - val world = worlds.getWorld(event.player.world) ?: return@l + val world = parcelProvider.getWorld(event.player.world) ?: return@l if (world.options.gameMode != null && !event.player.hasGamemodeBypass) { event.player.gameMode = world.options.gameMode } diff --git a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt index 7224dd1..88ee5fd 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt @@ -1,11 +1,8 @@ package io.dico.parcels2.storage -import io.dico.parcels2.AddedStatus -import io.dico.parcels2.Parcel -import io.dico.parcels2.ParcelData -import io.dico.parcels2.ParcelOwner -import kotlinx.coroutines.experimental.channels.ProducerScope -import java.util.* +import io.dico.parcels2.* +import kotlinx.coroutines.experimental.channels.SendChannel +import java.util.UUID interface Backing { @@ -22,31 +19,31 @@ interface Backing { * This producer function is capable of constantly reading parcels from a potentially infinite sequence, * and provide parcel data for it as read from the database. */ - suspend fun ProducerScope<Pair<Parcel, ParcelData?>>.produceParcelData(parcels: Sequence<Parcel>) + suspend fun produceParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>) - suspend fun ProducerScope<Pair<SerializableParcel, ParcelData?>>.produceAllParcelData() + suspend fun produceAllParcelData(channel: SendChannel<DataPair>) - suspend fun readParcelData(parcelFor: Parcel): ParcelData? + suspend fun readParcelData(parcel: ParcelId): ParcelData? - suspend fun getOwnedParcels(user: ParcelOwner): List<SerializableParcel> + suspend fun getOwnedParcels(user: ParcelOwner): List<ParcelId> suspend fun getNumParcels(user: ParcelOwner): Int = getOwnedParcels(user).size - suspend fun setParcelData(parcelFor: Parcel, data: ParcelData?) + suspend fun setParcelData(parcel: ParcelId, data: ParcelData?) - suspend fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) + suspend fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?) - suspend fun setLocalPlayerStatus(parcelFor: Parcel, player: UUID, status: AddedStatus) + suspend fun setLocalPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus) - suspend fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean) + suspend fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) - suspend fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean) + suspend fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean) - suspend fun ProducerScope<Pair<ParcelOwner, MutableMap<UUID, AddedStatus>>>.produceAllGlobalAddedData() + suspend fun produceAllGlobalAddedData(channel: SendChannel<AddedDataPair<ParcelOwner>>) - suspend fun readGlobalAddedData(owner: ParcelOwner): MutableMap<UUID, AddedStatus> + suspend fun readGlobalAddedData(owner: ParcelOwner): MutableAddedDataMap suspend fun setGlobalPlayerStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) diff --git a/src/main/kotlin/io/dico/parcels2/storage/Jackson.kt b/src/main/kotlin/io/dico/parcels2/storage/Jackson.kt index 97225b8..ede33ae 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Jackson.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Jackson.kt @@ -8,7 +8,7 @@ import com.fasterxml.jackson.databind.ser.BeanSerializerModifier import com.fasterxml.jackson.databind.ser.std.StdSerializer import com.fasterxml.jackson.dataformat.yaml.YAMLFactory import com.fasterxml.jackson.module.kotlin.KotlinModule -import io.dico.parcels2.GeneratorFactory +import io.dico.parcels2.GeneratorFactories import io.dico.parcels2.GeneratorOptions import io.dico.parcels2.StorageOptions import org.bukkit.Bukkit @@ -100,7 +100,7 @@ class GeneratorOptionsDeserializer : JsonDeserializer<GeneratorOptions>() { val node = parser!!.readValueAsTree<JsonNode>() val name = node.get("name").asText() val optionsNode = node.get("options") - val factory = GeneratorFactory.getFactory(name) ?: throw IllegalStateException("Unknown generator: $name") + val factory = GeneratorFactories.getFactory(name) ?: throw IllegalStateException("Unknown generator: $name") return parser.codec.treeToValue(optionsNode, factory.optionsClass.java) } diff --git a/src/main/kotlin/io/dico/parcels2/storage/SerializableTypes.kt b/src/main/kotlin/io/dico/parcels2/storage/SerializableTypes.kt deleted file mode 100644 index 1f659fb..0000000 --- a/src/main/kotlin/io/dico/parcels2/storage/SerializableTypes.kt +++ /dev/null @@ -1,40 +0,0 @@ -package io.dico.parcels2.storage - -import io.dico.parcels2.Parcel -import io.dico.parcels2.ParcelWorld -import io.dico.parcels2.Worlds -import io.dico.parcels2.util.Vec2i -import org.bukkit.Bukkit -import org.bukkit.World -import java.util.* - -data class SerializableWorld(val name: String? = null, - val uid: UUID? = null) { - - init { - uid ?: name ?: throw IllegalArgumentException("uuid and/or name must be present") - } - - val world: World? by lazy { uid?.let { Bukkit.getWorld(it) } ?: name?.let { Bukkit.getWorld(it) } } - //val parcelWorld: ParcelWorld? by lazy { TODO() } - - constructor(world: World) : this(world.name, world.uid) -} - -/** - * Used by storage backing options to encompass the location of a parcel - */ -data class SerializableParcel(val world: SerializableWorld, - val pos: Vec2i) { - - //val parcel: Parcel? by lazy { TODO() } -} - -fun Worlds.getWorldBySerializedValue(input: SerializableWorld): ParcelWorld? { - return input.world?.let { getWorld(it) } -} - -fun Worlds.getParcelBySerializedValue(input: SerializableParcel): Parcel? { - return getWorldBySerializedValue(input.world) - ?.parcelByID(input.pos) -}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt index 36f241e..6c3d68f 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt @@ -1,16 +1,19 @@ +@file:Suppress("NOTHING_TO_INLINE") + package io.dico.parcels2.storage -import io.dico.parcels2.AddedStatus -import io.dico.parcels2.Parcel -import io.dico.parcels2.ParcelData -import io.dico.parcels2.ParcelOwner +import io.dico.parcels2.* import kotlinx.coroutines.experimental.* +import kotlinx.coroutines.experimental.channels.ProducerScope import kotlinx.coroutines.experimental.channels.ReceiveChannel import kotlinx.coroutines.experimental.channels.produce -import java.util.* +import java.util.UUID import java.util.concurrent.Executor import java.util.concurrent.Executors +typealias DataPair = Pair<ParcelId, ParcelData?> +typealias AddedDataPair<TAttach> = Pair<TAttach, MutableAddedDataMap> + interface Storage { val name: String val syncDispatcher: CoroutineDispatcher @@ -22,31 +25,31 @@ interface Storage { fun shutdown(): Job - fun readParcelData(parcelFor: Parcel): Deferred<ParcelData?> + fun readParcelData(parcel: ParcelId): Deferred<ParcelData?> - fun readParcelData(parcelsFor: Sequence<Parcel>): ReceiveChannel<Pair<Parcel, ParcelData?>> + fun readParcelData(parcels: Sequence<ParcelId>): ReceiveChannel<DataPair> - fun readAllParcelData(): ReceiveChannel<Pair<SerializableParcel, ParcelData?>> + fun readAllParcelData(): ReceiveChannel<DataPair> - fun getOwnedParcels(user: ParcelOwner): Deferred<List<SerializableParcel>> + fun getOwnedParcels(user: ParcelOwner): Deferred<List<ParcelId>> fun getNumParcels(user: ParcelOwner): Deferred<Int> - fun setParcelData(parcelFor: Parcel, data: ParcelData?): Job + fun setParcelData(parcel: ParcelId, data: ParcelData?): Job - fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?): Job + fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?): Job - fun setParcelPlayerStatus(parcelFor: Parcel, player: UUID, status: AddedStatus): Job + fun setParcelPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus): Job - fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean): Job + fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean): Job - fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean): Job + fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean): Job - fun readAllGlobalAddedData(): ReceiveChannel<Pair<ParcelOwner, MutableMap<UUID, AddedStatus>>> + fun readAllGlobalAddedData(): ReceiveChannel<AddedDataPair<ParcelOwner>> - fun readGlobalAddedData(owner: ParcelOwner): Deferred<MutableMap<UUID, AddedStatus>?> + fun readGlobalAddedData(owner: ParcelOwner): Deferred<MutableAddedDataMap?> fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus): Job } @@ -59,48 +62,47 @@ class StorageWithCoroutineBacking internal constructor(val backing: Backing) : S override val isConnected get() = backing.isConnected val channelCapacity = 16 - @Suppress("NOTHING_TO_INLINE") private inline fun <T> defer(noinline block: suspend CoroutineScope.() -> T): Deferred<T> { return async(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block) } - @Suppress("NOTHING_TO_INLINE") private inline fun job(noinline block: suspend CoroutineScope.() -> Unit): Job { return launch(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block) } + private inline fun <T> openChannel(noinline block: suspend ProducerScope<T>.() -> Unit): ReceiveChannel<T> { + return produce(asyncDispatcher, capacity = channelCapacity, block = block) + } + override fun init() = job { backing.init() } override fun shutdown() = job { backing.shutdown() } - override fun readParcelData(parcelFor: Parcel) = defer { backing.readParcelData(parcelFor) } + override fun readParcelData(parcel: ParcelId) = defer { backing.readParcelData(parcel) } - override fun readParcelData(parcelsFor: Sequence<Parcel>) = - produce(asyncDispatcher, capacity = channelCapacity) { with(backing) { produceParcelData(parcelsFor) } } + override fun readParcelData(parcels: Sequence<ParcelId>) = openChannel<DataPair> { backing.produceParcelData(channel, parcels) } - override fun readAllParcelData(): ReceiveChannel<Pair<SerializableParcel, ParcelData?>> = - produce(asyncDispatcher, capacity = channelCapacity) { with(backing) { produceAllParcelData() } } + override fun readAllParcelData() = openChannel<DataPair> { backing.produceAllParcelData(channel) } override fun getOwnedParcels(user: ParcelOwner) = defer { backing.getOwnedParcels(user) } override fun getNumParcels(user: ParcelOwner) = defer { backing.getNumParcels(user) } - override fun setParcelData(parcelFor: Parcel, data: ParcelData?) = job { backing.setParcelData(parcelFor, data) } + override fun setParcelData(parcel: ParcelId, data: ParcelData?) = job { backing.setParcelData(parcel, data) } - override fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) = job { backing.setParcelOwner(parcelFor, owner) } + override fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?) = job { backing.setParcelOwner(parcel, owner) } - override fun setParcelPlayerStatus(parcelFor: Parcel, player: UUID, status: AddedStatus) = job { backing.setLocalPlayerStatus(parcelFor, player, status) } + override fun setParcelPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus) = job { backing.setLocalPlayerStatus(parcel, player, status) } - override fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean) = job { backing.setParcelAllowsInteractInventory(parcel, value) } + override fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) = job { backing.setParcelAllowsInteractInventory(parcel, value) } - override fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean) = job { backing.setParcelAllowsInteractInputs(parcel, value) } + override fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean) = job { backing.setParcelAllowsInteractInputs(parcel, value) } - override fun readAllGlobalAddedData(): ReceiveChannel<Pair<ParcelOwner, MutableMap<UUID, AddedStatus>>> = - produce(asyncDispatcher, capacity = channelCapacity) { with(backing) { produceAllGlobalAddedData() } } + override fun readAllGlobalAddedData(): ReceiveChannel<AddedDataPair<ParcelOwner>> = openChannel { backing.produceAllGlobalAddedData(channel) } - override fun readGlobalAddedData(owner: ParcelOwner): Deferred<MutableMap<UUID, AddedStatus>?> = defer { backing.readGlobalAddedData(owner) } + override fun readGlobalAddedData(owner: ParcelOwner): Deferred<MutableAddedDataMap?> = defer { backing.readGlobalAddedData(owner) } override fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) = job { backing.setGlobalPlayerStatus(owner, player, status) } } diff --git a/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt b/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt index 90992c6..e798df9 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt @@ -40,4 +40,4 @@ class ConnectionStorageFactory : StorageFactory { return StorageWithCoroutineBacking(ExposedBacking(dataSourceFactory)) } -}
\ No newline at end of file +} diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt index 483bb16..5685346 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt @@ -5,18 +5,18 @@ package io.dico.parcels2.storage.exposed import com.zaxxer.hikari.HikariDataSource import io.dico.parcels2.* import io.dico.parcels2.storage.Backing -import io.dico.parcels2.storage.SerializableParcel +import io.dico.parcels2.storage.DataPair import io.dico.parcels2.util.toUUID import kotlinx.coroutines.experimental.CoroutineStart import kotlinx.coroutines.experimental.Unconfined -import kotlinx.coroutines.experimental.channels.ProducerScope +import kotlinx.coroutines.experimental.channels.SendChannel import kotlinx.coroutines.experimental.launch import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SchemaUtils.create import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.vendors.DatabaseDialect import org.joda.time.DateTime -import java.util.* +import java.util.UUID import javax.sql.DataSource class ExposedDatabaseException(message: String? = null) : Exception(message) @@ -63,7 +63,7 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) : isShutdown = true } - override suspend fun ProducerScope<Pair<Parcel, ParcelData?>>.produceParcelData(parcels: Sequence<Parcel>) { + override suspend fun produceParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>) { for (parcel in parcels) { val data = readParcelData(parcel) channel.send(parcel to data) @@ -71,32 +71,32 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) : channel.close() } - override suspend fun ProducerScope<Pair<SerializableParcel, ParcelData?>>.produceAllParcelData() = transactionLaunch { + override suspend fun produceAllParcelData(channel: SendChannel<Pair<ParcelId, ParcelData?>>) = transactionLaunch { ParcelsT.selectAll().forEach { row -> - val parcel = ParcelsT.getSerializable(row) ?: return@forEach + val parcel = ParcelsT.getId(row) ?: return@forEach val data = rowToParcelData(row) channel.send(parcel to data) } channel.close() } - override suspend fun readParcelData(parcelFor: Parcel): ParcelData? = transaction { - val row = ParcelsT.getRow(parcelFor) ?: return@transaction null + override suspend fun readParcelData(parcel: ParcelId): ParcelData? = transaction { + val row = ParcelsT.getRow(parcel) ?: return@transaction null rowToParcelData(row) } - override suspend fun getOwnedParcels(user: ParcelOwner): List<SerializableParcel> = transaction { + override suspend fun getOwnedParcels(user: ParcelOwner): List<ParcelId> = transaction { val user_id = OwnersT.getId(user) ?: return@transaction emptyList() ParcelsT.select { ParcelsT.owner_id eq user_id } .orderBy(ParcelsT.claim_time, isAsc = true) - .mapNotNull(ParcelsT::getSerializable) + .mapNotNull(ParcelsT::getId) .toList() } - override suspend fun setParcelData(parcelFor: Parcel, data: ParcelData?) { + override suspend fun setParcelData(parcel: ParcelId, data: ParcelData?) { if (data == null) { transaction { - ParcelsT.getId(parcelFor)?.let { id -> + ParcelsT.getId(parcel)?.let { id -> ParcelsT.deleteIgnoreWhere { ParcelsT.id eq id } // Below should cascade automatically @@ -111,25 +111,25 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) : } transaction { - val id = ParcelsT.getOrInitId(parcelFor) + val id = ParcelsT.getOrInitId(parcel) AddedLocalT.deleteIgnoreWhere { AddedLocalT.attach_id eq id } } - setParcelOwner(parcelFor, data.owner) + setParcelOwner(parcel, data.owner) for ((uuid, status) in data.addedMap) { - setLocalPlayerStatus(parcelFor, uuid, status) + setLocalPlayerStatus(parcel, uuid, status) } - setParcelAllowsInteractInputs(parcelFor, data.allowInteractInputs) - setParcelAllowsInteractInventory(parcelFor, data.allowInteractInventory) + setParcelAllowsInteractInputs(parcel, data.allowInteractInputs) + setParcelAllowsInteractInventory(parcel, data.allowInteractInventory) } - override suspend fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) = transaction { + override suspend fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?) = transaction { val id = if (owner == null) - ParcelsT.getId(parcelFor) ?: return@transaction + ParcelsT.getId(parcel) ?: return@transaction else - ParcelsT.getOrInitId(parcelFor) + ParcelsT.getOrInitId(parcel) val owner_id = owner?.let { OwnersT.getOrInitId(it) } val time = owner?.let { DateTime.now() } @@ -140,11 +140,11 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) : } } - override suspend fun setLocalPlayerStatus(parcelFor: Parcel, player: UUID, status: AddedStatus) = transaction { - AddedLocalT.setPlayerStatus(parcelFor, player, status) + override suspend fun setLocalPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus) = transaction { + AddedLocalT.setPlayerStatus(parcel, player, status) } - override suspend fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean): Unit = transaction { + override suspend fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean): Unit = transaction { val id = ParcelsT.getOrInitId(parcel) ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { it[ParcelOptionsT.parcel_id] = id @@ -152,7 +152,7 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) : } } - override suspend fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean): Unit = transaction { + override suspend fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean): Unit = transaction { val id = ParcelsT.getOrInitId(parcel) ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { it[ParcelOptionsT.parcel_id] = id @@ -160,7 +160,7 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) : } } - override suspend fun ProducerScope<Pair<ParcelOwner, MutableMap<UUID, AddedStatus>>>.produceAllGlobalAddedData() = transactionLaunch { + override suspend fun produceAllGlobalAddedData(channel: SendChannel<Pair<ParcelOwner, MutableMap<UUID, AddedStatus>>>) = transactionLaunch { AddedGlobalT.sendAllAddedData(channel) channel.close() } @@ -174,7 +174,7 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) : } private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply { - owner = row[ParcelsT.owner_id]?.let { OwnersT.getSerializable(it) } + owner = row[ParcelsT.owner_id]?.let { OwnersT.getId(it) } since = row[ParcelsT.claim_time] val parcelId = row[ParcelsT.id] diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedExtensions.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedExtensions.kt index e20e11b..9f7f599 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedExtensions.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedExtensions.kt @@ -40,7 +40,7 @@ class UpsertStatement<Key : Any>(table: Table, conflictColumn: Column<*>? = null } else { - append (" ON DUPLICATE KEY UPDATE ") + append(" ON DUPLICATE KEY UPDATE ") values.keys.filter { it !in indexColumns }.joinTo(this) { "${transaction.identity(it)}=VALUES(${transaction.identity(it)})" } } diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt index 60e9bc0..ac6e431 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt @@ -2,20 +2,16 @@ package io.dico.parcels2.storage.exposed -import io.dico.parcels2.Parcel +import io.dico.parcels2.ParcelId import io.dico.parcels2.ParcelOwner -import io.dico.parcels2.ParcelWorld -import io.dico.parcels2.storage.SerializableParcel -import io.dico.parcels2.storage.SerializableWorld -import io.dico.parcels2.util.Vec2i +import io.dico.parcels2.ParcelWorldId import io.dico.parcels2.util.toByteArray import io.dico.parcels2.util.toUUID import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.statements.InsertStatement -import java.util.* +import java.util.UUID -sealed class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj, SerializableObj>, - QueryObj, SerializableObj>(tableName: String, columnName: String) +sealed class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj>, QueryObj>(tableName: String, columnName: String) : Table(tableName) { val id = integer(columnName).autoIncrement().primaryKey() @@ -35,31 +31,32 @@ sealed class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj, abstract fun getId(obj: QueryObj): Int? abstract fun getOrInitId(obj: QueryObj): Int - fun getSerializable(id: Int): SerializableObj? = select { this@IdTransactionsTable.id eq id }.firstOrNull()?.let { getSerializable(it) } - abstract fun getSerializable(row: ResultRow): SerializableObj? + fun getId(id: Int): QueryObj? = select { this@IdTransactionsTable.id eq id }.firstOrNull()?.let { getId(it) } + abstract fun getId(row: ResultRow): QueryObj? } -object WorldsT : IdTransactionsTable<WorldsT, ParcelWorld, SerializableWorld>("parcel_worlds", "world_id") { +object WorldsT : IdTransactionsTable<WorldsT, ParcelWorldId>("parcel_worlds", "world_id") { val name = varchar("name", 50) - val uid = binary("uid", 16) + val uid = binary("uid", 16).nullable() + val index_name = uniqueIndexR("index_name", name) val index_uid = uniqueIndexR("index_uid", uid) - internal inline fun getId(binaryUid: ByteArray): Int? = getId { uid eq binaryUid } - internal inline fun getId(uid: UUID): Int? = getId(uid.toByteArray()) - internal inline fun getOrInitId(worldUid: UUID, worldName: String): Int = worldUid.toByteArray().let { binaryUid -> - getId(binaryUid) - ?: insertAndGetId("world named $worldName") { it[uid] = binaryUid; it[name] = worldName } + internal inline fun getId(worldName: String, binaryUid: ByteArray?): Int? = getId { (name eq worldName).let { if (binaryUid == null) it else it or (uid eq binaryUid) } } + internal inline fun getId(worldName: String, uid: UUID?): Int? = getId(worldName, uid?.toByteArray()) + internal inline fun getOrInitId(worldName: String, worldUid: UUID?): Int = worldUid?.toByteArray().let { binaryUid -> + getId(worldName, binaryUid) + ?: insertAndGetId("world named $worldName") { it[name] = worldName; binaryUid?.let { buid -> it[uid] = buid } } } - override fun getId(world: ParcelWorld): Int? = getId(world.world.uid) - override fun getOrInitId(world: ParcelWorld): Int = world.world.let { getOrInitId(it.uid, it.name) } + override fun getId(world: ParcelWorldId): Int? = getId(world.name, world.uid) + override fun getOrInitId(world: ParcelWorldId): Int = getOrInitId(world.name, world.uid) - override fun getSerializable(row: ResultRow): SerializableWorld { - return SerializableWorld(row[name], row[uid].toUUID()) + override fun getId(row: ResultRow): ParcelWorldId { + return ParcelWorldId(row[name], row[uid]?.toUUID()) } } -object ParcelsT : IdTransactionsTable<ParcelsT, Parcel, SerializableParcel>("parcels", "parcel_id") { +object ParcelsT : IdTransactionsTable<ParcelsT, ParcelId>("parcels", "parcel_id") { val world_id = integer("world_id").references(WorldsT.id) val px = integer("px") val pz = integer("pz") @@ -68,27 +65,27 @@ object ParcelsT : IdTransactionsTable<ParcelsT, Parcel, SerializableParcel>("par val index_location = uniqueIndexR("index_location", world_id, px, pz) private inline fun getId(worldId: Int, parcelX: Int, parcelZ: Int): Int? = getId { world_id.eq(worldId) and px.eq(parcelX) and pz.eq(parcelZ) } - private inline fun getId(worldUid: UUID, parcelX: Int, parcelZ: Int): Int? = WorldsT.getId(worldUid)?.let { getId(it, parcelX, parcelZ) } - private inline fun getOrInitId(worldUid: UUID, worldName: String, parcelX: Int, parcelZ: Int): Int { - val worldId = WorldsT.getOrInitId(worldUid, worldName) + private inline fun getId(worldName: String, worldUid: UUID?, parcelX: Int, parcelZ: Int): Int? = WorldsT.getId(worldName, worldUid)?.let { getId(it, parcelX, parcelZ) } + private inline fun getOrInitId(worldName: String, worldUid: UUID?, parcelX: Int, parcelZ: Int): Int { + val worldId = WorldsT.getOrInitId(worldName, worldUid) return getId(worldId, parcelX, parcelZ) ?: insertAndGetId("parcel at $worldName($parcelX, $parcelZ)") { it[world_id] = worldId; it[px] = parcelX; it[pz] = parcelZ } } - override fun getId(parcel: Parcel): Int? = getId(parcel.world.world.uid, parcel.pos.x, parcel.pos.z) - override fun getOrInitId(parcel: Parcel): Int = parcel.world.world.let { getOrInitId(it.uid, it.name, parcel.pos.x, parcel.pos.z) } + override fun getId(parcel: ParcelId): Int? = getId(parcel.worldId.name, parcel.worldId.uid, parcel.x, parcel.z) + override fun getOrInitId(parcel: ParcelId): Int = getOrInitId(parcel.worldId.name, parcel.worldId.uid, parcel.x, parcel.z) private inline fun getRow(id: Int): ResultRow? = select { ParcelsT.id eq id }.firstOrNull() - fun getRow(parcel: Parcel): ResultRow? = getId(parcel)?.let { getRow(it) } + fun getRow(parcel: ParcelId): ResultRow? = getId(parcel)?.let { getRow(it) } - override fun getSerializable(row: ResultRow): SerializableParcel? { + override fun getId(row: ResultRow): ParcelId? { val worldId = row[world_id] - val world = WorldsT.getSerializable(worldId) ?: return null - return SerializableParcel(world, Vec2i(row[px], row[pz])) + val world = WorldsT.getId(worldId) ?: return null + return ParcelId(world, row[px], row[pz]) } } -object OwnersT : IdTransactionsTable<OwnersT, ParcelOwner, ParcelOwner>("parcel_owners", "owner_id") { +object OwnersT : IdTransactionsTable<OwnersT, ParcelOwner>("parcel_owners", "owner_id") { val uuid = binary("uuid", 16).nullable() val name = varchar("name", 32) val index_pair = uniqueIndexR("index_pair", uuid, name) @@ -115,7 +112,7 @@ object OwnersT : IdTransactionsTable<OwnersT, ParcelOwner, ParcelOwner>("parcel_ if (owner.hasUUID) getOrInitId(owner.uuid!!, owner.notNullName) else getOrInitId(owner.name!!) - override fun getSerializable(row: ResultRow): ParcelOwner { + override fun getId(row: ResultRow): ParcelOwner { return row[uuid]?.toUUID()?.let { ParcelOwner(it) } ?: ParcelOwner(row[name]) } } diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt index 3e1438a..20b36b1 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt @@ -3,17 +3,16 @@ package io.dico.parcels2.storage.exposed import io.dico.parcels2.AddedStatus -import io.dico.parcels2.Parcel +import io.dico.parcels2.ParcelId import io.dico.parcels2.ParcelOwner -import io.dico.parcels2.storage.SerializableParcel import io.dico.parcels2.util.toByteArray import io.dico.parcels2.util.toUUID import kotlinx.coroutines.experimental.channels.SendChannel import org.jetbrains.exposed.sql.* -import java.util.* +import java.util.UUID -object AddedLocalT : AddedTable<Parcel, SerializableParcel>("parcels_added_local", ParcelsT) -object AddedGlobalT : AddedTable<ParcelOwner, ParcelOwner>("parcels_added_global", OwnersT) +object AddedLocalT : AddedTable<ParcelId>("parcels_added_local", ParcelsT) +object AddedGlobalT : AddedTable<ParcelOwner>("parcels_added_global", OwnersT) object ParcelOptionsT : Table("parcel_options") { val parcel_id = integer("parcel_id").primaryKey().references(ParcelsT.id, ReferenceOption.CASCADE) @@ -23,7 +22,7 @@ object ParcelOptionsT : Table("parcel_options") { typealias AddedStatusSendChannel<AttachT> = SendChannel<Pair<AttachT, MutableMap<UUID, AddedStatus>>> -sealed class AddedTable<AttachT, SerializableT>(name: String, val idTable: IdTransactionsTable<*, AttachT, SerializableT>) : Table(name) { +sealed class AddedTable<AttachT>(name: String, val idTable: IdTransactionsTable<*, AttachT>) : Table(name) { val attach_id = integer("attach_id").references(idTable.id, ReferenceOption.CASCADE) val player_uuid = binary("player_uuid", 16) val allowed_flag = bool("allowed_flag") @@ -52,7 +51,7 @@ sealed class AddedTable<AttachT, SerializableT>(name: String, val idTable: IdTra .associateByTo(hashMapOf(), { it[player_uuid].toUUID() }, { it[allowed_flag].asAddedStatus() }) } - suspend fun sendAllAddedData(channel: AddedStatusSendChannel<SerializableT>) { + suspend fun sendAllAddedData(channel: AddedStatusSendChannel<AttachT>) { /* val iterator = selectAll().orderBy(attach_id).iterator() @@ -63,7 +62,7 @@ sealed class AddedTable<AttachT, SerializableT>(name: String, val idTable: IdTra var map: MutableMap<UUID, AddedStatus>? = null fun initAttachAndMap() { - attach = idTable.getSerializable(id) + attach = idTable.getId(id) map = attach?.let { mutableMapOf() } } diff --git a/src/main/kotlin/io/dico/parcels2/storage/migration/Migration.kt b/src/main/kotlin/io/dico/parcels2/storage/migration/Migration.kt new file mode 100644 index 0000000..c8bc93c --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/storage/migration/Migration.kt @@ -0,0 +1,8 @@ +package io.dico.parcels2.storage.migration + +import io.dico.parcels2.storage.Storage + +interface Migration { + fun migrateTo(storage: Storage) +} + diff --git a/src/main/kotlin/io/dico/parcels2/storage/migration/MigrationFactory.kt b/src/main/kotlin/io/dico/parcels2/storage/migration/MigrationFactory.kt new file mode 100644 index 0000000..4fb3088 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/storage/migration/MigrationFactory.kt @@ -0,0 +1,5 @@ +package io.dico.parcels2.storage.migration + +interface MigrationFactory { + fun getMigration() +}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt new file mode 100644 index 0000000..e5b7d9d --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt @@ -0,0 +1,118 @@ +package io.dico.parcels2.storage.migration.plotme + +import com.zaxxer.hikari.HikariDataSource +import io.dico.parcels2.* +import io.dico.parcels2.storage.Storage +import io.dico.parcels2.storage.migration.Migration +import io.dico.parcels2.util.Vec2i +import io.dico.parcels2.util.isValid +import io.dico.parcels2.util.toUUID +import io.dico.parcels2.util.uuid +import kotlinx.coroutines.experimental.asCoroutineDispatcher +import kotlinx.coroutines.experimental.launch +import org.bukkit.Bukkit +import org.jetbrains.exposed.sql.* +import org.slf4j.LoggerFactory +import java.io.ByteArrayOutputStream +import java.sql.Blob +import java.util.UUID +import java.util.concurrent.Executors +import javax.sql.DataSource + +class PlotmeMigration(val parcelProvider: ParcelProvider, + val worldMapper: Map<String, String>, + val dataSourceFactory: () -> DataSource) : Migration { + private var dataSource: DataSource? = null + private var database: Database? = null + private var isShutdown: Boolean = false + private val dispatcher = Executors.newSingleThreadExecutor { Thread(it, "PlotMe Migration Thread") }.asCoroutineDispatcher() + private val mlogger = LoggerFactory.getLogger("PlotMe Migrator") + + private fun <T> transaction(statement: Transaction.() -> T) = org.jetbrains.exposed.sql.transactions.transaction(database!!, statement) + + override fun migrateTo(storage: Storage) { + launch(context = dispatcher) { + init() + doWork(storage) + shutdown() + } + } + + fun init() { + if (isShutdown) throw IllegalStateException() + dataSource = dataSourceFactory() + database = Database.connect(dataSource!!) + } + + fun shutdown() { + if (isShutdown) throw IllegalStateException() + dataSource?.let { + (it as? HikariDataSource)?.close() + } + database = null + isShutdown = true + } + + val parcelsCache = hashMapOf<String, MutableMap<Vec2i, ParcelData>>() + + private fun getMap(worldName: String): MutableMap<Vec2i, ParcelData>? { + val mapped = worldMapper[worldName] ?: return null + return parcelsCache.computeIfAbsent(mapped) { mutableMapOf() } + } + + private fun getData(worldName: String, position: Vec2i): ParcelData? { + return getMap(worldName)?.computeIfAbsent(position) { ParcelDataHolder() } + } + + fun doWork(target: Storage): Unit = transaction { + if (!PlotmePlotsT.exists()) { + mlogger.warn("Plotme tables don't appear to exist. Exiting.") + return@transaction + } + parcelsCache.clear() + + iterPlotmeTable(PlotmePlotsT) { data, row -> + // in practice, owner_uuid is not null for any plot currently. It will convert well. + data.owner = ParcelOwner(row[owner_uuid]?.toUUID(), row[owner_name]) + } + + iterPlotmeTable(PlotmeAllowedT) { data, row -> + val uuid = row[player_uuid]?.toUUID() + ?: Bukkit.getOfflinePlayer(row[player_name]).takeIf { it.isValid }?.uuid + ?: return@iterPlotmeTable + + data.setAddedStatus(uuid, AddedStatus.ALLOWED) + } + + iterPlotmeTable(PlotmeDeniedT) { data, row -> + val uuid = row[PlotmeAllowedT.player_uuid]?.toUUID() + ?: Bukkit.getOfflinePlayer(row[PlotmeAllowedT.player_name]).takeIf { it.isValid }?.uuid + ?: return@iterPlotmeTable + + data.setAddedStatus(uuid, AddedStatus.BANNED) + } + + for ((worldName, map) in parcelsCache) { + val world = ParcelWorldId(worldName) + for ((pos, data) in map) { + val parcel = ParcelId(world, pos) + target.setParcelData(parcel, data) + } + } + + } + + private fun Blob.toUUID(): UUID { + val out = ByteArrayOutputStream(16) + binaryStream.copyTo(out, bufferSize = 16) + return out.toByteArray().toUUID() + } + + private inline fun <T : PlotmeTable> iterPlotmeTable(table: T, block: T.(ParcelData, ResultRow) -> Unit) { + table.selectAll().forEach { row -> + val data = getData(row[table.world_name], Vec2i(row[table.px], row[table.pz])) ?: return@forEach + table.block(data, row) + } + } + +}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeTables.kt b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeTables.kt new file mode 100644 index 0000000..3d07955 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeTables.kt @@ -0,0 +1,26 @@ +package io.dico.parcels2.storage.migration.plotme + +import org.jetbrains.exposed.sql.Table + +const val uppercase: Boolean = false +@Suppress("ConstantConditionIf") +fun String.toCorrectCase() = if (uppercase) this else toLowerCase() + +sealed class PlotmeTable(name: String) : Table(name) { + val px = PlotmePlotsT.integer("idX").primaryKey() + val pz = PlotmePlotsT.integer("idZ").primaryKey() + val world_name = PlotmePlotsT.varchar("world", 32).primaryKey() +} + +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 = PlotmePlotsT.varchar("player", 32) + val player_uuid = PlotmePlotsT.blob("playerid").nullable() +} + +object PlotmeAllowedT : PlotmePlotPlayerMap("plotmeAllowed".toCorrectCase()) +object PlotmeDeniedT : PlotmePlotPlayerMap("plotmeDenied".toCorrectCase()) diff --git a/src/main/kotlin/io/dico/parcels2/util/BukkitAwait.kt b/src/main/kotlin/io/dico/parcels2/util/BukkitAwait.kt deleted file mode 100644 index 0df14e7..0000000 --- a/src/main/kotlin/io/dico/parcels2/util/BukkitAwait.kt +++ /dev/null @@ -1,72 +0,0 @@ -package io.dico.parcels2.util - -import org.bukkit.plugin.Plugin -import org.bukkit.scheduler.BukkitTask - -inline fun Plugin.doAwait(checkNow: Boolean = true, configure: AwaitTask.() -> Unit) { - with(AwaitTask()) { - configure() - start(checkNow = checkNow) - } -} - -private typealias Action<T> = () -> T - -class AwaitTask : Runnable { - //@formatter:off - var cond: Action<Boolean>? = null ; set(value) { checkNotRunning(); field = value } - var onSuccess: Action<Unit>? = null ; set(value) { checkNotRunning(); field = value } - var onFailure: Action<Unit>? = null ; set(value) { checkNotRunning(); field = value } - var delay: Int = -1 ; set(value) { checkNotRunning(); field = value } - var interval: Int = 20 ; set(value) { checkNotRunning(); field = value } - var maxChecks: Int = 0 ; set(value) { checkNotRunning(); field = value } - - var task: BukkitTask? = null ; private set - var elapsedChecks = 0 ; private set - var cancelled = false ; private set - //@formatter:on - - fun Plugin.start(checkNow: Boolean = true) { - if (cancelled) throw IllegalStateException() - - requireNotNull(cond) - requireNotNull(onSuccess) - - if (checkNow && check()) { - cancel() - onSuccess!!.invoke() - return - } - - task = server.scheduler.runTaskTimer(this, this@AwaitTask, delay.toLong(), interval.toLong()) - } - - override fun run() { - if (task?.isCancelled != false) return - - if (check()) { - cancel() - onSuccess!!.invoke() - } - - if (maxChecks in 1 until elapsedChecks) { - cancel() - onFailure?.invoke() - } - } - - private fun check(): Boolean { - elapsedChecks++ - return cond!!.invoke() - } - - fun cancel() { - task?.cancel() - cancelled = true - } - - private fun checkNotRunning() { - if (cancelled || task != null) throw IllegalStateException() - } - -}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/util/MaterialExtensions.kt b/src/main/kotlin/io/dico/parcels2/util/MaterialExtensions.kt index f38f687..a2aefc8 100644 --- a/src/main/kotlin/io/dico/parcels2/util/MaterialExtensions.kt +++ b/src/main/kotlin/io/dico/parcels2/util/MaterialExtensions.kt @@ -10,62 +10,67 @@ wood: OAK_$, BIRCH_$, SPRUCE_$, JUNGLE_$, ACACIA_$, DARK_OAK_$, */ -val Material.isBed get() = when(this) { - WHITE_BED, - ORANGE_BED, - MAGENTA_BED, - LIGHT_BLUE_BED, - YELLOW_BED, - LIME_BED, - PINK_BED, - GRAY_BED, - LIGHT_GRAY_BED, - CYAN_BED, - PURPLE_BED, - BLUE_BED, - BROWN_BED, - GREEN_BED, - RED_BED, - BLACK_BED -> true - else -> false -} +val Material.isBed + get() = when (this) { + WHITE_BED, + ORANGE_BED, + MAGENTA_BED, + LIGHT_BLUE_BED, + YELLOW_BED, + LIME_BED, + PINK_BED, + GRAY_BED, + LIGHT_GRAY_BED, + CYAN_BED, + PURPLE_BED, + BLUE_BED, + BROWN_BED, + GREEN_BED, + RED_BED, + BLACK_BED -> true + else -> false + } -val Material.isWoodDoor get() = when(this) { - OAK_DOOR, - BIRCH_DOOR, - SPRUCE_DOOR, - JUNGLE_DOOR, - ACACIA_DOOR, - DARK_OAK_DOOR -> true - else -> false -} +val Material.isWoodDoor + get() = when (this) { + OAK_DOOR, + BIRCH_DOOR, + SPRUCE_DOOR, + JUNGLE_DOOR, + ACACIA_DOOR, + DARK_OAK_DOOR -> true + else -> false + } -val Material.isWoodTrapdoor get() = when(this) { - OAK_TRAPDOOR, - BIRCH_TRAPDOOR, - SPRUCE_TRAPDOOR, - JUNGLE_TRAPDOOR, - ACACIA_TRAPDOOR, - DARK_OAK_TRAPDOOR -> true - else -> false -} +val Material.isWoodTrapdoor + get() = when (this) { + OAK_TRAPDOOR, + BIRCH_TRAPDOOR, + SPRUCE_TRAPDOOR, + JUNGLE_TRAPDOOR, + ACACIA_TRAPDOOR, + DARK_OAK_TRAPDOOR -> true + else -> false + } -val Material.isWoodFenceGate get() = when(this) { - OAK_FENCE_GATE, - BIRCH_FENCE_GATE, - SPRUCE_FENCE_GATE, - JUNGLE_FENCE_GATE, - ACACIA_FENCE_GATE, - DARK_OAK_FENCE_GATE -> true - else -> false -} +val Material.isWoodFenceGate + get() = when (this) { + OAK_FENCE_GATE, + BIRCH_FENCE_GATE, + SPRUCE_FENCE_GATE, + JUNGLE_FENCE_GATE, + ACACIA_FENCE_GATE, + DARK_OAK_FENCE_GATE -> true + else -> false + } -val Material.isWoodButton get() = when(this) { - OAK_BUTTON, - BIRCH_BUTTON, - SPRUCE_BUTTON, - JUNGLE_BUTTON, - ACACIA_BUTTON, - DARK_OAK_BUTTON -> true - else -> false -} +val Material.isWoodButton + get() = when (this) { + OAK_BUTTON, + BIRCH_BUTTON, + SPRUCE_BUTTON, + JUNGLE_BUTTON, + ACACIA_BUTTON, + DARK_OAK_BUTTON -> true + else -> false + } diff --git a/src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt b/src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt index 6597441..8713da7 100644 --- a/src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt +++ b/src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt @@ -9,7 +9,8 @@ import org.bukkit.plugin.java.JavaPlugin inline val OfflinePlayer.uuid get() = uniqueId @Suppress("UsePropertyAccessSyntax") -inline val OfflinePlayer.isValid get() = isOnline() || hasPlayedBefore() +inline val OfflinePlayer.isValid + get() = isOnline() || hasPlayedBefore() inline val Player.hasBanBypass get() = hasPermission("parcels.admin.bypass.ban") inline val Player.hasGamemodeBypass get() = hasPermission("parcels.admin.bypass.gamemode") diff --git a/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt b/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt index b93dec2..bca2428 100644 --- a/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt +++ b/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt @@ -1,9 +1,8 @@ package io.dico.parcels2.util import org.bukkit.Bukkit -import org.jetbrains.annotations.Contract import java.nio.ByteBuffer -import java.util.* +import java.util.UUID @Suppress("UsePropertyAccessSyntax") fun getPlayerNameOrDefault(uuid: UUID?, ifUnknown: String? = null): String { |