summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDico <dico.karssiens@gmail.com>2018-08-02 19:10:54 +0100
committerDico <dico.karssiens@gmail.com>2018-08-02 19:10:54 +0100
commitf5497945e27a0bc5203f829b19fb9e6c25e37ed4 (patch)
treed4795f671065261d32647eb2221ff8f3af42067b
parent5d5b6550996a5e322ff153c2accee1532fbdde32 (diff)
parent7cd9844670896c5a67ca723de85a0ebe120dddfc (diff)
Merge branch 'dev'
-rw-r--r--build.gradle.kts7
-rw-r--r--dicore3/command/src/main/java/io/dico/dicore/command/chat/AbstractChatController.java2
-rw-r--r--src/main/kotlin/io/dico/parcels2/AddedData.kt57
-rw-r--r--src/main/kotlin/io/dico/parcels2/Options.kt51
-rw-r--r--src/main/kotlin/io/dico/parcels2/Parcel.kt177
-rw-r--r--src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt83
-rw-r--r--src/main/kotlin/io/dico/parcels2/ParcelId.kt49
-rw-r--r--src/main/kotlin/io/dico/parcels2/ParcelOwner.kt52
-rw-r--r--src/main/kotlin/io/dico/parcels2/ParcelWorld.kt242
-rw-r--r--src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt60
-rw-r--r--src/main/kotlin/io/dico/parcels2/WorldGenerator.kt307
-rw-r--r--src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt4
-rw-r--r--src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraversal.kt2
-rw-r--r--src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt1
-rw-r--r--src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt64
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt11
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusGlobal.kt65
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusLocal.kt (renamed from src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatus.kt)6
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt27
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt71
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt17
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt14
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt86
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt162
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/ParcelsChatController.kt3
-rw-r--r--src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt65
-rw-r--r--src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt267
-rw-r--r--src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalAddedDataManagerImpl.kt39
-rw-r--r--src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt163
-rw-r--r--src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt119
-rw-r--r--src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt94
-rw-r--r--src/main/kotlin/io/dico/parcels2/listener/ListenerHelper.kt16
-rw-r--r--src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt6
-rw-r--r--src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt90
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/Backing.kt33
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt322
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/Jackson.kt4
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/SerializableTypes.kt37
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/Storage.kt68
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt5
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt196
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedExtensions.kt (renamed from src/main/kotlin/io/dico/parcels2/storage/ExposedExtensions.kt)4
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt118
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt102
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/migration/Migration.kt8
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/migration/MigrationFactory.kt5
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt118
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeTables.kt26
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/BukkitAwait.kt74
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/FunctionHelper.kt53
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/MaterialExtensions.kt115
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt3
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt21
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/Vec3i.kt35
54 files changed, 2340 insertions, 1486 deletions
diff --git a/build.gradle.kts b/build.gradle.kts
index 878e17c..11975a1 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -10,7 +10,7 @@ import java.net.URL
val stdout = PrintWriter("gradle-output.txt")
group = "io.dico"
-version = "0.1"
+version = "0.2"
plugins {
java
@@ -51,7 +51,7 @@ project(":dicore3:dicore3-command") {
dependencies {
c.kotlinStd(kotlin("stdlib-jdk8"))
c.kotlinStd(kotlin("reflect"))
- c.kotlinStd(kotlinx("coroutines-core:0.23.4"))
+ c.kotlinStd(kotlinx("coroutines-core:0.24.0"))
compile(project(":dicore3:dicore3-core"))
compile("com.thoughtworks.paranamer:paranamer:2.8")
@@ -72,6 +72,7 @@ dependencies {
compile("org.jetbrains.exposed:exposed:0.10.3") { isTransitive = false }
compile("joda-time:joda-time:2.10")
compile("com.zaxxer:HikariCP:3.2.0")
+ compile("ch.qos.logback:logback-classic:1.2.3")
val jacksonVersion = "2.9.6"
compile("com.fasterxml.jackson.core:jackson-core:$jacksonVersion")
@@ -86,6 +87,8 @@ tasks {
val compileKotlin by getting(KotlinCompile::class) {
kotlinOptions {
javaParameters = true
+ suppressWarnings = true
+ //freeCompilerArgs = listOf("-XXLanguage:+InlineClasses", "-Xuse-experimental=kotlin.Experimental")
}
}
diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/chat/AbstractChatController.java b/dicore3/command/src/main/java/io/dico/dicore/command/chat/AbstractChatController.java
index 52c1e30..d88e852 100644
--- a/dicore3/command/src/main/java/io/dico/dicore/command/chat/AbstractChatController.java
+++ b/dicore3/command/src/main/java/io/dico/dicore/command/chat/AbstractChatController.java
@@ -36,7 +36,7 @@ public class AbstractChatController implements IChatController {
@Override
public void sendMessage(CommandSender sender, EMessageType type, String message) {
if (message != null && !message.isEmpty()) {
- sender.sendMessage(getMessagePrefixForType(type) + getChatFormatForType(type) + message);
+ sender.sendMessage(filterMessage(getMessagePrefixForType(type) + getChatFormatForType(type) + message));
}
}
diff --git a/src/main/kotlin/io/dico/parcels2/AddedData.kt b/src/main/kotlin/io/dico/parcels2/AddedData.kt
new file mode 100644
index 0000000..633fe72
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/AddedData.kt
@@ -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
+}
diff --git a/src/main/kotlin/io/dico/parcels2/Options.kt b/src/main/kotlin/io/dico/parcels2/Options.kt
index c349d8b..aefa9e1 100644
--- a/src/main/kotlin/io/dico/parcels2/Options.kt
+++ b/src/main/kotlin/io/dico/parcels2/Options.kt
@@ -1,27 +1,33 @@
package io.dico.parcels2
-
import com.fasterxml.jackson.annotation.JsonIgnore
import io.dico.parcels2.blockvisitor.TickWorktimeOptions
+import io.dico.parcels2.defaultimpl.DefaultGeneratorOptions
import io.dico.parcels2.storage.Storage
import io.dico.parcels2.storage.StorageFactory
import io.dico.parcels2.storage.yamlObjectMapper
-import org.bukkit.Bukkit.createBlockData
import org.bukkit.GameMode
import org.bukkit.Material
-import org.bukkit.block.Biome
-import org.bukkit.block.data.BlockData
import java.io.Reader
import java.io.Writer
-import java.util.*
+import java.util.EnumSet
class Options {
- var worlds: Map<String, WorldOptions> = HashMap()
+ var worlds: Map<String, WorldOptionsHolder> = hashMapOf()
private set
var storage: StorageOptions = StorageOptions("postgresql", DataConnectionOptions())
var tickWorktime: TickWorktimeOptions = TickWorktimeOptions(20, 1)
- fun addWorld(name: String, options: WorldOptions) = (worlds as MutableMap).put(name, options)
+ fun addWorld(name: String,
+ generatorOptions: GeneratorOptions? = null,
+ worldOptions: WorldOptions? = null) {
+ val optionsHolder = WorldOptionsHolder(
+ generatorOptions ?: DefaultGeneratorOptions(),
+ worldOptions ?: WorldOptions()
+ )
+
+ (worlds as MutableMap).put(name, optionsHolder)
+ }
fun writeTo(writer: Writer) = yamlObjectMapper.writeValue(writer, this)
@@ -31,6 +37,9 @@ class Options {
}
+class WorldOptionsHolder(var generator: GeneratorOptions = DefaultGeneratorOptions(),
+ var runtime: WorldOptions = WorldOptions())
+
data class WorldOptions(var gameMode: GameMode? = GameMode.CREATIVE,
var dayTime: Boolean = true,
var noWeather: Boolean = true,
@@ -42,8 +51,7 @@ data class WorldOptions(var gameMode: GameMode? = GameMode.CREATIVE,
var blockPortalCreation: Boolean = true,
var blockMobSpawning: Boolean = true,
var blockedItems: Set<Material> = EnumSet.of(Material.FLINT_AND_STEEL, Material.SNOWBALL),
- var axisLimit: Int = 10,
- var generator: GeneratorOptions = DefaultGeneratorOptions()) {
+ var axisLimit: Int = 10) {
}
@@ -51,23 +59,7 @@ abstract class GeneratorOptions {
abstract fun generatorFactory(): GeneratorFactory
- fun getGenerator(worlds: Worlds, worldName: String) = generatorFactory().newParcelGenerator(worlds, worldName, this)
-
-}
-
-data class DefaultGeneratorOptions(var defaultBiome: Biome = Biome.JUNGLE,
- var wallType: BlockData = createBlockData(Material.STONE_SLAB),
- var floorType: BlockData = createBlockData(Material.QUARTZ_BLOCK),
- var fillType: BlockData = createBlockData(Material.QUARTZ_BLOCK),
- var pathMainType: BlockData = createBlockData(Material.SANDSTONE),
- var pathAltType: BlockData = createBlockData(Material.REDSTONE_BLOCK),
- var parcelSize: Int = 101,
- var pathSize: Int = 9,
- var floorHeight: Int = 64,
- var offsetX: Int = 0,
- var offsetZ: Int = 0) : GeneratorOptions() {
-
- override fun generatorFactory(): GeneratorFactory = DefaultParcelGenerator.Factory
+ fun newGenerator(worldName: String) = generatorFactory().newParcelGenerator(worldName, this)
}
@@ -104,4 +96,9 @@ data class DataConnectionOptions(val address: String = "localhost",
}
-data class DataFileOptions(val location: String = "/flatfile-storage/") \ No newline at end of file
+data class DataFileOptions(val location: String = "/flatfile-storage/")
+
+class MigrationOptions() {
+
+
+}
diff --git a/src/main/kotlin/io/dico/parcels2/Parcel.kt b/src/main/kotlin/io/dico/parcels2/Parcel.kt
index e86667b..a69116a 100644
--- a/src/main/kotlin/io/dico/parcels2/Parcel.kt
+++ b/src/main/kotlin/io/dico/parcels2/Parcel.kt
@@ -1,52 +1,11 @@
package io.dico.parcels2
import io.dico.parcels2.util.Vec2i
-import io.dico.parcels2.util.getPlayerName
import io.dico.parcels2.util.hasBuildAnywhere
-import io.dico.parcels2.util.isValid
-import io.dico.parcels2.util.uuid
-import org.bukkit.Bukkit
import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player
import org.joda.time.DateTime
-import java.util.*
-
-interface AddedData {
- val added: Map<UUID, AddedStatus>
-
- 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)
-}
-
-interface ParcelData : AddedData {
- var owner: ParcelOwner?
-
- fun canBuild(player: OfflinePlayer, checkAdmin: Boolean = true, checkGlobal: Boolean = true): Boolean
-
- var allowInteractInputs: Boolean
- var allowInteractInventory: Boolean
-
- fun isOwner(uuid: UUID): Boolean {
- return owner?.uuid == uuid
- }
-}
+import java.util.UUID
/**
* Parcel implementation of ParcelData will update the database when changes are made.
@@ -56,77 +15,41 @@ interface ParcelData : AddedData {
* 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
-
+interface Parcel : ParcelData {
+ val id: ParcelId
+ val world: ParcelWorld
+ val pos: Vec2i
+ val x: Int
+ val z: Int
+ val data: ParcelData
val infoString: String
- get() {
- return "$id; owned by ${owner?.let { it.name ?: Bukkit.getOfflinePlayer(it.uuid).name }}"
- }
-
- var data: ParcelData = ParcelDataHolder(); private set
+ val hasBlockVisitors: Boolean
- fun copyDataIgnoringDatabase(data: ParcelData) {
- this.data = data
- }
-
- fun copyData(data: ParcelData) {
- world.storage.setParcelData(this, data)
- this.data = data
- }
+ fun copyDataIgnoringDatabase(data: ParcelData)
- 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)
+ fun copyData(data: ParcelData)
- 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)
- }
- }
+ fun dispose()
+}
- override var allowInteractInputs: Boolean
- get() = data.allowInteractInputs
- set(value) {
- if (data.allowInteractInputs == value) return
- world.storage.setParcelAllowsInteractInputs(this, value)
- data.allowInteractInputs = value
- }
+interface ParcelData : AddedData {
+ var owner: ParcelOwner?
+ val since: DateTime?
- override var allowInteractInventory: Boolean
- get() = data.allowInteractInventory
- set(value) {
- if (data.allowInteractInventory == value) return
- world.storage.setParcelAllowsInteractInventory(this, value)
- data.allowInteractInventory = value
- }
+ fun canBuild(player: OfflinePlayer, checkAdmin: Boolean = true, checkGlobal: Boolean = true): Boolean
- var hasBlockVisitors: Boolean = false; private set
-}
+ var allowInteractInputs: Boolean
+ var allowInteractInventory: Boolean
-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
+ fun isOwner(uuid: UUID): Boolean {
+ return owner?.uuid == uuid
+ }
}
class ParcelDataHolder : AddedDataHolder(), ParcelData {
+
override var owner: ParcelOwner? = null
+ override var since: DateTime? = null
override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean) = isAllowed(player.uniqueId)
|| owner.let { it != null && it.matches(player, allowNameMatch = false) }
|| (checkAdmin && player is Player && player.hasBuildAnywhere)
@@ -135,55 +58,3 @@ class ParcelDataHolder : AddedDataHolder(), ParcelData {
override var allowInteractInventory = true
}
-enum class AddedStatus {
- DEFAULT,
- ALLOWED,
- BANNED;
-
- val asBoolean
- get() = when (this) {
- DEFAULT -> null
- ALLOWED -> true
- BANNED -> false
- }
-}
-
-@Suppress("UsePropertyAccessSyntax")
-class ParcelOwner(val uuid: UUID? = null,
- name: String? = null,
- val since: DateTime? = null) {
-
- companion object {
- fun create(uuid: UUID?, name: String?, time: DateTime? = null): ParcelOwner? {
- return uuid?.let { ParcelOwner(uuid, name, time) }
- ?: name?.let { ParcelOwner(uuid, name, time) }
- }
- }
-
- val name: String?
-
- init {
- uuid ?: name ?: throw IllegalArgumentException("uuid and/or name must be present")
-
- if (name != null) this.name = name
- else {
- val offlinePlayer = Bukkit.getOfflinePlayer(uuid).takeIf { it.isValid }
- this.name = offlinePlayer?.name
- }
- }
-
- val playerName get() = getPlayerName(uuid, name)
-
- fun matches(player: OfflinePlayer, allowNameMatch: Boolean = false): Boolean {
- return uuid?.let { it == player.uniqueId } ?: false
- || (allowNameMatch && name?.let { it == player.name } ?: false)
- }
-
- val onlinePlayer: Player? get() = uuid?.let { Bukkit.getPlayer(uuid) }
- val onlinePlayerAllowingNameMatch: Player? get() = onlinePlayer ?: name?.let { Bukkit.getPlayer(name) }
-
- @Suppress("DEPRECATION")
- val offlinePlayer
- get() = (uuid?.let { Bukkit.getOfflinePlayer(it) } ?: Bukkit.getOfflinePlayer(name))
- ?.takeIf { it.isValid }
-}
diff --git a/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt b/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt
new file mode 100644
index 0000000..ff28537
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt
@@ -0,0 +1,83 @@
+package io.dico.parcels2
+
+import io.dico.parcels2.blockvisitor.RegionTraversal
+import io.dico.parcels2.blockvisitor.Worker
+import io.dico.parcels2.blockvisitor.WorktimeLimiter
+import io.dico.parcels2.defaultimpl.DefaultParcelGenerator
+import io.dico.parcels2.util.Vec2i
+import org.bukkit.Chunk
+import org.bukkit.Location
+import org.bukkit.World
+import org.bukkit.block.Biome
+import org.bukkit.block.Block
+import org.bukkit.entity.Entity
+import org.bukkit.generator.BlockPopulator
+import org.bukkit.generator.ChunkGenerator
+import java.util.HashMap
+import java.util.Random
+import kotlin.reflect.KClass
+
+object GeneratorFactories {
+ private val map: MutableMap<String, GeneratorFactory> = HashMap()
+
+ fun registerFactory(generator: GeneratorFactory): Boolean = map.putIfAbsent(generator.name, generator) == null
+
+ fun getFactory(name: String): GeneratorFactory? = map.get(name)
+
+ init {
+ registerFactory(DefaultParcelGenerator.Factory)
+ }
+}
+
+interface GeneratorFactory {
+ val name: String
+
+ val optionsClass: KClass<out GeneratorOptions>
+
+ fun newParcelGenerator(worldName: String, options: GeneratorOptions): ParcelGenerator
+}
+
+abstract class ParcelGenerator : ChunkGenerator() {
+ abstract val world: World
+
+ abstract override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData
+
+ abstract fun populate(world: World?, random: Random?, chunk: Chunk?)
+
+ abstract override fun getFixedSpawnLocation(world: World?, random: Random?): Location
+
+ override fun getDefaultPopulators(world: World?): MutableList<BlockPopulator> {
+ return mutableListOf(object : BlockPopulator() {
+ override fun populate(world: World?, random: Random?, chunk: Chunk?) {
+ this@ParcelGenerator.populate(world, random, chunk)
+ }
+ })
+ }
+
+ abstract fun makeParcelBlockManager(worktimeLimiter: WorktimeLimiter): ParcelBlockManager
+
+ abstract fun makeParcelLocator(container: ParcelContainer): ParcelLocator
+}
+
+interface ParcelBlockManager {
+ val world: World
+ val worktimeLimiter: WorktimeLimiter
+
+ fun getBottomBlock(parcel: ParcelId): Vec2i
+
+ fun getHomeLocation(parcel: ParcelId): Location
+
+ fun setOwnerBlock(parcel: ParcelId, owner: ParcelOwner?)
+
+ @Deprecated("")
+ fun getEntities(parcel: ParcelId): Collection<Entity> = TODO()
+
+ @Deprecated("")
+ fun getBlocks(parcel: ParcelId, yRange: IntRange = 0..255): Iterator<Block> = TODO()
+
+ fun setBiome(parcel: ParcelId, biome: Biome): Worker
+
+ fun clearParcel(parcel: ParcelId): Worker
+
+ fun doBlockOperation(parcel: ParcelId, direction: RegionTraversal = RegionTraversal.DOWNWARD, operation: (Block) -> Unit): Worker
+}
diff --git a/src/main/kotlin/io/dico/parcels2/ParcelId.kt b/src/main/kotlin/io/dico/parcels2/ParcelId.kt
new file mode 100644
index 0000000..951a172
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/ParcelId.kt
@@ -0,0 +1,49 @@
+package io.dico.parcels2
+
+import io.dico.parcels2.util.Vec2i
+import org.bukkit.Bukkit
+import org.bukkit.World
+import java.util.UUID
+
+/**
+ * Used by storage backing options to encompass the identity of a world
+ * Does NOT support equality operator.
+ */
+interface ParcelWorldId {
+ val name: String
+ val uid: UUID?
+ fun equals(id: ParcelWorldId): Boolean = name == name || (uid != null && uid == id.uid)
+
+ val bukkitWorld: World? get() = Bukkit.getWorld(name) ?: uid?.let { Bukkit.getWorld(it) }
+
+ companion object {
+ operator fun invoke(worldName: String, worldUid: UUID?): ParcelWorldId = ParcelWorldIdImpl(worldName, worldUid)
+ operator fun invoke(worldName: String): ParcelWorldId = ParcelWorldIdImpl(worldName, null)
+ }
+}
+
+/**
+ * Used by storage backing options to encompass the location of a parcel
+ * Does NOT support equality operator.
+ */
+interface ParcelId {
+ val worldId: ParcelWorldId
+ val x: Int
+ val z: Int
+ val pos: Vec2i get() = Vec2i(x, z)
+ fun equals(id: ParcelId): Boolean = x == id.x && z == id.z && worldId.equals(id.worldId)
+
+ companion object {
+ operator fun invoke(worldId: ParcelWorldId, pos: Vec2i) = invoke(worldId, pos.x, pos.z)
+ operator fun invoke(worldName: String, worldUid: UUID?, pos: Vec2i) = invoke(worldName, worldUid, pos.x, pos.z)
+ operator fun invoke(worldName: String, worldUid: UUID?, x: Int, z: Int) = invoke(ParcelWorldId(worldName, worldUid), x, z)
+ operator fun invoke(worldId: ParcelWorldId, x: Int, z: Int): ParcelId = ParcelIdImpl(worldId, x, z)
+ }
+}
+
+private class ParcelWorldIdImpl(override val name: String,
+ override val uid: UUID?) : ParcelWorldId
+
+private class ParcelIdImpl(override val worldId: ParcelWorldId,
+ override val x: Int,
+ override val z: Int) : ParcelId
diff --git a/src/main/kotlin/io/dico/parcels2/ParcelOwner.kt b/src/main/kotlin/io/dico/parcels2/ParcelOwner.kt
new file mode 100644
index 0000000..5a36cac
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/ParcelOwner.kt
@@ -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()
+ }
+
+} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt
index 9a50b31..16f108f 100644
--- a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt
+++ b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt
@@ -1,230 +1,86 @@
package io.dico.parcels2
-import io.dico.parcels2.storage.SerializableParcel
-import io.dico.parcels2.storage.SerializableWorld
import io.dico.parcels2.storage.Storage
import io.dico.parcels2.util.Vec2i
-import io.dico.parcels2.util.doAwait
import io.dico.parcels2.util.floor
-import kotlinx.coroutines.experimental.launch
-import org.bukkit.Bukkit
import org.bukkit.Location
import org.bukkit.World
-import org.bukkit.WorldCreator
import org.bukkit.block.Block
import org.bukkit.entity.Entity
-import org.bukkit.entity.Player
-import java.util.*
-import kotlin.coroutines.experimental.buildIterator
-import kotlin.coroutines.experimental.buildSequence
-import kotlin.reflect.jvm.javaMethod
-import kotlin.reflect.jvm.kotlinFunction
+import java.util.UUID
-class Worlds(val plugin: ParcelsPlugin) {
- val worlds: Map<String, ParcelWorld> get() = _worlds
- private val _worlds: MutableMap<String, ParcelWorld> = HashMap()
+interface ParcelProvider {
+ val worlds: Map<String, ParcelWorld>
+
+ fun getWorldById(id: ParcelWorldId): ParcelWorld?
+
+ fun getParcelById(id: ParcelId): Parcel?
- fun getWorld(name: String): ParcelWorld? = _worlds[name]
+ fun getWorld(name: String): ParcelWorld?
fun getWorld(world: World): ParcelWorld? = getWorld(world.name)
- fun getParcelAt(block: Block): Parcel? = getParcelAt(block.world, block.x, block.z)
+ fun getWorld(block: Block): ParcelWorld? = getWorld(block.world)
+
+ fun getWorld(loc: Location): ParcelWorld? = getWorld(loc.world)
- fun getParcelAt(player: Player): Parcel? = getParcelAt(player.location)
+ fun getWorld(entity: Entity): ParcelWorld? = getWorld(entity.location)
- fun getParcelAt(location: Location): Parcel? = getParcelAt(location.world, location.x.floor(), location.z.floor())
+ fun getParcelAt(worldName: String, x: Int, z: Int): Parcel? = getWorld(worldName)?.locator?.getParcelAt(x, z)
fun getParcelAt(world: World, x: Int, z: Int): Parcel? = getParcelAt(world.name, x, z)
- fun getParcelAt(world: String, x: Int, z: Int): Parcel? {
- with(getWorld(world) ?: return null) {
- return generator.parcelAt(x, z)
- }
- }
-
- init {
- val function = ::loadWorlds
- function.javaMethod!!.kotlinFunction
- }
-
- operator fun SerializableParcel.invoke(): Parcel? {
- return world()?.parcelByID(pos)
- }
-
- operator fun SerializableWorld.invoke(): ParcelWorld? {
- return world?.let { getWorld(it) }
- }
-
- fun loadWorlds(options: Options) {
- for ((worldName, worldOptions) in options.worlds.entries) {
- val world: ParcelWorld
- try {
-
- world = ParcelWorld(
- worldName,
- worldOptions,
- worldOptions.generator.getGenerator(this, worldName),
- plugin.storage)
-
- } catch (ex: Exception) {
- ex.printStackTrace()
- continue
- }
-
- _worlds.put(worldName, world)
-
- if (Bukkit.getWorld(worldName) == null) {
- plugin.doAwait {
- cond = {
- try {
- // server.getDefaultGameMode() throws an error before any worlds are initialized.
- // createWorld() below calls that method.
- // Plugin needs to load on STARTUP for generators to be registered correctly.
- // Means we need to await the initial worlds getting loaded.
-
- plugin.server.defaultGameMode; true
- } catch (ex: Throwable) {
- false
- }
- }
-
- onSuccess = {
- val bworld = WorldCreator(worldName).generator(world.generator).createWorld()
- val spawn = world.generator.getFixedSpawnLocation(bworld, null)
- bworld.setSpawnLocation(spawn.x.floor(), spawn.y.floor(), spawn.z.floor())
- }
- }
- }
-
- }
-
- }
+ fun getParcelAt(world: World, vec: Vec2i): Parcel? = getParcelAt(world, vec.x, vec.z)
+
+ fun getParcelAt(loc: Location): Parcel? = getParcelAt(loc.world, loc.x.floor(), loc.z.floor())
+
+ fun getParcelAt(entity: Entity): Parcel? = getParcelAt(entity.location)
+
+ fun getParcelAt(block: Block): Parcel? = getParcelAt(block.world, block.x, block.z)
+
+ fun getWorldGenerator(worldName: String): ParcelGenerator?
+
+ fun loadWorlds()
}
-interface ParcelProvider {
+interface ParcelLocator {
+ val world: World
- fun parcelAt(x: Int, z: Int): Parcel?
+ fun getParcelIdAt(x: Int, z: Int): ParcelId?
- fun parcelAt(vec: Vec2i): Parcel? = parcelAt(vec.x, vec.z)
+ fun getParcelAt(x: Int, z: Int): Parcel?
- fun parcelAt(loc: Location): Parcel? = parcelAt(loc.x.floor(), loc.z.floor())
+ fun getParcelAt(vec: Vec2i): Parcel? = getParcelAt(vec.x, vec.z)
- fun parcelAt(entity: Entity): Parcel? = parcelAt(entity.location)
+ fun getParcelAt(loc: Location): Parcel? = getParcelAt(loc.x.floor(), loc.z.floor()).takeIf { loc.world == world }
- fun parcelAt(block: Block): Parcel? = parcelAt(block.x, block.z)
-}
+ fun getParcelAt(entity: Entity): Parcel? = getParcelAt(entity.location).takeIf { entity.world == world }
-class ParcelWorld constructor(val name: String,
- val options: WorldOptions,
- val generator: ParcelGenerator,
- val storage: Storage) : ParcelProvider by generator, ParcelContainer {
- val world: World by lazy {
- Bukkit.getWorld(name) ?: throw NullPointerException("World $name does not appear to be loaded")
- }
- val container: ParcelContainer = DefaultParcelContainer(this, storage)
-
- override fun parcelByID(x: Int, z: Int): Parcel? {
- return container.parcelByID(x, z)
- }
-
- override fun nextEmptyParcel(): Parcel? {
- return container.nextEmptyParcel()
- }
-
- fun parcelByID(id: Vec2i): Parcel? = parcelByID(id.x, id.z)
-
- fun enforceOptionsIfApplicable() {
- val world = world
- val options = options
- if (options.dayTime) {
- world.setGameRuleValue("doDaylightCycle", "false")
- world.setTime(6000)
- }
-
- if (options.noWeather) {
- world.setStorm(false)
- world.setThundering(false)
- world.weatherDuration = Integer.MAX_VALUE
- }
-
- world.setGameRuleValue("doTileDrops", "${options.doTileDrops}")
- }
+ fun getParcelAt(block: Block): Parcel? = getParcelAt(block.x, block.z).takeIf { block.world == world }
}
+typealias ParcelContainerFactory = (ParcelWorld) -> ParcelContainer
+
interface ParcelContainer {
- fun parcelByID(x: Int, z: Int): Parcel?
+ fun getParcelById(x: Int, z: Int): Parcel?
+
+ fun getParcelById(id: Vec2i): Parcel? = getParcelById(id.x, id.z)
fun nextEmptyParcel(): Parcel?
}
-typealias ParcelContainerFactory = (ParcelWorld) -> ParcelContainer
-
-class DefaultParcelContainer(private val world: ParcelWorld,
- private val storage: Storage) : ParcelContainer {
- private var parcels: Array<Array<Parcel>>
-
- init {
- parcels = initArray(world.options.axisLimit, world)
- }
-
- fun resizeIfSizeChanged() {
- if (parcels.size / 2 != world.options.axisLimit) {
- resize(world.options.axisLimit)
- }
- }
-
- fun resize(axisLimit: Int) {
- parcels = initArray(axisLimit, world, this)
- }
-
- fun initArray(axisLimit: Int, world: ParcelWorld, cur: DefaultParcelContainer? = null): Array<Array<Parcel>> {
- val arraySize = 2 * axisLimit + 1
- return Array(arraySize) {
- val x = it - axisLimit
- Array(arraySize) {
- val z = it - axisLimit
- cur?.parcelByID(x, z) ?: Parcel(world, Vec2i(x, z))
- }
- }
- }
-
- override fun parcelByID(x: Int, z: Int): Parcel? {
- return parcels.getOrNull(x + world.options.axisLimit)?.getOrNull(z + world.options.axisLimit)
- }
-
- override fun nextEmptyParcel(): Parcel? {
- return walkInCircle().find { it.owner == null }
- }
-
- private fun walkInCircle(): Iterable<Parcel> = Iterable {
- buildIterator {
- val center = world.options.axisLimit
- for (radius in 0..center) {
- var x = center - radius; var z = center - radius
- repeat(radius * 2) { yield(parcels[x++][z]) }
- repeat(radius * 2) { yield(parcels[x][z++]) }
- repeat(radius * 2) { yield(parcels[x--][z]) }
- repeat(radius * 2) { yield(parcels[x][z--]) }
- }
- }
- }
-
- fun allParcels(): Sequence<Parcel> = buildSequence {
- for (array in parcels) {
- yieldAll(array.iterator())
- }
- }
-
- fun loadAllData() {
- val channel = storage.readParcelData(allParcels(), 100)
- launch(storage.asyncDispatcher) {
- for ((parcel, data) in channel) {
- data?.let { parcel.copyDataIgnoringDatabase(it) }
- }
- }
- }
-
-} \ No newline at end of file
+interface ParcelWorld : ParcelLocator, ParcelContainer, ParcelBlockManager {
+ val id: ParcelWorldId
+ val name: String
+ val uid: UUID?
+ val options: WorldOptions
+ val generator: ParcelGenerator
+ val storage: Storage
+ val container: ParcelContainer
+ val locator: ParcelLocator
+ val blockManager: ParcelBlockManager
+ val globalAddedData: GlobalAddedDataManager
+}
diff --git a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt
index cc06f85..6d08d28 100644
--- a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt
+++ b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt
@@ -6,31 +6,37 @@ import io.dico.dicore.command.ICommandDispatcher
import io.dico.parcels2.blockvisitor.TickWorktimeLimiter
import io.dico.parcels2.blockvisitor.WorktimeLimiter
import io.dico.parcels2.command.getParcelCommands
+import io.dico.parcels2.defaultimpl.GlobalAddedDataManagerImpl
+import io.dico.parcels2.defaultimpl.ParcelProviderImpl
import io.dico.parcels2.listener.ParcelEntityTracker
import io.dico.parcels2.listener.ParcelListeners
import io.dico.parcels2.storage.Storage
import io.dico.parcels2.storage.yamlObjectMapper
+import io.dico.parcels2.util.FunctionHelper
import io.dico.parcels2.util.tryCreate
-import kotlinx.coroutines.experimental.asCoroutineDispatcher
import org.bukkit.Bukkit
+import org.bukkit.generator.ChunkGenerator
import org.bukkit.plugin.java.JavaPlugin
+import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.File
-import java.util.concurrent.Executor
-val logger = LoggerFactory.getLogger("ParcelsPlugin")
+val logger: Logger = LoggerFactory.getLogger("ParcelsPlugin")
private inline val plogger get() = logger
class ParcelsPlugin : JavaPlugin() {
lateinit var optionsFile: File; private set
lateinit var options: Options; private set
- lateinit var worlds: Worlds; private set
+ lateinit var parcelProvider: ParcelProvider; private set
lateinit var storage: Storage; private set
+ lateinit var globalAddedData: GlobalAddedDataManager; private set
val registrator = Registrator(this)
lateinit var entityTracker: ParcelEntityTracker; private set
private var listeners: ParcelListeners? = null
private var cmdDispatcher: ICommandDispatcher? = null
+
+ val functionHelper: FunctionHelper = FunctionHelper(this)
val worktimeLimiter: WorktimeLimiter by lazy { TickWorktimeLimiter(this, options.tickWorktime) }
override fun onEnable() {
@@ -41,13 +47,14 @@ class ParcelsPlugin : JavaPlugin() {
}
override fun onDisable() {
+ worktimeLimiter.completeAllTasks()
cmdDispatcher?.unregisterFromCommandMap()
}
private fun init(): Boolean {
optionsFile = File(dataFolder, "options.yml")
options = Options()
- worlds = Worlds(this)
+ parcelProvider = ParcelProviderImpl(this)
try {
if (!loadOptions()) return false
@@ -60,39 +67,46 @@ class ParcelsPlugin : JavaPlugin() {
return false
}
- worlds.loadWorlds(options)
+ globalAddedData = GlobalAddedDataManagerImpl(this)
+ entityTracker = ParcelEntityTracker(parcelProvider)
} catch (ex: Exception) {
plogger.error("Error loading options", ex)
return false
}
- entityTracker = ParcelEntityTracker(worlds)
registerListeners()
registerCommands()
+ parcelProvider.loadWorlds()
return true
}
fun loadOptions(): Boolean {
- if (optionsFile.exists()) {
- yamlObjectMapper.readerForUpdating(options).readValue<Options>(optionsFile)
- } else if (optionsFile.tryCreate()) {
- options.addWorld("parcels", WorldOptions())
- try {
- yamlObjectMapper.writeValue(optionsFile, options)
- } catch (ex: Throwable) {
- optionsFile.delete()
- throw ex
+ when {
+ optionsFile.exists() -> yamlObjectMapper.readerForUpdating(options).readValue<Options>(optionsFile)
+ optionsFile.tryCreate() -> {
+ options.addWorld("parcels")
+ try {
+ yamlObjectMapper.writeValue(optionsFile, options)
+ } catch (ex: Throwable) {
+ optionsFile.delete()
+ throw ex
+ }
+ plogger.warn("Created options file with a world template. Please review it before next start.")
+ return false
+ }
+ else -> {
+ plogger.error("Failed to save options file ${optionsFile.canonicalPath}")
+ return false
}
- plogger.warn("Created options file with a world template. Please review it before next start.")
- return false
- } else {
- plogger.error("Failed to save options file ${optionsFile.canonicalPath}")
- return false
}
return true
}
+ override fun getDefaultWorldGenerator(worldName: String, generatorId: String?): ChunkGenerator? {
+ return parcelProvider.getWorldGenerator(worldName)
+ }
+
private fun registerCommands() {
cmdDispatcher = getParcelCommands(this).apply {
registerToCommandMap("parcels:", EOverridePolicy.FALLBACK_ONLY)
@@ -100,8 +114,8 @@ class ParcelsPlugin : JavaPlugin() {
}
private fun registerListeners() {
- if (listeners != null) {
- listeners = ParcelListeners(worlds, entityTracker)
+ if (listeners == null) {
+ listeners = ParcelListeners(parcelProvider, entityTracker)
registrator.registerListeners(listeners!!)
}
}
diff --git a/src/main/kotlin/io/dico/parcels2/WorldGenerator.kt b/src/main/kotlin/io/dico/parcels2/WorldGenerator.kt
deleted file mode 100644
index 72ca3bd..0000000
--- a/src/main/kotlin/io/dico/parcels2/WorldGenerator.kt
+++ /dev/null
@@ -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)
- }
- }
-
-} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt
index c046940..9403f34 100644
--- a/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt
+++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/Attachables.kt
@@ -2,7 +2,7 @@ package io.dico.parcels2.blockvisitor
import org.bukkit.Material
import org.bukkit.Material.*
-import java.util.*
+import java.util.EnumSet
val attachables: Set<Material> = EnumSet.of(
ACACIA_DOOR,
@@ -64,4 +64,4 @@ val attachables: Set<Material> = EnumSet.of(
WALL_SIGN,
LILY_PAD,
DANDELION
-); \ No newline at end of file
+) \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraversal.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraversal.kt
index 19eb7ee..85fe946 100644
--- a/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraversal.kt
+++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraversal.kt
@@ -20,7 +20,7 @@ enum class RegionTraversal(private val builder: suspend SequenceBuilder<Vec3i>.(
}),
- UPDARD({ region ->
+ UPWARD({ region ->
val origin = region.origin
val size = region.size
diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt
index 41df083..c375e5a 100644
--- a/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt
+++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt
@@ -6,6 +6,7 @@ import io.dico.parcels2.util.get
import org.bukkit.World
import org.bukkit.block.data.BlockData
+// TODO order paste such that attachables are placed after the block they depend on
class Schematic {
val size: Vec3i get() = _size!!
private var _size: Vec3i? = null
diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt
index 45196f2..30eaabd 100644
--- a/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt
+++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt
@@ -1,11 +1,12 @@
package io.dico.parcels2.blockvisitor
-import kotlinx.coroutines.experimental.*
-import org.bukkit.plugin.Plugin
+import io.dico.parcels2.ParcelsPlugin
+import io.dico.parcels2.util.FunctionHelper
+import kotlinx.coroutines.experimental.CancellationException
+import kotlinx.coroutines.experimental.Job
import org.bukkit.scheduler.BukkitTask
import java.lang.System.currentTimeMillis
-import java.util.*
-import java.util.concurrent.Executor
+import java.util.LinkedList
import java.util.logging.Level
import kotlin.coroutines.experimental.Continuation
import kotlin.coroutines.experimental.intrinsics.COROUTINE_SUSPENDED
@@ -27,6 +28,11 @@ sealed class WorktimeLimiter {
* Get a list of all workers
*/
abstract val workers: List<Worker>
+
+ /**
+ * Attempts to complete any remaining tasks immediately, without suspension.
+ */
+ abstract fun completeAllTasks()
}
interface Timed {
@@ -90,8 +96,14 @@ interface WorkerScope : Timed {
private interface WorkerContinuation : Worker, WorkerScope {
/**
- * Start or resume the execution of this worker
- * returns true if the worker completed
+ * Start or resumes the execution of this worker
+ * and returns true if the worker completed
+ *
+ * [worktime] is the maximum amount of time, in milliseconds,
+ * that this job may run for until suspension.
+ *
+ * If [worktime] is not positive, the worker will complete
+ * without suspension and this method will always return true.
*/
fun resume(worktime: Long): Boolean
}
@@ -101,19 +113,17 @@ private interface WorkerContinuation : Worker, WorkerScope {
* There is a configurable maxiumum amount of milliseconds that can be allocated to all workers together in each server tick
* This object attempts to split that maximum amount of milliseconds equally between all jobs
*/
-class TickWorktimeLimiter(private val plugin: Plugin, var options: TickWorktimeOptions) : WorktimeLimiter() {
- // Coroutine dispatcher for jobs
- private val dispatcher = Executor(Runnable::run).asCoroutineDispatcher()
+class TickWorktimeLimiter(private val plugin: ParcelsPlugin, var options: TickWorktimeOptions) : WorktimeLimiter() {
// The currently registered bukkit scheduler task
private var bukkitTask: BukkitTask? = null
// The workers.
- private var _workers = LinkedList<WorkerContinuation>()
+ private val _workers = LinkedList<WorkerContinuation>()
override val workers: List<Worker> = _workers
override fun submit(task: TimeLimitedTask): Worker {
- val worker: WorkerContinuation = WorkerImpl(plugin, dispatcher, task)
+ val worker: WorkerContinuation = WorkerImpl(plugin.functionHelper, task)
_workers.addFirst(worker)
- if (bukkitTask == null) bukkitTask = plugin.server.scheduler.runTaskTimer(plugin, ::tickJobs, 0, options.tickInterval.toLong())
+ if (bukkitTask == null) bukkitTask = plugin.functionHelper.scheduleRepeating(0, options.tickInterval) { tickJobs() }
return worker
}
@@ -144,10 +154,18 @@ class TickWorktimeLimiter(private val plugin: Plugin, var options: TickWorktimeO
}
}
+ override fun completeAllTasks() {
+ _workers.forEach {
+ it.resume(-1)
+ }
+ _workers.clear()
+ bukkitTask?.cancel()
+ bukkitTask = null
+ }
+
}
-private class WorkerImpl(val plugin: Plugin,
- val dispatcher: CoroutineDispatcher,
+private class WorkerImpl(val functionHelper: FunctionHelper,
val task: TimeLimitedTask) : WorkerContinuation {
override var job: Job? = null; private set
@@ -170,6 +188,7 @@ private class WorkerImpl(val plugin: Plugin,
private var onCompleted: WorkerUpdateLister? = null
private var continuation: Continuation<Unit>? = null
private var nextSuspensionTime: Long = 0L
+ private var completeForcefully = false
private fun initJob(job: Job) {
this.job?.let { throw IllegalStateException() }
@@ -179,7 +198,7 @@ private class WorkerImpl(val plugin: Plugin,
// report any error that occurred
completionException = exception?.also {
if (it !is CancellationException)
- plugin.logger.log(Level.SEVERE, "TimeLimitedTask for plugin ${plugin.name} generated an exception", it)
+ functionHelper.plugin.logger.log(Level.SEVERE, "TimeLimitedTask for plugin ${functionHelper.plugin.name} generated an exception", it)
}
// convert to elapsed time here
@@ -204,7 +223,7 @@ private class WorkerImpl(val plugin: Plugin,
}
override suspend fun markSuspensionPoint() {
- if (System.currentTimeMillis() >= nextSuspensionTime)
+ if (System.currentTimeMillis() >= nextSuspensionTime && !completeForcefully)
suspendCoroutineUninterceptedOrReturn { cont: Continuation<Unit> ->
continuation = cont
COROUTINE_SUSPENDED
@@ -222,7 +241,11 @@ private class WorkerImpl(val plugin: Plugin,
}
override fun resume(worktime: Long): Boolean {
- nextSuspensionTime = currentTimeMillis() + worktime
+ if (worktime > 0) {
+ nextSuspensionTime = currentTimeMillis() + worktime
+ } else {
+ completeForcefully = true
+ }
continuation?.let {
continuation = null
@@ -236,10 +259,9 @@ private class WorkerImpl(val plugin: Plugin,
}
try {
- launch(context = dispatcher, start = CoroutineStart.UNDISPATCHED) {
- initJob(job = kotlin.coroutines.experimental.coroutineContext[Job]!!)
- task()
- }
+ val job = functionHelper.launchLazilyOnMainThread { task() }
+ initJob(job = job)
+ job.start()
} catch (t: Throwable) {
// do nothing: handled by job.invokeOnCompletion()
}
diff --git a/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt b/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt
index 2cee99a..2fceb5b 100644
--- a/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt
+++ b/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt
@@ -4,10 +4,10 @@ import io.dico.dicore.command.CommandException
import io.dico.dicore.command.ExecutionContext
import io.dico.dicore.command.ICommandReceiver
import io.dico.parcels2.ParcelOwner
+import io.dico.parcels2.ParcelWorld
import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.util.hasAdminManage
import io.dico.parcels2.util.parcelLimit
-import io.dico.parcels2.util.uuid
import org.bukkit.entity.Player
import org.bukkit.plugin.Plugin
import java.lang.reflect.Method
@@ -16,10 +16,10 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei
override fun getPlugin(): Plugin = plugin
override fun getReceiver(context: ExecutionContext, target: Method, cmdName: String): ICommandReceiver {
- return getParcelCommandReceiver(plugin.worlds, context, target, cmdName)
+ return getParcelCommandReceiver(plugin.parcelProvider, context, target, cmdName)
}
- protected inline val worlds get() = plugin.worlds
+ protected inline val worlds get() = plugin.parcelProvider
protected fun error(message: String): Nothing {
throw CommandException(message)
@@ -29,9 +29,10 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei
if (!plugin.storage.isConnected) error("Parcels cannot $action right now because of a database error")
}
- protected suspend fun checkParcelLimit(player: Player) {
+ protected suspend fun checkParcelLimit(player: Player, world: ParcelWorld) {
if (player.hasAdminManage) return
- val numOwnedParcels = plugin.storage.getNumParcels(ParcelOwner(uuid = player.uuid)).await()
+ val numOwnedParcels = plugin.storage.getOwnedParcels(ParcelOwner(player)).await()
+ .filter { it.worldId.equals(world.id) }.size
val limit = player.parcelLimit
if (numOwnedParcels >= limit) {
diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusGlobal.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusGlobal.kt
new file mode 100644
index 0000000..d483126
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusGlobal.kt
@@ -0,0 +1,65 @@
+package io.dico.parcels2.command
+
+import io.dico.dicore.command.Validate
+import io.dico.dicore.command.annotation.Cmd
+import io.dico.dicore.command.annotation.Desc
+import io.dico.parcels2.GlobalAddedData
+import io.dico.parcels2.GlobalAddedDataManager
+import io.dico.parcels2.ParcelOwner
+import io.dico.parcels2.ParcelsPlugin
+import org.bukkit.OfflinePlayer
+import org.bukkit.entity.Player
+
+class CommandsAddedStatusGlobal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
+ private inline val data get() = plugin.globalAddedData
+ @Suppress("NOTHING_TO_INLINE")
+ private inline operator fun GlobalAddedDataManager.get(player: OfflinePlayer): GlobalAddedData = data[ParcelOwner(player)]
+
+ @Cmd("allow", aliases = ["add", "permit"])
+ @Desc("Globally allows a player to build on all",
+ "the parcels that you own.",
+ shortVersion = "globally allows a player to build on your parcels")
+ @ParcelRequire(owner = true)
+ fun cmdAllow(sender: Player, player: OfflinePlayer): Any? {
+ Validate.isTrue(player != sender, "The target cannot be yourself")
+ Validate.isTrue(data[sender].allow(player), "${player.name} is already allowed globally")
+ return "${player.name} is now allowed to build on all your parcels"
+ }
+
+ @Cmd("disallow", aliases = ["remove", "forbid"])
+ @Desc("Globally disallows a player to build on",
+ "the parcels that you own.",
+ "If the player is allowed to build on specific",
+ "parcels, they can still build there.",
+ shortVersion = "globally disallows a player to build on your parcels")
+ @ParcelRequire(owner = true)
+ fun cmdDisallow(sender: Player, player: OfflinePlayer): Any? {
+ Validate.isTrue(player != sender, "The target cannot be yourself")
+ Validate.isTrue(data[sender].disallow(player), "${player.name} is not currently allowed globally")
+ return "${player.name} is not allowed to build on all your parcels anymore"
+ }
+
+ @Cmd("ban", aliases = ["deny"])
+ @Desc("Globally bans a player from all the parcels",
+ "that you own, making them unable to enter.",
+ shortVersion = "globally bans a player from your parcels")
+ @ParcelRequire(owner = true)
+ fun cmdBan(sender: Player, player: OfflinePlayer): Any? {
+ Validate.isTrue(player != sender, "The target cannot be yourself")
+ Validate.isTrue(data[sender].ban(player), "${player.name} is already banned from all your parcels")
+ return "${player.name} is now banned from all your parcels"
+ }
+
+ @Cmd("unban", aliases = ["undeny"])
+ @Desc("Globally unbans a player from all the parcels",
+ "that you own, they can enter again.",
+ "If the player is banned from specific parcels,",
+ "they will still be banned there.",
+ shortVersion = "globally unbans a player from your parcels")
+ @ParcelRequire(owner = true)
+ fun cmdUnban(sender: Player, player: OfflinePlayer): Any? {
+ Validate.isTrue(data[sender].unban(player), "${player.name} is not currently banned from all your parcels")
+ return "${player.name} is not banned from all your parcels anymore"
+ }
+
+} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatus.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusLocal.kt
index adc8e60..69da341 100644
--- a/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatus.kt
+++ b/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusLocal.kt
@@ -8,14 +8,14 @@ import io.dico.parcels2.util.hasAdminManage
import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player
-class CommandsAddedStatus(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
+class CommandsAddedStatusLocal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@Cmd("allow", aliases = ["add", "permit"])
@Desc("Allows a player to build on this parcel",
shortVersion = "allows a player to build on this parcel")
@ParcelRequire(owner = true)
fun ParcelScope.cmdAllow(sender: Player, player: OfflinePlayer): Any? {
- Validate.isTrue(parcel.owner != null && !sender.hasAdminManage, "This parcel is unowned")
+ Validate.isTrue(parcel.owner != null || sender.hasAdminManage, "This parcel is unowned")
Validate.isTrue(!parcel.owner!!.matches(player), "The target already owns the parcel")
Validate.isTrue(parcel.allow(player), "${player.name} is already allowed to build on this parcel")
return "${player.name} is now allowed to build on this parcel"
@@ -37,7 +37,7 @@ class CommandsAddedStatus(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin
shortVersion = "bans a player from this parcel")
@ParcelRequire(owner = true)
fun ParcelScope.cmdBan(sender: Player, player: OfflinePlayer): Any? {
- Validate.isTrue(parcel.owner != null && !sender.hasAdminManage, "This parcel is unowned")
+ Validate.isTrue(parcel.owner != null || sender.hasAdminManage, "This parcel is unowned")
Validate.isTrue(!parcel.owner!!.matches(player), "The owner cannot be banned from the parcel")
Validate.isTrue(parcel.ban(player), "${player.name} is already banned from this parcel")
return "${player.name} is now banned from this parcel"
diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt
index 8f7f6ba..bf5a870 100644
--- a/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt
+++ b/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt
@@ -1,12 +1,17 @@
package io.dico.parcels2.command
import io.dico.dicore.command.CommandException
+import io.dico.dicore.command.EMessageType
+import io.dico.dicore.command.ExecutionContext
import io.dico.dicore.command.annotation.Cmd
import io.dico.parcels2.ParcelsPlugin
+import io.dico.parcels2.blockvisitor.RegionTraversal
import org.bukkit.Bukkit
+import org.bukkit.Material
import org.bukkit.entity.Player
+import java.util.Random
-class CommandsDebug(val plugin: ParcelsPlugin) {
+class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@Cmd("reloadoptions")
fun reloadOptions() {
@@ -23,4 +28,24 @@ class CommandsDebug(val plugin: ParcelsPlugin) {
return "Teleported you to $worldName spawn"
}
+ @Cmd("make_mess")
+ @ParcelRequire(owner = true)
+ fun ParcelScope.cmdMakeMess(context: ExecutionContext) {
+ val server = plugin.server
+ val blockDatas = arrayOf(
+ server.createBlockData(Material.STICKY_PISTON),
+ server.createBlockData(Material.GLASS),
+ server.createBlockData(Material.STONE_SLAB),
+ server.createBlockData(Material.QUARTZ_BLOCK)
+ )
+ val random = Random()
+
+ world.doBlockOperation(parcel.id, direction = RegionTraversal.UPWARD) { block ->
+ block.blockData = blockDatas[random.nextInt(4)]
+ }.onProgressUpdate(1000, 1000) { progress, elapsedTime ->
+ context.sendMessage(EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed"
+ .format(progress * 100, elapsedTime / 1000.0))
+ }
+ }
+
} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt
index 78a989e..5395b1c 100644
--- a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt
+++ b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt
@@ -4,20 +4,15 @@ import io.dico.dicore.command.EMessageType
import io.dico.dicore.command.ExecutionContext
import io.dico.dicore.command.annotation.Cmd
import io.dico.dicore.command.annotation.Desc
+import io.dico.dicore.command.annotation.Flag
import io.dico.dicore.command.annotation.RequireParameters
import io.dico.parcels2.ParcelOwner
import io.dico.parcels2.ParcelsPlugin
-import io.dico.parcels2.blockvisitor.RegionTraversal
-import io.dico.parcels2.command.NamedParcelDefaultValue.FIRST_OWNED
-import io.dico.parcels2.storage.getParcelBySerializedValue
import io.dico.parcels2.util.hasAdminManage
import io.dico.parcels2.util.hasParcelHomeOthers
import io.dico.parcels2.util.uuid
-import org.bukkit.Material
import org.bukkit.entity.Player
-import java.util.*
-//@Suppress("unused")
class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@Cmd("auto")
@@ -26,12 +21,12 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
shortVersion = "sets you up with a fresh, unclaimed parcel")
suspend fun WorldScope.cmdAuto(player: Player): Any? {
checkConnected("be claimed")
- checkParcelLimit(player)
+ checkParcelLimit(player, world)
val parcel = world.nextEmptyParcel()
?: error("This world is full, please ask an admin to upsize it")
parcel.owner = ParcelOwner(uuid = player.uuid)
- player.teleport(parcel.homeLocation)
+ player.teleport(parcel.world.getHomeLocation(parcel.id))
return "Enjoy your new parcel!"
}
@@ -48,23 +43,22 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
"more than one parcel",
shortVersion = "teleports you to parcels")
@RequireParameters(0)
- suspend fun cmdHome(player: Player,
- @NamedParcelDefault(FIRST_OWNED) target: NamedParcelTarget): Any? {
- if (player !== target.player && !player.hasParcelHomeOthers) {
+ suspend fun cmdHome(player: Player, @ParcelTarget.Kind(ParcelTarget.OWNER_REAL) target: ParcelTarget): Any? {
+ val ownerTarget = target as ParcelTarget.ByOwner
+ if (!ownerTarget.owner.matches(player) && !player.hasParcelHomeOthers) {
error("You do not have permission to teleport to other people's parcels")
}
- val ownedParcelsResult = plugin.storage.getOwnedParcels(ParcelOwner(uuid = target.player.uuid)).await()
+ val ownedParcelsResult = plugin.storage.getOwnedParcels(ownerTarget.owner).await()
- val uuid = target.player.uuid
val ownedParcels = ownedParcelsResult
- .map { worlds.getParcelBySerializedValue(it) }
- .filter { it != null && it.world == target.world && it.owner?.uuid == uuid }
+ .map { worlds.getParcelById(it) }
+ .filter { it != null && ownerTarget.world == it.world }
val targetMatch = ownedParcels.getOrNull(target.index)
?: error("The specified parcel could not be matched")
- player.teleport(targetMatch.homeLocation)
+ player.teleport(targetMatch.world.getHomeLocation(targetMatch.id))
return ""
}
@@ -77,38 +71,39 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
error(if (it.matches(player)) "You already own this parcel" else "This parcel is not available")
}
- checkParcelLimit(player)
- parcel.owner = ParcelOwner(uuid = player.uuid, name = player.name)
+ checkParcelLimit(player, world)
+ parcel.owner = ParcelOwner(player)
return "Enjoy your new parcel!"
}
+ @Cmd("unclaim")
+ @Desc("Unclaims this parcel")
+ @ParcelRequire(owner = true)
+ fun ParcelScope.cmdUnclaim(player: Player): Any? {
+ parcel.dispose()
+ return "Your parcel has been disposed"
+ }
+
@Cmd("clear")
@ParcelRequire(owner = true)
- fun ParcelScope.cmdClear(context: ExecutionContext) {
- world.generator.clearParcel(parcel)
+ fun ParcelScope.cmdClear(context: ExecutionContext, @Flag sure: Boolean): Any? {
+ if (!sure) return "Are you sure? You cannot undo this action!\n" +
+ "Run \"/${context.rawInput} -sure\" if you want to go through with this."
+
+ world.clearParcel(parcel.id)
.onProgressUpdate(1000, 1000) { progress, elapsedTime ->
- context.sendMessage(EMessageType.INFORMATIVE, "Clear progress: %.02f%%, %.2fs elapsed"
+ val alt = context.getFormat(EMessageType.NUMBER)
+ val main = context.getFormat(EMessageType.INFORMATIVE)
+ context.sendMessage(EMessageType.INFORMATIVE, false, "Clear progress: $alt%.02f$main%%, $alt%.2f${main}s elapsed"
.format(progress * 100, elapsedTime / 1000.0))
}
+
+ return null
}
- @Cmd("make_mess")
- @ParcelRequire(owner = true)
- fun ParcelScope.cmdMakeMess(context: ExecutionContext) {
- val server = plugin.server
- val blockDatas = arrayOf(
- server.createBlockData(Material.STICKY_PISTON),
- server.createBlockData(Material.GLASS),
- server.createBlockData(Material.STONE_SLAB),
- server.createBlockData(Material.QUARTZ_BLOCK)
- )
- val random = Random()
- world.generator.doBlockOperation(parcel, direction = RegionTraversal.UPDARD) { block ->
- block.blockData = blockDatas[random.nextInt(4)]
- }.onProgressUpdate(1000, 1000) { progress, elapsedTime ->
- context.sendMessage(EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed"
- .format(progress * 100, elapsedTime / 1000.0))
- }
+ @Cmd("swap")
+ fun ParcelScope.cmdSwap(context: ExecutionContext, @Flag sure: Boolean): Any? {
+ TODO()
}
} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt
index 2ba16e2..b633c3e 100644
--- a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt
+++ b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt
@@ -3,29 +3,32 @@ package io.dico.parcels2.command
import io.dico.dicore.command.CommandBuilder
import io.dico.dicore.command.ICommandAddress
import io.dico.dicore.command.ICommandDispatcher
-import io.dico.dicore.command.predef.PredefinedCommand
import io.dico.dicore.command.registration.reflect.ReflectiveRegistration
import io.dico.parcels2.ParcelsPlugin
-import io.dico.parcels2.logger
-import java.util.*
+import java.util.LinkedList
+import java.util.Queue
@Suppress("UsePropertyAccessSyntax")
fun getParcelCommands(plugin: ParcelsPlugin): ICommandDispatcher {
//@formatter:off
return CommandBuilder()
.setChatController(ParcelsChatController())
- .addParameterType(false, ParcelParameterType(plugin.worlds))
- .addParameterType(true, ParcelHomeParameterType(plugin.worlds))
+ .addParameterType(false, ParcelParameterType(plugin.parcelProvider))
+ .addParameterType(true, ParcelTarget.PType(plugin.parcelProvider))
.group("parcel", "plot", "plots", "p")
.registerCommands(CommandsGeneral(plugin))
- .registerCommands(CommandsAddedStatus(plugin))
+ .registerCommands(CommandsAddedStatusLocal(plugin))
- .group("option")
+ .group("option", "opt", "o")
//.apply { CommandsParcelOptions.setGroupDescription(this) }
.registerCommands(CommandsParcelOptions(plugin))
.parent()
+ .group("global", "g")
+ .registerCommands(CommandsAddedStatusGlobal(plugin))
+ .parent()
+
.group("admin", "a")
.registerCommands(CommandsAdmin(plugin))
.parent()
diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt
index 5dd8270..eab02c4 100644
--- a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt
+++ b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt
@@ -5,8 +5,8 @@ import io.dico.dicore.command.ExecutionContext
import io.dico.dicore.command.ICommandReceiver
import io.dico.dicore.command.Validate
import io.dico.parcels2.Parcel
+import io.dico.parcels2.ParcelProvider
import io.dico.parcels2.ParcelWorld
-import io.dico.parcels2.Worlds
import io.dico.parcels2.util.hasAdminManage
import io.dico.parcels2.util.uuid
import org.bukkit.entity.Player
@@ -30,7 +30,7 @@ open class ParcelScope(val parcel: Parcel) : WorldScope(parcel.world) {
"You must own this parcel to $action")
}
-fun getParcelCommandReceiver(worlds: Worlds, context: ExecutionContext, method: Method, cmdName: String): ICommandReceiver {
+fun getParcelCommandReceiver(parcelProvider: ParcelProvider, context: ExecutionContext, method: Method, cmdName: String): ICommandReceiver {
val player = context.sender as Player
val function = method.kotlinFunction!!
val receiverType = function.extensionReceiverParameter!!.type
@@ -39,20 +39,20 @@ fun getParcelCommandReceiver(worlds: Worlds, context: ExecutionContext, method:
val owner = require?.owner == true
return when (receiverType.jvmErasure) {
- ParcelScope::class -> ParcelScope(worlds.getParcelRequired(player, admin, owner))
- WorldScope::class -> WorldScope(worlds.getWorldRequired(player, admin))
+ ParcelScope::class -> ParcelScope(parcelProvider.getParcelRequired(player, admin, owner))
+ WorldScope::class -> WorldScope(parcelProvider.getWorldRequired(player, admin))
else -> throw InternalError("Invalid command receiver type")
}
}
-fun Worlds.getWorldRequired(player: Player, admin: Boolean = false): ParcelWorld {
+fun ParcelProvider.getWorldRequired(player: Player, admin: Boolean = false): ParcelWorld {
if (admin) Validate.isTrue(player.hasAdminManage, "You must have admin rights to use that command")
return getWorld(player.world)
?: throw CommandException("You must be in a parcel world to use that command")
}
-fun Worlds.getParcelRequired(player: Player, admin: Boolean = false, own: Boolean = false): Parcel {
- val parcel = getWorldRequired(player, admin = admin).parcelAt(player)
+fun ParcelProvider.getParcelRequired(player: Player, admin: Boolean = false, own: Boolean = false): Parcel {
+ val parcel = getWorldRequired(player, admin = admin).getParcelAt(player)
?: throw CommandException("You must be in a parcel to use that command")
if (own) Validate.isTrue(parcel.isOwner(player.uuid) || player.hasAdminManage,
"You must own this parcel to use that command")
diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt
index ab97023..de3cf64 100644
--- a/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt
+++ b/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt
@@ -3,14 +3,10 @@ package io.dico.parcels2.command
import io.dico.dicore.command.CommandException
import io.dico.dicore.command.parameter.ArgumentBuffer
import io.dico.dicore.command.parameter.Parameter
-import io.dico.dicore.command.parameter.type.ParameterConfig
import io.dico.dicore.command.parameter.type.ParameterType
import io.dico.parcels2.Parcel
+import io.dico.parcels2.ParcelProvider
import io.dico.parcels2.ParcelWorld
-import io.dico.parcels2.Worlds
-import io.dico.parcels2.util.isValid
-import org.bukkit.Bukkit
-import org.bukkit.OfflinePlayer
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
@@ -18,7 +14,7 @@ fun invalidInput(parameter: Parameter<*, *>, message: String): Nothing {
throw CommandException("invalid input for ${parameter.name}: $message")
}
-fun Worlds.getTargetWorld(input: String?, sender: CommandSender, parameter: Parameter<*, *>): ParcelWorld {
+fun ParcelProvider.getTargetWorld(input: String?, sender: CommandSender, parameter: Parameter<*, *>): ParcelWorld {
val worldName = input
?.takeUnless { it.isEmpty() }
?: (sender as? Player)?.world?.name
@@ -28,14 +24,14 @@ fun Worlds.getTargetWorld(input: String?, sender: CommandSender, parameter: Para
?: invalidInput(parameter, "$worldName is not a parcel world")
}
-class ParcelParameterType(val worlds: Worlds) : ParameterType<Parcel, Void>(Parcel::class.java) {
+class ParcelParameterType(val parcelProvider: ParcelProvider) : ParameterType<Parcel, Void>(Parcel::class.java) {
val regex = Regex.fromLiteral("((.+)->)?([0-9]+):([0-9]+)")
override fun parse(parameter: Parameter<Parcel, Void>, sender: CommandSender, buffer: ArgumentBuffer): Parcel {
val matchResult = regex.matchEntire(buffer.next())
?: invalidInput(parameter, "must match (w->)?a:b (/${regex.pattern}/)")
- val world = worlds.getTargetWorld(matchResult.groupValues[2], sender, parameter)
+ val world = parcelProvider.getTargetWorld(matchResult.groupValues[2], sender, parameter)
val x = matchResult.groupValues[3].toIntOrNull()
?: invalidInput(parameter, "couldn't parse int")
@@ -43,80 +39,8 @@ class ParcelParameterType(val worlds: Worlds) : ParameterType<Parcel, Void>(Parc
val z = matchResult.groupValues[4].toIntOrNull()
?: invalidInput(parameter, "couldn't parse int")
- return world.parcelByID(x, z)
+ return world.getParcelById(x, z)
?: invalidInput(parameter, "parcel id is out of range")
}
}
-
-
-class NamedParcelTarget(val world: ParcelWorld, val player: OfflinePlayer, val index: Int)
-
-@Target(AnnotationTarget.VALUE_PARAMETER)
-@Retention(AnnotationRetention.RUNTIME)
-annotation class NamedParcelDefault(val value: NamedParcelDefaultValue)
-
-enum class NamedParcelDefaultValue {
- FIRST_OWNED,
- NULL
-}
-
-class NamedParcelTargetConfig : ParameterConfig<NamedParcelDefault,
- NamedParcelDefaultValue>(NamedParcelDefault::class.java) {
-
- override fun toParameterInfo(annotation: NamedParcelDefault): NamedParcelDefaultValue {
- return annotation.value
- }
-}
-
-class ParcelHomeParameterType(val worlds: Worlds) : ParameterType<NamedParcelTarget,
- NamedParcelDefaultValue>(NamedParcelTarget::class.java, NamedParcelTargetConfig()) {
-
- val regex = Regex.fromLiteral("((.+)->)?(.+)|((.+):([0-9]+))")
-
- private fun requirePlayer(sender: CommandSender, parameter: Parameter<*, *>): Player {
- if (sender !is Player) invalidInput(parameter, "console cannot omit the player name")
- return sender
- }
-
- @Suppress("UsePropertyAccessSyntax")
- private fun getOfflinePlayer(input: String, parameter: Parameter<*, *>) = Bukkit.getOfflinePlayer(input)
- ?.takeIf { it.isValid }
- ?: invalidInput(parameter, "do not know who $input is")
-
- override fun parse(parameter: Parameter<NamedParcelTarget, NamedParcelDefaultValue>,
- sender: CommandSender, buffer: ArgumentBuffer): NamedParcelTarget {
- val matchResult = regex.matchEntire(buffer.next())
- ?: invalidInput(parameter, "must be a player, index, or player:index (/${regex.pattern}/)")
-
- val world = worlds.getTargetWorld(matchResult.groupValues[2], sender, parameter)
-
- matchResult.groupValues[3].takeUnless { it.isEmpty() }?.let {
- // first group was matched, it's a player or an int
- it.toIntOrNull()?.let {
- requirePlayer(sender, parameter)
- return NamedParcelTarget(world, sender as Player, it)
- }
-
- return NamedParcelTarget(world, getOfflinePlayer(it, parameter), 0)
- }
-
- val player = getOfflinePlayer(matchResult.groupValues[5], parameter)
- val index = matchResult.groupValues[6].toIntOrNull()
- ?: invalidInput(parameter, "couldn't parse int")
-
- return NamedParcelTarget(world, player, index)
- }
-
- override fun getDefaultValue(parameter: Parameter<NamedParcelTarget, NamedParcelDefaultValue>,
- sender: CommandSender, buffer: ArgumentBuffer): NamedParcelTarget? {
- if (parameter.paramInfo == NamedParcelDefaultValue.NULL) {
- return null
- }
-
- val world = worlds.getTargetWorld(null, sender, parameter)
- val player = requirePlayer(sender, parameter)
- return NamedParcelTarget(world, player, 0)
- }
-
-}
diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt
new file mode 100644
index 0000000..5504e6b
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt
@@ -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)
+ }
+ }
+}
diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelsChatController.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelsChatController.kt
index 456893b..fe9ca0d 100644
--- a/src/main/kotlin/io/dico/parcels2/command/ParcelsChatController.kt
+++ b/src/main/kotlin/io/dico/parcels2/command/ParcelsChatController.kt
@@ -4,5 +4,8 @@ import io.dico.dicore.command.chat.AbstractChatController
class ParcelsChatController : AbstractChatController() {
+ override fun filterMessage(message: String?): String {
+ return "[Parcels] $message"
+ }
} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt
new file mode 100644
index 0000000..0597a9f
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt
@@ -0,0 +1,65 @@
+package io.dico.parcels2.defaultimpl
+
+import io.dico.parcels2.Parcel
+import io.dico.parcels2.ParcelContainer
+import io.dico.parcels2.ParcelWorld
+import kotlin.coroutines.experimental.buildIterator
+import kotlin.coroutines.experimental.buildSequence
+
+class DefaultParcelContainer(val world: ParcelWorld) : ParcelContainer {
+ private var parcels: Array<Array<Parcel>>
+
+ init {
+ parcels = initArray(world.options.axisLimit, world)
+ }
+
+ fun resizeIfSizeChanged() {
+ if (parcels.size != world.options.axisLimit * 2 + 1) {
+ resize(world.options.axisLimit)
+ }
+ }
+
+ fun resize(axisLimit: Int) {
+ parcels = initArray(axisLimit, world, this)
+ }
+
+ fun initArray(axisLimit: Int, world: ParcelWorld, cur: DefaultParcelContainer? = null): Array<Array<Parcel>> {
+ val arraySize = 2 * axisLimit + 1
+ return Array(arraySize) {
+ val x = it - axisLimit
+ Array(arraySize) {
+ val z = it - axisLimit
+ cur?.getParcelById(x, z) ?: ParcelImpl(world, x, z)
+ }
+ }
+ }
+
+ override fun getParcelById(x: Int, z: Int): Parcel? {
+ return parcels.getOrNull(x + world.options.axisLimit)?.getOrNull(z + world.options.axisLimit)
+ }
+
+ override fun nextEmptyParcel(): Parcel? {
+ return walkInCircle().find { it.owner == null }
+ }
+
+ private fun walkInCircle(): Iterable<Parcel> = Iterable {
+ buildIterator {
+ val center = world.options.axisLimit
+ for (radius in 0..center) {
+ var x = center - radius;
+ var z = center - radius
+ repeat(radius * 2) { yield(parcels[x++][z]) }
+ repeat(radius * 2) { yield(parcels[x][z++]) }
+ repeat(radius * 2) { yield(parcels[x--][z]) }
+ repeat(radius * 2) { yield(parcels[x][z--]) }
+ }
+ }
+ }
+
+ fun allParcels(): Sequence<Parcel> = buildSequence {
+ for (array in parcels) {
+ yieldAll(array.iterator())
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt
new file mode 100644
index 0000000..90eb631
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt
@@ -0,0 +1,267 @@
+package io.dico.parcels2.defaultimpl
+
+import io.dico.parcels2.*
+import io.dico.parcels2.blockvisitor.RegionTraversal
+import io.dico.parcels2.blockvisitor.Worker
+import io.dico.parcels2.blockvisitor.WorktimeLimiter
+import io.dico.parcels2.util.*
+import org.bukkit.*
+import org.bukkit.block.Biome
+import org.bukkit.block.Block
+import org.bukkit.block.BlockFace
+import org.bukkit.block.Skull
+import org.bukkit.block.data.BlockData
+import org.bukkit.block.data.type.Sign
+import org.bukkit.block.data.type.Slab
+import java.util.Random
+
+private val airType = Bukkit.createBlockData(Material.AIR)
+
+data class DefaultGeneratorOptions(var defaultBiome: Biome = Biome.JUNGLE,
+ var wallType: BlockData = Bukkit.createBlockData(Material.STONE_SLAB),
+ var floorType: BlockData = Bukkit.createBlockData(Material.QUARTZ_BLOCK),
+ var fillType: BlockData = Bukkit.createBlockData(Material.QUARTZ_BLOCK),
+ var pathMainType: BlockData = Bukkit.createBlockData(Material.SANDSTONE),
+ var pathAltType: BlockData = Bukkit.createBlockData(Material.REDSTONE_BLOCK),
+ var parcelSize: Int = 101,
+ var pathSize: Int = 9,
+ var floorHeight: Int = 64,
+ var offsetX: Int = 0,
+ var offsetZ: Int = 0) : GeneratorOptions() {
+
+ override fun generatorFactory(): GeneratorFactory = DefaultParcelGenerator.Factory
+}
+
+class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOptions) : ParcelGenerator() {
+ private var _world: World? = null
+ override val world: World
+ get() {
+ if (_world == null) _world = Bukkit.getWorld(name)!!.also {
+ maxHeight = it.maxHeight
+ return it
+ }
+ return _world!!
+ }
+
+ private var maxHeight = 0
+
+ companion object Factory : GeneratorFactory {
+ override val name get() = "default"
+ override val optionsClass get() = DefaultGeneratorOptions::class
+ override fun newParcelGenerator(worldName: String, options: GeneratorOptions): ParcelGenerator {
+ return DefaultParcelGenerator(worldName, options as DefaultGeneratorOptions)
+ }
+ }
+
+ val sectionSize = o.parcelSize + o.pathSize
+ val pathOffset = (if (o.pathSize % 2 == 0) o.pathSize + 2 else o.pathSize + 1) / 2
+ val makePathMain = o.pathSize > 2
+ val makePathAlt = o.pathSize > 4
+
+ private inline fun <T> generate(chunkX: Int,
+ chunkZ: Int,
+ floor: T, wall:
+ T, pathMain: T,
+ pathAlt: T,
+ fill: T,
+ setter: (Int, Int, Int, T) -> Unit) {
+
+ val floorHeight = o.floorHeight
+ val parcelSize = o.parcelSize
+ val sectionSize = sectionSize
+ val pathOffset = pathOffset
+ val makePathMain = makePathMain
+ val makePathAlt = makePathAlt
+
+ // parcel bottom x and z
+ // umod is unsigned %: the result is always >= 0
+ val pbx = ((chunkX shl 4) - o.offsetX) umod sectionSize
+ val pbz = ((chunkZ shl 4) - o.offsetZ) umod sectionSize
+
+ var curHeight: Int
+ var x: Int
+ var z: Int
+ for (cx in 0..15) {
+ for (cz in 0..15) {
+ x = (pbx + cx) % sectionSize - pathOffset
+ z = (pbz + cz) % sectionSize - pathOffset
+ curHeight = floorHeight
+
+ val type = when {
+ (x in 0 until parcelSize && z in 0 until parcelSize) -> floor
+ (x in -1..parcelSize && z in -1..parcelSize) -> {
+ curHeight++
+ wall
+ }
+ (makePathAlt && x in -2 until parcelSize + 2 && z in -2 until parcelSize + 2) -> pathAlt
+ (makePathMain) -> pathMain
+ else -> {
+ curHeight++
+ wall
+ }
+ }
+
+ for (y in 0 until curHeight) {
+ setter(cx, y, cz, fill)
+ }
+ setter(cx, curHeight, cz, type)
+ }
+ }
+ }
+
+ override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData {
+ val out = Bukkit.createChunkData(world)
+ generate(chunkX, chunkZ, o.floorType, o.wallType, o.pathMainType, o.pathAltType, o.fillType) { x, y, z, type ->
+ out.setBlock(x, y, z, type)
+ }
+ return out
+ }
+
+ override fun populate(world: World?, random: Random?, chunk: Chunk?) {
+ // do nothing
+ }
+
+ override fun getFixedSpawnLocation(world: World?, random: Random?): Location {
+ val fix = if (o.parcelSize.even) 0.5 else 0.0
+ return Location(world, o.offsetX + fix, o.floorHeight + 1.0, o.offsetZ + fix)
+ }
+
+ override fun makeParcelBlockManager(worktimeLimiter: WorktimeLimiter): ParcelBlockManager {
+ return ParcelBlockManagerImpl(worktimeLimiter)
+ }
+
+ override fun makeParcelLocator(container: ParcelContainer): ParcelLocator {
+ return ParcelLocatorImpl(container)
+ }
+
+ private inline fun <T> convertBlockLocationToId(x: Int, z: Int, mapper: (Int, Int) -> T): T? {
+ val sectionSize = sectionSize
+ val parcelSize = o.parcelSize
+ val absX = x - o.offsetX - pathOffset
+ val absZ = z - o.offsetZ - pathOffset
+ val modX = absX umod sectionSize
+ val modZ = absZ umod sectionSize
+ if (modX in 0 until parcelSize && modZ in 0 until parcelSize) {
+ return mapper((absX - modX) / sectionSize, (absZ - modZ) / sectionSize)
+ }
+ return null
+ }
+
+ private inner class ParcelLocatorImpl(val container: ParcelContainer) : ParcelLocator {
+ override val world: World = this@DefaultParcelGenerator.world
+ override fun getParcelAt(x: Int, z: Int): Parcel? {
+ return convertBlockLocationToId(x, z, container::getParcelById)
+ }
+
+ override fun getParcelIdAt(x: Int, z: Int): ParcelId? {
+ return convertBlockLocationToId(x, z) { idx, idz -> ParcelId(world.name, world.uid, idx, idz) }
+ }
+ }
+
+ @Suppress("DEPRECATION")
+ private inner class ParcelBlockManagerImpl(override val worktimeLimiter: WorktimeLimiter) : ParcelBlockManager {
+ override val world: World = this@DefaultParcelGenerator.world
+
+ override fun getBottomBlock(parcel: ParcelId): Vec2i = Vec2i(
+ sectionSize * parcel.pos.x + pathOffset + o.offsetX,
+ sectionSize * parcel.pos.z + pathOffset + o.offsetZ
+ )
+
+ override fun getHomeLocation(parcel: ParcelId): Location {
+ val bottom = getBottomBlock(parcel)
+ return Location(world, bottom.x.toDouble(), o.floorHeight + 1.0, bottom.z + (o.parcelSize - 1) / 2.0, -90F, 0F)
+ }
+
+ override fun setOwnerBlock(parcel: ParcelId, owner: ParcelOwner?) {
+ val b = getBottomBlock(parcel)
+
+ val wallBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 1)
+ val signBlock = world.getBlockAt(b.x - 2, o.floorHeight + 1, b.z - 1)
+ val skullBlock = world.getBlockAt(b.x - 1, o.floorHeight + 2, b.z - 1)
+
+ if (owner == null) {
+ wallBlock.blockData = o.wallType
+ signBlock.type = Material.AIR
+ skullBlock.type = Material.AIR
+ } else {
+
+ val wallBlockType: BlockData = if (o.wallType is Slab)
+ (o.wallType.clone() as Slab).apply { type = Slab.Type.DOUBLE }
+ else
+ o.wallType
+
+ wallBlock.blockData = wallBlockType
+
+ signBlock.blockData = (Bukkit.createBlockData(Material.WALL_SIGN) as Sign).apply { rotation = BlockFace.NORTH }
+
+ val sign = signBlock.state as org.bukkit.block.Sign
+ sign.setLine(0, "${parcel.x},${parcel.z}")
+ sign.setLine(2, owner.name)
+ sign.update()
+
+ skullBlock.type = Material.PLAYER_HEAD
+ val skull = skullBlock.state as Skull
+ if (owner.uuid != null) {
+ skull.owningPlayer = owner.offlinePlayer
+ } else {
+ skull.owner = owner.name
+ }
+ skull.rotation = BlockFace.WEST
+ skull.update()
+ }
+ }
+
+ override fun setBiome(parcel: ParcelId, biome: Biome): Worker = worktimeLimiter.submit {
+ val world = world
+ val b = getBottomBlock(parcel)
+ val parcelSize = o.parcelSize
+ for (x in b.x until b.x + parcelSize) {
+ for (z in b.z until b.z + parcelSize) {
+ markSuspensionPoint()
+ world.setBiome(x, z, biome)
+ }
+ }
+ }
+
+ override fun clearParcel(parcel: ParcelId): Worker = worktimeLimiter.submit {
+ val bottom = getBottomBlock(parcel)
+ val region = Region(Vec3i(bottom.x, 0, bottom.z), Vec3i(o.parcelSize, maxHeight + 1, o.parcelSize))
+ val blocks = RegionTraversal.DOWNWARD.regionTraverser(region)
+ val blockCount = region.blockCount.toDouble()
+
+ val world = world
+ val floorHeight = o.floorHeight
+ val airType = airType
+ val floorType = o.floorType
+ val fillType = o.fillType
+
+ for ((index, vec) in blocks.withIndex()) {
+ markSuspensionPoint()
+ val y = vec.y
+ val blockType = when {
+ y > floorHeight -> airType
+ y == floorHeight -> floorType
+ else -> fillType
+ }
+ world[vec].blockData = blockType
+ setProgress((index + 1) / blockCount)
+ }
+ }
+
+ override fun doBlockOperation(parcel: ParcelId, direction: RegionTraversal, operation: (Block) -> Unit): Worker = worktimeLimiter.submit {
+ val bottom = getBottomBlock(parcel)
+ val region = Region(Vec3i(bottom.x, 0, bottom.z), Vec3i(o.parcelSize, maxHeight + 1, o.parcelSize))
+ val blocks = direction.regionTraverser(region)
+ val blockCount = region.blockCount.toDouble()
+ val world = world
+
+ for ((index, vec) in blocks.withIndex()) {
+ markSuspensionPoint()
+ operation(world[vec])
+ setProgress((index + 1) / blockCount)
+ }
+ }
+
+ }
+
+} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalAddedDataManagerImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalAddedDataManagerImpl.kt
new file mode 100644
index 0000000..1ac053f
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalAddedDataManagerImpl.kt
@@ -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
+ }
+
+} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt
new file mode 100644
index 0000000..c3d7c22
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt
@@ -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(")")
+ }
+ }
+
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt
new file mode 100644
index 0000000..52e675d
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt
@@ -0,0 +1,119 @@
+package io.dico.parcels2.defaultimpl
+
+import io.dico.parcels2.*
+import org.bukkit.Bukkit
+import org.bukkit.WorldCreator
+
+class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
+ inline val options get() = plugin.options
+ override val worlds: Map<String, ParcelWorld> get() = _worlds
+ private val _worlds: MutableMap<String, ParcelWorld> = hashMapOf()
+ private val _generators: MutableMap<String, ParcelGenerator> = hashMapOf()
+ private var _worldsLoaded = false
+ private var _dataIsLoaded = false
+
+ // disabled while !_dataIsLoaded. getParcelById() will work though for data loading.
+ override fun getWorld(name: String): ParcelWorld? = _worlds[name]?.takeIf { _dataIsLoaded }
+
+ override fun getWorldById(id: ParcelWorldId): ParcelWorld? {
+ if (id is ParcelWorld) return id
+ return _worlds[id.name] ?: id.bukkitWorld?.let { getWorld(it) }
+ }
+
+ override fun getParcelById(id: ParcelId): Parcel? {
+ if (id is Parcel) return id
+ return getWorldById(id.worldId)?.container?.getParcelById(id.x, id.z)
+ }
+
+ override fun getWorldGenerator(worldName: String): ParcelGenerator? {
+ return _worlds[worldName]?.generator
+ ?: _generators[worldName]
+ ?: options.worlds[worldName]?.generator?.newGenerator(worldName)?.also { _generators[worldName] = it }
+ }
+
+ override fun loadWorlds() {
+ if (_worldsLoaded) throw IllegalStateException()
+ _worldsLoaded = true
+ loadWorlds0()
+ }
+
+ private fun loadWorlds0() {
+ if (Bukkit.getWorlds().isEmpty()) {
+ plugin.functionHelper.schedule(::loadWorlds0)
+ plugin.logger.warning("Scheduling to load worlds in the next tick, \nbecause no bukkit worlds are loaded yet")
+ return
+ }
+
+ for ((worldName, worldOptions) in options.worlds.entries) {
+ var parcelWorld = _worlds[worldName]
+ if (parcelWorld != null) continue
+
+ val generator: ParcelGenerator = getWorldGenerator(worldName)!!
+ val bukkitWorld = Bukkit.getWorld(worldName) ?: WorldCreator(worldName).generator(generator).createWorld()
+ parcelWorld = ParcelWorldImpl(bukkitWorld, generator, worldOptions.runtime, plugin.storage,
+ plugin.globalAddedData, ::DefaultParcelContainer, plugin.worktimeLimiter)
+ _worlds[worldName] = parcelWorld
+ }
+
+ loadStoredData()
+ }
+
+ private fun loadStoredData() {
+ plugin.functionHelper.launchLazilyOnMainThread {
+ val channel = plugin.storage.readAllParcelData()
+ do {
+ val pair = channel.receiveOrNull() ?: break
+ val parcel = getParcelById(pair.first) ?: continue
+ pair.second?.let { parcel.copyDataIgnoringDatabase(it) }
+ } while (true)
+
+ _dataIsLoaded = true
+ }.start()
+ }
+
+ /*
+ fun loadWorlds(options: Options) {
+ for ((worldName, worldOptions) in options.worlds.entries) {
+ val world: ParcelWorld
+ try {
+
+ world = ParcelWorldImpl(
+ worldName,
+ worldOptions,
+ worldOptions.generator.newGenerator(this, worldName),
+ plugin.storage,
+ plugin.globalAddedData,
+ ::DefaultParcelContainer)
+
+ } catch (ex: Exception) {
+ ex.printStackTrace()
+ continue
+ }
+
+ _worlds[worldName] = world
+ }
+
+ plugin.functionHelper.schedule(10) {
+ println("Parcels generating parcelProvider now")
+ for ((name, world) in _worlds) {
+ if (Bukkit.getWorld(name) == null) {
+ val bworld = WorldCreator(name).generator(world.generator).createWorld()
+ val spawn = world.generator.getFixedSpawnLocation(bworld, null)
+ bworld.setSpawnLocation(spawn.x.floor(), spawn.y.floor(), spawn.z.floor())
+ }
+ }
+
+ val channel = plugin.storage.readAllParcelData()
+ val job = plugin.functionHelper.launchLazilyOnMainThread {
+ do {
+ val pair = channel.receiveOrNull() ?: break
+ val parcel = getParcelById(pair.first) ?: continue
+ pair.second?.let { parcel.copyDataIgnoringDatabase(it) }
+ } while (true)
+ }
+ job.start()
+ }
+
+ }
+ */
+} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt
new file mode 100644
index 0000000..590794d
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt
@@ -0,0 +1,94 @@
+@file:Suppress("CanBePrimaryConstructorProperty", "UsePropertyAccessSyntax")
+
+package io.dico.parcels2.defaultimpl
+
+import io.dico.parcels2.*
+import io.dico.parcels2.blockvisitor.WorktimeLimiter
+import io.dico.parcels2.storage.Storage
+import org.bukkit.World
+import java.util.UUID
+
+class ParcelWorldImpl private
+constructor(override val world: World,
+ override val generator: ParcelGenerator,
+ override var options: WorldOptions,
+ override val storage: Storage,
+ override val globalAddedData: GlobalAddedDataManager,
+ containerFactory: ParcelContainerFactory,
+ blockManager: ParcelBlockManager)
+ : ParcelWorld,
+ ParcelWorldId,
+ ParcelContainer, // missing delegation
+ ParcelLocator, // missing delegation
+ ParcelBlockManager by blockManager {
+ override val id: ParcelWorldId get() = this
+ override val uid: UUID? get() = world.uid
+
+ init {
+ if (generator.world != world) {
+ throw IllegalArgumentException()
+ }
+ }
+
+ override val name: String = world.name!!
+ override val container: ParcelContainer = containerFactory(this)
+ override val locator: ParcelLocator = generator.makeParcelLocator(container)
+ override val blockManager: ParcelBlockManager = blockManager
+
+ init {
+ enforceOptions()
+ }
+
+ fun enforceOptions() {
+ if (options.dayTime) {
+ world.setGameRuleValue("doDaylightCycle", "false")
+ world.setTime(6000)
+ }
+
+ if (options.noWeather) {
+ world.setStorm(false)
+ world.setThundering(false)
+ world.weatherDuration = Integer.MAX_VALUE
+ }
+
+ world.setGameRuleValue("doTileDrops", "${options.doTileDrops}")
+ }
+
+ /*
+ Interface delegation needs to be implemented manually because JetBrains has yet to fix it.
+ */
+
+ companion object {
+ // Use this to be able to delegate blockManager and assign it to a property too, at least.
+ operator fun invoke(world: World,
+ generator: ParcelGenerator,
+ options: WorldOptions,
+ storage: Storage,
+ globalAddedData: GlobalAddedDataManager,
+ containerFactory: ParcelContainerFactory,
+ worktimeLimiter: WorktimeLimiter): ParcelWorldImpl {
+ val blockManager = generator.makeParcelBlockManager(worktimeLimiter)
+ return ParcelWorldImpl(world, generator, options, storage, globalAddedData, containerFactory, blockManager)
+ }
+ }
+
+ // ParcelLocator interface
+ override fun getParcelAt(x: Int, z: Int): Parcel? {
+ return locator.getParcelAt(x, z)
+ }
+
+ override fun getParcelIdAt(x: Int, z: Int): ParcelId? {
+ return locator.getParcelIdAt(x, z)
+ }
+
+ // ParcelContainer interface
+ override fun getParcelById(x: Int, z: Int): Parcel? {
+ return container.getParcelById(x, z)
+ }
+
+ override fun nextEmptyParcel(): Parcel? {
+ return container.nextEmptyParcel()
+ }
+
+
+}
diff --git a/src/main/kotlin/io/dico/parcels2/listener/ListenerHelper.kt b/src/main/kotlin/io/dico/parcels2/listener/ListenerHelper.kt
new file mode 100644
index 0000000..e8617fd
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/listener/ListenerHelper.kt
@@ -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 ->
+
+
+}
+
+
diff --git a/src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt b/src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt
index 2f64ab1..285fd3e 100644
--- a/src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt
+++ b/src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt
@@ -1,12 +1,12 @@
package io.dico.parcels2.listener
import io.dico.parcels2.Parcel
-import io.dico.parcels2.Worlds
+import io.dico.parcels2.ParcelProvider
import io.dico.parcels2.util.editLoop
import io.dico.parcels2.util.isPresentAnd
import org.bukkit.entity.Entity
-class ParcelEntityTracker(val worlds: Worlds) {
+class ParcelEntityTracker(val parcelProvider: ParcelProvider) {
val map = mutableMapOf<Entity, Parcel?>()
fun untrack(entity: Entity) {
@@ -32,7 +32,7 @@ class ParcelEntityTracker(val worlds: Worlds) {
if (parcel.isPresentAnd { hasBlockVisitors }) {
remove()
}
- val newParcel = worlds.getParcelAt(entity.location)
+ val newParcel = parcelProvider.getParcelAt(entity.location)
if (newParcel !== parcel && !newParcel.isPresentAnd { hasBlockVisitors }) {
remove()
entity.remove()
diff --git a/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt b/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt
index f5eb5ca..d34f8bf 100644
--- a/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt
+++ b/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt
@@ -4,8 +4,8 @@ import gnu.trove.TLongCollection
import io.dico.dicore.ListenerMarker
import io.dico.dicore.RegistratorListener
import io.dico.parcels2.Parcel
+import io.dico.parcels2.ParcelProvider
import io.dico.parcels2.ParcelWorld
-import io.dico.parcels2.Worlds
import io.dico.parcels2.util.*
import org.bukkit.Material.*
import org.bukkit.World
@@ -26,11 +26,10 @@ import org.bukkit.event.player.*
import org.bukkit.event.vehicle.VehicleMoveEvent
import org.bukkit.event.weather.WeatherChangeEvent
import org.bukkit.event.world.StructureGrowEvent
-import org.bukkit.event.world.WorldLoadEvent
import org.bukkit.inventory.InventoryHolder
@Suppress("NOTHING_TO_INLINE")
-class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker) {
+class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: ParcelEntityTracker) {
private inline fun Parcel?.canBuildN(user: Player) = isPresentAnd { canBuild(user) } || user.hasBuildAnywhere
/**
@@ -40,8 +39,8 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
* returns null if not in a registered parcel world
*/
private fun getWoAndPPa(block: Block): Pair<ParcelWorld, Parcel?>? {
- val world = worlds.getWorld(block.world) ?: return null
- return world to world.parcelAt(block)
+ val world = parcelProvider.getWorld(block.world) ?: return null
+ return world to world.getParcelAt(block)
}
/*
@@ -51,10 +50,10 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
val onPlayerMoveEvent = RegistratorListener<PlayerMoveEvent> l@{ event ->
val user = event.player
if (user.hasBanBypass) return@l
- val parcel = worlds.getParcelAt(event.to) ?: return@l
+ val parcel = parcelProvider.getParcelAt(event.to) ?: return@l
if (parcel.isBanned(user.uuid)) {
- worlds.getParcelAt(event.from)?.also {
- user.teleport(it.homeLocation)
+ parcelProvider.getParcelAt(event.from)?.also {
+ user.teleport(it.world.getHomeLocation(it.id))
user.sendParcelMessage(nopermit = true, message = "You are banned from this parcel")
} ?: run { event.to = event.from }
}
@@ -113,7 +112,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
private inline fun TLongCollection.troveForEach(block: (Long) -> Unit) = iterator().let { while (it.hasNext()) block(it.next()) }
//@formatter:on
private fun checkPistonMovement(event: BlockPistonEvent, blocks: List<Block>) {
- val world = worlds.getWorld(event.block.world) ?: return
+ val world = parcelProvider.getWorld(event.block.world) ?: return
val direction = event.direction
val columns = gnu.trove.set.hash.TLongHashSet(blocks.size * 2)
@@ -123,7 +122,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
}
columns.troveForEach {
- val ppa = world.parcelAt(it.columnX, it.columnZ)
+ val ppa = world.getParcelAt(it.columnX, it.columnZ)
if (ppa.isNullOr { hasBlockVisitors }) {
event.isCancelled = true
return
@@ -150,8 +149,8 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
@field:ListenerMarker(priority = NORMAL)
val onEntityExplodeEvent = RegistratorListener<EntityExplodeEvent> l@{ event ->
entityTracker.untrack(event.entity)
- val world = worlds.getWorld(event.entity.world) ?: return@l
- if (world.options.disableExplosions || world.parcelAt(event.entity).isPresentAnd { hasBlockVisitors }) {
+ val world = parcelProvider.getWorld(event.entity.world) ?: return@l
+ if (world.options.disableExplosions || world.getParcelAt(event.entity).isPresentAnd { hasBlockVisitors }) {
event.isCancelled = true
}
}
@@ -175,9 +174,9 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
@field:ListenerMarker(priority = NORMAL)
val onPlayerInteractEvent = RegistratorListener<PlayerInteractEvent> l@{ event ->
val user = event.player
- val world = worlds.getWorld(user.world) ?: return@l
+ val world = parcelProvider.getWorld(user.world) ?: return@l
val clickedBlock = event.clickedBlock
- val parcel = clickedBlock?.let { world.parcelAt(it) }
+ val parcel = clickedBlock?.let { world.getParcelAt(it) }
if (!user.hasBuildAnywhere && parcel.isPresentAnd { isBanned(user.uuid) }) {
user.sendParcelMessage(nopermit = true, message = "You cannot interact with parcels you're banned from")
@@ -300,7 +299,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
*/
@field:ListenerMarker(priority = NORMAL)
val onEntityCreatePortalEvent = RegistratorListener<EntityCreatePortalEvent> l@{ event ->
- val world = worlds.getWorld(event.entity.world) ?: return@l
+ val world = parcelProvider.getWorld(event.entity.world) ?: return@l
if (world.options.blockPortalCreation) event.isCancelled = true
}
@@ -341,7 +340,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
*/
@field:ListenerMarker(priority = NORMAL)
val onWeatherChangeEvent = RegistratorListener<WeatherChangeEvent> l@{ event ->
- val world = worlds.getWorld(event.world) ?: return@l
+ val world = parcelProvider.getWorld(event.world) ?: return@l
if (world.options.noWeather && event.toWeatherState()) {
event.isCancelled = true
}
@@ -353,29 +352,6 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
world.weatherDuration = Int.MAX_VALUE
}
- /*
- * Sets time to day and doDayLightCycle gamerule if requested by the config for that world
- * Sets the weather to sunny if requested by the config for that world.
- */
- @field:ListenerMarker(priority = NORMAL)
- val onWorldLoadEvent = RegistratorListener<WorldLoadEvent> l@{ event ->
- enforceWorldSettingsIfApplicable(event.world)
- }
-
- fun enforceWorldSettingsIfApplicable(w: World) {
- val world = worlds.getWorld(w) ?: return
- if (world.options.dayTime) {
- w.setGameRuleValue("doDaylightCycle", "false")
- w.time = 6000
- }
-
- if (world.options.noWeather) {
- resetWeather(w)
- }
-
- w.setGameRuleValue("doTileDrops", world.options.doTileDrops.toString())
- }
-
// TODO: BlockFormEvent, BlockSpreadEvent, BlockFadeEvent
/*
@@ -396,10 +372,10 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
val cancel: Boolean = when (block.type) {
- // prevent ice generation from Frost Walkers enchantment
+ // prevent ice generation from Frost Walkers enchantment
ICE -> player != null && !ppa.canBuild(player)
- // prevent snow generation from weather
+ // prevent snow generation from weather
SNOW -> !hasEntity && wo.options.preventWeatherBlockChanges
else -> false
@@ -415,10 +391,10 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
*/
@field:ListenerMarker(priority = NORMAL)
val onEntitySpawnEvent = RegistratorListener<EntitySpawnEvent> l@{ event ->
- val world = worlds.getWorld(event.entity.world) ?: return@l
+ val world = parcelProvider.getWorld(event.entity.world) ?: return@l
if (event.entity is Creature && world.options.blockMobSpawning) {
event.isCancelled = true
- } else if (world.parcelAt(event.entity).isPresentAnd { hasBlockVisitors }) {
+ } else if (world.getParcelAt(event.entity).isPresentAnd { hasBlockVisitors }) {
event.isCancelled = true
}
}
@@ -448,7 +424,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
*/
@field:ListenerMarker(priority = NORMAL)
val onEntityDamageByEntityEvent = RegistratorListener<EntityDamageByEntityEvent> l@{ event ->
- val world = worlds.getWorld(event.entity.world) ?: return@l
+ val world = parcelProvider.getWorld(event.entity.world) ?: return@l
if (world.options.disableExplosions && event.damager is ExplosiveMinecart || event.damager is Creeper) {
event.isCancelled = true; return@l
}
@@ -457,19 +433,19 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
?: (event.damager as? Projectile)?.let { it.shooter as? Player }
?: return@l
- if (!world.parcelAt(event.entity).canBuildN(user)) {
+ if (!world.getParcelAt(event.entity).canBuildN(user)) {
event.isCancelled = true
}
}
@field:ListenerMarker(priority = NORMAL)
val onHangingBreakEvent = RegistratorListener<HangingBreakEvent> l@{ event ->
- val world = worlds.getWorld(event.entity.world) ?: return@l
+ val world = parcelProvider.getWorld(event.entity.world) ?: return@l
if (event.cause == HangingBreakEvent.RemoveCause.EXPLOSION && world.options.disableExplosions) {
event.isCancelled = true; return@l
}
- if (world.parcelAt(event.entity).isPresentAnd { hasBlockVisitors }) {
+ if (world.getParcelAt(event.entity).isPresentAnd { hasBlockVisitors }) {
event.isCancelled = true
}
}
@@ -480,9 +456,9 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
*/
@field:ListenerMarker(priority = NORMAL)
val onHangingBreakByEntityEvent = RegistratorListener<HangingBreakByEntityEvent> l@{ event ->
- val world = worlds.getWorld(event.entity.world) ?: return@l
+ val world = parcelProvider.getWorld(event.entity.world) ?: return@l
val user = event.remover as? Player ?: return@l
- if (!world.parcelAt(event.entity).canBuildN(user)) {
+ if (!world.getParcelAt(event.entity).canBuildN(user)) {
event.isCancelled = true
}
}
@@ -492,9 +468,9 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
*/
@field:ListenerMarker(priority = NORMAL)
val onHangingPlaceEvent = RegistratorListener<HangingPlaceEvent> l@{ event ->
- val world = worlds.getWorld(event.entity.world) ?: return@l
+ val world = parcelProvider.getWorld(event.entity.world) ?: return@l
val block = event.block.getRelative(event.blockFace)
- if (!world.parcelAt(block).canBuildN(event.player)) {
+ if (!world.getParcelAt(block).canBuildN(event.player)) {
event.isCancelled = true
}
}
@@ -513,7 +489,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
event.isCancelled = true; return@l
}
- event.blocks.removeIf { wo.parcelAt(it.block) !== ppa }
+ event.blocks.removeIf { wo.getParcelAt(it.block) !== ppa }
}
/*
@@ -523,10 +499,10 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
val onBlockDispenseEvent = RegistratorListener<BlockDispenseEvent> l@{ event ->
val block = event.block
if (!block.type.let { it == DISPENSER || it == DROPPER }) return@l
- val world = worlds.getWorld(block.world) ?: return@l
+ val world = parcelProvider.getWorld(block.world) ?: return@l
val data = block.blockData as Directional
val targetBlock = block.getRelative(data.facing)
- if (world.parcelAt(targetBlock) == null) {
+ if (world.getParcelAt(targetBlock) == null) {
event.isCancelled = true
}
}
@@ -547,7 +523,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
@field:ListenerMarker(priority = NORMAL)
val onEntityTeleportEvent = RegistratorListener<EntityTeleportEvent> l@{ event ->
val (wo, ppa) = getWoAndPPa(event.from.block) ?: return@l
- if (ppa !== wo.parcelAt(event.to)) {
+ if (ppa !== wo.getParcelAt(event.to)) {
event.isCancelled = true
}
}
@@ -572,7 +548,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
@field:ListenerMarker(priority = NORMAL)
val onEntityDeathEvent = RegistratorListener<EntityDeathEvent> l@{ event ->
entityTracker.untrack(event.entity)
- val world = worlds.getWorld(event.entity.world) ?: return@l
+ val world = parcelProvider.getWorld(event.entity.world) ?: return@l
if (!world.options.dropEntityItems) {
event.drops.clear()
event.droppedExp = 0
@@ -584,7 +560,7 @@ class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker
*/
@field:ListenerMarker(priority = NORMAL)
val onPlayerChangedWorldEvent = RegistratorListener<PlayerChangedWorldEvent> l@{ event ->
- val world = worlds.getWorld(event.player.world) ?: return@l
+ val world = parcelProvider.getWorld(event.player.world) ?: return@l
if (world.options.gameMode != null && !event.player.hasGamemodeBypass) {
event.player.gameMode = world.options.gameMode
}
diff --git a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt
index 31b6574..88ee5fd 100644
--- a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt
+++ b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt
@@ -1,11 +1,8 @@
package io.dico.parcels2.storage
-import io.dico.parcels2.AddedData
-import io.dico.parcels2.Parcel
-import io.dico.parcels2.ParcelData
-import io.dico.parcels2.ParcelOwner
-import kotlinx.coroutines.experimental.channels.ProducerScope
-import java.util.*
+import io.dico.parcels2.*
+import kotlinx.coroutines.experimental.channels.SendChannel
+import java.util.UUID
interface Backing {
@@ -22,30 +19,32 @@ interface Backing {
* This producer function is capable of constantly reading parcels from a potentially infinite sequence,
* and provide parcel data for it as read from the database.
*/
- suspend fun ProducerScope<Pair<Parcel, ParcelData?>>.produceParcelData(parcels: Sequence<Parcel>)
+ suspend fun produceParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>)
- suspend fun ProducerScope<Pair<SerializableParcel, ParcelData?>>.produceAllParcelData()
+ suspend fun produceAllParcelData(channel: SendChannel<DataPair>)
- suspend fun readParcelData(parcelFor: Parcel): ParcelData?
+ suspend fun readParcelData(parcel: ParcelId): ParcelData?
- suspend fun getOwnedParcels(user: ParcelOwner): List<SerializableParcel>
+ suspend fun getOwnedParcels(user: ParcelOwner): List<ParcelId>
suspend fun getNumParcels(user: ParcelOwner): Int = getOwnedParcels(user).size
- suspend fun setParcelData(parcelFor: Parcel, data: ParcelData?)
+ suspend fun setParcelData(parcel: ParcelId, data: ParcelData?)
- suspend fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?)
+ suspend fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?)
- suspend fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?)
+ suspend fun setLocalPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus)
- suspend fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean)
+ suspend fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean)
- suspend fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean)
+ suspend fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean)
- suspend fun readGlobalPlayerStateData(owner: ParcelOwner): AddedData?
+ suspend fun produceAllGlobalAddedData(channel: SendChannel<AddedDataPair<ParcelOwner>>)
- suspend fun setGlobalPlayerState(owner: ParcelOwner, player: UUID, state: Boolean?)
+ suspend fun readGlobalAddedData(owner: ParcelOwner): MutableAddedDataMap
+
+ suspend fun setGlobalPlayerStatus(owner: ParcelOwner, player: UUID, status: AddedStatus)
} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt b/src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt
deleted file mode 100644
index cbcb6f4..0000000
--- a/src/main/kotlin/io/dico/parcels2/storage/ExposedBacking.kt
+++ /dev/null
@@ -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]
- }
- }
-
-}
-
-
-
-
-
-
-
diff --git a/src/main/kotlin/io/dico/parcels2/storage/Jackson.kt b/src/main/kotlin/io/dico/parcels2/storage/Jackson.kt
index 97225b8..ede33ae 100644
--- a/src/main/kotlin/io/dico/parcels2/storage/Jackson.kt
+++ b/src/main/kotlin/io/dico/parcels2/storage/Jackson.kt
@@ -8,7 +8,7 @@ import com.fasterxml.jackson.databind.ser.BeanSerializerModifier
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.KotlinModule
-import io.dico.parcels2.GeneratorFactory
+import io.dico.parcels2.GeneratorFactories
import io.dico.parcels2.GeneratorOptions
import io.dico.parcels2.StorageOptions
import org.bukkit.Bukkit
@@ -100,7 +100,7 @@ class GeneratorOptionsDeserializer : JsonDeserializer<GeneratorOptions>() {
val node = parser!!.readValueAsTree<JsonNode>()
val name = node.get("name").asText()
val optionsNode = node.get("options")
- val factory = GeneratorFactory.getFactory(name) ?: throw IllegalStateException("Unknown generator: $name")
+ val factory = GeneratorFactories.getFactory(name) ?: throw IllegalStateException("Unknown generator: $name")
return parser.codec.treeToValue(optionsNode, factory.optionsClass.java)
}
diff --git a/src/main/kotlin/io/dico/parcels2/storage/SerializableTypes.kt b/src/main/kotlin/io/dico/parcels2/storage/SerializableTypes.kt
deleted file mode 100644
index 8d8d938..0000000
--- a/src/main/kotlin/io/dico/parcels2/storage/SerializableTypes.kt
+++ /dev/null
@@ -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)
-} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt
index cb3c3d0..6c3d68f 100644
--- a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt
+++ b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt
@@ -1,16 +1,19 @@
+@file:Suppress("NOTHING_TO_INLINE")
+
package io.dico.parcels2.storage
-import io.dico.parcels2.AddedData
-import io.dico.parcels2.Parcel
-import io.dico.parcels2.ParcelData
-import io.dico.parcels2.ParcelOwner
+import io.dico.parcels2.*
import kotlinx.coroutines.experimental.*
+import kotlinx.coroutines.experimental.channels.ProducerScope
import kotlinx.coroutines.experimental.channels.ReceiveChannel
import kotlinx.coroutines.experimental.channels.produce
-import java.util.*
+import java.util.UUID
import java.util.concurrent.Executor
import java.util.concurrent.Executors
+typealias DataPair = Pair<ParcelId, ParcelData?>
+typealias AddedDataPair<TAttach> = Pair<TAttach, MutableAddedDataMap>
+
interface Storage {
val name: String
val syncDispatcher: CoroutineDispatcher
@@ -22,31 +25,33 @@ interface Storage {
fun shutdown(): Job
- fun readParcelData(parcelFor: Parcel): Deferred<ParcelData?>
+ fun readParcelData(parcel: ParcelId): Deferred<ParcelData?>
- fun readParcelData(parcelsFor: Sequence<Parcel>, channelCapacity: Int): ReceiveChannel<Pair<Parcel, ParcelData?>>
+ fun readParcelData(parcels: Sequence<ParcelId>): ReceiveChannel<DataPair>
- fun readAllParcelData(channelCapacity: Int): ReceiveChannel<Pair<SerializableParcel, ParcelData?>>
+ fun readAllParcelData(): ReceiveChannel<DataPair>
- fun getOwnedParcels(user: ParcelOwner): Deferred<List<SerializableParcel>>
+ fun getOwnedParcels(user: ParcelOwner): Deferred<List<ParcelId>>
fun getNumParcels(user: ParcelOwner): Deferred<Int>
- fun setParcelData(parcelFor: Parcel, data: ParcelData?): Job
+ fun setParcelData(parcel: ParcelId, data: ParcelData?): Job
+
+ fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?): Job
- fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?): Job
+ fun setParcelPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus): Job
- fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?): Job
+ fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean): Job
- fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean): Job
+ fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean): Job
- fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean): Job
+ fun readAllGlobalAddedData(): ReceiveChannel<AddedDataPair<ParcelOwner>>
- fun readGlobalPlayerStateData(owner: ParcelOwner): Deferred<AddedData?>
+ fun readGlobalAddedData(owner: ParcelOwner): Deferred<MutableAddedDataMap?>
- fun setGlobalPlayerState(owner: ParcelOwner, player: UUID, state: Boolean?): Job
+ fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus): Job
}
class StorageWithCoroutineBacking internal constructor(val backing: Backing) : Storage {
@@ -55,46 +60,49 @@ class StorageWithCoroutineBacking internal constructor(val backing: Backing) : S
val poolSize: Int get() = 4
override val asyncDispatcher = Executors.newFixedThreadPool(poolSize) { Thread(it, "Parcels2_StorageThread") }.asCoroutineDispatcher()
override val isConnected get() = backing.isConnected
+ val channelCapacity = 16
- @Suppress("NOTHING_TO_INLINE")
private inline fun <T> defer(noinline block: suspend CoroutineScope.() -> T): Deferred<T> {
return async(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block)
}
- @Suppress("NOTHING_TO_INLINE")
private inline fun job(noinline block: suspend CoroutineScope.() -> Unit): Job {
return launch(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block)
}
+ private inline fun <T> openChannel(noinline block: suspend ProducerScope<T>.() -> Unit): ReceiveChannel<T> {
+ return produce(asyncDispatcher, capacity = channelCapacity, block = block)
+ }
+
override fun init() = job { backing.init() }
override fun shutdown() = job { backing.shutdown() }
- override fun readParcelData(parcelFor: Parcel) = defer { backing.readParcelData(parcelFor) }
+ override fun readParcelData(parcel: ParcelId) = defer { backing.readParcelData(parcel) }
- override fun readParcelData(parcelsFor: Sequence<Parcel>, channelCapacity: Int) =
- produce(asyncDispatcher, capacity = channelCapacity) { with(backing) { produceParcelData(parcelsFor) } }
+ override fun readParcelData(parcels: Sequence<ParcelId>) = openChannel<DataPair> { backing.produceParcelData(channel, parcels) }
- override fun readAllParcelData(channelCapacity: Int): ReceiveChannel<Pair<SerializableParcel, ParcelData?>> =
- produce(asyncDispatcher, capacity = channelCapacity) { with(backing) { produceAllParcelData() } }
+ override fun readAllParcelData() = openChannel<DataPair> { backing.produceAllParcelData(channel) }
override fun getOwnedParcels(user: ParcelOwner) = defer { backing.getOwnedParcels(user) }
override fun getNumParcels(user: ParcelOwner) = defer { backing.getNumParcels(user) }
- override fun setParcelData(parcelFor: Parcel, data: ParcelData?) = job { backing.setParcelData(parcelFor, data) }
+ override fun setParcelData(parcel: ParcelId, data: ParcelData?) = job { backing.setParcelData(parcel, data) }
+
+ override fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?) = job { backing.setParcelOwner(parcel, owner) }
- override fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) = job { backing.setParcelOwner(parcelFor, owner) }
+ override fun setParcelPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus) = job { backing.setLocalPlayerStatus(parcel, player, status) }
- override fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?) = job { backing.setParcelPlayerState(parcelFor, player, state) }
+ override fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) = job { backing.setParcelAllowsInteractInventory(parcel, value) }
- override fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean) = job { backing.setParcelAllowsInteractInventory(parcel, value) }
+ override fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean) = job { backing.setParcelAllowsInteractInputs(parcel, value) }
- override fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean) = job { backing.setParcelAllowsInteractInputs(parcel, value) }
+ override fun readAllGlobalAddedData(): ReceiveChannel<AddedDataPair<ParcelOwner>> = openChannel { backing.produceAllGlobalAddedData(channel) }
- override fun readGlobalPlayerStateData(owner: ParcelOwner): Deferred<AddedData?> = defer { backing.readGlobalPlayerStateData(owner) }
+ override fun readGlobalAddedData(owner: ParcelOwner): Deferred<MutableAddedDataMap?> = defer { backing.readGlobalAddedData(owner) }
- override fun setGlobalPlayerState(owner: ParcelOwner, player: UUID, state: Boolean?) = job { backing.setGlobalPlayerState(owner, player, state) }
+ override fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) = job { backing.setGlobalPlayerStatus(owner, player, status) }
}
diff --git a/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt b/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt
index bb5013a..e798df9 100644
--- a/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt
+++ b/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt
@@ -2,6 +2,7 @@ package io.dico.parcels2.storage
import com.zaxxer.hikari.HikariDataSource
import io.dico.parcels2.DataConnectionOptions
+import io.dico.parcels2.storage.exposed.ExposedBacking
import kotlin.reflect.KClass
interface StorageFactory {
@@ -35,8 +36,8 @@ class ConnectionStorageFactory : StorageFactory {
override fun newStorageInstance(dialect: String, options: Any): Storage {
val hikariConfig = getHikariConfig(dialect, options as DataConnectionOptions)
- val dataSourceFactory = { HikariDataSource(hikariConfig) }
+ val dataSourceFactory = suspend { HikariDataSource(hikariConfig) }
return StorageWithCoroutineBacking(ExposedBacking(dataSourceFactory))
}
-} \ No newline at end of file
+}
diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt
new file mode 100644
index 0000000..5685346
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt
@@ -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]
+ }
+ }
+
+}
+
diff --git a/src/main/kotlin/io/dico/parcels2/storage/ExposedExtensions.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedExtensions.kt
index 816ff97..9f7f599 100644
--- a/src/main/kotlin/io/dico/parcels2/storage/ExposedExtensions.kt
+++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedExtensions.kt
@@ -1,4 +1,4 @@
-package io.dico.parcels2.storage
+package io.dico.parcels2.storage.exposed
import org.jetbrains.exposed.sql.Column
import org.jetbrains.exposed.sql.Index
@@ -40,7 +40,7 @@ class UpsertStatement<Key : Any>(table: Table, conflictColumn: Column<*>? = null
} else {
- append (" ON DUPLICATE KEY UPDATE ")
+ append(" ON DUPLICATE KEY UPDATE ")
values.keys.filter { it !in indexColumns }.joinTo(this) { "${transaction.identity(it)}=VALUES(${transaction.identity(it)})" }
}
diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt
new file mode 100644
index 0000000..ac6e431
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt
@@ -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])
+ }
+}
diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt
new file mode 100644
index 0000000..20b36b1
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt
@@ -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
+
+}
diff --git a/src/main/kotlin/io/dico/parcels2/storage/migration/Migration.kt b/src/main/kotlin/io/dico/parcels2/storage/migration/Migration.kt
new file mode 100644
index 0000000..c8bc93c
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/storage/migration/Migration.kt
@@ -0,0 +1,8 @@
+package io.dico.parcels2.storage.migration
+
+import io.dico.parcels2.storage.Storage
+
+interface Migration {
+ fun migrateTo(storage: Storage)
+}
+
diff --git a/src/main/kotlin/io/dico/parcels2/storage/migration/MigrationFactory.kt b/src/main/kotlin/io/dico/parcels2/storage/migration/MigrationFactory.kt
new file mode 100644
index 0000000..4fb3088
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/storage/migration/MigrationFactory.kt
@@ -0,0 +1,5 @@
+package io.dico.parcels2.storage.migration
+
+interface MigrationFactory {
+ fun getMigration()
+} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt
new file mode 100644
index 0000000..e5b7d9d
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt
@@ -0,0 +1,118 @@
+package io.dico.parcels2.storage.migration.plotme
+
+import com.zaxxer.hikari.HikariDataSource
+import io.dico.parcels2.*
+import io.dico.parcels2.storage.Storage
+import io.dico.parcels2.storage.migration.Migration
+import io.dico.parcels2.util.Vec2i
+import io.dico.parcels2.util.isValid
+import io.dico.parcels2.util.toUUID
+import io.dico.parcels2.util.uuid
+import kotlinx.coroutines.experimental.asCoroutineDispatcher
+import kotlinx.coroutines.experimental.launch
+import org.bukkit.Bukkit
+import org.jetbrains.exposed.sql.*
+import org.slf4j.LoggerFactory
+import java.io.ByteArrayOutputStream
+import java.sql.Blob
+import java.util.UUID
+import java.util.concurrent.Executors
+import javax.sql.DataSource
+
+class PlotmeMigration(val parcelProvider: ParcelProvider,
+ val worldMapper: Map<String, String>,
+ val dataSourceFactory: () -> DataSource) : Migration {
+ private var dataSource: DataSource? = null
+ private var database: Database? = null
+ private var isShutdown: Boolean = false
+ private val dispatcher = Executors.newSingleThreadExecutor { Thread(it, "PlotMe Migration Thread") }.asCoroutineDispatcher()
+ private val mlogger = LoggerFactory.getLogger("PlotMe Migrator")
+
+ private fun <T> transaction(statement: Transaction.() -> T) = org.jetbrains.exposed.sql.transactions.transaction(database!!, statement)
+
+ override fun migrateTo(storage: Storage) {
+ launch(context = dispatcher) {
+ init()
+ doWork(storage)
+ shutdown()
+ }
+ }
+
+ fun init() {
+ if (isShutdown) throw IllegalStateException()
+ dataSource = dataSourceFactory()
+ database = Database.connect(dataSource!!)
+ }
+
+ fun shutdown() {
+ if (isShutdown) throw IllegalStateException()
+ dataSource?.let {
+ (it as? HikariDataSource)?.close()
+ }
+ database = null
+ isShutdown = true
+ }
+
+ val parcelsCache = hashMapOf<String, MutableMap<Vec2i, ParcelData>>()
+
+ private fun getMap(worldName: String): MutableMap<Vec2i, ParcelData>? {
+ val mapped = worldMapper[worldName] ?: return null
+ return parcelsCache.computeIfAbsent(mapped) { mutableMapOf() }
+ }
+
+ private fun getData(worldName: String, position: Vec2i): ParcelData? {
+ return getMap(worldName)?.computeIfAbsent(position) { ParcelDataHolder() }
+ }
+
+ fun doWork(target: Storage): Unit = transaction {
+ if (!PlotmePlotsT.exists()) {
+ mlogger.warn("Plotme tables don't appear to exist. Exiting.")
+ return@transaction
+ }
+ parcelsCache.clear()
+
+ iterPlotmeTable(PlotmePlotsT) { data, row ->
+ // in practice, owner_uuid is not null for any plot currently. It will convert well.
+ data.owner = ParcelOwner(row[owner_uuid]?.toUUID(), row[owner_name])
+ }
+
+ iterPlotmeTable(PlotmeAllowedT) { data, row ->
+ val uuid = row[player_uuid]?.toUUID()
+ ?: Bukkit.getOfflinePlayer(row[player_name]).takeIf { it.isValid }?.uuid
+ ?: return@iterPlotmeTable
+
+ data.setAddedStatus(uuid, AddedStatus.ALLOWED)
+ }
+
+ iterPlotmeTable(PlotmeDeniedT) { data, row ->
+ val uuid = row[PlotmeAllowedT.player_uuid]?.toUUID()
+ ?: Bukkit.getOfflinePlayer(row[PlotmeAllowedT.player_name]).takeIf { it.isValid }?.uuid
+ ?: return@iterPlotmeTable
+
+ data.setAddedStatus(uuid, AddedStatus.BANNED)
+ }
+
+ for ((worldName, map) in parcelsCache) {
+ val world = ParcelWorldId(worldName)
+ for ((pos, data) in map) {
+ val parcel = ParcelId(world, pos)
+ target.setParcelData(parcel, data)
+ }
+ }
+
+ }
+
+ private fun Blob.toUUID(): UUID {
+ val out = ByteArrayOutputStream(16)
+ binaryStream.copyTo(out, bufferSize = 16)
+ return out.toByteArray().toUUID()
+ }
+
+ private inline fun <T : PlotmeTable> iterPlotmeTable(table: T, block: T.(ParcelData, ResultRow) -> Unit) {
+ table.selectAll().forEach { row ->
+ val data = getData(row[table.world_name], Vec2i(row[table.px], row[table.pz])) ?: return@forEach
+ table.block(data, row)
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeTables.kt b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeTables.kt
new file mode 100644
index 0000000..3d07955
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeTables.kt
@@ -0,0 +1,26 @@
+package io.dico.parcels2.storage.migration.plotme
+
+import org.jetbrains.exposed.sql.Table
+
+const val uppercase: Boolean = false
+@Suppress("ConstantConditionIf")
+fun String.toCorrectCase() = if (uppercase) this else toLowerCase()
+
+sealed class PlotmeTable(name: String) : Table(name) {
+ val px = PlotmePlotsT.integer("idX").primaryKey()
+ val pz = PlotmePlotsT.integer("idZ").primaryKey()
+ val world_name = PlotmePlotsT.varchar("world", 32).primaryKey()
+}
+
+object PlotmePlotsT : PlotmeTable("plotmePlots".toCorrectCase()) {
+ val owner_name = varchar("owner", 32)
+ val owner_uuid = blob("ownerid").nullable()
+}
+
+sealed class PlotmePlotPlayerMap(name: String) : PlotmeTable(name) {
+ val player_name = PlotmePlotsT.varchar("player", 32)
+ val player_uuid = PlotmePlotsT.blob("playerid").nullable()
+}
+
+object PlotmeAllowedT : PlotmePlotPlayerMap("plotmeAllowed".toCorrectCase())
+object PlotmeDeniedT : PlotmePlotPlayerMap("plotmeDenied".toCorrectCase())
diff --git a/src/main/kotlin/io/dico/parcels2/util/BukkitAwait.kt b/src/main/kotlin/io/dico/parcels2/util/BukkitAwait.kt
deleted file mode 100644
index 4ca549f..0000000
--- a/src/main/kotlin/io/dico/parcels2/util/BukkitAwait.kt
+++ /dev/null
@@ -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()
- }
-
-} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/util/FunctionHelper.kt b/src/main/kotlin/io/dico/parcels2/util/FunctionHelper.kt
new file mode 100644
index 0000000..ea16652
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/util/FunctionHelper.kt
@@ -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)
+ }
+ }
+
+}
diff --git a/src/main/kotlin/io/dico/parcels2/util/MaterialExtensions.kt b/src/main/kotlin/io/dico/parcels2/util/MaterialExtensions.kt
index f38f687..a2aefc8 100644
--- a/src/main/kotlin/io/dico/parcels2/util/MaterialExtensions.kt
+++ b/src/main/kotlin/io/dico/parcels2/util/MaterialExtensions.kt
@@ -10,62 +10,67 @@ wood:
OAK_$, BIRCH_$, SPRUCE_$, JUNGLE_$, ACACIA_$, DARK_OAK_$,
*/
-val Material.isBed get() = when(this) {
- WHITE_BED,
- ORANGE_BED,
- MAGENTA_BED,
- LIGHT_BLUE_BED,
- YELLOW_BED,
- LIME_BED,
- PINK_BED,
- GRAY_BED,
- LIGHT_GRAY_BED,
- CYAN_BED,
- PURPLE_BED,
- BLUE_BED,
- BROWN_BED,
- GREEN_BED,
- RED_BED,
- BLACK_BED -> true
- else -> false
-}
+val Material.isBed
+ get() = when (this) {
+ WHITE_BED,
+ ORANGE_BED,
+ MAGENTA_BED,
+ LIGHT_BLUE_BED,
+ YELLOW_BED,
+ LIME_BED,
+ PINK_BED,
+ GRAY_BED,
+ LIGHT_GRAY_BED,
+ CYAN_BED,
+ PURPLE_BED,
+ BLUE_BED,
+ BROWN_BED,
+ GREEN_BED,
+ RED_BED,
+ BLACK_BED -> true
+ else -> false
+ }
-val Material.isWoodDoor get() = when(this) {
- OAK_DOOR,
- BIRCH_DOOR,
- SPRUCE_DOOR,
- JUNGLE_DOOR,
- ACACIA_DOOR,
- DARK_OAK_DOOR -> true
- else -> false
-}
+val Material.isWoodDoor
+ get() = when (this) {
+ OAK_DOOR,
+ BIRCH_DOOR,
+ SPRUCE_DOOR,
+ JUNGLE_DOOR,
+ ACACIA_DOOR,
+ DARK_OAK_DOOR -> true
+ else -> false
+ }
-val Material.isWoodTrapdoor get() = when(this) {
- OAK_TRAPDOOR,
- BIRCH_TRAPDOOR,
- SPRUCE_TRAPDOOR,
- JUNGLE_TRAPDOOR,
- ACACIA_TRAPDOOR,
- DARK_OAK_TRAPDOOR -> true
- else -> false
-}
+val Material.isWoodTrapdoor
+ get() = when (this) {
+ OAK_TRAPDOOR,
+ BIRCH_TRAPDOOR,
+ SPRUCE_TRAPDOOR,
+ JUNGLE_TRAPDOOR,
+ ACACIA_TRAPDOOR,
+ DARK_OAK_TRAPDOOR -> true
+ else -> false
+ }
-val Material.isWoodFenceGate get() = when(this) {
- OAK_FENCE_GATE,
- BIRCH_FENCE_GATE,
- SPRUCE_FENCE_GATE,
- JUNGLE_FENCE_GATE,
- ACACIA_FENCE_GATE,
- DARK_OAK_FENCE_GATE -> true
- else -> false
-}
+val Material.isWoodFenceGate
+ get() = when (this) {
+ OAK_FENCE_GATE,
+ BIRCH_FENCE_GATE,
+ SPRUCE_FENCE_GATE,
+ JUNGLE_FENCE_GATE,
+ ACACIA_FENCE_GATE,
+ DARK_OAK_FENCE_GATE -> true
+ else -> false
+ }
-val Material.isWoodButton get() = when(this) {
- OAK_BUTTON,
- BIRCH_BUTTON,
- SPRUCE_BUTTON,
- JUNGLE_BUTTON,
- ACACIA_BUTTON,
- DARK_OAK_BUTTON -> true
- else -> false
-}
+val Material.isWoodButton
+ get() = when (this) {
+ OAK_BUTTON,
+ BIRCH_BUTTON,
+ SPRUCE_BUTTON,
+ JUNGLE_BUTTON,
+ ACACIA_BUTTON,
+ DARK_OAK_BUTTON -> true
+ else -> false
+ }
diff --git a/src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt b/src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt
index 6597441..8713da7 100644
--- a/src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt
+++ b/src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt
@@ -9,7 +9,8 @@ import org.bukkit.plugin.java.JavaPlugin
inline val OfflinePlayer.uuid get() = uniqueId
@Suppress("UsePropertyAccessSyntax")
-inline val OfflinePlayer.isValid get() = isOnline() || hasPlayedBefore()
+inline val OfflinePlayer.isValid
+ get() = isOnline() || hasPlayedBefore()
inline val Player.hasBanBypass get() = hasPermission("parcels.admin.bypass.ban")
inline val Player.hasGamemodeBypass get() = hasPermission("parcels.admin.bypass.gamemode")
diff --git a/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt b/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt
index 10fbbbb..bca2428 100644
--- a/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt
+++ b/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt
@@ -1,26 +1,25 @@
package io.dico.parcels2.util
import org.bukkit.Bukkit
-import org.jetbrains.annotations.Contract
import java.nio.ByteBuffer
-import java.util.*
+import java.util.UUID
@Suppress("UsePropertyAccessSyntax")
-fun getPlayerName(uuid: UUID?, ifUnknown: String? = null): String {
- return uuid?.let { Bukkit.getOfflinePlayer(uuid)?.takeIf { it.isValid }?.name }
+fun getPlayerNameOrDefault(uuid: UUID?, ifUnknown: String? = null): String {
+ return uuid
+ ?.let { getPlayerName(it) }
?: ifUnknown
?: ":unknown_name:"
}
-@Contract("null -> null; !null -> !null", pure = true)
-fun UUID?.toByteArray(): ByteArray? = this?.let {
+fun getPlayerName(uuid: UUID): String? {
+ return Bukkit.getOfflinePlayer(uuid)?.takeIf { it.isValid }?.name
+}
+
+fun UUID.toByteArray(): ByteArray =
ByteBuffer.allocate(16).apply {
putLong(mostSignificantBits)
putLong(leastSignificantBits)
}.array()
-}
-@Contract("null -> null; !null -> !null", pure = true)
-fun ByteArray?.toUUID(): UUID? = this?.let {
- ByteBuffer.wrap(it).run { UUID(long, long) }
-} \ No newline at end of file
+fun ByteArray.toUUID(): UUID = ByteBuffer.wrap(this).run { UUID(long, long) }
diff --git a/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt b/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt
index a4655d0..6db98af 100644
--- a/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt
+++ b/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt
@@ -16,4 +16,37 @@ data class Vec3i(
}
@Suppress("NOTHING_TO_INLINE")
-inline operator fun World.get(vec: Vec3i): Block = getBlockAt(vec.x, vec.y, vec.z) \ No newline at end of file
+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)
+
+}
+*/