Archived
0

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

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

View File

@@ -72,6 +72,7 @@ dependencies {
compile("org.jetbrains.exposed:exposed:0.10.3") { isTransitive = false } compile("org.jetbrains.exposed:exposed:0.10.3") { isTransitive = false }
compile("joda-time:joda-time:2.10") compile("joda-time:joda-time:2.10")
compile("com.zaxxer:HikariCP:3.2.0") compile("com.zaxxer:HikariCP:3.2.0")
compile("ch.qos.logback:logback-classic:1.2.3")
val jacksonVersion = "2.9.6" val jacksonVersion = "2.9.6"
compile("com.fasterxml.jackson.core:jackson-core:$jacksonVersion") compile("com.fasterxml.jackson.core:jackson-core:$jacksonVersion")

View File

@@ -2,10 +2,13 @@ package io.dico.parcels2
import io.dico.parcels2.util.uuid import io.dico.parcels2.util.uuid
import org.bukkit.OfflinePlayer import org.bukkit.OfflinePlayer
import java.util.* import java.util.UUID
typealias MutableAddedDataMap = MutableMap<UUID, AddedStatus>
typealias AddedDataMap = Map<UUID, AddedStatus>
interface AddedData { interface AddedData {
val addedMap: Map<UUID, AddedStatus> val addedMap: AddedDataMap
fun getAddedStatus(uuid: UUID): AddedStatus fun getAddedStatus(uuid: UUID): AddedStatus
fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean
@@ -28,8 +31,7 @@ interface AddedData {
fun unban(player: OfflinePlayer) = unban(player.uuid) fun unban(player: OfflinePlayer) = unban(player.uuid)
} }
open class AddedDataHolder(override var addedMap: MutableMap<UUID, AddedStatus> open class AddedDataHolder(override var addedMap: MutableAddedDataMap = mutableMapOf()) : AddedData {
= mutableMapOf<UUID, AddedStatus>()) : AddedData {
override fun getAddedStatus(uuid: UUID): AddedStatus = addedMap.getOrDefault(uuid, AddedStatus.DEFAULT) override fun getAddedStatus(uuid: UUID): AddedStatus = addedMap.getOrDefault(uuid, AddedStatus.DEFAULT)
override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean = status.takeIf { it != AddedStatus.DEFAULT } override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean = status.takeIf { it != AddedStatus.DEFAULT }
?.let { addedMap.put(uuid, it) != it } ?.let { addedMap.put(uuid, it) != it }
@@ -44,4 +46,12 @@ enum class AddedStatus {
val isDefault get() = this == DEFAULT val isDefault get() = this == DEFAULT
val isAllowed get() = this == ALLOWED val isAllowed get() = this == ALLOWED
val isBanned get() = this == BANNED val isBanned get() = this == BANNED
} }
interface GlobalAddedData : AddedData {
val owner: ParcelOwner
}
interface GlobalAddedDataManager {
operator fun get(owner: ParcelOwner): GlobalAddedData
}

View File

@@ -1,27 +1,33 @@
package io.dico.parcels2 package io.dico.parcels2
import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonIgnore
import io.dico.parcels2.blockvisitor.TickWorktimeOptions import io.dico.parcels2.blockvisitor.TickWorktimeOptions
import io.dico.parcels2.defaultimpl.DefaultGeneratorOptions
import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.Storage
import io.dico.parcels2.storage.StorageFactory import io.dico.parcels2.storage.StorageFactory
import io.dico.parcels2.storage.yamlObjectMapper import io.dico.parcels2.storage.yamlObjectMapper
import org.bukkit.Bukkit.createBlockData
import org.bukkit.GameMode import org.bukkit.GameMode
import org.bukkit.Material import org.bukkit.Material
import org.bukkit.block.Biome
import org.bukkit.block.data.BlockData
import java.io.Reader import java.io.Reader
import java.io.Writer import java.io.Writer
import java.util.* import java.util.EnumSet
class Options { class Options {
var worlds: Map<String, WorldOptions> = HashMap() var worlds: Map<String, WorldOptionsHolder> = hashMapOf()
private set private set
var storage: StorageOptions = StorageOptions("postgresql", DataConnectionOptions()) var storage: StorageOptions = StorageOptions("postgresql", DataConnectionOptions())
var tickWorktime: TickWorktimeOptions = TickWorktimeOptions(20, 1) 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) 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, data class WorldOptions(var gameMode: GameMode? = GameMode.CREATIVE,
var dayTime: Boolean = true, var dayTime: Boolean = true,
var noWeather: Boolean = true, var noWeather: Boolean = true,
@@ -42,8 +51,7 @@ data class WorldOptions(var gameMode: GameMode? = GameMode.CREATIVE,
var blockPortalCreation: Boolean = true, var blockPortalCreation: Boolean = true,
var blockMobSpawning: Boolean = true, var blockMobSpawning: Boolean = true,
var blockedItems: Set<Material> = EnumSet.of(Material.FLINT_AND_STEEL, Material.SNOWBALL), var blockedItems: Set<Material> = EnumSet.of(Material.FLINT_AND_STEEL, Material.SNOWBALL),
var axisLimit: Int = 10, var axisLimit: Int = 10) {
var generator: GeneratorOptions = DefaultGeneratorOptions()) {
} }
@@ -51,23 +59,7 @@ abstract class GeneratorOptions {
abstract fun generatorFactory(): GeneratorFactory abstract fun generatorFactory(): GeneratorFactory
fun getGenerator(worlds: Worlds, worldName: String) = generatorFactory().newParcelGenerator(worlds, worldName, this) fun newGenerator(worldName: String) = generatorFactory().newParcelGenerator(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
} }
@@ -104,4 +96,9 @@ data class DataConnectionOptions(val address: String = "localhost",
} }
data class DataFileOptions(val location: String = "/flatfile-storage/") data class DataFileOptions(val location: String = "/flatfile-storage/")
class MigrationOptions() {
}

View File

@@ -1,14 +1,11 @@
package io.dico.parcels2 package io.dico.parcels2
import io.dico.dicore.Formatting
import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.Vec2i
import io.dico.parcels2.util.getPlayerName
import io.dico.parcels2.util.hasBuildAnywhere import io.dico.parcels2.util.hasBuildAnywhere
import org.bukkit.OfflinePlayer import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.joda.time.DateTime import org.joda.time.DateTime
import java.util.* import java.util.UUID
import kotlin.reflect.KProperty
/** /**
* Parcel implementation of ParcelData will update the database when changes are made. * 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. * However, this implementation is intentionally not thread-safe.
* Therefore, database query callbacks should schedule their updates using the bukkit scheduler. * Therefore, database query callbacks should schedule their updates using the bukkit scheduler.
*/ */
class Parcel(val world: ParcelWorld, val pos: Vec2i) : ParcelData { interface Parcel : ParcelData {
var data: ParcelData = ParcelDataHolder(); private set 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}" fun copyDataIgnoringDatabase(data: ParcelData)
val homeLocation get() = world.generator.getHomeLocation(this)
val infoString by ParcelInfoStringComputer fun copyData(data: ParcelData)
fun copyDataIgnoringDatabase(data: ParcelData) { fun dispose()
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
} }
interface ParcelData : AddedData { interface ParcelData : AddedData {
@@ -113,59 +58,3 @@ class ParcelDataHolder : AddedDataHolder(), ParcelData {
override var allowInteractInventory = true 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(")")
}
}
}
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -1,4 +1,4 @@
@file:Suppress("unused") @file:Suppress("unused", "UsePropertyAccessSyntax", "DEPRECATION")
package io.dico.parcels2 package io.dico.parcels2
@@ -8,11 +8,10 @@ import io.dico.parcels2.util.uuid
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.OfflinePlayer import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player import org.bukkit.entity.Player
import java.util.* import java.util.UUID
@Suppress("UsePropertyAccessSyntax") class ParcelOwner(val uuid: UUID?,
class ParcelOwner private constructor(val uuid: UUID?, val name: String?) {
val name: String?) {
val notNullName: String by lazy { name ?: getPlayerNameOrDefault(uuid!!) } val notNullName: String by lazy { name ?: getPlayerNameOrDefault(uuid!!) }
constructor(name: String) : this(null, name) constructor(name: String) : this(null, name)
@@ -26,11 +25,11 @@ class ParcelOwner private constructor(val uuid: UUID?,
inline val hasUUID: Boolean get() = uuid != null inline val hasUUID: Boolean get() = uuid != null
val onlinePlayer: Player? get() = uuid?.let { Bukkit.getPlayer(uuid) } val onlinePlayer: Player? get() = uuid?.let { Bukkit.getPlayer(uuid) }
@Suppress("DEPRECATION")
val onlinePlayerAllowingNameMatch: Player? get() = onlinePlayer ?: name?.let { Bukkit.getPlayerExact(name) } val onlinePlayerAllowingNameMatch: Player? get() = onlinePlayer ?: name?.let { Bukkit.getPlayerExact(name) }
val offlinePlayer: OfflinePlayer? get() = uuid?.let { Bukkit.getOfflinePlayer(it).takeIf { it.isValid } } val offlinePlayer: OfflinePlayer? get() = uuid?.let { Bukkit.getOfflinePlayer(it).takeIf { it.isValid } }
@Suppress("DEPRECATION") val offlinePlayerAllowingNameMatch: OfflinePlayer?
val offlinePlayerAllowingNameMatch: OfflinePlayer? get() = offlinePlayer ?: Bukkit.getOfflinePlayer(name).takeIf { it.isValid } get() = offlinePlayer ?: Bukkit.getOfflinePlayer(name).takeIf { it.isValid }
fun matches(player: OfflinePlayer, allowNameMatch: Boolean = false): Boolean { fun matches(player: OfflinePlayer, allowNameMatch: Boolean = false): Boolean {
return uuid?.let { it == player.uniqueId } ?: false return uuid?.let { it == player.uniqueId } ?: false

View File

@@ -1,223 +1,86 @@
package io.dico.parcels2 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.Storage
import io.dico.parcels2.storage.getParcelBySerializedValue
import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.Vec2i
import io.dico.parcels2.util.floor import io.dico.parcels2.util.floor
import kotlinx.coroutines.experimental.launch
import org.bukkit.Bukkit
import org.bukkit.Location import org.bukkit.Location
import org.bukkit.World import org.bukkit.World
import org.bukkit.WorldCreator
import org.bukkit.block.Block import org.bukkit.block.Block
import org.bukkit.entity.Entity import org.bukkit.entity.Entity
import org.bukkit.entity.Player import java.util.UUID
import java.util.*
import kotlin.coroutines.experimental.buildIterator
import kotlin.coroutines.experimental.buildSequence
class Worlds(val plugin: ParcelsPlugin) { interface ParcelProvider {
val worlds: Map<String, ParcelWorld> get() = _worlds val worlds: Map<String, ParcelWorld>
private val _worlds: MutableMap<String, ParcelWorld> = HashMap()
fun getWorld(name: String): ParcelWorld? = _worlds[name] fun getWorldById(id: ParcelWorldId): ParcelWorld?
fun getParcelById(id: ParcelId): Parcel?
fun getWorld(name: String): ParcelWorld?
fun getWorld(world: World): ParcelWorld? = getWorld(world.name) 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 getParcelAt(player: Player): Parcel? = getParcelAt(player.location) fun getWorld(loc: Location): ParcelWorld? = getWorld(loc.world)
fun getParcelAt(location: Location): Parcel? = getParcelAt(location.world, location.x.floor(), location.z.floor()) fun getWorld(entity: Entity): ParcelWorld? = getWorld(entity.location)
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: World, x: Int, z: Int): Parcel? = getParcelAt(world.name, x, z)
fun getParcelAt(world: String, x: Int, z: Int): Parcel? { fun getParcelAt(world: World, vec: Vec2i): Parcel? = getParcelAt(world, vec.x, vec.z)
with(getWorld(world) ?: return null) {
return generator.parcelAt(x, z)
}
}
operator fun SerializableParcel.invoke(): Parcel? { fun getParcelAt(loc: Location): Parcel? = getParcelAt(loc.world, loc.x.floor(), loc.z.floor())
return world()?.parcelByID(pos)
}
operator fun SerializableWorld.invoke(): ParcelWorld? { fun getParcelAt(entity: Entity): Parcel? = getParcelAt(entity.location)
return world?.let { getWorld(it) }
}
fun loadWorlds(options: Options) { fun getParcelAt(block: Block): Parcel? = getParcelAt(block.world, block.x, block.z)
for ((worldName, worldOptions) in options.worlds.entries) {
val world: ParcelWorld
try {
world = ParcelWorld( fun getWorldGenerator(worldName: String): ParcelGenerator?
worldName,
worldOptions,
worldOptions.generator.getGenerator(this, worldName),
plugin.storage,
plugin.globalAddedData)
} catch (ex: Exception) { fun loadWorlds()
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()
}
}
} }
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, fun getParcelAt(block: Block): Parcel? = getParcelAt(block.x, block.z).takeIf { block.world == world }
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}")
}
}
interface ParcelContainer {
fun parcelByID(x: Int, z: Int): Parcel?
fun nextEmptyParcel(): Parcel?
} }
typealias ParcelContainerFactory = (ParcelWorld) -> ParcelContainer typealias ParcelContainerFactory = (ParcelWorld) -> ParcelContainer
class DefaultParcelContainer(private val world: ParcelWorld, interface ParcelContainer {
private val storage: Storage) : ParcelContainer {
private var parcels: Array<Array<Parcel>>
init { fun getParcelById(x: Int, z: Int): Parcel?
parcels = initArray(world.options.axisLimit, world)
}
fun resizeIfSizeChanged() { fun getParcelById(id: Vec2i): Parcel? = getParcelById(id.x, id.z)
if (parcels.size / 2 != world.options.axisLimit) {
resize(world.options.axisLimit)
}
}
fun resize(axisLimit: Int) { fun nextEmptyParcel(): Parcel?
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? { interface ParcelWorld : ParcelLocator, ParcelContainer, ParcelBlockManager {
return parcels.getOrNull(x + world.options.axisLimit)?.getOrNull(z + world.options.axisLimit) val id: ParcelWorldId
} val name: String
val uid: UUID?
override fun nextEmptyParcel(): Parcel? { val options: WorldOptions
return walkInCircle().find { it.owner == null } val generator: ParcelGenerator
} val storage: Storage
val container: ParcelContainer
private fun walkInCircle(): Iterable<Parcel> = Iterable { val locator: ParcelLocator
buildIterator { val blockManager: ParcelBlockManager
val center = world.options.axisLimit val globalAddedData: GlobalAddedDataManager
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) }
}
}
}
}

View File

@@ -6,6 +6,8 @@ import io.dico.dicore.command.ICommandDispatcher
import io.dico.parcels2.blockvisitor.TickWorktimeLimiter import io.dico.parcels2.blockvisitor.TickWorktimeLimiter
import io.dico.parcels2.blockvisitor.WorktimeLimiter import io.dico.parcels2.blockvisitor.WorktimeLimiter
import io.dico.parcels2.command.getParcelCommands 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.ParcelEntityTracker
import io.dico.parcels2.listener.ParcelListeners import io.dico.parcels2.listener.ParcelListeners
import io.dico.parcels2.storage.Storage 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.FunctionHelper
import io.dico.parcels2.util.tryCreate import io.dico.parcels2.util.tryCreate
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.generator.ChunkGenerator
import org.bukkit.plugin.java.JavaPlugin import org.bukkit.plugin.java.JavaPlugin
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@@ -24,7 +27,7 @@ private inline val plogger get() = logger
class ParcelsPlugin : JavaPlugin() { class ParcelsPlugin : JavaPlugin() {
lateinit var optionsFile: File; private set lateinit var optionsFile: File; private set
lateinit var options: Options; 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 storage: Storage; private set
lateinit var globalAddedData: GlobalAddedDataManager; private set lateinit var globalAddedData: GlobalAddedDataManager; private set
@@ -50,7 +53,7 @@ class ParcelsPlugin : JavaPlugin() {
private fun init(): Boolean { private fun init(): Boolean {
optionsFile = File(dataFolder, "options.yml") optionsFile = File(dataFolder, "options.yml")
options = Options() options = Options()
worlds = Worlds(this) parcelProvider = ParcelProviderImpl(this)
try { try {
if (!loadOptions()) return false if (!loadOptions()) return false
@@ -64,8 +67,7 @@ class ParcelsPlugin : JavaPlugin() {
} }
globalAddedData = GlobalAddedDataManagerImpl(this) globalAddedData = GlobalAddedDataManagerImpl(this)
worlds.loadWorlds(options) entityTracker = ParcelEntityTracker(parcelProvider)
entityTracker = ParcelEntityTracker(worlds)
} catch (ex: Exception) { } catch (ex: Exception) {
plogger.error("Error loading options", ex) plogger.error("Error loading options", ex)
return false return false
@@ -74,6 +76,7 @@ class ParcelsPlugin : JavaPlugin() {
registerListeners() registerListeners()
registerCommands() registerCommands()
parcelProvider.loadWorlds()
return true return true
} }
@@ -81,7 +84,7 @@ class ParcelsPlugin : JavaPlugin() {
when { when {
optionsFile.exists() -> yamlObjectMapper.readerForUpdating(options).readValue<Options>(optionsFile) optionsFile.exists() -> yamlObjectMapper.readerForUpdating(options).readValue<Options>(optionsFile)
optionsFile.tryCreate() -> { optionsFile.tryCreate() -> {
options.addWorld("parcels", WorldOptions()) options.addWorld("parcels")
try { try {
yamlObjectMapper.writeValue(optionsFile, options) yamlObjectMapper.writeValue(optionsFile, options)
} catch (ex: Throwable) { } catch (ex: Throwable) {
@@ -99,6 +102,10 @@ class ParcelsPlugin : JavaPlugin() {
return true return true
} }
override fun getDefaultWorldGenerator(worldName: String, generatorId: String?): ChunkGenerator? {
return parcelProvider.getWorldGenerator(worldName)
}
private fun registerCommands() { private fun registerCommands() {
cmdDispatcher = getParcelCommands(this).apply { cmdDispatcher = getParcelCommands(this).apply {
registerToCommandMap("parcels:", EOverridePolicy.FALLBACK_ONLY) registerToCommandMap("parcels:", EOverridePolicy.FALLBACK_ONLY)
@@ -107,7 +114,7 @@ class ParcelsPlugin : JavaPlugin() {
private fun registerListeners() { private fun registerListeners() {
if (listeners == null) { if (listeners == null) {
listeners = ParcelListeners(worlds, entityTracker) listeners = ParcelListeners(parcelProvider, entityTracker)
registrator.registerListeners(listeners!!) registrator.registerListeners(listeners!!)
} }
} }

View File

@@ -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)
}
}
}

View File

@@ -2,7 +2,7 @@ package io.dico.parcels2.blockvisitor
import org.bukkit.Material import org.bukkit.Material
import org.bukkit.Material.* import org.bukkit.Material.*
import java.util.* import java.util.EnumSet
val attachables: Set<Material> = EnumSet.of( val attachables: Set<Material> = EnumSet.of(
ACACIA_DOOR, ACACIA_DOOR,

View File

@@ -6,7 +6,7 @@ import kotlinx.coroutines.experimental.CancellationException
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
import org.bukkit.scheduler.BukkitTask import org.bukkit.scheduler.BukkitTask
import java.lang.System.currentTimeMillis import java.lang.System.currentTimeMillis
import java.util.* import java.util.LinkedList
import java.util.logging.Level import java.util.logging.Level
import kotlin.coroutines.experimental.Continuation import kotlin.coroutines.experimental.Continuation
import kotlin.coroutines.experimental.intrinsics.COROUTINE_SUSPENDED import kotlin.coroutines.experimental.intrinsics.COROUTINE_SUSPENDED

View File

@@ -16,10 +16,10 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei
override fun getPlugin(): Plugin = plugin override fun getPlugin(): Plugin = plugin
override fun getReceiver(context: ExecutionContext, target: Method, cmdName: String): ICommandReceiver { 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 { protected fun error(message: String): Nothing {
throw CommandException(message) throw CommandException(message)
@@ -32,7 +32,7 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei
protected suspend fun checkParcelLimit(player: Player, world: ParcelWorld) { protected suspend fun checkParcelLimit(player: Player, world: ParcelWorld) {
if (player.hasAdminManage) return if (player.hasAdminManage) return
val numOwnedParcels = plugin.storage.getOwnedParcels(ParcelOwner(player)).await() 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 val limit = player.parcelLimit
if (numOwnedParcels >= limit) { if (numOwnedParcels >= limit) {

View File

@@ -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"
}
}

View File

@@ -8,7 +8,7 @@ import io.dico.parcels2.util.hasAdminManage
import org.bukkit.OfflinePlayer import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player import org.bukkit.entity.Player
class CommandsAddedStatus(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { class CommandsAddedStatusLocal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@Cmd("allow", aliases = ["add", "permit"]) @Cmd("allow", aliases = ["add", "permit"])
@Desc("Allows a player to build on this parcel", @Desc("Allows a player to build on this parcel",

View File

@@ -9,7 +9,7 @@ import io.dico.parcels2.blockvisitor.RegionTraversal
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.Material import org.bukkit.Material
import org.bukkit.entity.Player import org.bukkit.entity.Player
import java.util.* import java.util.Random
class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@@ -39,7 +39,8 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
server.createBlockData(Material.QUARTZ_BLOCK) server.createBlockData(Material.QUARTZ_BLOCK)
) )
val random = Random() 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)] block.blockData = blockDatas[random.nextInt(4)]
}.onProgressUpdate(1000, 1000) { progress, elapsedTime -> }.onProgressUpdate(1000, 1000) { progress, elapsedTime ->
context.sendMessage(EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed" context.sendMessage(EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed"

View File

@@ -8,7 +8,6 @@ import io.dico.dicore.command.annotation.Flag
import io.dico.dicore.command.annotation.RequireParameters import io.dico.dicore.command.annotation.RequireParameters
import io.dico.parcels2.ParcelOwner import io.dico.parcels2.ParcelOwner
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.storage.getParcelBySerializedValue
import io.dico.parcels2.util.hasAdminManage import io.dico.parcels2.util.hasAdminManage
import io.dico.parcels2.util.hasParcelHomeOthers import io.dico.parcels2.util.hasParcelHomeOthers
import io.dico.parcels2.util.uuid import io.dico.parcels2.util.uuid
@@ -27,7 +26,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
val parcel = world.nextEmptyParcel() val parcel = world.nextEmptyParcel()
?: error("This world is full, please ask an admin to upsize it") ?: error("This world is full, please ask an admin to upsize it")
parcel.owner = ParcelOwner(uuid = player.uuid) parcel.owner = ParcelOwner(uuid = player.uuid)
player.teleport(parcel.homeLocation) player.teleport(parcel.world.getHomeLocation(parcel.id))
return "Enjoy your new parcel!" return "Enjoy your new parcel!"
} }
@@ -53,13 +52,13 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
val ownedParcelsResult = plugin.storage.getOwnedParcels(ownerTarget.owner).await() val ownedParcelsResult = plugin.storage.getOwnedParcels(ownerTarget.owner).await()
val ownedParcels = ownedParcelsResult val ownedParcels = ownedParcelsResult
.map { worlds.getParcelBySerializedValue(it) } .map { worlds.getParcelById(it) }
.filter { it != null && ownerTarget.world == it.world } .filter { it != null && ownerTarget.world == it.world }
val targetMatch = ownedParcels.getOrNull(target.index) val targetMatch = ownedParcels.getOrNull(target.index)
?: error("The specified parcel could not be matched") ?: error("The specified parcel could not be matched")
player.teleport(targetMatch.homeLocation) player.teleport(targetMatch.world.getHomeLocation(targetMatch.id))
return "" return ""
} }
@@ -91,7 +90,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
if (!sure) return "Are you sure? You cannot undo this action!\n" + if (!sure) return "Are you sure? You cannot undo this action!\n" +
"Type ${context.rawInput} -sure if you want to go through with this." "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 -> .onProgressUpdate(1000, 1000) { progress, elapsedTime ->
context.sendMessage(EMessageType.INFORMATIVE, "Clear progress: %.02f%%, %.2fs elapsed" context.sendMessage(EMessageType.INFORMATIVE, "Clear progress: %.02f%%, %.2fs elapsed"
.format(progress * 100, elapsedTime / 1000.0)) .format(progress * 100, elapsedTime / 1000.0))

View File

@@ -3,29 +3,32 @@ package io.dico.parcels2.command
import io.dico.dicore.command.CommandBuilder import io.dico.dicore.command.CommandBuilder
import io.dico.dicore.command.ICommandAddress import io.dico.dicore.command.ICommandAddress
import io.dico.dicore.command.ICommandDispatcher import io.dico.dicore.command.ICommandDispatcher
import io.dico.dicore.command.predef.PredefinedCommand
import io.dico.dicore.command.registration.reflect.ReflectiveRegistration import io.dico.dicore.command.registration.reflect.ReflectiveRegistration
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.logger import java.util.LinkedList
import java.util.* import java.util.Queue
@Suppress("UsePropertyAccessSyntax") @Suppress("UsePropertyAccessSyntax")
fun getParcelCommands(plugin: ParcelsPlugin): ICommandDispatcher { fun getParcelCommands(plugin: ParcelsPlugin): ICommandDispatcher {
//@formatter:off //@formatter:off
return CommandBuilder() return CommandBuilder()
.setChatController(ParcelsChatController()) .setChatController(ParcelsChatController())
.addParameterType(false, ParcelParameterType(plugin.worlds)) .addParameterType(false, ParcelParameterType(plugin.parcelProvider))
.addParameterType(true, ParcelTarget.PType(plugin.worlds)) .addParameterType(true, ParcelTarget.PType(plugin.parcelProvider))
.group("parcel", "plot", "plots", "p") .group("parcel", "plot", "plots", "p")
.registerCommands(CommandsGeneral(plugin)) .registerCommands(CommandsGeneral(plugin))
.registerCommands(CommandsAddedStatus(plugin)) .registerCommands(CommandsAddedStatusLocal(plugin))
.group("option") .group("option", "opt", "o")
//.apply { CommandsParcelOptions.setGroupDescription(this) } //.apply { CommandsParcelOptions.setGroupDescription(this) }
.registerCommands(CommandsParcelOptions(plugin)) .registerCommands(CommandsParcelOptions(plugin))
.parent() .parent()
.group("global", "g")
.registerCommands(CommandsAddedStatusGlobal(plugin))
.parent()
.group("admin", "a") .group("admin", "a")
.registerCommands(CommandsAdmin(plugin)) .registerCommands(CommandsAdmin(plugin))
.parent() .parent()

View File

@@ -5,8 +5,8 @@ import io.dico.dicore.command.ExecutionContext
import io.dico.dicore.command.ICommandReceiver import io.dico.dicore.command.ICommandReceiver
import io.dico.dicore.command.Validate import io.dico.dicore.command.Validate
import io.dico.parcels2.Parcel import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelProvider
import io.dico.parcels2.ParcelWorld import io.dico.parcels2.ParcelWorld
import io.dico.parcels2.Worlds
import io.dico.parcels2.util.hasAdminManage import io.dico.parcels2.util.hasAdminManage
import io.dico.parcels2.util.uuid import io.dico.parcels2.util.uuid
import org.bukkit.entity.Player 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") "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 player = context.sender as Player
val function = method.kotlinFunction!! val function = method.kotlinFunction!!
val receiverType = function.extensionReceiverParameter!!.type val receiverType = function.extensionReceiverParameter!!.type
@@ -39,20 +39,20 @@ fun getParcelCommandReceiver(worlds: Worlds, context: ExecutionContext, method:
val owner = require?.owner == true val owner = require?.owner == true
return when (receiverType.jvmErasure) { return when (receiverType.jvmErasure) {
ParcelScope::class -> ParcelScope(worlds.getParcelRequired(player, admin, owner)) ParcelScope::class -> ParcelScope(parcelProvider.getParcelRequired(player, admin, owner))
WorldScope::class -> WorldScope(worlds.getWorldRequired(player, admin)) WorldScope::class -> WorldScope(parcelProvider.getWorldRequired(player, admin))
else -> throw InternalError("Invalid command receiver type") 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") if (admin) Validate.isTrue(player.hasAdminManage, "You must have admin rights to use that command")
return getWorld(player.world) return getWorld(player.world)
?: throw CommandException("You must be in a parcel world to use that command") ?: 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 { fun ParcelProvider.getParcelRequired(player: Player, admin: Boolean = false, own: Boolean = false): Parcel {
val parcel = getWorldRequired(player, admin = admin).parcelAt(player) val parcel = getWorldRequired(player, admin = admin).getParcelAt(player)
?: throw CommandException("You must be in a parcel to use that command") ?: throw CommandException("You must be in a parcel to use that command")
if (own) Validate.isTrue(parcel.isOwner(player.uuid) || player.hasAdminManage, if (own) Validate.isTrue(parcel.isOwner(player.uuid) || player.hasAdminManage,
"You must own this parcel to use that command") "You must own this parcel to use that command")

View File

@@ -3,15 +3,10 @@ package io.dico.parcels2.command
import io.dico.dicore.command.CommandException import io.dico.dicore.command.CommandException
import io.dico.dicore.command.parameter.ArgumentBuffer import io.dico.dicore.command.parameter.ArgumentBuffer
import io.dico.dicore.command.parameter.Parameter 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.dicore.command.parameter.type.ParameterType
import io.dico.parcels2.Parcel import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelProvider
import io.dico.parcels2.ParcelWorld 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.command.CommandSender
import org.bukkit.entity.Player import org.bukkit.entity.Player
@@ -19,7 +14,7 @@ fun invalidInput(parameter: Parameter<*, *>, message: String): Nothing {
throw CommandException("invalid input for ${parameter.name}: $message") 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 val worldName = input
?.takeUnless { it.isEmpty() } ?.takeUnless { it.isEmpty() }
?: (sender as? Player)?.world?.name ?: (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") ?: 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]+)") val regex = Regex.fromLiteral("((.+)->)?([0-9]+):([0-9]+)")
override fun parse(parameter: Parameter<Parcel, Void>, sender: CommandSender, buffer: ArgumentBuffer): Parcel { override fun parse(parameter: Parameter<Parcel, Void>, sender: CommandSender, buffer: ArgumentBuffer): Parcel {
val matchResult = regex.matchEntire(buffer.next()) val matchResult = regex.matchEntire(buffer.next())
?: invalidInput(parameter, "must match (w->)?a:b (/${regex.pattern}/)") ?: 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() val x = matchResult.groupValues[3].toIntOrNull()
?: invalidInput(parameter, "couldn't parse int") ?: 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() val z = matchResult.groupValues[4].toIntOrNull()
?: invalidInput(parameter, "couldn't parse int") ?: invalidInput(parameter, "couldn't parse int")
return world.parcelByID(x, z) return world.getParcelById(x, z)
?: invalidInput(parameter, "parcel id is out of range") ?: invalidInput(parameter, "parcel id is out of range")
} }

View File

@@ -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.ParameterConfig
import io.dico.dicore.command.parameter.type.ParameterType import io.dico.dicore.command.parameter.type.ParameterType
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.storage.getParcelBySerializedValue
import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.Vec2i
import io.dico.parcels2.util.floor import io.dico.parcels2.util.floor
import io.dico.parcels2.util.isValid 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) { class ByID(world: ParcelWorld, val id: Vec2i?, isDefault: Boolean) : ParcelTarget(world, isDefault) {
override suspend fun ParcelsPlugin.getParcelSuspend(): Parcel? = getParcel() 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 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? { override suspend fun ParcelsPlugin.getParcelSuspend(): Parcel? {
val ownedParcelsSerialized = storage.getOwnedParcels(owner).await() val ownedParcelsSerialized = storage.getOwnedParcels(owner).await()
val ownedParcels = ownedParcelsSerialized val ownedParcels = ownedParcelsSerialized
.map { worlds.getParcelBySerializedValue(it) } .map { parcelProvider.getParcelById(it) }
.filter { it != null && world == it.world && owner == it.owner } .filter { it != null && world == it.world && owner == it.owner }
return ownedParcels.getOrNull(index) 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 // 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 { override fun parse(parameter: Parameter<ParcelTarget, Int>, sender: CommandSender, buffer: ArgumentBuffer): ParcelTarget {
var input = buffer.next() var input = buffer.next()
@@ -68,19 +67,19 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
val world = if (worldString.isEmpty()) { val world = if (worldString.isEmpty()) {
val player = requirePlayer(sender, parameter, "the world") 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") ?: invalidInput(parameter, "You cannot omit the world if you're not in a parcel world")
} else { } 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 val kind = parameter.paramInfo ?: DEFAULT_KIND
if (input.contains(',')) { 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) 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) val (owner, index) = getHomeIndex(parameter, sender, input)
return ByOwner(world, owner, index, false) return ByOwner(world, owner, index, false)
} }
@@ -106,7 +105,7 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
indexString = input indexString = input
} else { } else {
ownerString = input.substring(0, splitIdx) ownerString = input.substring(0, splitIdx)
indexString = input.substring(0, splitIdx + 1) indexString = input.substring(splitIdx + 1)
} }
val owner = if (ownerString.isEmpty()) 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 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) { 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) return ByID(world, id, true)
} }

View File

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

View File

@@ -0,0 +1,267 @@
package io.dico.parcels2.defaultimpl
import io.dico.parcels2.*
import io.dico.parcels2.blockvisitor.RegionTraversal
import io.dico.parcels2.blockvisitor.Worker
import io.dico.parcels2.blockvisitor.WorktimeLimiter
import io.dico.parcels2.util.*
import org.bukkit.*
import org.bukkit.block.Biome
import org.bukkit.block.Block
import org.bukkit.block.BlockFace
import org.bukkit.block.Skull
import org.bukkit.block.data.BlockData
import org.bukkit.block.data.type.Sign
import org.bukkit.block.data.type.Slab
import java.util.Random
private val airType = Bukkit.createBlockData(Material.AIR)
data class DefaultGeneratorOptions(var defaultBiome: Biome = Biome.JUNGLE,
var wallType: BlockData = Bukkit.createBlockData(Material.STONE_SLAB),
var floorType: BlockData = Bukkit.createBlockData(Material.QUARTZ_BLOCK),
var fillType: BlockData = Bukkit.createBlockData(Material.QUARTZ_BLOCK),
var pathMainType: BlockData = Bukkit.createBlockData(Material.SANDSTONE),
var pathAltType: BlockData = Bukkit.createBlockData(Material.REDSTONE_BLOCK),
var parcelSize: Int = 101,
var pathSize: Int = 9,
var floorHeight: Int = 64,
var offsetX: Int = 0,
var offsetZ: Int = 0) : GeneratorOptions() {
override fun generatorFactory(): GeneratorFactory = DefaultParcelGenerator.Factory
}
class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOptions) : ParcelGenerator() {
private var _world: World? = null
override val world: World
get() {
if (_world == null) _world = Bukkit.getWorld(name)!!.also {
maxHeight = it.maxHeight
return it
}
return _world!!
}
private var maxHeight = 0
companion object Factory : GeneratorFactory {
override val name get() = "default"
override val optionsClass get() = DefaultGeneratorOptions::class
override fun newParcelGenerator(worldName: String, options: GeneratorOptions): ParcelGenerator {
return DefaultParcelGenerator(worldName, options as DefaultGeneratorOptions)
}
}
val sectionSize = o.parcelSize + o.pathSize
val pathOffset = (if (o.pathSize % 2 == 0) o.pathSize + 2 else o.pathSize + 1) / 2
val makePathMain = o.pathSize > 2
val makePathAlt = o.pathSize > 4
private inline fun <T> generate(chunkX: Int,
chunkZ: Int,
floor: T, wall:
T, pathMain: T,
pathAlt: T,
fill: T,
setter: (Int, Int, Int, T) -> Unit) {
val floorHeight = o.floorHeight
val parcelSize = o.parcelSize
val sectionSize = sectionSize
val pathOffset = pathOffset
val makePathMain = makePathMain
val makePathAlt = makePathAlt
// parcel bottom x and z
// umod is unsigned %: the result is always >= 0
val pbx = ((chunkX shl 4) - o.offsetX) umod sectionSize
val pbz = ((chunkZ shl 4) - o.offsetZ) umod sectionSize
var curHeight: Int
var x: Int
var z: Int
for (cx in 0..15) {
for (cz in 0..15) {
x = (pbx + cx) % sectionSize - pathOffset
z = (pbz + cz) % sectionSize - pathOffset
curHeight = floorHeight
val type = when {
(x in 0 until parcelSize && z in 0 until parcelSize) -> floor
(x in -1..parcelSize && z in -1..parcelSize) -> {
curHeight++
wall
}
(makePathAlt && x in -2 until parcelSize + 2 && z in -2 until parcelSize + 2) -> pathAlt
(makePathMain) -> pathMain
else -> {
curHeight++
wall
}
}
for (y in 0 until curHeight) {
setter(cx, y, cz, fill)
}
setter(cx, curHeight, cz, type)
}
}
}
override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData {
val out = Bukkit.createChunkData(world)
generate(chunkX, chunkZ, o.floorType, o.wallType, o.pathMainType, o.pathAltType, o.fillType) { x, y, z, type ->
out.setBlock(x, y, z, type)
}
return out
}
override fun populate(world: World?, random: Random?, chunk: Chunk?) {
// do nothing
}
override fun getFixedSpawnLocation(world: World?, random: Random?): Location {
val fix = if (o.parcelSize.even) 0.5 else 0.0
return Location(world, o.offsetX + fix, o.floorHeight + 1.0, o.offsetZ + fix)
}
override fun makeParcelBlockManager(worktimeLimiter: WorktimeLimiter): ParcelBlockManager {
return ParcelBlockManagerImpl(worktimeLimiter)
}
override fun makeParcelLocator(container: ParcelContainer): ParcelLocator {
return ParcelLocatorImpl(container)
}
private inline fun <T> convertBlockLocationToId(x: Int, z: Int, mapper: (Int, Int) -> T): T? {
val sectionSize = sectionSize
val parcelSize = o.parcelSize
val absX = x - o.offsetX - pathOffset
val absZ = z - o.offsetZ - pathOffset
val modX = absX umod sectionSize
val modZ = absZ umod sectionSize
if (modX in 0 until parcelSize && modZ in 0 until parcelSize) {
return mapper((absX - modX) / sectionSize, (absZ - modZ) / sectionSize)
}
return null
}
private inner class ParcelLocatorImpl(val container: ParcelContainer) : ParcelLocator {
override val world: World = this@DefaultParcelGenerator.world
override fun getParcelAt(x: Int, z: Int): Parcel? {
return convertBlockLocationToId(x, z, container::getParcelById)
}
override fun getParcelIdAt(x: Int, z: Int): ParcelId? {
return convertBlockLocationToId(x, z) { idx, idz -> ParcelId(world.name, world.uid, idx, idz) }
}
}
@Suppress("DEPRECATION")
private inner class ParcelBlockManagerImpl(override val worktimeLimiter: WorktimeLimiter) : ParcelBlockManager {
override val world: World = this@DefaultParcelGenerator.world
override fun getBottomBlock(parcel: ParcelId): Vec2i = Vec2i(
sectionSize * parcel.pos.x + pathOffset + o.offsetX,
sectionSize * parcel.pos.z + pathOffset + o.offsetZ
)
override fun getHomeLocation(parcel: ParcelId): Location {
val bottom = getBottomBlock(parcel)
return Location(world, bottom.x.toDouble(), o.floorHeight + 1.0, bottom.z + (o.parcelSize - 1) / 2.0, -90F, 0F)
}
override fun setOwnerBlock(parcel: ParcelId, owner: ParcelOwner?) {
val b = getBottomBlock(parcel)
val wallBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 1)
val signBlock = world.getBlockAt(b.x - 2, o.floorHeight + 1, b.z - 1)
val skullBlock = world.getBlockAt(b.x - 1, o.floorHeight + 2, b.z - 1)
if (owner == null) {
wallBlock.blockData = o.wallType
signBlock.type = Material.AIR
skullBlock.type = Material.AIR
} else {
val wallBlockType: BlockData = if (o.wallType is Slab)
(o.wallType.clone() as Slab).apply { type = Slab.Type.DOUBLE }
else
o.wallType
wallBlock.blockData = wallBlockType
signBlock.blockData = (Bukkit.createBlockData(Material.WALL_SIGN) as Sign).apply { rotation = BlockFace.NORTH }
val sign = signBlock.state as org.bukkit.block.Sign
sign.setLine(0, "${parcel.x},${parcel.z}")
sign.setLine(2, owner.name)
sign.update()
skullBlock.type = Material.PLAYER_HEAD
val skull = skullBlock.state as Skull
if (owner.uuid != null) {
skull.owningPlayer = owner.offlinePlayer
} else {
skull.owner = owner.name
}
skull.rotation = BlockFace.WEST
skull.update()
}
}
override fun setBiome(parcel: ParcelId, biome: Biome): Worker = worktimeLimiter.submit {
val world = world
val b = getBottomBlock(parcel)
val parcelSize = o.parcelSize
for (x in b.x until b.x + parcelSize) {
for (z in b.z until b.z + parcelSize) {
markSuspensionPoint()
world.setBiome(x, z, biome)
}
}
}
override fun clearParcel(parcel: ParcelId): Worker = worktimeLimiter.submit {
val bottom = getBottomBlock(parcel)
val region = Region(Vec3i(bottom.x, 0, bottom.z), Vec3i(o.parcelSize, maxHeight + 1, o.parcelSize))
val blocks = RegionTraversal.DOWNWARD.regionTraverser(region)
val blockCount = region.blockCount.toDouble()
val world = world
val floorHeight = o.floorHeight
val airType = airType
val floorType = o.floorType
val fillType = o.fillType
for ((index, vec) in blocks.withIndex()) {
markSuspensionPoint()
val y = vec.y
val blockType = when {
y > floorHeight -> airType
y == floorHeight -> floorType
else -> fillType
}
world[vec].blockData = blockType
setProgress((index + 1) / blockCount)
}
}
override fun doBlockOperation(parcel: ParcelId, direction: RegionTraversal, operation: (Block) -> Unit): Worker = worktimeLimiter.submit {
val bottom = getBottomBlock(parcel)
val region = Region(Vec3i(bottom.x, 0, bottom.z), Vec3i(o.parcelSize, maxHeight + 1, o.parcelSize))
val blocks = direction.regionTraverser(region)
val blockCount = region.blockCount.toDouble()
val world = world
for ((index, vec) in blocks.withIndex()) {
markSuspensionPoint()
operation(world[vec])
setProgress((index + 1) / blockCount)
}
}
}
}

View File

@@ -1,16 +1,10 @@
@file:Suppress("UNCHECKED_CAST") @file:Suppress("UNCHECKED_CAST")
package io.dico.parcels2 package io.dico.parcels2.defaultimpl
import java.util.* import io.dico.parcels2.*
import java.util.Collections
interface GlobalAddedData : AddedData { import java.util.UUID
val owner: ParcelOwner
}
interface GlobalAddedDataManager {
operator fun get(owner: ParcelOwner): GlobalAddedData
}
class GlobalAddedDataManagerImpl(val plugin: ParcelsPlugin) : GlobalAddedDataManager { class GlobalAddedDataManagerImpl(val plugin: ParcelsPlugin) : GlobalAddedDataManager {
private val map = mutableMapOf<ParcelOwner, GlobalAddedData>() private val map = mutableMapOf<ParcelOwner, GlobalAddedData>()
@@ -20,7 +14,7 @@ class GlobalAddedDataManagerImpl(val plugin: ParcelsPlugin) : GlobalAddedDataMan
} }
private inner class GlobalAddedDataImpl(override val owner: ParcelOwner, private inner class GlobalAddedDataImpl(override val owner: ParcelOwner,
data: MutableMap<UUID, AddedStatus> = emptyData) data: MutableAddedDataMap = emptyData)
: AddedDataHolder(data), GlobalAddedData { : AddedDataHolder(data), GlobalAddedData {
private inline var data get() = addedMap; set(value) = run { addedMap = value } private inline var data get() = addedMap; set(value) = run { addedMap = value }
@@ -39,10 +33,7 @@ class GlobalAddedDataManagerImpl(val plugin: ParcelsPlugin) : GlobalAddedDataMan
} }
private companion object { private companion object {
val emptyData = Collections.emptyMap<UUID, AddedStatus>() as MutableMap<UUID, AddedStatus> val emptyData = Collections.emptyMap<Any, Any>() as MutableAddedDataMap
} }
} }

View File

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

View File

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

View File

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

View File

@@ -8,14 +8,7 @@ interface HasPlugin {
val plugin: ParcelsPlugin 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 ->
} }

View File

@@ -1,12 +1,12 @@
package io.dico.parcels2.listener package io.dico.parcels2.listener
import io.dico.parcels2.Parcel 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.editLoop
import io.dico.parcels2.util.isPresentAnd import io.dico.parcels2.util.isPresentAnd
import org.bukkit.entity.Entity import org.bukkit.entity.Entity
class ParcelEntityTracker(val worlds: Worlds) { class ParcelEntityTracker(val parcelProvider: ParcelProvider) {
val map = mutableMapOf<Entity, Parcel?>() val map = mutableMapOf<Entity, Parcel?>()
fun untrack(entity: Entity) { fun untrack(entity: Entity) {
@@ -32,7 +32,7 @@ class ParcelEntityTracker(val worlds: Worlds) {
if (parcel.isPresentAnd { hasBlockVisitors }) { if (parcel.isPresentAnd { hasBlockVisitors }) {
remove() remove()
} }
val newParcel = worlds.getParcelAt(entity.location) val newParcel = parcelProvider.getParcelAt(entity.location)
if (newParcel !== parcel && !newParcel.isPresentAnd { hasBlockVisitors }) { if (newParcel !== parcel && !newParcel.isPresentAnd { hasBlockVisitors }) {
remove() remove()
entity.remove() entity.remove()

View File

@@ -4,8 +4,8 @@ import gnu.trove.TLongCollection
import io.dico.dicore.ListenerMarker import io.dico.dicore.ListenerMarker
import io.dico.dicore.RegistratorListener import io.dico.dicore.RegistratorListener
import io.dico.parcels2.Parcel import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelProvider
import io.dico.parcels2.ParcelWorld import io.dico.parcels2.ParcelWorld
import io.dico.parcels2.Worlds
import io.dico.parcels2.util.* import io.dico.parcels2.util.*
import org.bukkit.Material.* import org.bukkit.Material.*
import org.bukkit.World import org.bukkit.World
@@ -26,11 +26,10 @@ import org.bukkit.event.player.*
import org.bukkit.event.vehicle.VehicleMoveEvent import org.bukkit.event.vehicle.VehicleMoveEvent
import org.bukkit.event.weather.WeatherChangeEvent import org.bukkit.event.weather.WeatherChangeEvent
import org.bukkit.event.world.StructureGrowEvent import org.bukkit.event.world.StructureGrowEvent
import org.bukkit.event.world.WorldLoadEvent
import org.bukkit.inventory.InventoryHolder import org.bukkit.inventory.InventoryHolder
@Suppress("NOTHING_TO_INLINE") @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 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 * returns null if not in a registered parcel world
*/ */
private fun getWoAndPPa(block: Block): Pair<ParcelWorld, Parcel?>? { private fun getWoAndPPa(block: Block): Pair<ParcelWorld, Parcel?>? {
val world = worlds.getWorld(block.world) ?: return null val world = parcelProvider.getWorld(block.world) ?: return null
return world to world.parcelAt(block) 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 onPlayerMoveEvent = RegistratorListener<PlayerMoveEvent> l@{ event ->
val user = event.player val user = event.player
if (user.hasBanBypass) return@l 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)) { if (parcel.isBanned(user.uuid)) {
worlds.getParcelAt(event.from)?.also { parcelProvider.getParcelAt(event.from)?.also {
user.teleport(it.homeLocation) user.teleport(it.world.getHomeLocation(it.id))
user.sendParcelMessage(nopermit = true, message = "You are banned from this parcel") user.sendParcelMessage(nopermit = true, message = "You are banned from this parcel")
} ?: run { event.to = event.from } } ?: 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()) } private inline fun TLongCollection.troveForEach(block: (Long) -> Unit) = iterator().let { while (it.hasNext()) block(it.next()) }
//@formatter:on //@formatter:on
private fun checkPistonMovement(event: BlockPistonEvent, blocks: List<Block>) { 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 direction = event.direction
val columns = gnu.trove.set.hash.TLongHashSet(blocks.size * 2) val columns = gnu.trove.set.hash.TLongHashSet(blocks.size * 2)
@@ -123,7 +122,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
} }
columns.troveForEach { columns.troveForEach {
val ppa = world.parcelAt(it.columnX, it.columnZ) val ppa = world.getParcelAt(it.columnX, it.columnZ)
if (ppa.isNullOr { hasBlockVisitors }) { if (ppa.isNullOr { hasBlockVisitors }) {
event.isCancelled = true event.isCancelled = true
return return
@@ -150,8 +149,8 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
@field:ListenerMarker(priority = NORMAL) @field:ListenerMarker(priority = NORMAL)
val onEntityExplodeEvent = RegistratorListener<EntityExplodeEvent> l@{ event -> val onEntityExplodeEvent = RegistratorListener<EntityExplodeEvent> l@{ event ->
entityTracker.untrack(event.entity) 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.disableExplosions || world.parcelAt(event.entity).isPresentAnd { hasBlockVisitors }) { if (world.options.disableExplosions || world.getParcelAt(event.entity).isPresentAnd { hasBlockVisitors }) {
event.isCancelled = true event.isCancelled = true
} }
} }
@@ -175,9 +174,9 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
@field:ListenerMarker(priority = NORMAL) @field:ListenerMarker(priority = NORMAL)
val onPlayerInteractEvent = RegistratorListener<PlayerInteractEvent> l@{ event -> val onPlayerInteractEvent = RegistratorListener<PlayerInteractEvent> l@{ event ->
val user = event.player 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 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) }) { if (!user.hasBuildAnywhere && parcel.isPresentAnd { isBanned(user.uuid) }) {
user.sendParcelMessage(nopermit = true, message = "You cannot interact with parcels you're banned from") 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) @field:ListenerMarker(priority = NORMAL)
val onEntityCreatePortalEvent = RegistratorListener<EntityCreatePortalEvent> l@{ event -> 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 if (world.options.blockPortalCreation) event.isCancelled = true
} }
@@ -341,7 +340,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
*/ */
@field:ListenerMarker(priority = NORMAL) @field:ListenerMarker(priority = NORMAL)
val onWeatherChangeEvent = RegistratorListener<WeatherChangeEvent> l@{ event -> 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()) { if (world.options.noWeather && event.toWeatherState()) {
event.isCancelled = true event.isCancelled = true
} }
@@ -353,29 +352,6 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
world.weatherDuration = Int.MAX_VALUE 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 // TODO: BlockFormEvent, BlockSpreadEvent, BlockFadeEvent
/* /*
@@ -396,10 +372,10 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
val cancel: Boolean = when (block.type) { 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) ICE -> player != null && !ppa.canBuild(player)
// prevent snow generation from weather // prevent snow generation from weather
SNOW -> !hasEntity && wo.options.preventWeatherBlockChanges SNOW -> !hasEntity && wo.options.preventWeatherBlockChanges
else -> false else -> false
@@ -415,10 +391,10 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
*/ */
@field:ListenerMarker(priority = NORMAL) @field:ListenerMarker(priority = NORMAL)
val onEntitySpawnEvent = RegistratorListener<EntitySpawnEvent> l@{ event -> 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) { if (event.entity is Creature && world.options.blockMobSpawning) {
event.isCancelled = true event.isCancelled = true
} else if (world.parcelAt(event.entity).isPresentAnd { hasBlockVisitors }) { } else if (world.getParcelAt(event.entity).isPresentAnd { hasBlockVisitors }) {
event.isCancelled = true event.isCancelled = true
} }
} }
@@ -448,7 +424,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
*/ */
@field:ListenerMarker(priority = NORMAL) @field:ListenerMarker(priority = NORMAL)
val onEntityDamageByEntityEvent = RegistratorListener<EntityDamageByEntityEvent> l@{ event -> 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) { if (world.options.disableExplosions && event.damager is ExplosiveMinecart || event.damager is Creeper) {
event.isCancelled = true; return@l 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 } ?: (event.damager as? Projectile)?.let { it.shooter as? Player }
?: return@l ?: return@l
if (!world.parcelAt(event.entity).canBuildN(user)) { if (!world.getParcelAt(event.entity).canBuildN(user)) {
event.isCancelled = true event.isCancelled = true
} }
} }
@field:ListenerMarker(priority = NORMAL) @field:ListenerMarker(priority = NORMAL)
val onHangingBreakEvent = RegistratorListener<HangingBreakEvent> l@{ event -> 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) { if (event.cause == HangingBreakEvent.RemoveCause.EXPLOSION && world.options.disableExplosions) {
event.isCancelled = true; return@l event.isCancelled = true; return@l
} }
if (world.parcelAt(event.entity).isPresentAnd { hasBlockVisitors }) { if (world.getParcelAt(event.entity).isPresentAnd { hasBlockVisitors }) {
event.isCancelled = true event.isCancelled = true
} }
} }
@@ -480,9 +456,9 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
*/ */
@field:ListenerMarker(priority = NORMAL) @field:ListenerMarker(priority = NORMAL)
val onHangingBreakByEntityEvent = RegistratorListener<HangingBreakByEntityEvent> l@{ event -> 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 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 event.isCancelled = true
} }
} }
@@ -492,9 +468,9 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
*/ */
@field:ListenerMarker(priority = NORMAL) @field:ListenerMarker(priority = NORMAL)
val onHangingPlaceEvent = RegistratorListener<HangingPlaceEvent> l@{ event -> 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) val block = event.block.getRelative(event.blockFace)
if (!world.parcelAt(block).canBuildN(event.player)) { if (!world.getParcelAt(block).canBuildN(event.player)) {
event.isCancelled = true event.isCancelled = true
} }
} }
@@ -513,7 +489,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
event.isCancelled = true; return@l 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 onBlockDispenseEvent = RegistratorListener<BlockDispenseEvent> l@{ event ->
val block = event.block val block = event.block
if (!block.type.let { it == DISPENSER || it == DROPPER }) return@l 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 data = block.blockData as Directional
val targetBlock = block.getRelative(data.facing) val targetBlock = block.getRelative(data.facing)
if (world.parcelAt(targetBlock) == null) { if (world.getParcelAt(targetBlock) == null) {
event.isCancelled = true event.isCancelled = true
} }
} }
@@ -547,7 +523,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
@field:ListenerMarker(priority = NORMAL) @field:ListenerMarker(priority = NORMAL)
val onEntityTeleportEvent = RegistratorListener<EntityTeleportEvent> l@{ event -> val onEntityTeleportEvent = RegistratorListener<EntityTeleportEvent> l@{ event ->
val (wo, ppa) = getWoAndPPa(event.from.block) ?: return@l val (wo, ppa) = getWoAndPPa(event.from.block) ?: return@l
if (ppa !== wo.parcelAt(event.to)) { if (ppa !== wo.getParcelAt(event.to)) {
event.isCancelled = true event.isCancelled = true
} }
} }
@@ -572,7 +548,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
@field:ListenerMarker(priority = NORMAL) @field:ListenerMarker(priority = NORMAL)
val onEntityDeathEvent = RegistratorListener<EntityDeathEvent> l@{ event -> val onEntityDeathEvent = RegistratorListener<EntityDeathEvent> l@{ event ->
entityTracker.untrack(event.entity) 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) { if (!world.options.dropEntityItems) {
event.drops.clear() event.drops.clear()
event.droppedExp = 0 event.droppedExp = 0
@@ -584,7 +560,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
*/ */
@field:ListenerMarker(priority = NORMAL) @field:ListenerMarker(priority = NORMAL)
val onPlayerChangedWorldEvent = RegistratorListener<PlayerChangedWorldEvent> l@{ event -> 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) { if (world.options.gameMode != null && !event.player.hasGamemodeBypass) {
event.player.gameMode = world.options.gameMode event.player.gameMode = world.options.gameMode
} }

View File

@@ -1,11 +1,8 @@
package io.dico.parcels2.storage package io.dico.parcels2.storage
import io.dico.parcels2.AddedStatus import io.dico.parcels2.*
import io.dico.parcels2.Parcel import kotlinx.coroutines.experimental.channels.SendChannel
import io.dico.parcels2.ParcelData import java.util.UUID
import io.dico.parcels2.ParcelOwner
import kotlinx.coroutines.experimental.channels.ProducerScope
import java.util.*
interface Backing { interface Backing {
@@ -22,31 +19,31 @@ interface Backing {
* This producer function is capable of constantly reading parcels from a potentially infinite sequence, * 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. * 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 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) suspend fun setGlobalPlayerStatus(owner: ParcelOwner, player: UUID, status: AddedStatus)

View File

@@ -8,7 +8,7 @@ import com.fasterxml.jackson.databind.ser.BeanSerializerModifier
import com.fasterxml.jackson.databind.ser.std.StdSerializer import com.fasterxml.jackson.databind.ser.std.StdSerializer
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.KotlinModule 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.GeneratorOptions
import io.dico.parcels2.StorageOptions import io.dico.parcels2.StorageOptions
import org.bukkit.Bukkit import org.bukkit.Bukkit
@@ -100,7 +100,7 @@ class GeneratorOptionsDeserializer : JsonDeserializer<GeneratorOptions>() {
val node = parser!!.readValueAsTree<JsonNode>() val node = parser!!.readValueAsTree<JsonNode>()
val name = node.get("name").asText() val name = node.get("name").asText()
val optionsNode = node.get("options") 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) return parser.codec.treeToValue(optionsNode, factory.optionsClass.java)
} }

View File

@@ -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)
}

View File

@@ -1,16 +1,19 @@
@file:Suppress("NOTHING_TO_INLINE")
package io.dico.parcels2.storage package io.dico.parcels2.storage
import io.dico.parcels2.AddedStatus import io.dico.parcels2.*
import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelData
import io.dico.parcels2.ParcelOwner
import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.*
import kotlinx.coroutines.experimental.channels.ProducerScope
import kotlinx.coroutines.experimental.channels.ReceiveChannel import kotlinx.coroutines.experimental.channels.ReceiveChannel
import kotlinx.coroutines.experimental.channels.produce import kotlinx.coroutines.experimental.channels.produce
import java.util.* import java.util.UUID
import java.util.concurrent.Executor import java.util.concurrent.Executor
import java.util.concurrent.Executors import java.util.concurrent.Executors
typealias DataPair = Pair<ParcelId, ParcelData?>
typealias AddedDataPair<TAttach> = Pair<TAttach, MutableAddedDataMap>
interface Storage { interface Storage {
val name: String val name: String
val syncDispatcher: CoroutineDispatcher val syncDispatcher: CoroutineDispatcher
@@ -22,31 +25,31 @@ interface Storage {
fun shutdown(): Job 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 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 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 override val isConnected get() = backing.isConnected
val channelCapacity = 16 val channelCapacity = 16
@Suppress("NOTHING_TO_INLINE")
private inline fun <T> defer(noinline block: suspend CoroutineScope.() -> T): Deferred<T> { private inline fun <T> defer(noinline block: suspend CoroutineScope.() -> T): Deferred<T> {
return async(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block) return async(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block)
} }
@Suppress("NOTHING_TO_INLINE")
private inline fun job(noinline block: suspend CoroutineScope.() -> Unit): Job { private inline fun job(noinline block: suspend CoroutineScope.() -> Unit): Job {
return launch(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block) 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 init() = job { backing.init() }
override fun shutdown() = job { backing.shutdown() } 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>) = override fun readParcelData(parcels: Sequence<ParcelId>) = openChannel<DataPair> { backing.produceParcelData(channel, parcels) }
produce(asyncDispatcher, capacity = channelCapacity) { with(backing) { produceParcelData(parcelsFor) } }
override fun readAllParcelData(): ReceiveChannel<Pair<SerializableParcel, ParcelData?>> = override fun readAllParcelData() = openChannel<DataPair> { backing.produceAllParcelData(channel) }
produce(asyncDispatcher, capacity = channelCapacity) { with(backing) { produceAllParcelData() } }
override fun getOwnedParcels(user: ParcelOwner) = defer { backing.getOwnedParcels(user) } override fun getOwnedParcels(user: ParcelOwner) = defer { backing.getOwnedParcels(user) }
override fun getNumParcels(user: ParcelOwner) = defer { backing.getNumParcels(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>>> = override fun readAllGlobalAddedData(): ReceiveChannel<AddedDataPair<ParcelOwner>> = openChannel { backing.produceAllGlobalAddedData(channel) }
produce(asyncDispatcher, capacity = channelCapacity) { with(backing) { produceAllGlobalAddedData() } }
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) } override fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) = job { backing.setGlobalPlayerStatus(owner, player, status) }
} }

View File

@@ -40,4 +40,4 @@ class ConnectionStorageFactory : StorageFactory {
return StorageWithCoroutineBacking(ExposedBacking(dataSourceFactory)) return StorageWithCoroutineBacking(ExposedBacking(dataSourceFactory))
} }
} }

View File

@@ -5,18 +5,18 @@ package io.dico.parcels2.storage.exposed
import com.zaxxer.hikari.HikariDataSource import com.zaxxer.hikari.HikariDataSource
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.storage.Backing 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 io.dico.parcels2.util.toUUID
import kotlinx.coroutines.experimental.CoroutineStart import kotlinx.coroutines.experimental.CoroutineStart
import kotlinx.coroutines.experimental.Unconfined import kotlinx.coroutines.experimental.Unconfined
import kotlinx.coroutines.experimental.channels.ProducerScope import kotlinx.coroutines.experimental.channels.SendChannel
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.launch
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SchemaUtils.create import org.jetbrains.exposed.sql.SchemaUtils.create
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.vendors.DatabaseDialect import org.jetbrains.exposed.sql.vendors.DatabaseDialect
import org.joda.time.DateTime import org.joda.time.DateTime
import java.util.* import java.util.UUID
import javax.sql.DataSource import javax.sql.DataSource
class ExposedDatabaseException(message: String? = null) : Exception(message) class ExposedDatabaseException(message: String? = null) : Exception(message)
@@ -63,7 +63,7 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) :
isShutdown = true 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) { for (parcel in parcels) {
val data = readParcelData(parcel) val data = readParcelData(parcel)
channel.send(parcel to data) channel.send(parcel to data)
@@ -71,32 +71,32 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) :
channel.close() 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 -> ParcelsT.selectAll().forEach { row ->
val parcel = ParcelsT.getSerializable(row) ?: return@forEach val parcel = ParcelsT.getId(row) ?: return@forEach
val data = rowToParcelData(row) val data = rowToParcelData(row)
channel.send(parcel to data) channel.send(parcel to data)
} }
channel.close() channel.close()
} }
override suspend fun readParcelData(parcelFor: Parcel): ParcelData? = transaction { override suspend fun readParcelData(parcel: ParcelId): ParcelData? = transaction {
val row = ParcelsT.getRow(parcelFor) ?: return@transaction null val row = ParcelsT.getRow(parcel) ?: return@transaction null
rowToParcelData(row) 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() val user_id = OwnersT.getId(user) ?: return@transaction emptyList()
ParcelsT.select { ParcelsT.owner_id eq user_id } ParcelsT.select { ParcelsT.owner_id eq user_id }
.orderBy(ParcelsT.claim_time, isAsc = true) .orderBy(ParcelsT.claim_time, isAsc = true)
.mapNotNull(ParcelsT::getSerializable) .mapNotNull(ParcelsT::getId)
.toList() .toList()
} }
override suspend fun setParcelData(parcelFor: Parcel, data: ParcelData?) { override suspend fun setParcelData(parcel: ParcelId, data: ParcelData?) {
if (data == null) { if (data == null) {
transaction { transaction {
ParcelsT.getId(parcelFor)?.let { id -> ParcelsT.getId(parcel)?.let { id ->
ParcelsT.deleteIgnoreWhere { ParcelsT.id eq id } ParcelsT.deleteIgnoreWhere { ParcelsT.id eq id }
// Below should cascade automatically // Below should cascade automatically
@@ -111,25 +111,25 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) :
} }
transaction { transaction {
val id = ParcelsT.getOrInitId(parcelFor) val id = ParcelsT.getOrInitId(parcel)
AddedLocalT.deleteIgnoreWhere { AddedLocalT.attach_id eq id } AddedLocalT.deleteIgnoreWhere { AddedLocalT.attach_id eq id }
} }
setParcelOwner(parcelFor, data.owner) setParcelOwner(parcel, data.owner)
for ((uuid, status) in data.addedMap) { for ((uuid, status) in data.addedMap) {
setLocalPlayerStatus(parcelFor, uuid, status) setLocalPlayerStatus(parcel, uuid, status)
} }
setParcelAllowsInteractInputs(parcelFor, data.allowInteractInputs) setParcelAllowsInteractInputs(parcel, data.allowInteractInputs)
setParcelAllowsInteractInventory(parcelFor, data.allowInteractInventory) 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) val id = if (owner == null)
ParcelsT.getId(parcelFor) ?: return@transaction ParcelsT.getId(parcel) ?: return@transaction
else else
ParcelsT.getOrInitId(parcelFor) ParcelsT.getOrInitId(parcel)
val owner_id = owner?.let { OwnersT.getOrInitId(it) } val owner_id = owner?.let { OwnersT.getOrInitId(it) }
val time = owner?.let { DateTime.now() } 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 { override suspend fun setLocalPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus) = transaction {
AddedLocalT.setPlayerStatus(parcelFor, player, status) 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) val id = ParcelsT.getOrInitId(parcel)
ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
it[ParcelOptionsT.parcel_id] = 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) val id = ParcelsT.getOrInitId(parcel)
ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
it[ParcelOptionsT.parcel_id] = 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) AddedGlobalT.sendAllAddedData(channel)
channel.close() channel.close()
} }
@@ -174,7 +174,7 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) :
} }
private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply { 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] since = row[ParcelsT.claim_time]
val parcelId = row[ParcelsT.id] val parcelId = row[ParcelsT.id]

View File

@@ -40,7 +40,7 @@ class UpsertStatement<Key : Any>(table: Table, conflictColumn: Column<*>? = null
} else { } 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)})" } values.keys.filter { it !in indexColumns }.joinTo(this) { "${transaction.identity(it)}=VALUES(${transaction.identity(it)})" }
} }

View File

@@ -2,20 +2,16 @@
package io.dico.parcels2.storage.exposed 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.ParcelOwner
import io.dico.parcels2.ParcelWorld import io.dico.parcels2.ParcelWorldId
import io.dico.parcels2.storage.SerializableParcel
import io.dico.parcels2.storage.SerializableWorld
import io.dico.parcels2.util.Vec2i
import io.dico.parcels2.util.toByteArray import io.dico.parcels2.util.toByteArray
import io.dico.parcels2.util.toUUID import io.dico.parcels2.util.toUUID
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.InsertStatement import org.jetbrains.exposed.sql.statements.InsertStatement
import java.util.* import java.util.UUID
sealed class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj, SerializableObj>, sealed class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj>, QueryObj>(tableName: String, columnName: String)
QueryObj, SerializableObj>(tableName: String, columnName: String)
: Table(tableName) { : Table(tableName) {
val id = integer(columnName).autoIncrement().primaryKey() 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 getId(obj: QueryObj): Int?
abstract fun getOrInitId(obj: QueryObj): Int abstract fun getOrInitId(obj: QueryObj): Int
fun getSerializable(id: Int): SerializableObj? = select { this@IdTransactionsTable.id eq id }.firstOrNull()?.let { getSerializable(it) } fun getId(id: Int): QueryObj? = select { this@IdTransactionsTable.id eq id }.firstOrNull()?.let { getId(it) }
abstract fun getSerializable(row: ResultRow): SerializableObj? 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 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) val index_uid = uniqueIndexR("index_uid", uid)
internal inline fun getId(binaryUid: ByteArray): Int? = getId { uid eq binaryUid } 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(uid: UUID): Int? = getId(uid.toByteArray()) internal inline fun getId(worldName: String, uid: UUID?): Int? = getId(worldName, uid?.toByteArray())
internal inline fun getOrInitId(worldUid: UUID, worldName: String): Int = worldUid.toByteArray().let { binaryUid -> internal inline fun getOrInitId(worldName: String, worldUid: UUID?): Int = worldUid?.toByteArray().let { binaryUid ->
getId(binaryUid) getId(worldName, binaryUid)
?: insertAndGetId("world named $worldName") { it[uid] = binaryUid; it[name] = worldName } ?: 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 getId(world: ParcelWorldId): Int? = getId(world.name, world.uid)
override fun getOrInitId(world: ParcelWorld): Int = world.world.let { getOrInitId(it.uid, it.name) } override fun getOrInitId(world: ParcelWorldId): Int = getOrInitId(world.name, world.uid)
override fun getSerializable(row: ResultRow): SerializableWorld { override fun getId(row: ResultRow): ParcelWorldId {
return SerializableWorld(row[name], row[uid].toUUID()) 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 world_id = integer("world_id").references(WorldsT.id)
val px = integer("px") val px = integer("px")
val pz = integer("pz") 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) 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(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 getId(worldName: String, worldUid: UUID?, parcelX: Int, parcelZ: Int): Int? = WorldsT.getId(worldName, worldUid)?.let { getId(it, parcelX, parcelZ) }
private inline fun getOrInitId(worldUid: UUID, worldName: String, parcelX: Int, parcelZ: Int): Int { private inline fun getOrInitId(worldName: String, worldUid: UUID?, parcelX: Int, parcelZ: Int): Int {
val worldId = WorldsT.getOrInitId(worldUid, worldName) val worldId = WorldsT.getOrInitId(worldName, worldUid)
return getId(worldId, parcelX, parcelZ) return getId(worldId, parcelX, parcelZ)
?: insertAndGetId("parcel at $worldName($parcelX, $parcelZ)") { it[world_id] = worldId; it[px] = parcelX; it[pz] = 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 getId(parcel: ParcelId): Int? = getId(parcel.worldId.name, parcel.worldId.uid, parcel.x, parcel.z)
override fun getOrInitId(parcel: Parcel): Int = parcel.world.world.let { getOrInitId(it.uid, it.name, parcel.pos.x, parcel.pos.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() 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 worldId = row[world_id]
val world = WorldsT.getSerializable(worldId) ?: return null val world = WorldsT.getId(worldId) ?: return null
return SerializableParcel(world, Vec2i(row[px], row[pz])) 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 uuid = binary("uuid", 16).nullable()
val name = varchar("name", 32) val name = varchar("name", 32)
val index_pair = uniqueIndexR("index_pair", uuid, name) 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) if (owner.hasUUID) getOrInitId(owner.uuid!!, owner.notNullName)
else getOrInitId(owner.name!!) 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]) return row[uuid]?.toUUID()?.let { ParcelOwner(it) } ?: ParcelOwner(row[name])
} }
} }

View File

@@ -3,17 +3,16 @@
package io.dico.parcels2.storage.exposed package io.dico.parcels2.storage.exposed
import io.dico.parcels2.AddedStatus import io.dico.parcels2.AddedStatus
import io.dico.parcels2.Parcel import io.dico.parcels2.ParcelId
import io.dico.parcels2.ParcelOwner import io.dico.parcels2.ParcelOwner
import io.dico.parcels2.storage.SerializableParcel
import io.dico.parcels2.util.toByteArray import io.dico.parcels2.util.toByteArray
import io.dico.parcels2.util.toUUID import io.dico.parcels2.util.toUUID
import kotlinx.coroutines.experimental.channels.SendChannel import kotlinx.coroutines.experimental.channels.SendChannel
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import java.util.* import java.util.UUID
object AddedLocalT : AddedTable<Parcel, SerializableParcel>("parcels_added_local", ParcelsT) object AddedLocalT : AddedTable<ParcelId>("parcels_added_local", ParcelsT)
object AddedGlobalT : AddedTable<ParcelOwner, ParcelOwner>("parcels_added_global", OwnersT) object AddedGlobalT : AddedTable<ParcelOwner>("parcels_added_global", OwnersT)
object ParcelOptionsT : Table("parcel_options") { object ParcelOptionsT : Table("parcel_options") {
val parcel_id = integer("parcel_id").primaryKey().references(ParcelsT.id, ReferenceOption.CASCADE) 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>>> 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 attach_id = integer("attach_id").references(idTable.id, ReferenceOption.CASCADE)
val player_uuid = binary("player_uuid", 16) val player_uuid = binary("player_uuid", 16)
val allowed_flag = bool("allowed_flag") 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() }) .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() 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 var map: MutableMap<UUID, AddedStatus>? = null
fun initAttachAndMap() { fun initAttachAndMap() {
attach = idTable.getSerializable(id) attach = idTable.getId(id)
map = attach?.let { mutableMapOf() } map = attach?.let { mutableMapOf() }
} }

View File

@@ -0,0 +1,8 @@
package io.dico.parcels2.storage.migration
import io.dico.parcels2.storage.Storage
interface Migration {
fun migrateTo(storage: Storage)
}

View File

@@ -0,0 +1,5 @@
package io.dico.parcels2.storage.migration
interface MigrationFactory {
fun getMigration()
}

View File

@@ -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)
}
}
}

View File

@@ -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())

View File

@@ -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()
}
}

View File

@@ -10,62 +10,67 @@ wood:
OAK_$, BIRCH_$, SPRUCE_$, JUNGLE_$, ACACIA_$, DARK_OAK_$, OAK_$, BIRCH_$, SPRUCE_$, JUNGLE_$, ACACIA_$, DARK_OAK_$,
*/ */
val Material.isBed get() = when(this) { val Material.isBed
WHITE_BED, get() = when (this) {
ORANGE_BED, WHITE_BED,
MAGENTA_BED, ORANGE_BED,
LIGHT_BLUE_BED, MAGENTA_BED,
YELLOW_BED, LIGHT_BLUE_BED,
LIME_BED, YELLOW_BED,
PINK_BED, LIME_BED,
GRAY_BED, PINK_BED,
LIGHT_GRAY_BED, GRAY_BED,
CYAN_BED, LIGHT_GRAY_BED,
PURPLE_BED, CYAN_BED,
BLUE_BED, PURPLE_BED,
BROWN_BED, BLUE_BED,
GREEN_BED, BROWN_BED,
RED_BED, GREEN_BED,
BLACK_BED -> true RED_BED,
else -> false BLACK_BED -> true
} else -> false
}
val Material.isWoodDoor get() = when(this) { val Material.isWoodDoor
OAK_DOOR, get() = when (this) {
BIRCH_DOOR, OAK_DOOR,
SPRUCE_DOOR, BIRCH_DOOR,
JUNGLE_DOOR, SPRUCE_DOOR,
ACACIA_DOOR, JUNGLE_DOOR,
DARK_OAK_DOOR -> true ACACIA_DOOR,
else -> false DARK_OAK_DOOR -> true
} else -> false
}
val Material.isWoodTrapdoor get() = when(this) { val Material.isWoodTrapdoor
OAK_TRAPDOOR, get() = when (this) {
BIRCH_TRAPDOOR, OAK_TRAPDOOR,
SPRUCE_TRAPDOOR, BIRCH_TRAPDOOR,
JUNGLE_TRAPDOOR, SPRUCE_TRAPDOOR,
ACACIA_TRAPDOOR, JUNGLE_TRAPDOOR,
DARK_OAK_TRAPDOOR -> true ACACIA_TRAPDOOR,
else -> false DARK_OAK_TRAPDOOR -> true
} else -> false
}
val Material.isWoodFenceGate get() = when(this) { val Material.isWoodFenceGate
OAK_FENCE_GATE, get() = when (this) {
BIRCH_FENCE_GATE, OAK_FENCE_GATE,
SPRUCE_FENCE_GATE, BIRCH_FENCE_GATE,
JUNGLE_FENCE_GATE, SPRUCE_FENCE_GATE,
ACACIA_FENCE_GATE, JUNGLE_FENCE_GATE,
DARK_OAK_FENCE_GATE -> true ACACIA_FENCE_GATE,
else -> false DARK_OAK_FENCE_GATE -> true
} else -> false
}
val Material.isWoodButton get() = when(this) { val Material.isWoodButton
OAK_BUTTON, get() = when (this) {
BIRCH_BUTTON, OAK_BUTTON,
SPRUCE_BUTTON, BIRCH_BUTTON,
JUNGLE_BUTTON, SPRUCE_BUTTON,
ACACIA_BUTTON, JUNGLE_BUTTON,
DARK_OAK_BUTTON -> true ACACIA_BUTTON,
else -> false DARK_OAK_BUTTON -> true
} else -> false
}

View File

@@ -9,7 +9,8 @@ import org.bukkit.plugin.java.JavaPlugin
inline val OfflinePlayer.uuid get() = uniqueId inline val OfflinePlayer.uuid get() = uniqueId
@Suppress("UsePropertyAccessSyntax") @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.hasBanBypass get() = hasPermission("parcels.admin.bypass.ban")
inline val Player.hasGamemodeBypass get() = hasPermission("parcels.admin.bypass.gamemode") inline val Player.hasGamemodeBypass get() = hasPermission("parcels.admin.bypass.gamemode")

View File

@@ -1,9 +1,8 @@
package io.dico.parcels2.util package io.dico.parcels2.util
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.jetbrains.annotations.Contract
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.util.* import java.util.UUID
@Suppress("UsePropertyAccessSyntax") @Suppress("UsePropertyAccessSyntax")
fun getPlayerNameOrDefault(uuid: UUID?, ifUnknown: String? = null): String { fun getPlayerNameOrDefault(uuid: UUID?, ifUnknown: String? = null): String {