Archived
0

Merge branch 'dev'

This commit is contained in:
Dico
2018-08-02 19:10:54 +01:00
54 changed files with 2336 additions and 1482 deletions

View File

@@ -10,7 +10,7 @@ import java.net.URL
val stdout = PrintWriter("gradle-output.txt")
group = "io.dico"
version = "0.1"
version = "0.2"
plugins {
java
@@ -51,7 +51,7 @@ project(":dicore3:dicore3-command") {
dependencies {
c.kotlinStd(kotlin("stdlib-jdk8"))
c.kotlinStd(kotlin("reflect"))
c.kotlinStd(kotlinx("coroutines-core:0.23.4"))
c.kotlinStd(kotlinx("coroutines-core:0.24.0"))
compile(project(":dicore3:dicore3-core"))
compile("com.thoughtworks.paranamer:paranamer:2.8")
@@ -72,6 +72,7 @@ dependencies {
compile("org.jetbrains.exposed:exposed:0.10.3") { isTransitive = false }
compile("joda-time:joda-time:2.10")
compile("com.zaxxer:HikariCP:3.2.0")
compile("ch.qos.logback:logback-classic:1.2.3")
val jacksonVersion = "2.9.6"
compile("com.fasterxml.jackson.core:jackson-core:$jacksonVersion")
@@ -86,6 +87,8 @@ tasks {
val compileKotlin by getting(KotlinCompile::class) {
kotlinOptions {
javaParameters = true
suppressWarnings = true
//freeCompilerArgs = listOf("-XXLanguage:+InlineClasses", "-Xuse-experimental=kotlin.Experimental")
}
}

View File

@@ -36,7 +36,7 @@ public class AbstractChatController implements IChatController {
@Override
public void sendMessage(CommandSender sender, EMessageType type, String message) {
if (message != null && !message.isEmpty()) {
sender.sendMessage(getMessagePrefixForType(type) + getChatFormatForType(type) + message);
sender.sendMessage(filterMessage(getMessagePrefixForType(type) + getChatFormatForType(type) + message));
}
}

View File

@@ -0,0 +1,57 @@
package io.dico.parcels2
import io.dico.parcels2.util.uuid
import org.bukkit.OfflinePlayer
import java.util.UUID
typealias MutableAddedDataMap = MutableMap<UUID, AddedStatus>
typealias AddedDataMap = Map<UUID, AddedStatus>
interface AddedData {
val addedMap: AddedDataMap
fun getAddedStatus(uuid: UUID): AddedStatus
fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean
fun compareAndSetAddedStatus(uuid: UUID, expect: AddedStatus, status: AddedStatus): Boolean =
(getAddedStatus(uuid) == expect).also { if (it) setAddedStatus(uuid, status) }
fun isAllowed(uuid: UUID) = getAddedStatus(uuid) == AddedStatus.ALLOWED
fun allow(uuid: UUID) = setAddedStatus(uuid, AddedStatus.ALLOWED)
fun disallow(uuid: UUID) = compareAndSetAddedStatus(uuid, AddedStatus.ALLOWED, AddedStatus.DEFAULT)
fun isBanned(uuid: UUID) = getAddedStatus(uuid) == AddedStatus.BANNED
fun ban(uuid: UUID) = setAddedStatus(uuid, AddedStatus.BANNED)
fun unban(uuid: UUID) = compareAndSetAddedStatus(uuid, AddedStatus.BANNED, AddedStatus.DEFAULT)
fun isAllowed(player: OfflinePlayer) = isAllowed(player.uuid)
fun allow(player: OfflinePlayer) = allow(player.uuid)
fun disallow(player: OfflinePlayer) = disallow(player.uuid)
fun isBanned(player: OfflinePlayer) = isBanned(player.uuid)
fun ban(player: OfflinePlayer) = ban(player.uuid)
fun unban(player: OfflinePlayer) = unban(player.uuid)
}
open class AddedDataHolder(override var addedMap: MutableAddedDataMap = mutableMapOf()) : AddedData {
override fun getAddedStatus(uuid: UUID): AddedStatus = addedMap.getOrDefault(uuid, AddedStatus.DEFAULT)
override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean = status.takeIf { it != AddedStatus.DEFAULT }
?.let { addedMap.put(uuid, it) != it }
?: addedMap.remove(uuid) != null
}
enum class AddedStatus {
DEFAULT,
ALLOWED,
BANNED;
val isDefault get() = this == DEFAULT
val isAllowed get() = this == ALLOWED
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
import com.fasterxml.jackson.annotation.JsonIgnore
import io.dico.parcels2.blockvisitor.TickWorktimeOptions
import io.dico.parcels2.defaultimpl.DefaultGeneratorOptions
import io.dico.parcels2.storage.Storage
import io.dico.parcels2.storage.StorageFactory
import io.dico.parcels2.storage.yamlObjectMapper
import org.bukkit.Bukkit.createBlockData
import org.bukkit.GameMode
import org.bukkit.Material
import org.bukkit.block.Biome
import org.bukkit.block.data.BlockData
import java.io.Reader
import java.io.Writer
import java.util.*
import java.util.EnumSet
class Options {
var worlds: Map<String, WorldOptions> = HashMap()
var worlds: Map<String, WorldOptionsHolder> = hashMapOf()
private set
var storage: StorageOptions = StorageOptions("postgresql", DataConnectionOptions())
var tickWorktime: TickWorktimeOptions = TickWorktimeOptions(20, 1)
fun addWorld(name: String, options: WorldOptions) = (worlds as MutableMap).put(name, options)
fun addWorld(name: String,
generatorOptions: GeneratorOptions? = null,
worldOptions: WorldOptions? = null) {
val optionsHolder = WorldOptionsHolder(
generatorOptions ?: DefaultGeneratorOptions(),
worldOptions ?: WorldOptions()
)
(worlds as MutableMap).put(name, optionsHolder)
}
fun writeTo(writer: Writer) = yamlObjectMapper.writeValue(writer, this)
@@ -31,6 +37,9 @@ class Options {
}
class WorldOptionsHolder(var generator: GeneratorOptions = DefaultGeneratorOptions(),
var runtime: WorldOptions = WorldOptions())
data class WorldOptions(var gameMode: GameMode? = GameMode.CREATIVE,
var dayTime: Boolean = true,
var noWeather: Boolean = true,
@@ -42,8 +51,7 @@ data class WorldOptions(var gameMode: GameMode? = GameMode.CREATIVE,
var blockPortalCreation: Boolean = true,
var blockMobSpawning: Boolean = true,
var blockedItems: Set<Material> = EnumSet.of(Material.FLINT_AND_STEEL, Material.SNOWBALL),
var axisLimit: Int = 10,
var generator: GeneratorOptions = DefaultGeneratorOptions()) {
var axisLimit: Int = 10) {
}
@@ -51,23 +59,7 @@ abstract class GeneratorOptions {
abstract fun generatorFactory(): GeneratorFactory
fun getGenerator(worlds: Worlds, worldName: String) = generatorFactory().newParcelGenerator(worlds, worldName, this)
}
data class DefaultGeneratorOptions(var defaultBiome: Biome = Biome.JUNGLE,
var wallType: BlockData = createBlockData(Material.STONE_SLAB),
var floorType: BlockData = createBlockData(Material.QUARTZ_BLOCK),
var fillType: BlockData = createBlockData(Material.QUARTZ_BLOCK),
var pathMainType: BlockData = createBlockData(Material.SANDSTONE),
var pathAltType: BlockData = createBlockData(Material.REDSTONE_BLOCK),
var parcelSize: Int = 101,
var pathSize: Int = 9,
var floorHeight: Int = 64,
var offsetX: Int = 0,
var offsetZ: Int = 0) : GeneratorOptions() {
override fun generatorFactory(): GeneratorFactory = DefaultParcelGenerator.Factory
fun newGenerator(worldName: String) = generatorFactory().newParcelGenerator(worldName, this)
}
@@ -104,4 +96,9 @@ data class DataConnectionOptions(val address: String = "localhost",
}
data class DataFileOptions(val location: String = "/flatfile-storage/")
data class DataFileOptions(val location: String = "/flatfile-storage/")
class MigrationOptions() {
}

View File

@@ -1,42 +1,40 @@
package io.dico.parcels2
import io.dico.parcels2.util.Vec2i
import io.dico.parcels2.util.getPlayerName
import io.dico.parcels2.util.hasBuildAnywhere
import io.dico.parcels2.util.isValid
import io.dico.parcels2.util.uuid
import org.bukkit.Bukkit
import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player
import org.joda.time.DateTime
import java.util.*
import java.util.UUID
interface AddedData {
val added: Map<UUID, AddedStatus>
/**
* Parcel implementation of ParcelData will update the database when changes are made.
* To change the data without updating the database, defer to the data delegate instance.
*
* This should be used for example in database query callbacks.
* However, this implementation is intentionally not thread-safe.
* Therefore, database query callbacks should schedule their updates using the bukkit scheduler.
*/
interface Parcel : ParcelData {
val id: ParcelId
val world: ParcelWorld
val pos: Vec2i
val x: Int
val z: Int
val data: ParcelData
val infoString: String
val hasBlockVisitors: Boolean
fun getAddedStatus(uuid: UUID): AddedStatus
fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean
fun compareAndSetAddedStatus(uuid: UUID, expect: AddedStatus, status: AddedStatus): Boolean =
(getAddedStatus(uuid) == expect).also { if (it) setAddedStatus(uuid, status) }
fun isAllowed(uuid: UUID) = getAddedStatus(uuid) == AddedStatus.ALLOWED
fun allow(uuid: UUID) = setAddedStatus(uuid, AddedStatus.ALLOWED)
fun disallow(uuid: UUID) = compareAndSetAddedStatus(uuid, AddedStatus.ALLOWED, AddedStatus.DEFAULT)
fun isBanned(uuid: UUID) = getAddedStatus(uuid) == AddedStatus.BANNED
fun ban(uuid: UUID) = setAddedStatus(uuid, AddedStatus.BANNED)
fun unban(uuid: UUID) = compareAndSetAddedStatus(uuid, AddedStatus.BANNED, AddedStatus.DEFAULT)
fun copyDataIgnoringDatabase(data: ParcelData)
fun isAllowed(player: OfflinePlayer) = isAllowed(player.uuid)
fun allow(player: OfflinePlayer) = allow(player.uuid)
fun disallow(player: OfflinePlayer) = disallow(player.uuid)
fun isBanned(player: OfflinePlayer) = isBanned(player.uuid)
fun ban(player: OfflinePlayer) = ban(player.uuid)
fun unban(player: OfflinePlayer) = unban(player.uuid)
fun copyData(data: ParcelData)
fun dispose()
}
interface ParcelData : AddedData {
var owner: ParcelOwner?
val since: DateTime?
fun canBuild(player: OfflinePlayer, checkAdmin: Boolean = true, checkGlobal: Boolean = true): Boolean
@@ -48,85 +46,10 @@ interface ParcelData : AddedData {
}
}
/**
* Parcel implementation of ParcelData will update the database when changes are made.
* To change the data without updating the database, defer to the data delegate instance.
*
* This should be used for example in database query callbacks.
* However, this implementation is intentionally not thread-safe.
* Therefore, database query callbacks should schedule their updates using the bukkit scheduler.
*/
class Parcel(val world: ParcelWorld, val pos: Vec2i) : ParcelData {
val id get() = "${pos.x}:${pos.z}"
val homeLocation get() = world.generator.getHomeLocation(this)
private var blockVisitors = 0
val infoString: String
get() {
return "$id; owned by ${owner?.let { it.name ?: Bukkit.getOfflinePlayer(it.uuid).name }}"
}
var data: ParcelData = ParcelDataHolder(); private set
fun copyDataIgnoringDatabase(data: ParcelData) {
this.data = data
}
fun copyData(data: ParcelData) {
world.storage.setParcelData(this, data)
this.data = data
}
override val added: Map<UUID, AddedStatus> get() = data.added
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) = data.canBuild(player)
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.setParcelPlayerState(this, uuid, status.asBoolean)
}
}
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
}
open class AddedDataHolder : AddedData {
override var added = mutableMapOf<UUID, AddedStatus>()
override fun getAddedStatus(uuid: UUID): AddedStatus = added.getOrDefault(uuid, AddedStatus.DEFAULT)
override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean = status.takeIf { it != AddedStatus.DEFAULT }
?.let { added.put(uuid, it) != it }
?: added.remove(uuid) != null
}
class ParcelDataHolder : AddedDataHolder(), ParcelData {
override var owner: ParcelOwner? = null
override var since: DateTime? = null
override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean) = isAllowed(player.uniqueId)
|| owner.let { it != null && it.matches(player, allowNameMatch = false) }
|| (checkAdmin && player is Player && player.hasBuildAnywhere)
@@ -135,55 +58,3 @@ class ParcelDataHolder : AddedDataHolder(), ParcelData {
override var allowInteractInventory = true
}
enum class AddedStatus {
DEFAULT,
ALLOWED,
BANNED;
val asBoolean
get() = when (this) {
DEFAULT -> null
ALLOWED -> true
BANNED -> false
}
}
@Suppress("UsePropertyAccessSyntax")
class ParcelOwner(val uuid: UUID? = null,
name: String? = null,
val since: DateTime? = null) {
companion object {
fun create(uuid: UUID?, name: String?, time: DateTime? = null): ParcelOwner? {
return uuid?.let { ParcelOwner(uuid, name, time) }
?: name?.let { ParcelOwner(uuid, name, time) }
}
}
val name: String?
init {
uuid ?: name ?: throw IllegalArgumentException("uuid and/or name must be present")
if (name != null) this.name = name
else {
val offlinePlayer = Bukkit.getOfflinePlayer(uuid).takeIf { it.isValid }
this.name = offlinePlayer?.name
}
}
val playerName get() = getPlayerName(uuid, name)
fun matches(player: OfflinePlayer, allowNameMatch: Boolean = false): Boolean {
return uuid?.let { it == player.uniqueId } ?: false
|| (allowNameMatch && name?.let { it == player.name } ?: false)
}
val onlinePlayer: Player? get() = uuid?.let { Bukkit.getPlayer(uuid) }
val onlinePlayerAllowingNameMatch: Player? get() = onlinePlayer ?: name?.let { Bukkit.getPlayer(name) }
@Suppress("DEPRECATION")
val offlinePlayer
get() = (uuid?.let { Bukkit.getOfflinePlayer(it) } ?: Bukkit.getOfflinePlayer(name))
?.takeIf { it.isValid }
}

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

@@ -0,0 +1,52 @@
@file:Suppress("unused", "UsePropertyAccessSyntax", "DEPRECATION")
package io.dico.parcels2
import io.dico.parcels2.util.getPlayerNameOrDefault
import io.dico.parcels2.util.isValid
import io.dico.parcels2.util.uuid
import org.bukkit.Bukkit
import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player
import java.util.UUID
class ParcelOwner(val uuid: UUID?,
val name: String?) {
val notNullName: String by lazy { name ?: getPlayerNameOrDefault(uuid!!) }
constructor(name: String) : this(null, name)
constructor(uuid: UUID) : this(uuid, null)
constructor(player: OfflinePlayer) : this(player.uuid, player.name)
companion object {
fun nameless(player: OfflinePlayer) = ParcelOwner(player.uuid, null)
}
inline val hasUUID: Boolean get() = uuid != null
val onlinePlayer: Player? get() = uuid?.let { Bukkit.getPlayer(uuid) }
val onlinePlayerAllowingNameMatch: Player? get() = onlinePlayer ?: name?.let { Bukkit.getPlayerExact(name) }
val offlinePlayer: OfflinePlayer? get() = uuid?.let { Bukkit.getOfflinePlayer(it).takeIf { it.isValid } }
val offlinePlayerAllowingNameMatch: OfflinePlayer?
get() = offlinePlayer ?: Bukkit.getOfflinePlayer(name).takeIf { it.isValid }
fun matches(player: OfflinePlayer, allowNameMatch: Boolean = false): Boolean {
return uuid?.let { it == player.uniqueId } ?: false
|| (allowNameMatch && name?.let { it == player.name } ?: false)
}
fun equals(other: ParcelOwner): Boolean {
return if (hasUUID) other.hasUUID && uuid == other.uuid
else !other.hasUUID && name == other.name
}
override fun equals(other: Any?): Boolean {
return other is ParcelOwner && equals(other)
}
override fun hashCode(): Int {
return if (hasUUID) uuid!!.hashCode() else name!!.hashCode()
}
}

View File

@@ -1,230 +1,86 @@
package io.dico.parcels2
import io.dico.parcels2.storage.SerializableParcel
import io.dico.parcels2.storage.SerializableWorld
import io.dico.parcels2.storage.Storage
import io.dico.parcels2.util.Vec2i
import io.dico.parcels2.util.doAwait
import io.dico.parcels2.util.floor
import kotlinx.coroutines.experimental.launch
import org.bukkit.Bukkit
import org.bukkit.Location
import org.bukkit.World
import org.bukkit.WorldCreator
import org.bukkit.block.Block
import org.bukkit.entity.Entity
import org.bukkit.entity.Player
import java.util.*
import kotlin.coroutines.experimental.buildIterator
import kotlin.coroutines.experimental.buildSequence
import kotlin.reflect.jvm.javaMethod
import kotlin.reflect.jvm.kotlinFunction
import java.util.UUID
class Worlds(val plugin: ParcelsPlugin) {
val worlds: Map<String, ParcelWorld> get() = _worlds
private val _worlds: MutableMap<String, ParcelWorld> = HashMap()
interface ParcelProvider {
val worlds: Map<String, ParcelWorld>
fun 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 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: String, x: Int, z: Int): Parcel? {
with(getWorld(world) ?: return null) {
return generator.parcelAt(x, z)
}
}
fun getParcelAt(world: World, vec: Vec2i): Parcel? = getParcelAt(world, vec.x, vec.z)
init {
val function = ::loadWorlds
function.javaMethod!!.kotlinFunction
}
fun getParcelAt(loc: Location): Parcel? = getParcelAt(loc.world, loc.x.floor(), loc.z.floor())
operator fun SerializableParcel.invoke(): Parcel? {
return world()?.parcelByID(pos)
}
fun getParcelAt(entity: Entity): Parcel? = getParcelAt(entity.location)
operator fun SerializableWorld.invoke(): ParcelWorld? {
return world?.let { getWorld(it) }
}
fun getParcelAt(block: Block): Parcel? = getParcelAt(block.world, block.x, block.z)
fun loadWorlds(options: Options) {
for ((worldName, worldOptions) in options.worlds.entries) {
val world: ParcelWorld
try {
fun getWorldGenerator(worldName: String): ParcelGenerator?
world = ParcelWorld(
worldName,
worldOptions,
worldOptions.generator.getGenerator(this, worldName),
plugin.storage)
} catch (ex: Exception) {
ex.printStackTrace()
continue
}
_worlds.put(worldName, world)
if (Bukkit.getWorld(worldName) == null) {
plugin.doAwait {
cond = {
try {
// server.getDefaultGameMode() throws an error before any worlds are initialized.
// createWorld() below calls that method.
// Plugin needs to load on STARTUP for generators to be registered correctly.
// Means we need to await the initial worlds getting loaded.
plugin.server.defaultGameMode; true
} catch (ex: Throwable) {
false
}
}
onSuccess = {
val bworld = WorldCreator(worldName).generator(world.generator).createWorld()
val spawn = world.generator.getFixedSpawnLocation(bworld, null)
bworld.setSpawnLocation(spawn.x.floor(), spawn.y.floor(), spawn.z.floor())
}
}
}
}
}
fun loadWorlds()
}
interface ParcelProvider {
interface ParcelLocator {
val world: World
fun parcelAt(x: Int, z: Int): Parcel?
fun getParcelIdAt(x: Int, z: Int): ParcelId?
fun parcelAt(vec: Vec2i): Parcel? = parcelAt(vec.x, vec.z)
fun getParcelAt(x: Int, z: Int): Parcel?
fun parcelAt(loc: Location): Parcel? = parcelAt(loc.x.floor(), loc.z.floor())
fun getParcelAt(vec: Vec2i): Parcel? = getParcelAt(vec.x, vec.z)
fun parcelAt(entity: Entity): Parcel? = parcelAt(entity.location)
fun getParcelAt(loc: Location): Parcel? = getParcelAt(loc.x.floor(), loc.z.floor()).takeIf { loc.world == world }
fun parcelAt(block: Block): Parcel? = parcelAt(block.x, block.z)
}
fun getParcelAt(entity: Entity): Parcel? = getParcelAt(entity.location).takeIf { entity.world == world }
class ParcelWorld constructor(val name: String,
val options: WorldOptions,
val generator: ParcelGenerator,
val storage: Storage) : 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?
fun getParcelAt(block: Block): Parcel? = getParcelAt(block.x, block.z).takeIf { block.world == world }
}
typealias ParcelContainerFactory = (ParcelWorld) -> ParcelContainer
class DefaultParcelContainer(private val world: ParcelWorld,
private val storage: Storage) : ParcelContainer {
private var parcels: Array<Array<Parcel>>
interface ParcelContainer {
init {
parcels = initArray(world.options.axisLimit, world)
}
fun getParcelById(x: Int, z: Int): Parcel?
fun resizeIfSizeChanged() {
if (parcels.size / 2 != world.options.axisLimit) {
resize(world.options.axisLimit)
}
}
fun getParcelById(id: Vec2i): Parcel? = getParcelById(id.x, id.z)
fun resize(axisLimit: Int) {
parcels = initArray(axisLimit, world, this)
}
fun nextEmptyParcel(): Parcel?
fun initArray(axisLimit: Int, world: ParcelWorld, cur: DefaultParcelContainer? = null): Array<Array<Parcel>> {
val arraySize = 2 * axisLimit + 1
return Array(arraySize) {
val x = it - axisLimit
Array(arraySize) {
val z = it - axisLimit
cur?.parcelByID(x, z) ?: Parcel(world, Vec2i(x, z))
}
}
}
}
override fun parcelByID(x: Int, z: Int): Parcel? {
return parcels.getOrNull(x + world.options.axisLimit)?.getOrNull(z + world.options.axisLimit)
}
override fun nextEmptyParcel(): Parcel? {
return walkInCircle().find { it.owner == null }
}
private fun walkInCircle(): Iterable<Parcel> = Iterable {
buildIterator {
val center = world.options.axisLimit
for (radius in 0..center) {
var x = center - radius; var z = center - radius
repeat(radius * 2) { yield(parcels[x++][z]) }
repeat(radius * 2) { yield(parcels[x][z++]) }
repeat(radius * 2) { yield(parcels[x--][z]) }
repeat(radius * 2) { yield(parcels[x][z--]) }
}
}
}
fun allParcels(): Sequence<Parcel> = buildSequence {
for (array in parcels) {
yieldAll(array.iterator())
}
}
fun loadAllData() {
val channel = storage.readParcelData(allParcels(), 100)
launch(storage.asyncDispatcher) {
for ((parcel, data) in channel) {
data?.let { parcel.copyDataIgnoringDatabase(it) }
}
}
}
}
interface ParcelWorld : ParcelLocator, ParcelContainer, ParcelBlockManager {
val id: ParcelWorldId
val name: String
val uid: UUID?
val options: WorldOptions
val generator: ParcelGenerator
val storage: Storage
val container: ParcelContainer
val locator: ParcelLocator
val blockManager: ParcelBlockManager
val globalAddedData: GlobalAddedDataManager
}

View File

@@ -6,31 +6,37 @@ import io.dico.dicore.command.ICommandDispatcher
import io.dico.parcels2.blockvisitor.TickWorktimeLimiter
import io.dico.parcels2.blockvisitor.WorktimeLimiter
import io.dico.parcels2.command.getParcelCommands
import io.dico.parcels2.defaultimpl.GlobalAddedDataManagerImpl
import io.dico.parcels2.defaultimpl.ParcelProviderImpl
import io.dico.parcels2.listener.ParcelEntityTracker
import io.dico.parcels2.listener.ParcelListeners
import io.dico.parcels2.storage.Storage
import io.dico.parcels2.storage.yamlObjectMapper
import io.dico.parcels2.util.FunctionHelper
import io.dico.parcels2.util.tryCreate
import kotlinx.coroutines.experimental.asCoroutineDispatcher
import org.bukkit.Bukkit
import org.bukkit.generator.ChunkGenerator
import org.bukkit.plugin.java.JavaPlugin
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.File
import java.util.concurrent.Executor
val logger = LoggerFactory.getLogger("ParcelsPlugin")
val logger: Logger = LoggerFactory.getLogger("ParcelsPlugin")
private inline val plogger get() = logger
class ParcelsPlugin : JavaPlugin() {
lateinit var optionsFile: File; private set
lateinit var options: Options; private set
lateinit var worlds: Worlds; private set
lateinit var parcelProvider: ParcelProvider; private set
lateinit var storage: Storage; private set
lateinit var globalAddedData: GlobalAddedDataManager; private set
val registrator = Registrator(this)
lateinit var entityTracker: ParcelEntityTracker; private set
private var listeners: ParcelListeners? = null
private var cmdDispatcher: ICommandDispatcher? = null
val functionHelper: FunctionHelper = FunctionHelper(this)
val worktimeLimiter: WorktimeLimiter by lazy { TickWorktimeLimiter(this, options.tickWorktime) }
override fun onEnable() {
@@ -41,13 +47,14 @@ class ParcelsPlugin : JavaPlugin() {
}
override fun onDisable() {
worktimeLimiter.completeAllTasks()
cmdDispatcher?.unregisterFromCommandMap()
}
private fun init(): Boolean {
optionsFile = File(dataFolder, "options.yml")
options = Options()
worlds = Worlds(this)
parcelProvider = ParcelProviderImpl(this)
try {
if (!loadOptions()) return false
@@ -60,39 +67,46 @@ class ParcelsPlugin : JavaPlugin() {
return false
}
worlds.loadWorlds(options)
globalAddedData = GlobalAddedDataManagerImpl(this)
entityTracker = ParcelEntityTracker(parcelProvider)
} catch (ex: Exception) {
plogger.error("Error loading options", ex)
return false
}
entityTracker = ParcelEntityTracker(worlds)
registerListeners()
registerCommands()
parcelProvider.loadWorlds()
return true
}
fun loadOptions(): Boolean {
if (optionsFile.exists()) {
yamlObjectMapper.readerForUpdating(options).readValue<Options>(optionsFile)
} else if (optionsFile.tryCreate()) {
options.addWorld("parcels", WorldOptions())
try {
yamlObjectMapper.writeValue(optionsFile, options)
} catch (ex: Throwable) {
optionsFile.delete()
throw ex
when {
optionsFile.exists() -> yamlObjectMapper.readerForUpdating(options).readValue<Options>(optionsFile)
optionsFile.tryCreate() -> {
options.addWorld("parcels")
try {
yamlObjectMapper.writeValue(optionsFile, options)
} catch (ex: Throwable) {
optionsFile.delete()
throw ex
}
plogger.warn("Created options file with a world template. Please review it before next start.")
return false
}
else -> {
plogger.error("Failed to save options file ${optionsFile.canonicalPath}")
return false
}
plogger.warn("Created options file with a world template. Please review it before next start.")
return false
} else {
plogger.error("Failed to save options file ${optionsFile.canonicalPath}")
return false
}
return true
}
override fun getDefaultWorldGenerator(worldName: String, generatorId: String?): ChunkGenerator? {
return parcelProvider.getWorldGenerator(worldName)
}
private fun registerCommands() {
cmdDispatcher = getParcelCommands(this).apply {
registerToCommandMap("parcels:", EOverridePolicy.FALLBACK_ONLY)
@@ -100,8 +114,8 @@ class ParcelsPlugin : JavaPlugin() {
}
private fun registerListeners() {
if (listeners != null) {
listeners = ParcelListeners(worlds, entityTracker)
if (listeners == null) {
listeners = ParcelListeners(parcelProvider, entityTracker)
registrator.registerListeners(listeners!!)
}
}

View File

@@ -1,307 +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 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)
}
override fun parcelAt(x: Int, z: Int): Parcel? {
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 (0 <= modX && modX < parcelSize && 0 <= modZ && modZ < parcelSize) {
return world.parcelByID((absX - modX) / sectionSize, (absZ - modZ) / sectionSize)
}
return null
}
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.playerName)
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 java.util.*
import java.util.EnumSet
val attachables: Set<Material> = EnumSet.of(
ACACIA_DOOR,
@@ -64,4 +64,4 @@ val attachables: Set<Material> = EnumSet.of(
WALL_SIGN,
LILY_PAD,
DANDELION
);
)

View File

@@ -20,7 +20,7 @@ enum class RegionTraversal(private val builder: suspend SequenceBuilder<Vec3i>.(
}),
UPDARD({ region ->
UPWARD({ region ->
val origin = region.origin
val size = region.size

View File

@@ -6,6 +6,7 @@ import io.dico.parcels2.util.get
import org.bukkit.World
import org.bukkit.block.data.BlockData
// TODO order paste such that attachables are placed after the block they depend on
class Schematic {
val size: Vec3i get() = _size!!
private var _size: Vec3i? = null

View File

@@ -1,11 +1,12 @@
package io.dico.parcels2.blockvisitor
import kotlinx.coroutines.experimental.*
import org.bukkit.plugin.Plugin
import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.util.FunctionHelper
import kotlinx.coroutines.experimental.CancellationException
import kotlinx.coroutines.experimental.Job
import org.bukkit.scheduler.BukkitTask
import java.lang.System.currentTimeMillis
import java.util.*
import java.util.concurrent.Executor
import java.util.LinkedList
import java.util.logging.Level
import kotlin.coroutines.experimental.Continuation
import kotlin.coroutines.experimental.intrinsics.COROUTINE_SUSPENDED
@@ -27,6 +28,11 @@ sealed class WorktimeLimiter {
* Get a list of all workers
*/
abstract val workers: List<Worker>
/**
* Attempts to complete any remaining tasks immediately, without suspension.
*/
abstract fun completeAllTasks()
}
interface Timed {
@@ -90,8 +96,14 @@ interface WorkerScope : Timed {
private interface WorkerContinuation : Worker, WorkerScope {
/**
* Start or resume the execution of this worker
* returns true if the worker completed
* Start or resumes the execution of this worker
* and returns true if the worker completed
*
* [worktime] is the maximum amount of time, in milliseconds,
* that this job may run for until suspension.
*
* If [worktime] is not positive, the worker will complete
* without suspension and this method will always return true.
*/
fun resume(worktime: Long): Boolean
}
@@ -101,19 +113,17 @@ private interface WorkerContinuation : Worker, WorkerScope {
* There is a configurable maxiumum amount of milliseconds that can be allocated to all workers together in each server tick
* This object attempts to split that maximum amount of milliseconds equally between all jobs
*/
class TickWorktimeLimiter(private val plugin: Plugin, var options: TickWorktimeOptions) : WorktimeLimiter() {
// Coroutine dispatcher for jobs
private val dispatcher = Executor(Runnable::run).asCoroutineDispatcher()
class TickWorktimeLimiter(private val plugin: ParcelsPlugin, var options: TickWorktimeOptions) : WorktimeLimiter() {
// The currently registered bukkit scheduler task
private var bukkitTask: BukkitTask? = null
// The workers.
private var _workers = LinkedList<WorkerContinuation>()
private val _workers = LinkedList<WorkerContinuation>()
override val workers: List<Worker> = _workers
override fun submit(task: TimeLimitedTask): Worker {
val worker: WorkerContinuation = WorkerImpl(plugin, dispatcher, task)
val worker: WorkerContinuation = WorkerImpl(plugin.functionHelper, task)
_workers.addFirst(worker)
if (bukkitTask == null) bukkitTask = plugin.server.scheduler.runTaskTimer(plugin, ::tickJobs, 0, options.tickInterval.toLong())
if (bukkitTask == null) bukkitTask = plugin.functionHelper.scheduleRepeating(0, options.tickInterval) { tickJobs() }
return worker
}
@@ -144,10 +154,18 @@ class TickWorktimeLimiter(private val plugin: Plugin, var options: TickWorktimeO
}
}
override fun completeAllTasks() {
_workers.forEach {
it.resume(-1)
}
_workers.clear()
bukkitTask?.cancel()
bukkitTask = null
}
}
private class WorkerImpl(val plugin: Plugin,
val dispatcher: CoroutineDispatcher,
private class WorkerImpl(val functionHelper: FunctionHelper,
val task: TimeLimitedTask) : WorkerContinuation {
override var job: Job? = null; private set
@@ -170,6 +188,7 @@ private class WorkerImpl(val plugin: Plugin,
private var onCompleted: WorkerUpdateLister? = null
private var continuation: Continuation<Unit>? = null
private var nextSuspensionTime: Long = 0L
private var completeForcefully = false
private fun initJob(job: Job) {
this.job?.let { throw IllegalStateException() }
@@ -179,7 +198,7 @@ private class WorkerImpl(val plugin: Plugin,
// report any error that occurred
completionException = exception?.also {
if (it !is CancellationException)
plugin.logger.log(Level.SEVERE, "TimeLimitedTask for plugin ${plugin.name} generated an exception", it)
functionHelper.plugin.logger.log(Level.SEVERE, "TimeLimitedTask for plugin ${functionHelper.plugin.name} generated an exception", it)
}
// convert to elapsed time here
@@ -204,7 +223,7 @@ private class WorkerImpl(val plugin: Plugin,
}
override suspend fun markSuspensionPoint() {
if (System.currentTimeMillis() >= nextSuspensionTime)
if (System.currentTimeMillis() >= nextSuspensionTime && !completeForcefully)
suspendCoroutineUninterceptedOrReturn { cont: Continuation<Unit> ->
continuation = cont
COROUTINE_SUSPENDED
@@ -222,7 +241,11 @@ private class WorkerImpl(val plugin: Plugin,
}
override fun resume(worktime: Long): Boolean {
nextSuspensionTime = currentTimeMillis() + worktime
if (worktime > 0) {
nextSuspensionTime = currentTimeMillis() + worktime
} else {
completeForcefully = true
}
continuation?.let {
continuation = null
@@ -236,10 +259,9 @@ private class WorkerImpl(val plugin: Plugin,
}
try {
launch(context = dispatcher, start = CoroutineStart.UNDISPATCHED) {
initJob(job = kotlin.coroutines.experimental.coroutineContext[Job]!!)
task()
}
val job = functionHelper.launchLazilyOnMainThread { task() }
initJob(job = job)
job.start()
} catch (t: Throwable) {
// do nothing: handled by job.invokeOnCompletion()
}

View File

@@ -4,10 +4,10 @@ import io.dico.dicore.command.CommandException
import io.dico.dicore.command.ExecutionContext
import io.dico.dicore.command.ICommandReceiver
import io.dico.parcels2.ParcelOwner
import io.dico.parcels2.ParcelWorld
import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.util.hasAdminManage
import io.dico.parcels2.util.parcelLimit
import io.dico.parcels2.util.uuid
import org.bukkit.entity.Player
import org.bukkit.plugin.Plugin
import java.lang.reflect.Method
@@ -16,10 +16,10 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei
override fun getPlugin(): Plugin = plugin
override fun getReceiver(context: ExecutionContext, target: Method, cmdName: String): ICommandReceiver {
return getParcelCommandReceiver(plugin.worlds, context, target, cmdName)
return getParcelCommandReceiver(plugin.parcelProvider, context, target, cmdName)
}
protected inline val worlds get() = plugin.worlds
protected inline val worlds get() = plugin.parcelProvider
protected fun error(message: String): Nothing {
throw CommandException(message)
@@ -29,9 +29,10 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei
if (!plugin.storage.isConnected) error("Parcels cannot $action right now because of a database error")
}
protected suspend fun checkParcelLimit(player: Player) {
protected suspend fun checkParcelLimit(player: Player, world: ParcelWorld) {
if (player.hasAdminManage) return
val numOwnedParcels = plugin.storage.getNumParcels(ParcelOwner(uuid = player.uuid)).await()
val numOwnedParcels = plugin.storage.getOwnedParcels(ParcelOwner(player)).await()
.filter { it.worldId.equals(world.id) }.size
val limit = player.parcelLimit
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,14 +8,14 @@ import io.dico.parcels2.util.hasAdminManage
import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player
class CommandsAddedStatus(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
class CommandsAddedStatusLocal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@Cmd("allow", aliases = ["add", "permit"])
@Desc("Allows a player to build on this parcel",
shortVersion = "allows a player to build on this parcel")
@ParcelRequire(owner = true)
fun ParcelScope.cmdAllow(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(parcel.owner != null && !sender.hasAdminManage, "This parcel is unowned")
Validate.isTrue(parcel.owner != null || sender.hasAdminManage, "This parcel is unowned")
Validate.isTrue(!parcel.owner!!.matches(player), "The target already owns the parcel")
Validate.isTrue(parcel.allow(player), "${player.name} is already allowed to build on this parcel")
return "${player.name} is now allowed to build on this parcel"
@@ -37,7 +37,7 @@ class CommandsAddedStatus(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin
shortVersion = "bans a player from this parcel")
@ParcelRequire(owner = true)
fun ParcelScope.cmdBan(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(parcel.owner != null && !sender.hasAdminManage, "This parcel is unowned")
Validate.isTrue(parcel.owner != null || sender.hasAdminManage, "This parcel is unowned")
Validate.isTrue(!parcel.owner!!.matches(player), "The owner cannot be banned from the parcel")
Validate.isTrue(parcel.ban(player), "${player.name} is already banned from this parcel")
return "${player.name} is now banned from this parcel"

View File

@@ -1,12 +1,17 @@
package io.dico.parcels2.command
import io.dico.dicore.command.CommandException
import io.dico.dicore.command.EMessageType
import io.dico.dicore.command.ExecutionContext
import io.dico.dicore.command.annotation.Cmd
import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.blockvisitor.RegionTraversal
import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.entity.Player
import java.util.Random
class CommandsDebug(val plugin: ParcelsPlugin) {
class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@Cmd("reloadoptions")
fun reloadOptions() {
@@ -23,4 +28,24 @@ class CommandsDebug(val plugin: ParcelsPlugin) {
return "Teleported you to $worldName spawn"
}
@Cmd("make_mess")
@ParcelRequire(owner = true)
fun ParcelScope.cmdMakeMess(context: ExecutionContext) {
val server = plugin.server
val blockDatas = arrayOf(
server.createBlockData(Material.STICKY_PISTON),
server.createBlockData(Material.GLASS),
server.createBlockData(Material.STONE_SLAB),
server.createBlockData(Material.QUARTZ_BLOCK)
)
val random = Random()
world.doBlockOperation(parcel.id, direction = RegionTraversal.UPWARD) { block ->
block.blockData = blockDatas[random.nextInt(4)]
}.onProgressUpdate(1000, 1000) { progress, elapsedTime ->
context.sendMessage(EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed"
.format(progress * 100, elapsedTime / 1000.0))
}
}
}

View File

@@ -4,20 +4,15 @@ import io.dico.dicore.command.EMessageType
import io.dico.dicore.command.ExecutionContext
import io.dico.dicore.command.annotation.Cmd
import io.dico.dicore.command.annotation.Desc
import io.dico.dicore.command.annotation.Flag
import io.dico.dicore.command.annotation.RequireParameters
import io.dico.parcels2.ParcelOwner
import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.blockvisitor.RegionTraversal
import io.dico.parcels2.command.NamedParcelDefaultValue.FIRST_OWNED
import io.dico.parcels2.storage.getParcelBySerializedValue
import io.dico.parcels2.util.hasAdminManage
import io.dico.parcels2.util.hasParcelHomeOthers
import io.dico.parcels2.util.uuid
import org.bukkit.Material
import org.bukkit.entity.Player
import java.util.*
//@Suppress("unused")
class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@Cmd("auto")
@@ -26,12 +21,12 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
shortVersion = "sets you up with a fresh, unclaimed parcel")
suspend fun WorldScope.cmdAuto(player: Player): Any? {
checkConnected("be claimed")
checkParcelLimit(player)
checkParcelLimit(player, world)
val parcel = world.nextEmptyParcel()
?: error("This world is full, please ask an admin to upsize it")
parcel.owner = ParcelOwner(uuid = player.uuid)
player.teleport(parcel.homeLocation)
player.teleport(parcel.world.getHomeLocation(parcel.id))
return "Enjoy your new parcel!"
}
@@ -48,23 +43,22 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
"more than one parcel",
shortVersion = "teleports you to parcels")
@RequireParameters(0)
suspend fun cmdHome(player: Player,
@NamedParcelDefault(FIRST_OWNED) target: NamedParcelTarget): Any? {
if (player !== target.player && !player.hasParcelHomeOthers) {
suspend fun cmdHome(player: Player, @ParcelTarget.Kind(ParcelTarget.OWNER_REAL) target: ParcelTarget): Any? {
val ownerTarget = target as ParcelTarget.ByOwner
if (!ownerTarget.owner.matches(player) && !player.hasParcelHomeOthers) {
error("You do not have permission to teleport to other people's parcels")
}
val ownedParcelsResult = plugin.storage.getOwnedParcels(ParcelOwner(uuid = target.player.uuid)).await()
val ownedParcelsResult = plugin.storage.getOwnedParcels(ownerTarget.owner).await()
val uuid = target.player.uuid
val ownedParcels = ownedParcelsResult
.map { worlds.getParcelBySerializedValue(it) }
.filter { it != null && it.world == target.world && it.owner?.uuid == uuid }
.map { worlds.getParcelById(it) }
.filter { it != null && ownerTarget.world == it.world }
val targetMatch = ownedParcels.getOrNull(target.index)
?: error("The specified parcel could not be matched")
player.teleport(targetMatch.homeLocation)
player.teleport(targetMatch.world.getHomeLocation(targetMatch.id))
return ""
}
@@ -77,38 +71,39 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
error(if (it.matches(player)) "You already own this parcel" else "This parcel is not available")
}
checkParcelLimit(player)
parcel.owner = ParcelOwner(uuid = player.uuid, name = player.name)
checkParcelLimit(player, world)
parcel.owner = ParcelOwner(player)
return "Enjoy your new parcel!"
}
@Cmd("unclaim")
@Desc("Unclaims this parcel")
@ParcelRequire(owner = true)
fun ParcelScope.cmdUnclaim(player: Player): Any? {
parcel.dispose()
return "Your parcel has been disposed"
}
@Cmd("clear")
@ParcelRequire(owner = true)
fun ParcelScope.cmdClear(context: ExecutionContext) {
world.generator.clearParcel(parcel)
fun ParcelScope.cmdClear(context: ExecutionContext, @Flag sure: Boolean): Any? {
if (!sure) return "Are you sure? You cannot undo this action!\n" +
"Run \"/${context.rawInput} -sure\" if you want to go through with this."
world.clearParcel(parcel.id)
.onProgressUpdate(1000, 1000) { progress, elapsedTime ->
context.sendMessage(EMessageType.INFORMATIVE, "Clear progress: %.02f%%, %.2fs elapsed"
val alt = context.getFormat(EMessageType.NUMBER)
val main = context.getFormat(EMessageType.INFORMATIVE)
context.sendMessage(EMessageType.INFORMATIVE, false, "Clear progress: $alt%.02f$main%%, $alt%.2f${main}s elapsed"
.format(progress * 100, elapsedTime / 1000.0))
}
return null
}
@Cmd("make_mess")
@ParcelRequire(owner = true)
fun ParcelScope.cmdMakeMess(context: ExecutionContext) {
val server = plugin.server
val blockDatas = arrayOf(
server.createBlockData(Material.STICKY_PISTON),
server.createBlockData(Material.GLASS),
server.createBlockData(Material.STONE_SLAB),
server.createBlockData(Material.QUARTZ_BLOCK)
)
val random = Random()
world.generator.doBlockOperation(parcel, direction = RegionTraversal.UPDARD) { block ->
block.blockData = blockDatas[random.nextInt(4)]
}.onProgressUpdate(1000, 1000) { progress, elapsedTime ->
context.sendMessage(EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed"
.format(progress * 100, elapsedTime / 1000.0))
}
@Cmd("swap")
fun ParcelScope.cmdSwap(context: ExecutionContext, @Flag sure: Boolean): Any? {
TODO()
}
}

View File

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

View File

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

View File

@@ -3,14 +3,10 @@ package io.dico.parcels2.command
import io.dico.dicore.command.CommandException
import io.dico.dicore.command.parameter.ArgumentBuffer
import io.dico.dicore.command.parameter.Parameter
import io.dico.dicore.command.parameter.type.ParameterConfig
import io.dico.dicore.command.parameter.type.ParameterType
import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelProvider
import io.dico.parcels2.ParcelWorld
import io.dico.parcels2.Worlds
import io.dico.parcels2.util.isValid
import org.bukkit.Bukkit
import org.bukkit.OfflinePlayer
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
@@ -18,7 +14,7 @@ fun invalidInput(parameter: Parameter<*, *>, message: String): Nothing {
throw CommandException("invalid input for ${parameter.name}: $message")
}
fun Worlds.getTargetWorld(input: String?, sender: CommandSender, parameter: Parameter<*, *>): ParcelWorld {
fun ParcelProvider.getTargetWorld(input: String?, sender: CommandSender, parameter: Parameter<*, *>): ParcelWorld {
val worldName = input
?.takeUnless { it.isEmpty() }
?: (sender as? Player)?.world?.name
@@ -28,14 +24,14 @@ fun Worlds.getTargetWorld(input: String?, sender: CommandSender, parameter: Para
?: invalidInput(parameter, "$worldName is not a parcel world")
}
class ParcelParameterType(val worlds: Worlds) : ParameterType<Parcel, Void>(Parcel::class.java) {
class ParcelParameterType(val parcelProvider: ParcelProvider) : ParameterType<Parcel, Void>(Parcel::class.java) {
val regex = Regex.fromLiteral("((.+)->)?([0-9]+):([0-9]+)")
override fun parse(parameter: Parameter<Parcel, Void>, sender: CommandSender, buffer: ArgumentBuffer): Parcel {
val matchResult = regex.matchEntire(buffer.next())
?: invalidInput(parameter, "must match (w->)?a:b (/${regex.pattern}/)")
val world = worlds.getTargetWorld(matchResult.groupValues[2], sender, parameter)
val world = parcelProvider.getTargetWorld(matchResult.groupValues[2], sender, parameter)
val x = matchResult.groupValues[3].toIntOrNull()
?: invalidInput(parameter, "couldn't parse int")
@@ -43,80 +39,8 @@ class ParcelParameterType(val worlds: Worlds) : ParameterType<Parcel, Void>(Parc
val z = matchResult.groupValues[4].toIntOrNull()
?: invalidInput(parameter, "couldn't parse int")
return world.parcelByID(x, z)
return world.getParcelById(x, z)
?: invalidInput(parameter, "parcel id is out of range")
}
}
class NamedParcelTarget(val world: ParcelWorld, val player: OfflinePlayer, val index: Int)
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
annotation class NamedParcelDefault(val value: NamedParcelDefaultValue)
enum class NamedParcelDefaultValue {
FIRST_OWNED,
NULL
}
class NamedParcelTargetConfig : ParameterConfig<NamedParcelDefault,
NamedParcelDefaultValue>(NamedParcelDefault::class.java) {
override fun toParameterInfo(annotation: NamedParcelDefault): NamedParcelDefaultValue {
return annotation.value
}
}
class ParcelHomeParameterType(val worlds: Worlds) : ParameterType<NamedParcelTarget,
NamedParcelDefaultValue>(NamedParcelTarget::class.java, NamedParcelTargetConfig()) {
val regex = Regex.fromLiteral("((.+)->)?(.+)|((.+):([0-9]+))")
private fun requirePlayer(sender: CommandSender, parameter: Parameter<*, *>): Player {
if (sender !is Player) invalidInput(parameter, "console cannot omit the player name")
return sender
}
@Suppress("UsePropertyAccessSyntax")
private fun getOfflinePlayer(input: String, parameter: Parameter<*, *>) = Bukkit.getOfflinePlayer(input)
?.takeIf { it.isValid }
?: invalidInput(parameter, "do not know who $input is")
override fun parse(parameter: Parameter<NamedParcelTarget, NamedParcelDefaultValue>,
sender: CommandSender, buffer: ArgumentBuffer): NamedParcelTarget {
val matchResult = regex.matchEntire(buffer.next())
?: invalidInput(parameter, "must be a player, index, or player:index (/${regex.pattern}/)")
val world = worlds.getTargetWorld(matchResult.groupValues[2], sender, parameter)
matchResult.groupValues[3].takeUnless { it.isEmpty() }?.let {
// first group was matched, it's a player or an int
it.toIntOrNull()?.let {
requirePlayer(sender, parameter)
return NamedParcelTarget(world, sender as Player, it)
}
return NamedParcelTarget(world, getOfflinePlayer(it, parameter), 0)
}
val player = getOfflinePlayer(matchResult.groupValues[5], parameter)
val index = matchResult.groupValues[6].toIntOrNull()
?: invalidInput(parameter, "couldn't parse int")
return NamedParcelTarget(world, player, index)
}
override fun getDefaultValue(parameter: Parameter<NamedParcelTarget, NamedParcelDefaultValue>,
sender: CommandSender, buffer: ArgumentBuffer): NamedParcelTarget? {
if (parameter.paramInfo == NamedParcelDefaultValue.NULL) {
return null
}
val world = worlds.getTargetWorld(null, sender, parameter)
val player = requirePlayer(sender, parameter)
return NamedParcelTarget(world, player, 0)
}
}

View File

@@ -0,0 +1,162 @@
package io.dico.parcels2.command
import io.dico.dicore.command.parameter.ArgumentBuffer
import io.dico.dicore.command.parameter.Parameter
import io.dico.dicore.command.parameter.type.ParameterConfig
import io.dico.dicore.command.parameter.type.ParameterType
import io.dico.parcels2.*
import io.dico.parcels2.util.Vec2i
import io.dico.parcels2.util.floor
import io.dico.parcels2.util.isValid
import kotlinx.coroutines.experimental.Deferred
import org.bukkit.Bukkit
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
abstract suspend fun ParcelsPlugin.getParcelSuspend(): Parcel?
fun ParcelsPlugin.getParcelDeferred(): Deferred<Parcel?> = functionHelper.deferUndispatchedOnMainThread { getParcelSuspend() }
class ByID(world: ParcelWorld, val id: Vec2i?, isDefault: Boolean) : ParcelTarget(world, isDefault) {
override suspend fun ParcelsPlugin.getParcelSuspend(): Parcel? = getParcel()
fun getParcel() = id?.let { world.getParcelById(it) }
val isPath: Boolean get() = id == null
}
class ByOwner(world: ParcelWorld, val owner: ParcelOwner, val index: Int, isDefault: Boolean) : ParcelTarget(world, isDefault) {
init {
if (index < 0) throw IllegalArgumentException("Invalid parcel home index: $index")
}
override suspend fun ParcelsPlugin.getParcelSuspend(): Parcel? {
val ownedParcelsSerialized = storage.getOwnedParcels(owner).await()
val ownedParcels = ownedParcelsSerialized
.map { parcelProvider.getParcelById(it) }
.filter { it != null && world == it.world && owner == it.owner }
return ownedParcels.getOrNull(index)
}
}
annotation class Kind(val kind: Int)
companion object Config : ParameterConfig<Kind, Int>(Kind::class.java) {
override fun toParameterInfo(annotation: Kind): Int {
return annotation.kind
}
const val ID = 1 // ID
const val OWNER_REAL = 2 // an owner backed by a UUID
const val OWNER_FAKE = 3 // an owner not backed by a UUID
const val OWNER = OWNER_REAL or OWNER_FAKE // any owner
const val ANY = ID or OWNER_REAL or OWNER_FAKE // any
const val REAL = ID or OWNER_REAL // no owner not backed by a UUID
const val DEFAULT_KIND = REAL
const val PREFER_OWNED_FOR_DEFAULT = 4 // if the kind can be ID and OWNER_REAL, prefer OWNER_REAL for default
// instead of parcel that the player is in
}
class PType(val parcelProvider: ParcelProvider) : ParameterType<ParcelTarget, Int>(ParcelTarget::class.java, ParcelTarget.Config) {
override fun parse(parameter: Parameter<ParcelTarget, Int>, sender: CommandSender, buffer: ArgumentBuffer): ParcelTarget {
var input = buffer.next()
val worldString = input.substringBefore("->", missingDelimiterValue = "")
input = input.substringAfter("->")
val world = if (worldString.isEmpty()) {
val player = requirePlayer(sender, parameter, "the world")
parcelProvider.getWorld(player.world)
?: invalidInput(parameter, "You cannot omit the world if you're not in a parcel world")
} else {
parcelProvider.getWorld(worldString) ?: invalidInput(parameter, "$worldString is not a parcel world")
}
val kind = parameter.paramInfo ?: DEFAULT_KIND
if (input.contains(',')) {
if (kind and ID == 0) invalidInput(parameter, "You must specify a parcel by OWNER, that is, an owner and index")
return ByID(world, getId(parameter, input), false)
}
if (kind and OWNER == 0) invalidInput(parameter, "You must specify a parcel by ID, that is, the x and z component separated by a comma")
val (owner, index) = getHomeIndex(parameter, sender, input)
return ByOwner(world, owner, index, false)
}
private fun getId(parameter: Parameter<*, *>, input: String): Vec2i {
val x = input.substringBefore(',').run {
toIntOrNull() ?: invalidInput(parameter, "ID(x) must be an integer, $this is not an integer")
}
val z = input.substringAfter(',').run {
toIntOrNull() ?: invalidInput(parameter, "ID(z) must be an integer, $this is not an integer")
}
return Vec2i(x, z)
}
private fun getHomeIndex(parameter: Parameter<*, Int>, sender: CommandSender, input: String): Pair<ParcelOwner, Int> {
val splitIdx = input.indexOf(':')
val ownerString: String
val indexString: String
if (splitIdx == -1) {
// just the index.
ownerString = ""
indexString = input
} else {
ownerString = input.substring(0, splitIdx)
indexString = input.substring(splitIdx + 1)
}
val owner = if (ownerString.isEmpty())
ParcelOwner(requirePlayer(sender, parameter, "the player"))
else
inputAsOwner(parameter, ownerString)
val index = if (indexString.isEmpty()) 0 else indexString.toIntOrNull()
?: invalidInput(parameter, "The home index must be an integer, $indexString is not an integer")
return owner to index
}
private fun requirePlayer(sender: CommandSender, parameter: Parameter<*, *>, objName: String): Player {
if (sender !is Player) invalidInput(parameter, "console cannot omit the $objName")
return sender
}
@Suppress("DEPRECATION")
private fun inputAsOwner(parameter: Parameter<*, Int>, input: String): ParcelOwner {
val kind = parameter.paramInfo ?: DEFAULT_KIND
if (kind and OWNER_REAL == 0) {
return ParcelOwner(input)
}
val player = Bukkit.getOfflinePlayer(input).takeIf { it.isValid }
if (player == null) {
if (kind and OWNER_FAKE == 0) invalidInput(parameter, "The player $input does not exist")
return ParcelOwner(input)
}
return ParcelOwner(player)
}
override fun getDefaultValue(parameter: Parameter<ParcelTarget, Int>, sender: CommandSender, buffer: ArgumentBuffer): ParcelTarget? {
val kind = parameter.paramInfo ?: DEFAULT_KIND
val useLocation = when {
kind and REAL == REAL -> kind and PREFER_OWNED_FOR_DEFAULT == 0
kind and ID != 0 -> true
kind and OWNER_REAL != 0 -> false
else -> return null
}
val player = requirePlayer(sender, parameter, "the parcel")
val world = parcelProvider.getWorld(player.world) ?: invalidInput(parameter, "You must be in a parcel world to omit the parcel")
if (useLocation) {
val id = player.location.let { world.getParcelIdAt(it.x.floor(), it.z.floor())?.pos }
return ByID(world, id, true)
}
return ByOwner(world, ParcelOwner(player), 0, true)
}
}
}

View File

@@ -4,5 +4,8 @@ import io.dico.dicore.command.chat.AbstractChatController
class ParcelsChatController : AbstractChatController() {
override fun filterMessage(message: String?): String {
return "[Parcels] $message"
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,163 @@
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(field: StringBuilder.() -> Unit, value: StringBuilder.() -> Unit) {
append(infoStringColor1)
field()
append(": ")
append(infoStringColor2)
value()
append(' ')
}
private inline fun StringBuilder.appendField(name: String, value: StringBuilder.() -> Unit) {
append(infoStringColor1)
append(name)
append(": ")
append(infoStringColor2)
value()
append(' ')
}
private fun StringBuilder.appendAddedList(local: Map<UUID, AddedStatus>, global: Map<UUID, AddedStatus>, status: AddedStatus, fieldName: String) {
val globalSet = global.filterValues { it == status }.keys
val localList = local.filterValues { it == status }.keys.filter { it !in globalSet }
val stringList = globalSet.map(::getPlayerName).map { "(G)$it" } + localList.map(::getPlayerName)
if (stringList.isEmpty()) return
appendField({
append(fieldName)
append('(')
append(infoStringColor2)
append(stringList.size)
append(infoStringColor1)
append(')')
}) {
stringList.joinTo(this,
separator = infoStringColor1.toString() + ", " + infoStringColor2,
limit = 150)
}
}
operator fun getValue(parcel: Parcel, property: KProperty<*>): String = buildString {
appendField("ID") {
append(parcel.x)
append(',')
append(parcel.z)
}
val owner = parcel.owner
appendField("Owner") {
if (owner == null) {
append(infoStringColor1)
append("none")
} else {
append(owner.notNullName)
}
}
// plotme appends biome here
append('\n')
val global = owner?.let { parcel.world.globalAddedData[owner].addedMap } ?: emptyMap()
val local = parcel.addedMap
appendAddedList(local, global, AddedStatus.ALLOWED, "Allowed")
append('\n')
appendAddedList(local, global, AddedStatus.BANNED, "Banned")
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

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

View File

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

View File

@@ -1,11 +1,8 @@
package io.dico.parcels2.storage
import io.dico.parcels2.AddedData
import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelData
import io.dico.parcels2.ParcelOwner
import kotlinx.coroutines.experimental.channels.ProducerScope
import java.util.*
import io.dico.parcels2.*
import kotlinx.coroutines.experimental.channels.SendChannel
import java.util.UUID
interface Backing {
@@ -22,30 +19,32 @@ interface Backing {
* This producer function is capable of constantly reading parcels from a potentially infinite sequence,
* and provide parcel data for it as read from the database.
*/
suspend fun ProducerScope<Pair<Parcel, ParcelData?>>.produceParcelData(parcels: Sequence<Parcel>)
suspend fun produceParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>)
suspend fun ProducerScope<Pair<SerializableParcel, ParcelData?>>.produceAllParcelData()
suspend fun produceAllParcelData(channel: SendChannel<DataPair>)
suspend fun readParcelData(parcelFor: Parcel): ParcelData?
suspend fun readParcelData(parcel: ParcelId): ParcelData?
suspend fun getOwnedParcels(user: ParcelOwner): List<SerializableParcel>
suspend fun getOwnedParcels(user: ParcelOwner): List<ParcelId>
suspend fun getNumParcels(user: ParcelOwner): Int = getOwnedParcels(user).size
suspend fun setParcelData(parcelFor: Parcel, data: ParcelData?)
suspend fun setParcelData(parcel: ParcelId, data: ParcelData?)
suspend fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?)
suspend fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?)
suspend fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?)
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 readGlobalPlayerStateData(owner: ParcelOwner): AddedData?
suspend fun produceAllGlobalAddedData(channel: SendChannel<AddedDataPair<ParcelOwner>>)
suspend fun setGlobalPlayerState(owner: ParcelOwner, player: UUID, state: Boolean?)
suspend fun readGlobalAddedData(owner: ParcelOwner): MutableAddedDataMap
suspend fun setGlobalPlayerStatus(owner: ParcelOwner, player: UUID, status: AddedStatus)
}

View File

@@ -1,322 +0,0 @@
package io.dico.parcels2.storage
import com.zaxxer.hikari.HikariDataSource
import io.dico.parcels2.*
import io.dico.parcels2.util.Vec2i
import io.dico.parcels2.util.toByteArray
import io.dico.parcels2.util.toUUID
import kotlinx.coroutines.experimental.channels.ProducerScope
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SchemaUtils.create
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.vendors.DatabaseDialect
import org.joda.time.DateTime
import java.util.*
import javax.sql.DataSource
object WorldsT : Table("worlds") {
val id = integer("world_id").autoIncrement().primaryKey()
val name = varchar("name", 50)
val uid = binary("uid", 16)
val index_uid = uniqueIndexR("index_uid", uid)
}
object ParcelsT : Table("parcels") {
val id = integer("parcel_id").autoIncrement().primaryKey()
val px = integer("px")
val pz = integer("pz")
val world_id = integer("world_id").references(WorldsT.id)
val owner_uuid = binary("owner_uuid", 16).nullable()
val owner_name = varchar("owner_name", 16).nullable()
val claim_time = datetime("claim_time").nullable()
val index_location = uniqueIndexR("index_location", world_id, px, pz)
}
object AddedLocalT : Table("parcels_added_local") {
val parcel_id = integer("parcel_id").references(ParcelsT.id, ReferenceOption.CASCADE)
val player_uuid = binary("player_uuid", 16)
val allowed_flag = bool("allowed_flag")
val index_pair = uniqueIndexR("index_pair", parcel_id, player_uuid)
}
object AddedGlobalT : Table("parcels_added_global") {
val owner_uuid = binary("owner_uuid", 16)
val player_uuid = binary("player_uuid", 16)
val allowed_flag = bool("allowed_flag")
val index_pair = uniqueIndexR("index_pair", owner_uuid, player_uuid)
}
object ParcelOptionsT : Table("parcel_options") {
val parcel_id = integer("parcel_id").primaryKey().references(ParcelsT.id, ReferenceOption.CASCADE)
val interact_inventory = bool("interact_inventory").default(false)
val interact_inputs = bool("interact_inputs").default(false)
}
private class ExposedDatabaseException(message: String? = null) : Exception(message)
@Suppress("NOTHING_TO_INLINE")
class ExposedBacking(private val dataSourceFactory: () -> DataSource) : Backing {
override val name get() = "Exposed"
private var dataSource: DataSource? = null
private var database: Database? = null
private var isShutdown: Boolean = false
override val isConnected get() = database != null
companion object {
init {
Database.registerDialect("mariadb") {
Class.forName("org.jetbrains.exposed.sql.vendors.MysqlDialect").newInstance() as DatabaseDialect
}
}
}
override suspend fun init() {
if (isShutdown) throw IllegalStateException()
dataSource = dataSourceFactory()
database = Database.connect(dataSource!!)
transaction(database) {
create(WorldsT, ParcelsT, AddedLocalT, ParcelOptionsT)
}
}
override suspend fun shutdown() {
if (isShutdown) throw IllegalStateException()
dataSource?.let {
if (it is HikariDataSource) it.close()
}
database = null
isShutdown = true
}
private fun <T> transaction(statement: Transaction.() -> T) = transaction(database, statement)
private inline fun Transaction.getWorldId(binaryUid: ByteArray): Int? {
return WorldsT.select { WorldsT.uid eq binaryUid }.firstOrNull()?.let { it[WorldsT.id] }
}
private inline fun Transaction.getWorldId(worldUid: UUID): Int? {
return getWorldId(worldUid.toByteArray()!!)
}
private inline fun Transaction.getOrInitWorldId(worldUid: UUID, worldName: String): Int {
val binaryUid = worldUid.toByteArray()!!
return getWorldId(binaryUid)
?: WorldsT.insert /*Ignore*/ { it[uid] = binaryUid; it[name] = worldName }.get(WorldsT.id)
?: throw ExposedDatabaseException("This should not happen - failed to insert world named $worldName and get its id")
}
private inline fun Transaction.getParcelId(worldId: Int, parcelX: Int, parcelZ: Int): Int? {
return ParcelsT.select { (ParcelsT.world_id eq worldId) and (ParcelsT.px eq parcelX) and (ParcelsT.pz eq parcelZ) }
.firstOrNull()?.let { it[ParcelsT.id] }
}
private inline fun Transaction.getParcelId(worldUid: UUID, parcelX: Int, parcelZ: Int): Int? {
return getWorldId(worldUid)?.let { getParcelId(it, parcelX, parcelZ) }
}
private inline fun Transaction.getOrInitParcelId(worldUid: UUID, worldName: String, parcelX: Int, parcelZ: Int): Int {
val worldId = getOrInitWorldId(worldUid, worldName)
return getParcelId(worldId, parcelX, parcelZ)
?: ParcelsT.insert /*Ignore*/ { it[world_id] = worldId; it[px] = parcelX; it[pz] = parcelZ }.get(ParcelsT.id)
?: throw ExposedDatabaseException("This should not happen - failed to insert parcel at $worldName($parcelX, $parcelZ)")
}
private inline fun Transaction.getParcelRow(id: Int): ResultRow? {
return ParcelsT.select { ParcelsT.id eq id }.firstOrNull()
}
fun Transaction.getWorldId(world: ParcelWorld): Int? {
return getWorldId(world.world.uid)
}
fun Transaction.getOrInitWorldId(world: ParcelWorld): Int {
return world.world.let { getOrInitWorldId(it.uid, it.name) }
}
fun Transaction.getParcelId(parcel: Parcel): Int? {
return getParcelId(parcel.world.world.uid, parcel.pos.x, parcel.pos.z)
}
fun Transaction.getOrInitParcelId(parcel: Parcel): Int {
return parcel.world.world.let { getOrInitParcelId(it.uid, it.name, parcel.pos.x, parcel.pos.z) }
}
fun Transaction.getParcelRow(parcel: Parcel): ResultRow? {
return getParcelId(parcel)?.let { getParcelRow(it) }
}
override suspend fun ProducerScope<Pair<Parcel, ParcelData?>>.produceParcelData(parcels: Sequence<Parcel>) {
for (parcel in parcels) {
val data = readParcelData(parcel)
channel.send(parcel to data)
}
channel.close()
}
override suspend fun ProducerScope<Pair<SerializableParcel, ParcelData?>>.produceAllParcelData() {
ParcelsT.selectAll().forEach { row ->
val parcel = rowToSerializableParcel(row) ?: return@forEach
val data = rowToParcelData(row)
channel.send(parcel to data)
}
channel.close()
}
override suspend fun readParcelData(parcelFor: Parcel): ParcelData? = transaction {
val row = getParcelRow(parcelFor) ?: return@transaction null
rowToParcelData(row)
}
override suspend fun getOwnedParcels(user: ParcelOwner): List<SerializableParcel> = transaction {
val where: SqlExpressionBuilder.() -> Op<Boolean>
if (user.uuid != null) {
val binaryUuid = user.uuid.toByteArray()
where = { ParcelsT.owner_uuid eq binaryUuid }
} else {
val name = user.name
where = { ParcelsT.owner_name eq name }
}
ParcelsT.select(where)
.orderBy(ParcelsT.claim_time, isAsc = true)
.mapNotNull(::rowToSerializableParcel)
.toList()
}
override suspend fun setParcelData(parcelFor: Parcel, data: ParcelData?) {
if (data == null) {
transaction {
getParcelId(parcelFor)?.let { id ->
ParcelsT.deleteIgnoreWhere() { ParcelsT.id eq id }
// Below should cascade automatically
/*
AddedLocalT.deleteIgnoreWhere { AddedLocalT.parcel_id eq id }
ParcelOptionsT.deleteIgnoreWhere(limit = 1) { ParcelOptionsT.parcel_id eq id }
*/
}
}
return
}
val id = transaction {
val id = getOrInitParcelId(parcelFor)
AddedLocalT.deleteIgnoreWhere { AddedLocalT.parcel_id eq id }
id
}
setParcelOwner(parcelFor, data.owner)
for ((uuid, status) in data.added) {
val state = status.asBoolean
setParcelPlayerState(parcelFor, uuid, state)
}
setParcelAllowsInteractInputs(parcelFor, data.allowInteractInputs)
setParcelAllowsInteractInventory(parcelFor, data.allowInteractInventory)
}
override suspend fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) = transaction {
val binaryUuid = owner?.uuid?.toByteArray()
val name = owner?.name
val time = owner?.let { DateTime.now() }
val id = if (owner == null)
getParcelId(parcelFor) ?: return@transaction
else
getOrInitParcelId(parcelFor)
ParcelsT.update({ ParcelsT.id eq id }) {
it[ParcelsT.owner_uuid] = binaryUuid
it[ParcelsT.owner_name] = name
it[ParcelsT.claim_time] = time
}
}
override suspend fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?) = transaction {
val binaryUuid = player.toByteArray()!!
if (state == null) {
getParcelId(parcelFor)?.let { id ->
AddedLocalT.deleteWhere { (AddedLocalT.parcel_id eq id) and (AddedLocalT.player_uuid eq binaryUuid) }
}
return@transaction
}
val id = getOrInitParcelId(parcelFor)
AddedLocalT.upsert(AddedLocalT.parcel_id) {
it[AddedLocalT.parcel_id] = id
it[AddedLocalT.player_uuid] = binaryUuid
}
}
override suspend fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean): Unit = transaction {
val id = getOrInitParcelId(parcel)
/*ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
it[ParcelOptionsT.parcel_id] = id
it[ParcelOptionsT.interact_inventory] = value
}*/
ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
it[ParcelOptionsT.parcel_id] = id
it[ParcelOptionsT.interact_inventory] = value
}
}
override suspend fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean): Unit = transaction {
val id = getOrInitParcelId(parcel)
ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
it[ParcelOptionsT.parcel_id] = id
it[ParcelOptionsT.interact_inputs] = value
}
}
override suspend fun readGlobalPlayerStateData(owner: ParcelOwner): AddedData? {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override suspend fun setGlobalPlayerState(owner: ParcelOwner, player: UUID, state: Boolean?) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
private fun rowToSerializableParcel(row: ResultRow): SerializableParcel? {
val worldId = row[ParcelsT.world_id]
val worldRow = WorldsT.select { WorldsT.id eq worldId }.firstOrNull()
?: return null
val world = SerializableWorld(worldRow[WorldsT.name], worldRow[WorldsT.uid].toUUID())
return SerializableParcel(world, Vec2i(row[ParcelsT.px], row[ParcelsT.pz]))
}
private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply {
owner = ParcelOwner.create(
uuid = row[ParcelsT.owner_uuid]?.toUUID(),
name = row[ParcelsT.owner_name],
time = row[ParcelsT.claim_time]
)
val parcelId = row[ParcelsT.id]
AddedLocalT.select { AddedLocalT.parcel_id eq parcelId }.forEach {
val uuid = it[AddedLocalT.player_uuid].toUUID()!!
val status = if (it[AddedLocalT.allowed_flag]) AddedStatus.ALLOWED else AddedStatus.BANNED
setAddedStatus(uuid, status)
}
ParcelOptionsT.select { ParcelOptionsT.parcel_id eq parcelId }.firstOrNull()?.let {
allowInteractInputs = it[ParcelOptionsT.interact_inputs]
allowInteractInventory = it[ParcelOptionsT.interact_inventory]
}
}
}

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.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.KotlinModule
import io.dico.parcels2.GeneratorFactory
import io.dico.parcels2.GeneratorFactories
import io.dico.parcels2.GeneratorOptions
import io.dico.parcels2.StorageOptions
import org.bukkit.Bukkit
@@ -100,7 +100,7 @@ class GeneratorOptionsDeserializer : JsonDeserializer<GeneratorOptions>() {
val node = parser!!.readValueAsTree<JsonNode>()
val name = node.get("name").asText()
val optionsNode = node.get("options")
val factory = GeneratorFactory.getFactory(name) ?: throw IllegalStateException("Unknown generator: $name")
val factory = GeneratorFactories.getFactory(name) ?: throw IllegalStateException("Unknown generator: $name")
return parser.codec.treeToValue(optionsNode, factory.optionsClass.java)
}

View File

@@ -1,37 +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() }
}
/**
* 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
import io.dico.parcels2.AddedData
import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelData
import io.dico.parcels2.ParcelOwner
import io.dico.parcels2.*
import kotlinx.coroutines.experimental.*
import kotlinx.coroutines.experimental.channels.ProducerScope
import kotlinx.coroutines.experimental.channels.ReceiveChannel
import kotlinx.coroutines.experimental.channels.produce
import java.util.*
import java.util.UUID
import java.util.concurrent.Executor
import java.util.concurrent.Executors
typealias DataPair = Pair<ParcelId, ParcelData?>
typealias AddedDataPair<TAttach> = Pair<TAttach, MutableAddedDataMap>
interface Storage {
val name: String
val syncDispatcher: CoroutineDispatcher
@@ -22,31 +25,33 @@ interface Storage {
fun shutdown(): Job
fun readParcelData(parcelFor: Parcel): Deferred<ParcelData?>
fun readParcelData(parcel: ParcelId): Deferred<ParcelData?>
fun readParcelData(parcelsFor: Sequence<Parcel>, channelCapacity: Int): ReceiveChannel<Pair<Parcel, ParcelData?>>
fun readParcelData(parcels: Sequence<ParcelId>): ReceiveChannel<DataPair>
fun readAllParcelData(channelCapacity: Int): ReceiveChannel<Pair<SerializableParcel, ParcelData?>>
fun readAllParcelData(): ReceiveChannel<DataPair>
fun getOwnedParcels(user: ParcelOwner): Deferred<List<SerializableParcel>>
fun getOwnedParcels(user: ParcelOwner): Deferred<List<ParcelId>>
fun getNumParcels(user: ParcelOwner): Deferred<Int>
fun setParcelData(parcelFor: Parcel, data: ParcelData?): Job
fun setParcelData(parcel: ParcelId, data: ParcelData?): Job
fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?): Job
fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?): Job
fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?): 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 readGlobalPlayerStateData(owner: ParcelOwner): Deferred<AddedData?>
fun readAllGlobalAddedData(): ReceiveChannel<AddedDataPair<ParcelOwner>>
fun setGlobalPlayerState(owner: ParcelOwner, player: UUID, state: Boolean?): Job
fun readGlobalAddedData(owner: ParcelOwner): Deferred<MutableAddedDataMap?>
fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus): Job
}
class StorageWithCoroutineBacking internal constructor(val backing: Backing) : Storage {
@@ -55,46 +60,49 @@ class StorageWithCoroutineBacking internal constructor(val backing: Backing) : S
val poolSize: Int get() = 4
override val asyncDispatcher = Executors.newFixedThreadPool(poolSize) { Thread(it, "Parcels2_StorageThread") }.asCoroutineDispatcher()
override val isConnected get() = backing.isConnected
val channelCapacity = 16
@Suppress("NOTHING_TO_INLINE")
private inline fun <T> defer(noinline block: suspend CoroutineScope.() -> T): Deferred<T> {
return async(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block)
}
@Suppress("NOTHING_TO_INLINE")
private inline fun job(noinline block: suspend CoroutineScope.() -> Unit): Job {
return launch(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block)
}
private inline fun <T> openChannel(noinline block: suspend ProducerScope<T>.() -> Unit): ReceiveChannel<T> {
return produce(asyncDispatcher, capacity = channelCapacity, block = block)
}
override fun init() = job { backing.init() }
override fun shutdown() = job { backing.shutdown() }
override fun readParcelData(parcelFor: Parcel) = defer { backing.readParcelData(parcelFor) }
override fun readParcelData(parcel: ParcelId) = defer { backing.readParcelData(parcel) }
override fun readParcelData(parcelsFor: Sequence<Parcel>, channelCapacity: Int) =
produce(asyncDispatcher, capacity = channelCapacity) { with(backing) { produceParcelData(parcelsFor) } }
override fun readParcelData(parcels: Sequence<ParcelId>) = openChannel<DataPair> { backing.produceParcelData(channel, parcels) }
override fun readAllParcelData(channelCapacity: Int): ReceiveChannel<Pair<SerializableParcel, ParcelData?>> =
produce(asyncDispatcher, capacity = channelCapacity) { with(backing) { produceAllParcelData() } }
override fun readAllParcelData() = openChannel<DataPair> { backing.produceAllParcelData(channel) }
override fun getOwnedParcels(user: ParcelOwner) = defer { backing.getOwnedParcels(user) }
override fun getNumParcels(user: ParcelOwner) = defer { backing.getNumParcels(user) }
override fun setParcelData(parcelFor: Parcel, data: ParcelData?) = job { backing.setParcelData(parcelFor, data) }
override fun setParcelData(parcel: ParcelId, data: ParcelData?) = job { backing.setParcelData(parcel, data) }
override fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) = job { backing.setParcelOwner(parcelFor, owner) }
override fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?) = job { backing.setParcelOwner(parcel, owner) }
override fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?) = job { backing.setParcelPlayerState(parcelFor, player, state) }
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 readGlobalPlayerStateData(owner: ParcelOwner): Deferred<AddedData?> = defer { backing.readGlobalPlayerStateData(owner) }
override fun readAllGlobalAddedData(): ReceiveChannel<AddedDataPair<ParcelOwner>> = openChannel { backing.produceAllGlobalAddedData(channel) }
override fun setGlobalPlayerState(owner: ParcelOwner, player: UUID, state: Boolean?) = job { backing.setGlobalPlayerState(owner, player, state) }
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) }
}

View File

@@ -2,6 +2,7 @@ package io.dico.parcels2.storage
import com.zaxxer.hikari.HikariDataSource
import io.dico.parcels2.DataConnectionOptions
import io.dico.parcels2.storage.exposed.ExposedBacking
import kotlin.reflect.KClass
interface StorageFactory {
@@ -35,8 +36,8 @@ class ConnectionStorageFactory : StorageFactory {
override fun newStorageInstance(dialect: String, options: Any): Storage {
val hikariConfig = getHikariConfig(dialect, options as DataConnectionOptions)
val dataSourceFactory = { HikariDataSource(hikariConfig) }
val dataSourceFactory = suspend { HikariDataSource(hikariConfig) }
return StorageWithCoroutineBacking(ExposedBacking(dataSourceFactory))
}
}
}

View File

@@ -0,0 +1,196 @@
@file:Suppress("NOTHING_TO_INLINE", "PARAMETER_NAME_CHANGED_ON_OVERRIDE", "LocalVariableName")
package io.dico.parcels2.storage.exposed
import com.zaxxer.hikari.HikariDataSource
import io.dico.parcels2.*
import io.dico.parcels2.storage.Backing
import io.dico.parcels2.storage.DataPair
import io.dico.parcels2.util.toUUID
import kotlinx.coroutines.experimental.CoroutineStart
import kotlinx.coroutines.experimental.Unconfined
import kotlinx.coroutines.experimental.channels.SendChannel
import kotlinx.coroutines.experimental.launch
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SchemaUtils.create
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.vendors.DatabaseDialect
import org.joda.time.DateTime
import java.util.UUID
import javax.sql.DataSource
class ExposedDatabaseException(message: String? = null) : Exception(message)
class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) : Backing {
override val name get() = "Exposed"
private var dataSource: DataSource? = null
private var database: Database? = null
private var isShutdown: Boolean = false
override val isConnected get() = database != null
companion object {
init {
Database.registerDialect("mariadb") {
Class.forName("org.jetbrains.exposed.sql.vendors.MysqlDialect").newInstance() as DatabaseDialect
}
}
}
private fun <T> transaction(statement: Transaction.() -> T) = transaction(database!!, statement)
private suspend fun transactionLaunch(statement: suspend Transaction.() -> Unit): Unit = transaction(database!!) {
launch(context = Unconfined, start = CoroutineStart.UNDISPATCHED) {
statement(this@transaction)
}
}
override suspend fun init() {
if (isShutdown) throw IllegalStateException()
dataSource = dataSourceFactory()
database = Database.connect(dataSource!!)
transaction(database) {
create(WorldsT, OwnersT, ParcelsT, ParcelOptionsT, AddedLocalT, AddedGlobalT)
}
}
override suspend fun shutdown() {
if (isShutdown) throw IllegalStateException()
dataSource?.let {
(it as? HikariDataSource)?.close()
}
database = null
isShutdown = true
}
override suspend fun produceParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>) {
for (parcel in parcels) {
val data = readParcelData(parcel)
channel.send(parcel to data)
}
channel.close()
}
override suspend fun produceAllParcelData(channel: SendChannel<Pair<ParcelId, ParcelData?>>) = transactionLaunch {
ParcelsT.selectAll().forEach { row ->
val parcel = ParcelsT.getId(row) ?: return@forEach
val data = rowToParcelData(row)
channel.send(parcel to data)
}
channel.close()
}
override suspend fun readParcelData(parcel: ParcelId): ParcelData? = transaction {
val row = ParcelsT.getRow(parcel) ?: return@transaction null
rowToParcelData(row)
}
override suspend fun getOwnedParcels(user: ParcelOwner): List<ParcelId> = transaction {
val user_id = OwnersT.getId(user) ?: return@transaction emptyList()
ParcelsT.select { ParcelsT.owner_id eq user_id }
.orderBy(ParcelsT.claim_time, isAsc = true)
.mapNotNull(ParcelsT::getId)
.toList()
}
override suspend fun setParcelData(parcel: ParcelId, data: ParcelData?) {
if (data == null) {
transaction {
ParcelsT.getId(parcel)?.let { id ->
ParcelsT.deleteIgnoreWhere { ParcelsT.id eq id }
// Below should cascade automatically
/*
AddedLocalT.deleteIgnoreWhere { AddedLocalT.parcel_id eq id }
ParcelOptionsT.deleteIgnoreWhere(limit = 1) { ParcelOptionsT.parcel_id eq id }
*/
}
}
return
}
transaction {
val id = ParcelsT.getOrInitId(parcel)
AddedLocalT.deleteIgnoreWhere { AddedLocalT.attach_id eq id }
}
setParcelOwner(parcel, data.owner)
for ((uuid, status) in data.addedMap) {
setLocalPlayerStatus(parcel, uuid, status)
}
setParcelAllowsInteractInputs(parcel, data.allowInteractInputs)
setParcelAllowsInteractInventory(parcel, data.allowInteractInventory)
}
override suspend fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?) = transaction {
val id = if (owner == null)
ParcelsT.getId(parcel) ?: return@transaction
else
ParcelsT.getOrInitId(parcel)
val owner_id = owner?.let { OwnersT.getOrInitId(it) }
val time = owner?.let { DateTime.now() }
ParcelsT.update({ ParcelsT.id eq id }) {
it[ParcelsT.owner_id] = owner_id
it[claim_time] = time
}
}
override suspend fun setLocalPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus) = transaction {
AddedLocalT.setPlayerStatus(parcel, player, status)
}
override suspend fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean): Unit = transaction {
val id = ParcelsT.getOrInitId(parcel)
ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
it[ParcelOptionsT.parcel_id] = id
it[ParcelOptionsT.interact_inventory] = value
}
}
override suspend fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean): Unit = transaction {
val id = ParcelsT.getOrInitId(parcel)
ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
it[ParcelOptionsT.parcel_id] = id
it[ParcelOptionsT.interact_inputs] = value
}
}
override suspend fun produceAllGlobalAddedData(channel: SendChannel<Pair<ParcelOwner, MutableMap<UUID, AddedStatus>>>) = transactionLaunch {
AddedGlobalT.sendAllAddedData(channel)
channel.close()
}
override suspend fun readGlobalAddedData(owner: ParcelOwner): MutableMap<UUID, AddedStatus> = transaction {
return@transaction AddedGlobalT.readAddedData(OwnersT.getId(owner) ?: return@transaction hashMapOf())
}
override suspend fun setGlobalPlayerStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) = transaction {
AddedGlobalT.setPlayerStatus(owner, player, status)
}
private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply {
owner = row[ParcelsT.owner_id]?.let { OwnersT.getId(it) }
since = row[ParcelsT.claim_time]
val parcelId = row[ParcelsT.id]
addedMap = AddedLocalT.readAddedData(parcelId)
AddedLocalT.select { AddedLocalT.attach_id eq parcelId }.forEach {
val uuid = it[AddedLocalT.player_uuid].toUUID()
val status = if (it[AddedLocalT.allowed_flag]) AddedStatus.ALLOWED else AddedStatus.BANNED
setAddedStatus(uuid, status)
}
ParcelOptionsT.select { ParcelOptionsT.parcel_id eq parcelId }.firstOrNull()?.let {
allowInteractInputs = it[ParcelOptionsT.interact_inputs]
allowInteractInventory = it[ParcelOptionsT.interact_inventory]
}
}
}

View File

@@ -1,4 +1,4 @@
package io.dico.parcels2.storage
package io.dico.parcels2.storage.exposed
import org.jetbrains.exposed.sql.Column
import org.jetbrains.exposed.sql.Index
@@ -40,7 +40,7 @@ class UpsertStatement<Key : Any>(table: Table, conflictColumn: Column<*>? = null
} else {
append (" ON DUPLICATE KEY UPDATE ")
append(" ON DUPLICATE KEY UPDATE ")
values.keys.filter { it !in indexColumns }.joinTo(this) { "${transaction.identity(it)}=VALUES(${transaction.identity(it)})" }
}

View File

@@ -0,0 +1,118 @@
@file:Suppress("NOTHING_TO_INLINE", "PARAMETER_NAME_CHANGED_ON_OVERRIDE", "unused", "MemberVisibilityCanBePrivate")
package io.dico.parcels2.storage.exposed
import io.dico.parcels2.ParcelId
import io.dico.parcels2.ParcelOwner
import io.dico.parcels2.ParcelWorldId
import io.dico.parcels2.util.toByteArray
import io.dico.parcels2.util.toUUID
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.InsertStatement
import java.util.UUID
sealed class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj>, QueryObj>(tableName: String, columnName: String)
: Table(tableName) {
val id = integer(columnName).autoIncrement().primaryKey()
@Suppress("UNCHECKED_CAST")
inline val table: TableT
get() = this as TableT
internal inline fun getId(where: SqlExpressionBuilder.(TableT) -> Op<Boolean>): Int? {
return select { where(table) }.firstOrNull()?.let { it[id] }
}
internal inline fun insertAndGetId(objName: String, noinline body: TableT.(InsertStatement<Number>) -> Unit): Int {
return table.insert(body)[id] ?: insertError(objName)
}
private inline fun insertError(obj: String): Nothing = throw ExposedDatabaseException("This should not happen - failed to insert $obj and getParcelDeferred its id")
abstract fun getId(obj: QueryObj): Int?
abstract fun getOrInitId(obj: QueryObj): Int
fun getId(id: Int): QueryObj? = select { this@IdTransactionsTable.id eq id }.firstOrNull()?.let { getId(it) }
abstract fun getId(row: ResultRow): QueryObj?
}
object WorldsT : IdTransactionsTable<WorldsT, ParcelWorldId>("parcel_worlds", "world_id") {
val name = varchar("name", 50)
val uid = binary("uid", 16).nullable()
val index_name = uniqueIndexR("index_name", name)
val index_uid = uniqueIndexR("index_uid", uid)
internal inline fun getId(worldName: String, binaryUid: ByteArray?): Int? = getId { (name eq worldName).let { if (binaryUid == null) it else it or (uid eq binaryUid) } }
internal inline fun getId(worldName: String, uid: UUID?): Int? = getId(worldName, uid?.toByteArray())
internal inline fun getOrInitId(worldName: String, worldUid: UUID?): Int = worldUid?.toByteArray().let { binaryUid ->
getId(worldName, binaryUid)
?: insertAndGetId("world named $worldName") { it[name] = worldName; binaryUid?.let { buid -> it[uid] = buid } }
}
override fun getId(world: ParcelWorldId): Int? = getId(world.name, world.uid)
override fun getOrInitId(world: ParcelWorldId): Int = getOrInitId(world.name, world.uid)
override fun getId(row: ResultRow): ParcelWorldId {
return ParcelWorldId(row[name], row[uid]?.toUUID())
}
}
object ParcelsT : IdTransactionsTable<ParcelsT, ParcelId>("parcels", "parcel_id") {
val world_id = integer("world_id").references(WorldsT.id)
val px = integer("px")
val pz = integer("pz")
val owner_id = integer("owner_id").references(OwnersT.id).nullable()
val claim_time = datetime("claim_time").nullable()
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(worldName: String, worldUid: UUID?, parcelX: Int, parcelZ: Int): Int? = WorldsT.getId(worldName, worldUid)?.let { getId(it, parcelX, parcelZ) }
private inline fun getOrInitId(worldName: String, worldUid: UUID?, parcelX: Int, parcelZ: Int): Int {
val worldId = WorldsT.getOrInitId(worldName, worldUid)
return getId(worldId, parcelX, parcelZ)
?: insertAndGetId("parcel at $worldName($parcelX, $parcelZ)") { it[world_id] = worldId; it[px] = parcelX; it[pz] = parcelZ }
}
override fun getId(parcel: ParcelId): Int? = getId(parcel.worldId.name, parcel.worldId.uid, parcel.x, parcel.z)
override fun getOrInitId(parcel: ParcelId): Int = getOrInitId(parcel.worldId.name, parcel.worldId.uid, parcel.x, parcel.z)
private inline fun getRow(id: Int): ResultRow? = select { ParcelsT.id eq id }.firstOrNull()
fun getRow(parcel: ParcelId): ResultRow? = getId(parcel)?.let { getRow(it) }
override fun getId(row: ResultRow): ParcelId? {
val worldId = row[world_id]
val world = WorldsT.getId(worldId) ?: return null
return ParcelId(world, row[px], row[pz])
}
}
object OwnersT : IdTransactionsTable<OwnersT, ParcelOwner>("parcel_owners", "owner_id") {
val uuid = binary("uuid", 16).nullable()
val name = varchar("name", 32)
val index_pair = uniqueIndexR("index_pair", uuid, name)
private inline fun getId(binaryUuid: ByteArray) = getId { uuid eq binaryUuid }
private inline fun getId(uuid: UUID) = getId(uuid.toByteArray())
private inline fun getId(nameIn: String) = getId { uuid.isNull() and (name eq nameIn) }
private inline fun getOrInitId(uuid: UUID, name: String) = uuid.toByteArray().let { binaryUuid ->
getId(binaryUuid) ?: insertAndGetId("owner(uuid = $uuid)") {
it[this@OwnersT.uuid] = binaryUuid
it[this@OwnersT.name] = name
}
}
private inline fun getOrInitId(name: String) =
getId(name) ?: insertAndGetId("owner(name = $name)") { it[OwnersT.name] = name }
override fun getId(owner: ParcelOwner): Int? =
if (owner.hasUUID) getId(owner.uuid!!)
else getId(owner.name!!)
override fun getOrInitId(owner: ParcelOwner): Int =
if (owner.hasUUID) getOrInitId(owner.uuid!!, owner.notNullName)
else getOrInitId(owner.name!!)
override fun getId(row: ResultRow): ParcelOwner {
return row[uuid]?.toUUID()?.let { ParcelOwner(it) } ?: ParcelOwner(row[name])
}
}

View File

@@ -0,0 +1,102 @@
@file:Suppress("PropertyName", "LocalVariableName", "NOTHING_TO_INLINE")
package io.dico.parcels2.storage.exposed
import io.dico.parcels2.AddedStatus
import io.dico.parcels2.ParcelId
import io.dico.parcels2.ParcelOwner
import io.dico.parcels2.util.toByteArray
import io.dico.parcels2.util.toUUID
import kotlinx.coroutines.experimental.channels.SendChannel
import org.jetbrains.exposed.sql.*
import java.util.UUID
object AddedLocalT : AddedTable<ParcelId>("parcels_added_local", ParcelsT)
object AddedGlobalT : AddedTable<ParcelOwner>("parcels_added_global", OwnersT)
object ParcelOptionsT : Table("parcel_options") {
val parcel_id = integer("parcel_id").primaryKey().references(ParcelsT.id, ReferenceOption.CASCADE)
val interact_inventory = bool("interact_inventory").default(true)
val interact_inputs = bool("interact_inputs").default(true)
}
typealias AddedStatusSendChannel<AttachT> = SendChannel<Pair<AttachT, MutableMap<UUID, AddedStatus>>>
sealed class AddedTable<AttachT>(name: String, val idTable: IdTransactionsTable<*, AttachT>) : Table(name) {
val attach_id = integer("attach_id").references(idTable.id, ReferenceOption.CASCADE)
val player_uuid = binary("player_uuid", 16)
val allowed_flag = bool("allowed_flag")
val index_pair = uniqueIndexR("index_pair", attach_id, player_uuid)
fun setPlayerStatus(attachedOn: AttachT, player: UUID, status: AddedStatus) {
val binaryUuid = player.toByteArray()
if (status.isDefault) {
idTable.getId(attachedOn)?.let { id ->
deleteWhere { (attach_id eq id) and (player_uuid eq binaryUuid) }
}
return
}
val id = idTable.getOrInitId(attachedOn)
upsert(conflictIndex = index_pair) {
it[attach_id] = id
it[player_uuid] = binaryUuid
it[allowed_flag] = status.isAllowed
}
}
fun readAddedData(id: Int): MutableMap<UUID, AddedStatus> {
return slice(player_uuid, allowed_flag).select { attach_id eq id }
.associateByTo(hashMapOf(), { it[player_uuid].toUUID() }, { it[allowed_flag].asAddedStatus() })
}
suspend fun sendAllAddedData(channel: AddedStatusSendChannel<AttachT>) {
/*
val iterator = selectAll().orderBy(attach_id).iterator()
if (iterator.hasNext()) {
val firstRow = iterator.next()
var id: Int = firstRow[attach_id]
var attach: SerializableT? = null
var map: MutableMap<UUID, AddedStatus>? = null
fun initAttachAndMap() {
attach = idTable.getId(id)
map = attach?.let { mutableMapOf() }
}
suspend fun sendIfPresent() {
if (attach != null && map != null && map!!.isNotEmpty()) {
channel.send(attach!! to map!!)
}
attach = null
map = null
}
initAttachAndMap()
for (row in iterator) {
val rowId = row[attach_id]
if (rowId != id) {
sendIfPresent()
id = rowId
initAttachAndMap()
}
if (attach == null) {
continue // owner not found for this owner id
}
val player_uuid = row[player_uuid].toUUID()
val status = row[allowed_flag].asAddedStatus()
map!![player_uuid] = status
}
sendIfPresent()
}*/
}
private inline fun Boolean?.asAddedStatus() = if (this == null) AddedStatus.DEFAULT else if (this) AddedStatus.ALLOWED else AddedStatus.BANNED
}

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,74 +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()
}
elapsedChecks++
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

@@ -0,0 +1,53 @@
package io.dico.parcels2.util
import io.dico.parcels2.ParcelsPlugin
import kotlinx.coroutines.experimental.*
import org.bukkit.scheduler.BukkitTask
import kotlin.coroutines.experimental.CoroutineContext
@Suppress("NOTHING_TO_INLINE")
class FunctionHelper(val plugin: ParcelsPlugin) {
val mainThreadDispatcher: MainThreadDispatcher = MainThreadDispatcherImpl()
fun <T> deferLazilyOnMainThread(block: suspend CoroutineScope.() -> T): Deferred<T> {
return async(context = mainThreadDispatcher, start = CoroutineStart.LAZY, block = block)
}
fun <T> deferUndispatchedOnMainThread(block: suspend CoroutineScope.() -> T): Deferred<T> {
return async(context = mainThreadDispatcher, start = CoroutineStart.UNDISPATCHED, block = block)
}
fun launchLazilyOnMainThread(block: suspend CoroutineScope.() -> Unit): Job {
return launch(context = mainThreadDispatcher, start = CoroutineStart.LAZY, block = block)
}
inline fun schedule(noinline task: () -> Unit) = schedule(0, task)
fun schedule(delay: Int, task: () -> Unit): BukkitTask {
return plugin.server.scheduler.runTaskLater(plugin, task, delay.toLong())
}
fun scheduleRepeating(delay: Int, interval: Int, task: () -> Unit): BukkitTask {
return plugin.server.scheduler.runTaskTimer(plugin, task, delay.toLong(), interval.toLong())
}
abstract class MainThreadDispatcher : CoroutineDispatcher() {
abstract val mainThread: Thread
abstract fun runOnMainThread(task: Runnable)
}
private inner class MainThreadDispatcherImpl : MainThreadDispatcher() {
override val mainThread: Thread = Thread.currentThread()
override fun dispatch(context: CoroutineContext, block: Runnable) {
runOnMainThread(block)
}
@Suppress("OVERRIDE_BY_INLINE")
override inline fun runOnMainThread(task: Runnable) {
if (Thread.currentThread() === mainThread) task.run()
else plugin.server.scheduler.runTaskLater(plugin, task, 0)
}
}
}

View File

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

View File

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

View File

@@ -1,26 +1,25 @@
package io.dico.parcels2.util
import org.bukkit.Bukkit
import org.jetbrains.annotations.Contract
import java.nio.ByteBuffer
import java.util.*
import java.util.UUID
@Suppress("UsePropertyAccessSyntax")
fun getPlayerName(uuid: UUID?, ifUnknown: String? = null): String {
return uuid?.let { Bukkit.getOfflinePlayer(uuid)?.takeIf { it.isValid }?.name }
fun getPlayerNameOrDefault(uuid: UUID?, ifUnknown: String? = null): String {
return uuid
?.let { getPlayerName(it) }
?: ifUnknown
?: ":unknown_name:"
}
@Contract("null -> null; !null -> !null", pure = true)
fun UUID?.toByteArray(): ByteArray? = this?.let {
fun getPlayerName(uuid: UUID): String? {
return Bukkit.getOfflinePlayer(uuid)?.takeIf { it.isValid }?.name
}
fun UUID.toByteArray(): ByteArray =
ByteBuffer.allocate(16).apply {
putLong(mostSignificantBits)
putLong(leastSignificantBits)
}.array()
}
@Contract("null -> null; !null -> !null", pure = true)
fun ByteArray?.toUUID(): UUID? = this?.let {
ByteBuffer.wrap(it).run { UUID(long, long) }
}
fun ByteArray.toUUID(): UUID = ByteBuffer.wrap(this).run { UUID(long, long) }

View File

@@ -16,4 +16,37 @@ data class Vec3i(
}
@Suppress("NOTHING_TO_INLINE")
inline operator fun World.get(vec: Vec3i): Block = getBlockAt(vec.x, vec.y, vec.z)
inline operator fun World.get(vec: Vec3i): Block = getBlockAt(vec.x, vec.y, vec.z)
/*
private /*inline */class IVec3i(private val data: Long) {
private companion object {
const val mask = 0x001F_FFFF
const val max: Int = 0x000F_FFFF // +1048575
const val min: Int = -max - 1 // -1048575 // 0xFFF0_0000
@Suppress("NOTHING_TO_INLINE")
inline fun Int.compressIntoLong(offset: Int): Long {
if (this !in min..max) throw IllegalArgumentException()
return and(mask).toLong().shl(offset)
}
@Suppress("NOTHING_TO_INLINE")
inline fun Long.extractInt(offset: Int): Int {
val result = ushr(offset).toInt().and(mask)
return if (result > max) result or mask.inv() else result
}
}
constructor(x: Int, y: Int, z: Int) : this(
x.compressIntoLong(42)
or y.compressIntoLong(21)
or z.compressIntoLong(0))
val x: Int get() = data.extractInt(42)
val y: Int get() = data.extractInt(21)
val z: Int get() = data.extractInt(0)
}
*/