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

View File

@@ -36,7 +36,7 @@ public class AbstractChatController implements IChatController {
@Override @Override
public void sendMessage(CommandSender sender, EMessageType type, String message) { public void sendMessage(CommandSender sender, EMessageType type, String message) {
if (message != null && !message.isEmpty()) { 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 package io.dico.parcels2
import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonIgnore
import io.dico.parcels2.blockvisitor.TickWorktimeOptions import io.dico.parcels2.blockvisitor.TickWorktimeOptions
import io.dico.parcels2.defaultimpl.DefaultGeneratorOptions
import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.Storage
import io.dico.parcels2.storage.StorageFactory import io.dico.parcels2.storage.StorageFactory
import io.dico.parcels2.storage.yamlObjectMapper import io.dico.parcels2.storage.yamlObjectMapper
import org.bukkit.Bukkit.createBlockData
import org.bukkit.GameMode import org.bukkit.GameMode
import org.bukkit.Material import org.bukkit.Material
import org.bukkit.block.Biome
import org.bukkit.block.data.BlockData
import java.io.Reader import java.io.Reader
import java.io.Writer import java.io.Writer
import java.util.* import java.util.EnumSet
class Options { class Options {
var worlds: Map<String, WorldOptions> = HashMap() var worlds: Map<String, WorldOptionsHolder> = hashMapOf()
private set private set
var storage: StorageOptions = StorageOptions("postgresql", DataConnectionOptions()) var storage: StorageOptions = StorageOptions("postgresql", DataConnectionOptions())
var tickWorktime: TickWorktimeOptions = TickWorktimeOptions(20, 1) var tickWorktime: TickWorktimeOptions = TickWorktimeOptions(20, 1)
fun addWorld(name: String, options: WorldOptions) = (worlds as MutableMap).put(name, options) fun addWorld(name: String,
generatorOptions: GeneratorOptions? = null,
worldOptions: WorldOptions? = null) {
val optionsHolder = WorldOptionsHolder(
generatorOptions ?: DefaultGeneratorOptions(),
worldOptions ?: WorldOptions()
)
(worlds as MutableMap).put(name, optionsHolder)
}
fun writeTo(writer: Writer) = yamlObjectMapper.writeValue(writer, this) fun writeTo(writer: Writer) = yamlObjectMapper.writeValue(writer, this)
@@ -31,6 +37,9 @@ class Options {
} }
class WorldOptionsHolder(var generator: GeneratorOptions = DefaultGeneratorOptions(),
var runtime: WorldOptions = WorldOptions())
data class WorldOptions(var gameMode: GameMode? = GameMode.CREATIVE, data class WorldOptions(var gameMode: GameMode? = GameMode.CREATIVE,
var dayTime: Boolean = true, var dayTime: Boolean = true,
var noWeather: Boolean = true, var noWeather: Boolean = true,
@@ -42,8 +51,7 @@ data class WorldOptions(var gameMode: GameMode? = GameMode.CREATIVE,
var blockPortalCreation: Boolean = true, var blockPortalCreation: Boolean = true,
var blockMobSpawning: Boolean = true, var blockMobSpawning: Boolean = true,
var blockedItems: Set<Material> = EnumSet.of(Material.FLINT_AND_STEEL, Material.SNOWBALL), var blockedItems: Set<Material> = EnumSet.of(Material.FLINT_AND_STEEL, Material.SNOWBALL),
var axisLimit: Int = 10, var axisLimit: Int = 10) {
var generator: GeneratorOptions = DefaultGeneratorOptions()) {
} }
@@ -51,23 +59,7 @@ abstract class GeneratorOptions {
abstract fun generatorFactory(): GeneratorFactory abstract fun generatorFactory(): GeneratorFactory
fun getGenerator(worlds: Worlds, worldName: String) = generatorFactory().newParcelGenerator(worlds, worldName, this) fun newGenerator(worldName: String) = generatorFactory().newParcelGenerator(worldName, this)
}
data class DefaultGeneratorOptions(var defaultBiome: Biome = Biome.JUNGLE,
var wallType: BlockData = createBlockData(Material.STONE_SLAB),
var floorType: BlockData = createBlockData(Material.QUARTZ_BLOCK),
var fillType: BlockData = createBlockData(Material.QUARTZ_BLOCK),
var pathMainType: BlockData = createBlockData(Material.SANDSTONE),
var pathAltType: BlockData = createBlockData(Material.REDSTONE_BLOCK),
var parcelSize: Int = 101,
var pathSize: Int = 9,
var floorHeight: Int = 64,
var offsetX: Int = 0,
var offsetZ: Int = 0) : GeneratorOptions() {
override fun generatorFactory(): GeneratorFactory = DefaultParcelGenerator.Factory
} }
@@ -104,4 +96,9 @@ data class DataConnectionOptions(val address: String = "localhost",
} }
data class DataFileOptions(val location: String = "/flatfile-storage/") data class DataFileOptions(val location: String = "/flatfile-storage/")
class MigrationOptions() {
}

View File

@@ -1,42 +1,40 @@
package io.dico.parcels2 package io.dico.parcels2
import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.Vec2i
import io.dico.parcels2.util.getPlayerName
import io.dico.parcels2.util.hasBuildAnywhere import io.dico.parcels2.util.hasBuildAnywhere
import io.dico.parcels2.util.isValid
import io.dico.parcels2.util.uuid
import org.bukkit.Bukkit
import org.bukkit.OfflinePlayer import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.joda.time.DateTime import org.joda.time.DateTime
import java.util.* import java.util.UUID
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 copyDataIgnoringDatabase(data: ParcelData)
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 copyData(data: ParcelData)
fun allow(player: OfflinePlayer) = allow(player.uuid)
fun disallow(player: OfflinePlayer) = disallow(player.uuid) fun dispose()
fun isBanned(player: OfflinePlayer) = isBanned(player.uuid)
fun ban(player: OfflinePlayer) = ban(player.uuid)
fun unban(player: OfflinePlayer) = unban(player.uuid)
} }
interface ParcelData : AddedData { interface ParcelData : AddedData {
var owner: ParcelOwner? var owner: ParcelOwner?
val since: DateTime?
fun canBuild(player: OfflinePlayer, checkAdmin: Boolean = true, checkGlobal: Boolean = true): Boolean 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 { class ParcelDataHolder : AddedDataHolder(), ParcelData {
override var owner: ParcelOwner? = null override var owner: ParcelOwner? = null
override var since: DateTime? = null
override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean) = isAllowed(player.uniqueId) override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean) = isAllowed(player.uniqueId)
|| owner.let { it != null && it.matches(player, allowNameMatch = false) } || owner.let { it != null && it.matches(player, allowNameMatch = false) }
|| (checkAdmin && player is Player && player.hasBuildAnywhere) || (checkAdmin && player is Player && player.hasBuildAnywhere)
@@ -135,55 +58,3 @@ class ParcelDataHolder : AddedDataHolder(), ParcelData {
override var allowInteractInventory = true 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 package io.dico.parcels2
import io.dico.parcels2.storage.SerializableParcel
import io.dico.parcels2.storage.SerializableWorld
import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.Storage
import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.Vec2i
import io.dico.parcels2.util.doAwait
import io.dico.parcels2.util.floor import io.dico.parcels2.util.floor
import kotlinx.coroutines.experimental.launch
import org.bukkit.Bukkit
import org.bukkit.Location import org.bukkit.Location
import org.bukkit.World import org.bukkit.World
import org.bukkit.WorldCreator
import org.bukkit.block.Block import org.bukkit.block.Block
import org.bukkit.entity.Entity import org.bukkit.entity.Entity
import org.bukkit.entity.Player import java.util.UUID
import java.util.*
import kotlin.coroutines.experimental.buildIterator
import kotlin.coroutines.experimental.buildSequence
import kotlin.reflect.jvm.javaMethod
import kotlin.reflect.jvm.kotlinFunction
class Worlds(val plugin: ParcelsPlugin) { interface ParcelProvider {
val worlds: Map<String, ParcelWorld> get() = _worlds val worlds: Map<String, ParcelWorld>
private val _worlds: MutableMap<String, ParcelWorld> = HashMap()
fun getWorld(name: String): ParcelWorld? = _worlds[name] fun getWorldById(id: ParcelWorldId): ParcelWorld?
fun getParcelById(id: ParcelId): Parcel?
fun getWorld(name: String): ParcelWorld?
fun getWorld(world: World): ParcelWorld? = getWorld(world.name) fun getWorld(world: World): ParcelWorld? = getWorld(world.name)
fun getParcelAt(block: Block): Parcel? = getParcelAt(block.world, block.x, block.z) fun getWorld(block: Block): ParcelWorld? = getWorld(block.world)
fun getParcelAt(player: Player): Parcel? = getParcelAt(player.location) fun getWorld(loc: Location): ParcelWorld? = getWorld(loc.world)
fun getParcelAt(location: Location): Parcel? = getParcelAt(location.world, location.x.floor(), location.z.floor()) fun getWorld(entity: Entity): ParcelWorld? = getWorld(entity.location)
fun getParcelAt(worldName: String, x: Int, z: Int): Parcel? = getWorld(worldName)?.locator?.getParcelAt(x, z)
fun getParcelAt(world: World, x: Int, z: Int): Parcel? = getParcelAt(world.name, x, z) fun getParcelAt(world: World, x: Int, z: Int): Parcel? = getParcelAt(world.name, x, z)
fun getParcelAt(world: String, x: Int, z: Int): Parcel? { fun getParcelAt(world: World, vec: Vec2i): Parcel? = getParcelAt(world, vec.x, vec.z)
with(getWorld(world) ?: return null) {
return generator.parcelAt(x, z)
}
}
init { fun getParcelAt(loc: Location): Parcel? = getParcelAt(loc.world, loc.x.floor(), loc.z.floor())
val function = ::loadWorlds
function.javaMethod!!.kotlinFunction
}
operator fun SerializableParcel.invoke(): Parcel? { fun getParcelAt(entity: Entity): Parcel? = getParcelAt(entity.location)
return world()?.parcelByID(pos)
}
operator fun SerializableWorld.invoke(): ParcelWorld? { fun getParcelAt(block: Block): Parcel? = getParcelAt(block.world, block.x, block.z)
return world?.let { getWorld(it) }
}
fun loadWorlds(options: Options) { fun getWorldGenerator(worldName: String): ParcelGenerator?
for ((worldName, worldOptions) in options.worlds.entries) {
val world: ParcelWorld
try {
world = ParcelWorld( fun loadWorlds()
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())
}
}
}
}
}
} }
interface ParcelProvider { interface ParcelLocator {
val world: World
fun parcelAt(x: Int, z: Int): Parcel? fun getParcelIdAt(x: Int, z: Int): ParcelId?
fun parcelAt(vec: Vec2i): Parcel? = parcelAt(vec.x, vec.z) fun getParcelAt(x: Int, z: Int): Parcel?
fun parcelAt(loc: Location): Parcel? = parcelAt(loc.x.floor(), loc.z.floor()) fun getParcelAt(vec: Vec2i): Parcel? = getParcelAt(vec.x, vec.z)
fun parcelAt(entity: Entity): Parcel? = parcelAt(entity.location) fun getParcelAt(loc: Location): Parcel? = getParcelAt(loc.x.floor(), loc.z.floor()).takeIf { loc.world == world }
fun parcelAt(block: Block): Parcel? = parcelAt(block.x, block.z) fun getParcelAt(entity: Entity): Parcel? = getParcelAt(entity.location).takeIf { entity.world == world }
}
class ParcelWorld constructor(val name: String, fun getParcelAt(block: Block): Parcel? = getParcelAt(block.x, block.z).takeIf { block.world == world }
val options: WorldOptions,
val generator: ParcelGenerator,
val storage: Storage) : ParcelProvider by generator, ParcelContainer {
val world: World by lazy {
Bukkit.getWorld(name) ?: throw NullPointerException("World $name does not appear to be loaded")
}
val container: ParcelContainer = DefaultParcelContainer(this, storage)
override fun parcelByID(x: Int, z: Int): Parcel? {
return container.parcelByID(x, z)
}
override fun nextEmptyParcel(): Parcel? {
return container.nextEmptyParcel()
}
fun parcelByID(id: Vec2i): Parcel? = parcelByID(id.x, id.z)
fun enforceOptionsIfApplicable() {
val world = world
val options = options
if (options.dayTime) {
world.setGameRuleValue("doDaylightCycle", "false")
world.setTime(6000)
}
if (options.noWeather) {
world.setStorm(false)
world.setThundering(false)
world.weatherDuration = Integer.MAX_VALUE
}
world.setGameRuleValue("doTileDrops", "${options.doTileDrops}")
}
}
interface ParcelContainer {
fun parcelByID(x: Int, z: Int): Parcel?
fun nextEmptyParcel(): Parcel?
} }
typealias ParcelContainerFactory = (ParcelWorld) -> ParcelContainer typealias ParcelContainerFactory = (ParcelWorld) -> ParcelContainer
class DefaultParcelContainer(private val world: ParcelWorld, interface ParcelContainer {
private val storage: Storage) : ParcelContainer {
private var parcels: Array<Array<Parcel>>
init { fun getParcelById(x: Int, z: Int): Parcel?
parcels = initArray(world.options.axisLimit, world)
}
fun resizeIfSizeChanged() { fun getParcelById(id: Vec2i): Parcel? = getParcelById(id.x, id.z)
if (parcels.size / 2 != world.options.axisLimit) {
resize(world.options.axisLimit)
}
}
fun resize(axisLimit: Int) { fun nextEmptyParcel(): Parcel?
parcels = initArray(axisLimit, world, this)
}
fun initArray(axisLimit: Int, world: ParcelWorld, cur: DefaultParcelContainer? = null): Array<Array<Parcel>> { }
val arraySize = 2 * axisLimit + 1
return Array(arraySize) {
val x = it - axisLimit
Array(arraySize) {
val z = it - axisLimit
cur?.parcelByID(x, z) ?: Parcel(world, Vec2i(x, z))
}
}
}
override fun parcelByID(x: Int, z: Int): Parcel? { interface ParcelWorld : ParcelLocator, ParcelContainer, ParcelBlockManager {
return parcels.getOrNull(x + world.options.axisLimit)?.getOrNull(z + world.options.axisLimit) val id: ParcelWorldId
} val name: String
val uid: UUID?
override fun nextEmptyParcel(): Parcel? { val options: WorldOptions
return walkInCircle().find { it.owner == null } val generator: ParcelGenerator
} val storage: Storage
val container: ParcelContainer
private fun walkInCircle(): Iterable<Parcel> = Iterable { val locator: ParcelLocator
buildIterator { val blockManager: ParcelBlockManager
val center = world.options.axisLimit val globalAddedData: GlobalAddedDataManager
for (radius in 0..center) { }
var x = center - radius; var z = center - radius
repeat(radius * 2) { yield(parcels[x++][z]) }
repeat(radius * 2) { yield(parcels[x][z++]) }
repeat(radius * 2) { yield(parcels[x--][z]) }
repeat(radius * 2) { yield(parcels[x][z--]) }
}
}
}
fun allParcels(): Sequence<Parcel> = buildSequence {
for (array in parcels) {
yieldAll(array.iterator())
}
}
fun loadAllData() {
val channel = storage.readParcelData(allParcels(), 100)
launch(storage.asyncDispatcher) {
for ((parcel, data) in channel) {
data?.let { parcel.copyDataIgnoringDatabase(it) }
}
}
}
}

View File

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

View File

@@ -1,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 org.bukkit.Material.* import org.bukkit.Material.*
import java.util.* import java.util.EnumSet
val attachables: Set<Material> = EnumSet.of( val attachables: Set<Material> = EnumSet.of(
ACACIA_DOOR, ACACIA_DOOR,
@@ -64,4 +64,4 @@ val attachables: Set<Material> = EnumSet.of(
WALL_SIGN, WALL_SIGN,
LILY_PAD, LILY_PAD,
DANDELION 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 origin = region.origin
val size = region.size val size = region.size

View File

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

View File

@@ -1,11 +1,12 @@
package io.dico.parcels2.blockvisitor package io.dico.parcels2.blockvisitor
import kotlinx.coroutines.experimental.* import io.dico.parcels2.ParcelsPlugin
import org.bukkit.plugin.Plugin import io.dico.parcels2.util.FunctionHelper
import kotlinx.coroutines.experimental.CancellationException
import kotlinx.coroutines.experimental.Job
import org.bukkit.scheduler.BukkitTask import org.bukkit.scheduler.BukkitTask
import java.lang.System.currentTimeMillis import java.lang.System.currentTimeMillis
import java.util.* import java.util.LinkedList
import java.util.concurrent.Executor
import java.util.logging.Level import java.util.logging.Level
import kotlin.coroutines.experimental.Continuation import kotlin.coroutines.experimental.Continuation
import kotlin.coroutines.experimental.intrinsics.COROUTINE_SUSPENDED import kotlin.coroutines.experimental.intrinsics.COROUTINE_SUSPENDED
@@ -27,6 +28,11 @@ sealed class WorktimeLimiter {
* Get a list of all workers * Get a list of all workers
*/ */
abstract val workers: List<Worker> abstract val workers: List<Worker>
/**
* Attempts to complete any remaining tasks immediately, without suspension.
*/
abstract fun completeAllTasks()
} }
interface Timed { interface Timed {
@@ -90,8 +96,14 @@ interface WorkerScope : Timed {
private interface WorkerContinuation : Worker, WorkerScope { private interface WorkerContinuation : Worker, WorkerScope {
/** /**
* Start or resume the execution of this worker * Start or resumes the execution of this worker
* returns true if the worker completed * 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 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 * 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 * This object attempts to split that maximum amount of milliseconds equally between all jobs
*/ */
class TickWorktimeLimiter(private val plugin: Plugin, var options: TickWorktimeOptions) : WorktimeLimiter() { class TickWorktimeLimiter(private val plugin: ParcelsPlugin, var options: TickWorktimeOptions) : WorktimeLimiter() {
// Coroutine dispatcher for jobs
private val dispatcher = Executor(Runnable::run).asCoroutineDispatcher()
// The currently registered bukkit scheduler task // The currently registered bukkit scheduler task
private var bukkitTask: BukkitTask? = null private var bukkitTask: BukkitTask? = null
// The workers. // The workers.
private var _workers = LinkedList<WorkerContinuation>() private val _workers = LinkedList<WorkerContinuation>()
override val workers: List<Worker> = _workers override val workers: List<Worker> = _workers
override fun submit(task: TimeLimitedTask): Worker { override fun submit(task: TimeLimitedTask): Worker {
val worker: WorkerContinuation = WorkerImpl(plugin, dispatcher, task) val worker: WorkerContinuation = WorkerImpl(plugin.functionHelper, task)
_workers.addFirst(worker) _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 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, private class WorkerImpl(val functionHelper: FunctionHelper,
val dispatcher: CoroutineDispatcher,
val task: TimeLimitedTask) : WorkerContinuation { val task: TimeLimitedTask) : WorkerContinuation {
override var job: Job? = null; private set override var job: Job? = null; private set
@@ -170,6 +188,7 @@ private class WorkerImpl(val plugin: Plugin,
private var onCompleted: WorkerUpdateLister? = null private var onCompleted: WorkerUpdateLister? = null
private var continuation: Continuation<Unit>? = null private var continuation: Continuation<Unit>? = null
private var nextSuspensionTime: Long = 0L private var nextSuspensionTime: Long = 0L
private var completeForcefully = false
private fun initJob(job: Job) { private fun initJob(job: Job) {
this.job?.let { throw IllegalStateException() } this.job?.let { throw IllegalStateException() }
@@ -179,7 +198,7 @@ private class WorkerImpl(val plugin: Plugin,
// report any error that occurred // report any error that occurred
completionException = exception?.also { completionException = exception?.also {
if (it !is CancellationException) 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 // convert to elapsed time here
@@ -204,7 +223,7 @@ private class WorkerImpl(val plugin: Plugin,
} }
override suspend fun markSuspensionPoint() { override suspend fun markSuspensionPoint() {
if (System.currentTimeMillis() >= nextSuspensionTime) if (System.currentTimeMillis() >= nextSuspensionTime && !completeForcefully)
suspendCoroutineUninterceptedOrReturn { cont: Continuation<Unit> -> suspendCoroutineUninterceptedOrReturn { cont: Continuation<Unit> ->
continuation = cont continuation = cont
COROUTINE_SUSPENDED COROUTINE_SUSPENDED
@@ -222,7 +241,11 @@ private class WorkerImpl(val plugin: Plugin,
} }
override fun resume(worktime: Long): Boolean { override fun resume(worktime: Long): Boolean {
nextSuspensionTime = currentTimeMillis() + worktime if (worktime > 0) {
nextSuspensionTime = currentTimeMillis() + worktime
} else {
completeForcefully = true
}
continuation?.let { continuation?.let {
continuation = null continuation = null
@@ -236,10 +259,9 @@ private class WorkerImpl(val plugin: Plugin,
} }
try { try {
launch(context = dispatcher, start = CoroutineStart.UNDISPATCHED) { val job = functionHelper.launchLazilyOnMainThread { task() }
initJob(job = kotlin.coroutines.experimental.coroutineContext[Job]!!) initJob(job = job)
task() job.start()
}
} catch (t: Throwable) { } catch (t: Throwable) {
// do nothing: handled by job.invokeOnCompletion() // 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.ExecutionContext
import io.dico.dicore.command.ICommandReceiver import io.dico.dicore.command.ICommandReceiver
import io.dico.parcels2.ParcelOwner import io.dico.parcels2.ParcelOwner
import io.dico.parcels2.ParcelWorld
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.util.hasAdminManage import io.dico.parcels2.util.hasAdminManage
import io.dico.parcels2.util.parcelLimit import io.dico.parcels2.util.parcelLimit
import io.dico.parcels2.util.uuid
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.bukkit.plugin.Plugin import org.bukkit.plugin.Plugin
import java.lang.reflect.Method import java.lang.reflect.Method
@@ -16,10 +16,10 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei
override fun getPlugin(): Plugin = plugin override fun getPlugin(): Plugin = plugin
override fun getReceiver(context: ExecutionContext, target: Method, cmdName: String): ICommandReceiver { override fun getReceiver(context: ExecutionContext, target: Method, cmdName: String): ICommandReceiver {
return getParcelCommandReceiver(plugin.worlds, context, target, cmdName) return getParcelCommandReceiver(plugin.parcelProvider, context, target, cmdName)
} }
protected inline val worlds get() = plugin.worlds protected inline val worlds get() = plugin.parcelProvider
protected fun error(message: String): Nothing { protected fun error(message: String): Nothing {
throw CommandException(message) throw CommandException(message)
@@ -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") 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 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 val limit = player.parcelLimit
if (numOwnedParcels >= limit) { if (numOwnedParcels >= limit) {

View File

@@ -0,0 +1,65 @@
package io.dico.parcels2.command
import io.dico.dicore.command.Validate
import io.dico.dicore.command.annotation.Cmd
import io.dico.dicore.command.annotation.Desc
import io.dico.parcels2.GlobalAddedData
import io.dico.parcels2.GlobalAddedDataManager
import io.dico.parcels2.ParcelOwner
import io.dico.parcels2.ParcelsPlugin
import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player
class CommandsAddedStatusGlobal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
private inline val data get() = plugin.globalAddedData
@Suppress("NOTHING_TO_INLINE")
private inline operator fun GlobalAddedDataManager.get(player: OfflinePlayer): GlobalAddedData = data[ParcelOwner(player)]
@Cmd("allow", aliases = ["add", "permit"])
@Desc("Globally allows a player to build on all",
"the parcels that you own.",
shortVersion = "globally allows a player to build on your parcels")
@ParcelRequire(owner = true)
fun cmdAllow(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(player != sender, "The target cannot be yourself")
Validate.isTrue(data[sender].allow(player), "${player.name} is already allowed globally")
return "${player.name} is now allowed to build on all your parcels"
}
@Cmd("disallow", aliases = ["remove", "forbid"])
@Desc("Globally disallows a player to build on",
"the parcels that you own.",
"If the player is allowed to build on specific",
"parcels, they can still build there.",
shortVersion = "globally disallows a player to build on your parcels")
@ParcelRequire(owner = true)
fun cmdDisallow(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(player != sender, "The target cannot be yourself")
Validate.isTrue(data[sender].disallow(player), "${player.name} is not currently allowed globally")
return "${player.name} is not allowed to build on all your parcels anymore"
}
@Cmd("ban", aliases = ["deny"])
@Desc("Globally bans a player from all the parcels",
"that you own, making them unable to enter.",
shortVersion = "globally bans a player from your parcels")
@ParcelRequire(owner = true)
fun cmdBan(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(player != sender, "The target cannot be yourself")
Validate.isTrue(data[sender].ban(player), "${player.name} is already banned from all your parcels")
return "${player.name} is now banned from all your parcels"
}
@Cmd("unban", aliases = ["undeny"])
@Desc("Globally unbans a player from all the parcels",
"that you own, they can enter again.",
"If the player is banned from specific parcels,",
"they will still be banned there.",
shortVersion = "globally unbans a player from your parcels")
@ParcelRequire(owner = true)
fun cmdUnban(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(data[sender].unban(player), "${player.name} is not currently banned from all your parcels")
return "${player.name} is not banned from all your parcels anymore"
}
}

View File

@@ -8,14 +8,14 @@ import io.dico.parcels2.util.hasAdminManage
import org.bukkit.OfflinePlayer import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player import org.bukkit.entity.Player
class CommandsAddedStatus(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { class CommandsAddedStatusLocal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@Cmd("allow", aliases = ["add", "permit"]) @Cmd("allow", aliases = ["add", "permit"])
@Desc("Allows a player to build on this parcel", @Desc("Allows a player to build on this parcel",
shortVersion = "allows a player to build on this parcel") shortVersion = "allows a player to build on this parcel")
@ParcelRequire(owner = true) @ParcelRequire(owner = true)
fun ParcelScope.cmdAllow(sender: Player, player: OfflinePlayer): Any? { 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.owner!!.matches(player), "The target already owns the parcel")
Validate.isTrue(parcel.allow(player), "${player.name} is already allowed to build on this 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" 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") shortVersion = "bans a player from this parcel")
@ParcelRequire(owner = true) @ParcelRequire(owner = true)
fun ParcelScope.cmdBan(sender: Player, player: OfflinePlayer): Any? { 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.owner!!.matches(player), "The owner cannot be banned from the parcel")
Validate.isTrue(parcel.ban(player), "${player.name} is already banned from this parcel") Validate.isTrue(parcel.ban(player), "${player.name} is already banned from this parcel")
return "${player.name} is now banned from this parcel" return "${player.name} is now banned from this parcel"

View File

@@ -1,12 +1,17 @@
package io.dico.parcels2.command package io.dico.parcels2.command
import io.dico.dicore.command.CommandException 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.dicore.command.annotation.Cmd
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.blockvisitor.RegionTraversal
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.entity.Player import org.bukkit.entity.Player
import java.util.Random
class CommandsDebug(val plugin: ParcelsPlugin) { class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@Cmd("reloadoptions") @Cmd("reloadoptions")
fun reloadOptions() { fun reloadOptions() {
@@ -23,4 +28,24 @@ class CommandsDebug(val plugin: ParcelsPlugin) {
return "Teleported you to $worldName spawn" 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.ExecutionContext
import io.dico.dicore.command.annotation.Cmd import io.dico.dicore.command.annotation.Cmd
import io.dico.dicore.command.annotation.Desc import io.dico.dicore.command.annotation.Desc
import io.dico.dicore.command.annotation.Flag
import io.dico.dicore.command.annotation.RequireParameters import io.dico.dicore.command.annotation.RequireParameters
import io.dico.parcels2.ParcelOwner import io.dico.parcels2.ParcelOwner
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.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.hasAdminManage
import io.dico.parcels2.util.hasParcelHomeOthers import io.dico.parcels2.util.hasParcelHomeOthers
import io.dico.parcels2.util.uuid import io.dico.parcels2.util.uuid
import org.bukkit.Material
import org.bukkit.entity.Player import org.bukkit.entity.Player
import java.util.*
//@Suppress("unused")
class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@Cmd("auto") @Cmd("auto")
@@ -26,12 +21,12 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
shortVersion = "sets you up with a fresh, unclaimed parcel") shortVersion = "sets you up with a fresh, unclaimed parcel")
suspend fun WorldScope.cmdAuto(player: Player): Any? { suspend fun WorldScope.cmdAuto(player: Player): Any? {
checkConnected("be claimed") checkConnected("be claimed")
checkParcelLimit(player) checkParcelLimit(player, world)
val parcel = world.nextEmptyParcel() val parcel = world.nextEmptyParcel()
?: error("This world is full, please ask an admin to upsize it") ?: error("This world is full, please ask an admin to upsize it")
parcel.owner = ParcelOwner(uuid = player.uuid) parcel.owner = ParcelOwner(uuid = player.uuid)
player.teleport(parcel.homeLocation) player.teleport(parcel.world.getHomeLocation(parcel.id))
return "Enjoy your new parcel!" return "Enjoy your new parcel!"
} }
@@ -48,23 +43,22 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
"more than one parcel", "more than one parcel",
shortVersion = "teleports you to parcels") shortVersion = "teleports you to parcels")
@RequireParameters(0) @RequireParameters(0)
suspend fun cmdHome(player: Player, suspend fun cmdHome(player: Player, @ParcelTarget.Kind(ParcelTarget.OWNER_REAL) target: ParcelTarget): Any? {
@NamedParcelDefault(FIRST_OWNED) target: NamedParcelTarget): Any? { val ownerTarget = target as ParcelTarget.ByOwner
if (player !== target.player && !player.hasParcelHomeOthers) { if (!ownerTarget.owner.matches(player) && !player.hasParcelHomeOthers) {
error("You do not have permission to teleport to other people's parcels") 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 val ownedParcels = ownedParcelsResult
.map { worlds.getParcelBySerializedValue(it) } .map { worlds.getParcelById(it) }
.filter { it != null && it.world == target.world && it.owner?.uuid == uuid } .filter { it != null && ownerTarget.world == it.world }
val targetMatch = ownedParcels.getOrNull(target.index) val targetMatch = ownedParcels.getOrNull(target.index)
?: error("The specified parcel could not be matched") ?: error("The specified parcel could not be matched")
player.teleport(targetMatch.homeLocation) player.teleport(targetMatch.world.getHomeLocation(targetMatch.id))
return "" return ""
} }
@@ -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") error(if (it.matches(player)) "You already own this parcel" else "This parcel is not available")
} }
checkParcelLimit(player) checkParcelLimit(player, world)
parcel.owner = ParcelOwner(uuid = player.uuid, name = player.name) parcel.owner = ParcelOwner(player)
return "Enjoy your new parcel!" 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") @Cmd("clear")
@ParcelRequire(owner = true) @ParcelRequire(owner = true)
fun ParcelScope.cmdClear(context: ExecutionContext) { fun ParcelScope.cmdClear(context: ExecutionContext, @Flag sure: Boolean): Any? {
world.generator.clearParcel(parcel) 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 -> .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)) .format(progress * 100, elapsedTime / 1000.0))
} }
return null
} }
@Cmd("make_mess") @Cmd("swap")
@ParcelRequire(owner = true) fun ParcelScope.cmdSwap(context: ExecutionContext, @Flag sure: Boolean): Any? {
fun ParcelScope.cmdMakeMess(context: ExecutionContext) { TODO()
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))
}
} }
} }

View File

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

View File

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

View File

@@ -3,14 +3,10 @@ package io.dico.parcels2.command
import io.dico.dicore.command.CommandException import io.dico.dicore.command.CommandException
import io.dico.dicore.command.parameter.ArgumentBuffer import io.dico.dicore.command.parameter.ArgumentBuffer
import io.dico.dicore.command.parameter.Parameter import io.dico.dicore.command.parameter.Parameter
import io.dico.dicore.command.parameter.type.ParameterConfig
import io.dico.dicore.command.parameter.type.ParameterType import io.dico.dicore.command.parameter.type.ParameterType
import io.dico.parcels2.Parcel import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelProvider
import io.dico.parcels2.ParcelWorld import io.dico.parcels2.ParcelWorld
import io.dico.parcels2.Worlds
import io.dico.parcels2.util.isValid
import org.bukkit.Bukkit
import org.bukkit.OfflinePlayer
import org.bukkit.command.CommandSender import org.bukkit.command.CommandSender
import org.bukkit.entity.Player import org.bukkit.entity.Player
@@ -18,7 +14,7 @@ fun invalidInput(parameter: Parameter<*, *>, message: String): Nothing {
throw CommandException("invalid input for ${parameter.name}: $message") throw CommandException("invalid input for ${parameter.name}: $message")
} }
fun Worlds.getTargetWorld(input: String?, sender: CommandSender, parameter: Parameter<*, *>): ParcelWorld { fun ParcelProvider.getTargetWorld(input: String?, sender: CommandSender, parameter: Parameter<*, *>): ParcelWorld {
val worldName = input val worldName = input
?.takeUnless { it.isEmpty() } ?.takeUnless { it.isEmpty() }
?: (sender as? Player)?.world?.name ?: (sender as? Player)?.world?.name
@@ -28,14 +24,14 @@ fun Worlds.getTargetWorld(input: String?, sender: CommandSender, parameter: Para
?: invalidInput(parameter, "$worldName is not a parcel world") ?: invalidInput(parameter, "$worldName is not a parcel world")
} }
class ParcelParameterType(val worlds: Worlds) : ParameterType<Parcel, Void>(Parcel::class.java) { class ParcelParameterType(val parcelProvider: ParcelProvider) : ParameterType<Parcel, Void>(Parcel::class.java) {
val regex = Regex.fromLiteral("((.+)->)?([0-9]+):([0-9]+)") val regex = Regex.fromLiteral("((.+)->)?([0-9]+):([0-9]+)")
override fun parse(parameter: Parameter<Parcel, Void>, sender: CommandSender, buffer: ArgumentBuffer): Parcel { override fun parse(parameter: Parameter<Parcel, Void>, sender: CommandSender, buffer: ArgumentBuffer): Parcel {
val matchResult = regex.matchEntire(buffer.next()) val matchResult = regex.matchEntire(buffer.next())
?: invalidInput(parameter, "must match (w->)?a:b (/${regex.pattern}/)") ?: invalidInput(parameter, "must match (w->)?a:b (/${regex.pattern}/)")
val world = worlds.getTargetWorld(matchResult.groupValues[2], sender, parameter) val world = parcelProvider.getTargetWorld(matchResult.groupValues[2], sender, parameter)
val x = matchResult.groupValues[3].toIntOrNull() val x = matchResult.groupValues[3].toIntOrNull()
?: invalidInput(parameter, "couldn't parse int") ?: invalidInput(parameter, "couldn't parse int")
@@ -43,80 +39,8 @@ class ParcelParameterType(val worlds: Worlds) : ParameterType<Parcel, Void>(Parc
val z = matchResult.groupValues[4].toIntOrNull() val z = matchResult.groupValues[4].toIntOrNull()
?: invalidInput(parameter, "couldn't parse int") ?: invalidInput(parameter, "couldn't parse int")
return world.parcelByID(x, z) return world.getParcelById(x, z)
?: invalidInput(parameter, "parcel id is out of range") ?: invalidInput(parameter, "parcel id is out of range")
} }
} }
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() { 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 package io.dico.parcels2.listener
import io.dico.parcels2.Parcel import io.dico.parcels2.Parcel
import io.dico.parcels2.Worlds import io.dico.parcels2.ParcelProvider
import io.dico.parcels2.util.editLoop import io.dico.parcels2.util.editLoop
import io.dico.parcels2.util.isPresentAnd import io.dico.parcels2.util.isPresentAnd
import org.bukkit.entity.Entity import org.bukkit.entity.Entity
class ParcelEntityTracker(val worlds: Worlds) { class ParcelEntityTracker(val parcelProvider: ParcelProvider) {
val map = mutableMapOf<Entity, Parcel?>() val map = mutableMapOf<Entity, Parcel?>()
fun untrack(entity: Entity) { fun untrack(entity: Entity) {
@@ -32,7 +32,7 @@ class ParcelEntityTracker(val worlds: Worlds) {
if (parcel.isPresentAnd { hasBlockVisitors }) { if (parcel.isPresentAnd { hasBlockVisitors }) {
remove() remove()
} }
val newParcel = worlds.getParcelAt(entity.location) val newParcel = parcelProvider.getParcelAt(entity.location)
if (newParcel !== parcel && !newParcel.isPresentAnd { hasBlockVisitors }) { if (newParcel !== parcel && !newParcel.isPresentAnd { hasBlockVisitors }) {
remove() remove()
entity.remove() entity.remove()

View File

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

View File

@@ -1,11 +1,8 @@
package io.dico.parcels2.storage package io.dico.parcels2.storage
import io.dico.parcels2.AddedData import io.dico.parcels2.*
import io.dico.parcels2.Parcel import kotlinx.coroutines.experimental.channels.SendChannel
import io.dico.parcels2.ParcelData import java.util.UUID
import io.dico.parcels2.ParcelOwner
import kotlinx.coroutines.experimental.channels.ProducerScope
import java.util.*
interface Backing { interface Backing {
@@ -22,30 +19,32 @@ interface Backing {
* This producer function is capable of constantly reading parcels from a potentially infinite sequence, * This producer function is capable of constantly reading parcels from a potentially infinite sequence,
* and provide parcel data for it as read from the database. * and provide parcel data for it as read from the database.
*/ */
suspend fun ProducerScope<Pair<Parcel, ParcelData?>>.produceParcelData(parcels: Sequence<Parcel>) suspend fun produceParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>)
suspend fun ProducerScope<Pair<SerializableParcel, ParcelData?>>.produceAllParcelData() suspend fun produceAllParcelData(channel: SendChannel<DataPair>)
suspend fun readParcelData(parcelFor: Parcel): ParcelData? suspend fun readParcelData(parcel: ParcelId): ParcelData?
suspend fun getOwnedParcels(user: ParcelOwner): List<SerializableParcel> suspend fun getOwnedParcels(user: ParcelOwner): List<ParcelId>
suspend fun getNumParcels(user: ParcelOwner): Int = getOwnedParcels(user).size suspend fun getNumParcels(user: ParcelOwner): Int = getOwnedParcels(user).size
suspend fun setParcelData(parcelFor: Parcel, data: ParcelData?) suspend fun setParcelData(parcel: ParcelId, data: ParcelData?)
suspend fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) suspend fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?)
suspend fun 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.databind.ser.std.StdSerializer
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.KotlinModule import com.fasterxml.jackson.module.kotlin.KotlinModule
import io.dico.parcels2.GeneratorFactory import io.dico.parcels2.GeneratorFactories
import io.dico.parcels2.GeneratorOptions import io.dico.parcels2.GeneratorOptions
import io.dico.parcels2.StorageOptions import io.dico.parcels2.StorageOptions
import org.bukkit.Bukkit import org.bukkit.Bukkit
@@ -100,7 +100,7 @@ class GeneratorOptionsDeserializer : JsonDeserializer<GeneratorOptions>() {
val node = parser!!.readValueAsTree<JsonNode>() val node = parser!!.readValueAsTree<JsonNode>()
val name = node.get("name").asText() val name = node.get("name").asText()
val optionsNode = node.get("options") val optionsNode = node.get("options")
val factory = GeneratorFactory.getFactory(name) ?: throw IllegalStateException("Unknown generator: $name") val factory = GeneratorFactories.getFactory(name) ?: throw IllegalStateException("Unknown generator: $name")
return parser.codec.treeToValue(optionsNode, factory.optionsClass.java) return parser.codec.treeToValue(optionsNode, factory.optionsClass.java)
} }

View File

@@ -1,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 package io.dico.parcels2.storage
import io.dico.parcels2.AddedData import io.dico.parcels2.*
import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelData
import io.dico.parcels2.ParcelOwner
import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.*
import kotlinx.coroutines.experimental.channels.ProducerScope
import kotlinx.coroutines.experimental.channels.ReceiveChannel import kotlinx.coroutines.experimental.channels.ReceiveChannel
import kotlinx.coroutines.experimental.channels.produce import kotlinx.coroutines.experimental.channels.produce
import java.util.* import java.util.UUID
import java.util.concurrent.Executor import java.util.concurrent.Executor
import java.util.concurrent.Executors import java.util.concurrent.Executors
typealias DataPair = Pair<ParcelId, ParcelData?>
typealias AddedDataPair<TAttach> = Pair<TAttach, MutableAddedDataMap>
interface Storage { interface Storage {
val name: String val name: String
val syncDispatcher: CoroutineDispatcher val syncDispatcher: CoroutineDispatcher
@@ -22,31 +25,33 @@ interface Storage {
fun shutdown(): Job fun shutdown(): Job
fun readParcelData(parcelFor: Parcel): Deferred<ParcelData?> fun readParcelData(parcel: ParcelId): Deferred<ParcelData?>
fun readParcelData(parcelsFor: Sequence<Parcel>, 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 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 { 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 val poolSize: Int get() = 4
override val asyncDispatcher = Executors.newFixedThreadPool(poolSize) { Thread(it, "Parcels2_StorageThread") }.asCoroutineDispatcher() override val asyncDispatcher = Executors.newFixedThreadPool(poolSize) { Thread(it, "Parcels2_StorageThread") }.asCoroutineDispatcher()
override val isConnected get() = backing.isConnected 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> { private inline fun <T> defer(noinline block: suspend CoroutineScope.() -> T): Deferred<T> {
return async(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block) return async(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block)
} }
@Suppress("NOTHING_TO_INLINE")
private inline fun job(noinline block: suspend CoroutineScope.() -> Unit): Job { private inline fun job(noinline block: suspend CoroutineScope.() -> Unit): Job {
return launch(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block) return launch(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block)
} }
private inline fun <T> openChannel(noinline block: suspend ProducerScope<T>.() -> Unit): ReceiveChannel<T> {
return produce(asyncDispatcher, capacity = channelCapacity, block = block)
}
override fun init() = job { backing.init() } override fun init() = job { backing.init() }
override fun shutdown() = job { backing.shutdown() } override fun shutdown() = job { backing.shutdown() }
override fun readParcelData(parcelFor: Parcel) = defer { backing.readParcelData(parcelFor) } override fun readParcelData(parcel: ParcelId) = defer { backing.readParcelData(parcel) }
override fun readParcelData(parcelsFor: Sequence<Parcel>, channelCapacity: Int) = override fun readParcelData(parcels: Sequence<ParcelId>) = openChannel<DataPair> { backing.produceParcelData(channel, parcels) }
produce(asyncDispatcher, capacity = channelCapacity) { with(backing) { produceParcelData(parcelsFor) } }
override fun readAllParcelData(channelCapacity: Int): ReceiveChannel<Pair<SerializableParcel, ParcelData?>> = override fun readAllParcelData() = openChannel<DataPair> { backing.produceAllParcelData(channel) }
produce(asyncDispatcher, capacity = channelCapacity) { with(backing) { produceAllParcelData() } }
override fun getOwnedParcels(user: ParcelOwner) = defer { backing.getOwnedParcels(user) } override fun getOwnedParcels(user: ParcelOwner) = defer { backing.getOwnedParcels(user) }
override fun getNumParcels(user: ParcelOwner) = defer { backing.getNumParcels(user) } override fun getNumParcels(user: ParcelOwner) = defer { backing.getNumParcels(user) }
override fun setParcelData(parcelFor: Parcel, data: ParcelData?) = job { backing.setParcelData(parcelFor, data) } override fun setParcelData(parcel: ParcelId, data: ParcelData?) = job { backing.setParcelData(parcel, data) }
override fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) = job { backing.setParcelOwner(parcelFor, owner) } override fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?) = job { backing.setParcelOwner(parcel, owner) }
override fun 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 com.zaxxer.hikari.HikariDataSource
import io.dico.parcels2.DataConnectionOptions import io.dico.parcels2.DataConnectionOptions
import io.dico.parcels2.storage.exposed.ExposedBacking
import kotlin.reflect.KClass import kotlin.reflect.KClass
interface StorageFactory { interface StorageFactory {
@@ -35,8 +36,8 @@ class ConnectionStorageFactory : StorageFactory {
override fun newStorageInstance(dialect: String, options: Any): Storage { override fun newStorageInstance(dialect: String, options: Any): Storage {
val hikariConfig = getHikariConfig(dialect, options as DataConnectionOptions) val hikariConfig = getHikariConfig(dialect, options as DataConnectionOptions)
val dataSourceFactory = { HikariDataSource(hikariConfig) } val dataSourceFactory = suspend { HikariDataSource(hikariConfig) }
return StorageWithCoroutineBacking(ExposedBacking(dataSourceFactory)) 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.Column
import org.jetbrains.exposed.sql.Index import org.jetbrains.exposed.sql.Index
@@ -40,7 +40,7 @@ class UpsertStatement<Key : Any>(table: Table, conflictColumn: Column<*>? = null
} else { } else {
append (" ON DUPLICATE KEY UPDATE ") append(" ON DUPLICATE KEY UPDATE ")
values.keys.filter { it !in indexColumns }.joinTo(this) { "${transaction.identity(it)}=VALUES(${transaction.identity(it)})" } values.keys.filter { it !in indexColumns }.joinTo(this) { "${transaction.identity(it)}=VALUES(${transaction.identity(it)})" }
} }

View File

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

View File

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

View File

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

View File

@@ -16,4 +16,37 @@ data class Vec3i(
} }
@Suppress("NOTHING_TO_INLINE") @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)
}
*/