Archived
0

Merge branch 'dev'

This commit is contained in:
Dico
2018-08-05 06:46:27 +01:00
43 changed files with 1219 additions and 824 deletions

View File

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

View File

@@ -1,41 +1,50 @@
package io.dico.parcels2 package io.dico.parcels2
import io.dico.parcels2.util.uuid
import org.bukkit.OfflinePlayer import org.bukkit.OfflinePlayer
import java.util.UUID
typealias MutableAddedDataMap = MutableMap<UUID, AddedStatus> typealias StatusKey = PlayerProfile.Real
typealias AddedDataMap = Map<UUID, AddedStatus> typealias MutableAddedDataMap = MutableMap<StatusKey, AddedStatus>
typealias AddedDataMap = Map<StatusKey, AddedStatus>
@Suppress("FunctionName")
fun MutableAddedDataMap(): MutableAddedDataMap = hashMapOf()
interface AddedData { interface AddedData {
val addedMap: AddedDataMap val addedMap: AddedDataMap
var addedStatusOfStar: AddedStatus
fun getAddedStatus(uuid: UUID): AddedStatus fun getAddedStatus(key: StatusKey): AddedStatus
fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean fun setAddedStatus(key: StatusKey, status: AddedStatus): Boolean
fun compareAndSetAddedStatus(uuid: UUID, expect: AddedStatus, status: AddedStatus): Boolean = fun compareAndSetAddedStatus(key: StatusKey, expect: AddedStatus, status: AddedStatus): Boolean =
(getAddedStatus(uuid) == expect).also { if (it) setAddedStatus(uuid, status) } (getAddedStatus(key) == expect).also { if (it) setAddedStatus(key, status) }
fun isAllowed(uuid: UUID) = getAddedStatus(uuid) == AddedStatus.ALLOWED fun isAllowed(key: StatusKey) = getAddedStatus(key) == AddedStatus.ALLOWED
fun allow(uuid: UUID) = setAddedStatus(uuid, AddedStatus.ALLOWED) fun allow(key: StatusKey) = setAddedStatus(key, AddedStatus.ALLOWED)
fun disallow(uuid: UUID) = compareAndSetAddedStatus(uuid, AddedStatus.ALLOWED, AddedStatus.DEFAULT) fun disallow(key: StatusKey) = compareAndSetAddedStatus(key, AddedStatus.ALLOWED, AddedStatus.DEFAULT)
fun isBanned(uuid: UUID) = getAddedStatus(uuid) == AddedStatus.BANNED fun isBanned(key: StatusKey) = getAddedStatus(key) == AddedStatus.BANNED
fun ban(uuid: UUID) = setAddedStatus(uuid, AddedStatus.BANNED) fun ban(key: StatusKey) = setAddedStatus(key, AddedStatus.BANNED)
fun unban(uuid: UUID) = compareAndSetAddedStatus(uuid, AddedStatus.BANNED, AddedStatus.DEFAULT) fun unban(key: StatusKey) = compareAndSetAddedStatus(key, AddedStatus.BANNED, AddedStatus.DEFAULT)
fun isAllowed(player: OfflinePlayer) = isAllowed(player.uuid) fun isAllowed(player: OfflinePlayer) = isAllowed(player.statusKey)
fun allow(player: OfflinePlayer) = allow(player.uuid) fun allow(player: OfflinePlayer) = allow(player.statusKey)
fun disallow(player: OfflinePlayer) = disallow(player.uuid) fun disallow(player: OfflinePlayer) = disallow(player.statusKey)
fun isBanned(player: OfflinePlayer) = isBanned(player.uuid) fun isBanned(player: OfflinePlayer) = isBanned(player.statusKey)
fun ban(player: OfflinePlayer) = ban(player.uuid) fun ban(player: OfflinePlayer) = ban(player.statusKey)
fun unban(player: OfflinePlayer) = unban(player.uuid) fun unban(player: OfflinePlayer) = unban(player.statusKey)
} }
inline val OfflinePlayer.statusKey get() = PlayerProfile.nameless(this)
open class AddedDataHolder(override var addedMap: MutableAddedDataMap = mutableMapOf()) : AddedData { open class AddedDataHolder(override var addedMap: MutableAddedDataMap = mutableMapOf()) : AddedData {
override fun getAddedStatus(uuid: UUID): AddedStatus = addedMap.getOrDefault(uuid, AddedStatus.DEFAULT) override var addedStatusOfStar: AddedStatus = AddedStatus.DEFAULT
override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean = status.takeIf { it != AddedStatus.DEFAULT }
?.let { addedMap.put(uuid, it) != it } override fun getAddedStatus(key: StatusKey): AddedStatus = addedMap.getOrDefault(key, addedStatusOfStar)
?: addedMap.remove(uuid) != null
override fun setAddedStatus(key: StatusKey, status: AddedStatus): Boolean {
return if (status.isDefault) addedMap.remove(key) != null
else addedMap.put(key, status) != status
}
} }
enum class AddedStatus { enum class AddedStatus {
@@ -43,15 +52,15 @@ enum class AddedStatus {
ALLOWED, ALLOWED,
BANNED; BANNED;
val isDefault get() = this == DEFAULT inline val isDefault get() = this == DEFAULT
val isAllowed get() = this == ALLOWED inline val isAllowed get() = this == ALLOWED
val isBanned get() = this == BANNED inline val isBanned get() = this == BANNED
} }
interface GlobalAddedData : AddedData { interface GlobalAddedData : AddedData {
val owner: ParcelOwner val owner: PlayerProfile
} }
interface GlobalAddedDataManager { interface GlobalAddedDataManager {
operator fun get(owner: ParcelOwner): GlobalAddedData operator fun get(owner: PlayerProfile): GlobalAddedData
} }

View File

@@ -1,104 +0,0 @@
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.GameMode
import org.bukkit.Material
import java.io.Reader
import java.io.Writer
import java.util.EnumSet
class Options {
var worlds: Map<String, WorldOptionsHolder> = hashMapOf()
private set
var storage: StorageOptions = StorageOptions("postgresql", DataConnectionOptions())
var tickWorktime: TickWorktimeOptions = TickWorktimeOptions(20, 1)
fun addWorld(name: String,
generatorOptions: GeneratorOptions? = null,
worldOptions: WorldOptions? = null) {
val optionsHolder = WorldOptionsHolder(
generatorOptions ?: DefaultGeneratorOptions(),
worldOptions ?: WorldOptions()
)
(worlds as MutableMap).put(name, optionsHolder)
}
fun writeTo(writer: Writer) = yamlObjectMapper.writeValue(writer, this)
fun mergeFrom(reader: Reader) = yamlObjectMapper.readerForUpdating(this).readValue<Options>(reader)
override fun toString(): String = yamlObjectMapper.writeValueAsString(this)
}
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,
var preventWeatherBlockChanges: Boolean = true,
var preventBlockSpread: Boolean = true, // TODO
var dropEntityItems: Boolean = true,
var doTileDrops: Boolean = false,
var disableExplosions: Boolean = true,
var blockPortalCreation: Boolean = true,
var blockMobSpawning: Boolean = true,
var blockedItems: Set<Material> = EnumSet.of(Material.FLINT_AND_STEEL, Material.SNOWBALL),
var axisLimit: Int = 10) {
}
abstract class GeneratorOptions {
abstract fun generatorFactory(): GeneratorFactory
fun newGenerator(worldName: String) = generatorFactory().newParcelGenerator(worldName, this)
}
class StorageOptions(val dialect: String,
val options: Any) {
@get:JsonIgnore
val factory = StorageFactory.getFactory(dialect)
?: throw IllegalArgumentException("Invalid storage dialect: $dialect")
fun newStorageInstance(): Storage = factory.newStorageInstance(dialect, options)
}
data class DataConnectionOptions(val address: String = "localhost",
val database: String = "parcels",
val username: String = "root",
val password: String = "",
val poolSize: Int = 4) {
fun splitAddressAndPort(defaultPort: Int = 3306): Pair<String, Int>? {
val idx = address.indexOf(":").takeUnless { it == -1 } ?: return Pair(address, defaultPort)
val addressName = address.substring(0, idx).takeUnless { it.isBlank() } ?: return null.also {
logger.error("(Invalidly) blank address in data storage options")
}
val port = address.substring(idx + 1).toIntOrNull() ?: return null.also {
logger.error("Invalid port number in data storage options: $it, using $defaultPort as default")
}
return Pair(addressName, port)
}
}
data class DataFileOptions(val location: String = "/flatfile-storage/")
class MigrationOptions() {
}

View File

@@ -33,7 +33,7 @@ interface Parcel : ParcelData {
} }
interface ParcelData : AddedData { interface ParcelData : AddedData {
var owner: ParcelOwner? var owner: PlayerProfile?
val since: DateTime? val since: DateTime?
fun canBuild(player: OfflinePlayer, checkAdmin: Boolean = true, checkGlobal: Boolean = true): Boolean fun canBuild(player: OfflinePlayer, checkAdmin: Boolean = true, checkGlobal: Boolean = true): Boolean
@@ -46,11 +46,11 @@ interface ParcelData : AddedData {
} }
} }
class ParcelDataHolder : AddedDataHolder(), ParcelData { class ParcelDataHolder(addedMap: MutableAddedDataMap = mutableMapOf()) : AddedDataHolder(addedMap), ParcelData {
override var owner: ParcelOwner? = null override var owner: PlayerProfile? = null
override var since: DateTime? = null override var since: DateTime? = null
override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean) = isAllowed(player.uniqueId) override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean) = isAllowed(player.statusKey)
|| owner.let { it != null && it.matches(player, allowNameMatch = false) } || owner.let { it != null && it.matches(player, allowNameMatch = false) }
|| (checkAdmin && player is Player && player.hasBuildAnywhere) || (checkAdmin && player is Player && player.hasBuildAnywhere)

View File

@@ -3,7 +3,6 @@ package io.dico.parcels2
import io.dico.parcels2.blockvisitor.RegionTraversal import io.dico.parcels2.blockvisitor.RegionTraversal
import io.dico.parcels2.blockvisitor.Worker import io.dico.parcels2.blockvisitor.Worker
import io.dico.parcels2.blockvisitor.WorktimeLimiter import io.dico.parcels2.blockvisitor.WorktimeLimiter
import io.dico.parcels2.defaultimpl.DefaultParcelGenerator
import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.Vec2i
import org.bukkit.Chunk import org.bukkit.Chunk
import org.bukkit.Location import org.bukkit.Location
@@ -13,29 +12,7 @@ import org.bukkit.block.Block
import org.bukkit.entity.Entity import org.bukkit.entity.Entity
import org.bukkit.generator.BlockPopulator import org.bukkit.generator.BlockPopulator
import org.bukkit.generator.ChunkGenerator import org.bukkit.generator.ChunkGenerator
import java.util.HashMap
import java.util.Random 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 class ParcelGenerator : ChunkGenerator() {
abstract val world: World abstract val world: World
@@ -59,6 +36,7 @@ abstract class ParcelGenerator : ChunkGenerator() {
abstract fun makeParcelLocator(container: ParcelContainer): ParcelLocator abstract fun makeParcelLocator(container: ParcelContainer): ParcelLocator
} }
@Suppress("DeprecatedCallableAddReplaceWith")
interface ParcelBlockManager { interface ParcelBlockManager {
val world: World val world: World
val worktimeLimiter: WorktimeLimiter val worktimeLimiter: WorktimeLimiter
@@ -67,7 +45,7 @@ interface ParcelBlockManager {
fun getHomeLocation(parcel: ParcelId): Location fun getHomeLocation(parcel: ParcelId): Location
fun setOwnerBlock(parcel: ParcelId, owner: ParcelOwner?) fun setOwnerBlock(parcel: ParcelId, owner: PlayerProfile?)
@Deprecated("") @Deprecated("")
fun getEntities(parcel: ParcelId): Collection<Entity> = TODO() fun getEntities(parcel: ParcelId): Collection<Entity> = TODO()

View File

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

View File

@@ -1,5 +1,6 @@
package io.dico.parcels2 package io.dico.parcels2
import io.dico.parcels2.options.RuntimeWorldOptions
import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.Storage
import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.Vec2i
import io.dico.parcels2.util.floor import io.dico.parcels2.util.floor
@@ -76,7 +77,7 @@ interface ParcelWorld : ParcelLocator, ParcelContainer, ParcelBlockManager {
val id: ParcelWorldId val id: ParcelWorldId
val name: String val name: String
val uid: UUID? val uid: UUID?
val options: WorldOptions val options: RuntimeWorldOptions
val generator: ParcelGenerator val generator: ParcelGenerator
val storage: Storage val storage: Storage
val container: ParcelContainer val container: ParcelContainer

View File

@@ -10,8 +10,9 @@ import io.dico.parcels2.defaultimpl.GlobalAddedDataManagerImpl
import io.dico.parcels2.defaultimpl.ParcelProviderImpl import io.dico.parcels2.defaultimpl.ParcelProviderImpl
import io.dico.parcels2.listener.ParcelEntityTracker import io.dico.parcels2.listener.ParcelEntityTracker
import io.dico.parcels2.listener.ParcelListeners import io.dico.parcels2.listener.ParcelListeners
import io.dico.parcels2.options.Options
import io.dico.parcels2.options.optionsMapper
import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.Storage
import io.dico.parcels2.storage.yamlObjectMapper
import io.dico.parcels2.util.FunctionHelper import io.dico.parcels2.util.FunctionHelper
import io.dico.parcels2.util.tryCreate import io.dico.parcels2.util.tryCreate
import org.bukkit.Bukkit import org.bukkit.Bukkit
@@ -41,6 +42,7 @@ class ParcelsPlugin : JavaPlugin() {
override fun onEnable() { override fun onEnable() {
plogger.info("Debug enabled: ${plogger.isDebugEnabled}") plogger.info("Debug enabled: ${plogger.isDebugEnabled}")
plogger.debug(System.getProperty("user.dir"))
if (!init()) { if (!init()) {
Bukkit.getPluginManager().disablePlugin(this) Bukkit.getPluginManager().disablePlugin(this)
} }
@@ -60,7 +62,7 @@ class ParcelsPlugin : JavaPlugin() {
if (!loadOptions()) return false if (!loadOptions()) return false
try { try {
storage = options.storage.newStorageInstance() storage = options.storage.newInstance()
storage.init() storage.init()
} catch (ex: Exception) { } catch (ex: Exception) {
plogger.error("Failed to connect to database", ex) plogger.error("Failed to connect to database", ex)
@@ -83,26 +85,33 @@ class ParcelsPlugin : JavaPlugin() {
fun loadOptions(): Boolean { fun loadOptions(): Boolean {
when { when {
optionsFile.exists() -> yamlObjectMapper.readerForUpdating(options).readValue<Options>(optionsFile) optionsFile.exists() -> optionsMapper.readerForUpdating(options).readValue<Options>(optionsFile)
optionsFile.tryCreate() -> { else -> run {
options.addWorld("parcels") options.addWorld("parcels")
try { if (saveOptions()) {
yamlObjectMapper.writeValue(optionsFile, options) plogger.warn("Created options file with a world template. Please review it before next start.")
} catch (ex: Throwable) { } else {
optionsFile.delete() plogger.error("Failed to save options file ${optionsFile.canonicalPath}")
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 return false
} }
} }
return true return true
} }
fun saveOptions(): Boolean {
if (optionsFile.tryCreate()) {
try {
optionsMapper.writeValue(optionsFile, options)
} catch (ex: Throwable) {
optionsFile.delete()
throw ex
}
return true
}
return false
}
override fun getDefaultWorldGenerator(worldName: String, generatorId: String?): ChunkGenerator? { override fun getDefaultWorldGenerator(worldName: String, generatorId: String?): ChunkGenerator? {
return parcelProvider.getWorldGenerator(worldName) return parcelProvider.getWorldGenerator(worldName)
} }
@@ -118,6 +127,8 @@ class ParcelsPlugin : JavaPlugin() {
listeners = ParcelListeners(parcelProvider, entityTracker) listeners = ParcelListeners(parcelProvider, entityTracker)
registrator.registerListeners(listeners!!) registrator.registerListeners(listeners!!)
} }
functionHelper.scheduleRepeating(100, 5, entityTracker::tick)
} }
} }

View File

@@ -0,0 +1,287 @@
@file:Suppress("unused", "UsePropertyAccessSyntax", "DEPRECATION")
package io.dico.parcels2
import io.dico.parcels2.storage.Storage
import io.dico.parcels2.util.getPlayerNameOrDefault
import io.dico.parcels2.util.isValid
import io.dico.parcels2.util.uuid
import kotlinx.coroutines.experimental.Deferred
import kotlinx.coroutines.experimental.Unconfined
import kotlinx.coroutines.experimental.async
import org.bukkit.Bukkit
import org.bukkit.OfflinePlayer
import java.util.UUID
interface PlayerProfile {
val uuid: UUID? get() = null
val name: String?
val notNullName: String
val isStar: Boolean get() = false
fun matches(player: OfflinePlayer, allowNameMatch: Boolean = false): Boolean
fun equals(other: PlayerProfile): Boolean
override fun equals(other: Any?): Boolean
override fun hashCode(): Int
val isFake: Boolean get() = this is Fake
val isReal: Boolean get() = this is Real
companion object {
fun safe(uuid: UUID?, name: String?): PlayerProfile? {
if (uuid != null) return Real(uuid, name)
if (name != null) return invoke(name)
return null
}
operator fun invoke(uuid: UUID?, name: String?): PlayerProfile {
return safe(uuid, name) ?: throw IllegalArgumentException("One of uuid and name must not be null")
}
operator fun invoke(uuid: UUID): Real {
if (uuid == Star.uuid) return Star
return RealImpl(uuid, null)
}
operator fun invoke(name: String): PlayerProfile {
if (name == Star.name) return Star
return Fake(name)
}
operator fun invoke(player: OfflinePlayer): PlayerProfile {
return if (player.isValid) Real(player.uuid, player.name) else Fake(player.name)
}
fun nameless(player: OfflinePlayer): Real {
if (!player.isValid) throw IllegalArgumentException("The given OfflinePlayer is not valid")
return RealImpl(player.uuid, null)
}
fun byName(input: String, allowReal: Boolean = true, allowFake: Boolean = false): PlayerProfile {
if (!allowReal) {
if (!allowFake) throw IllegalArgumentException("at least one of allowReal and allowFake must be true")
return Fake(input)
}
if (input == Star.name) return Star
return Bukkit.getOfflinePlayer(input).takeIf { it.isValid }?.let { PlayerProfile(it) }
?: Unresolved(input)
}
}
interface Real : PlayerProfile {
override val uuid: UUID
override val notNullName: String
get() = name ?: getPlayerNameOrDefault(uuid)
val player: OfflinePlayer? get() = Bukkit.getOfflinePlayer(uuid).takeIf { it.isValid }
val playerUnchecked: OfflinePlayer get() = Bukkit.getOfflinePlayer(uuid)
override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean {
return uuid == player.uuid || (allowNameMatch && name?.let { it == player.name } == true)
}
override fun equals(other: PlayerProfile): Boolean {
return other is Real && uuid == other.uuid
}
companion object {
fun byName(name: String): PlayerProfile {
if (name == Star.name) return Star
return Unresolved(name)
}
operator fun invoke(uuid: UUID, name: String?): Real {
if (name == Star.name || uuid == Star.uuid) return Star
return RealImpl(uuid, name)
}
fun safe(uuid: UUID?, name: String?): Real? {
if (name == Star.name || uuid == Star.uuid) return Star
if (uuid == null) return null
return RealImpl(uuid, name)
}
}
}
object Star : BaseImpl(), Real {
override val name: String = "*"
override val uuid: UUID = UUID.fromString("7d09c4c6-117d-4f36-9778-c4d24618cee1")
override val isStar: Boolean get() = true
override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean {
return true
}
}
abstract class NameOnly(override val name: String) : BaseImpl() {
override val notNullName get() = name
override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean {
return allowNameMatch && player.name == name
}
}
class Fake(name: String) : NameOnly(name) {
override fun equals(other: PlayerProfile): Boolean {
return other is Fake && other.name == name
}
}
class Unresolved(name: String) : NameOnly(name) {
override fun equals(other: PlayerProfile): Boolean {
return other is Unresolved && name == other.name
}
fun tryResolve(storage: Storage): Deferred<Real?> {
return async(Unconfined) { tryResolveSuspendedly(storage) }
}
suspend fun tryResolveSuspendedly(storage: Storage): Real? {
return storage.getPlayerUuidForName(name).await()?.let { RealImpl(it, name) }
}
fun resolve(uuid: UUID): Real {
return RealImpl(uuid, name)
}
fun throwException(): Nothing {
throw IllegalArgumentException("A UUID for the player $name can not be found")
}
}
abstract class BaseImpl : PlayerProfile {
override fun equals(other: Any?): Boolean {
return this === other || (other is PlayerProfile && equals(other))
}
override fun hashCode(): Int {
return uuid?.hashCode() ?: name!!.hashCode()
}
}
private class RealImpl(override val uuid: UUID, override val name: String?) : BaseImpl(), Real
}
/*
/**
* This class can represent:
*
* An existing player
* A fake player (with only a name)
* An existing player who must have its uuid resolved from the database (after checking against Bukkit OfflinePlayer)
* STAR profile, which matches everyone. This profile is considered a REAL player, because it can have an added status.
*/
class PlayerProfile2 private constructor(uuid: UUID?,
val name: String?,
val isReal: Boolean = uuid != null) {
private var _uuid: UUID? = uuid
val notNullName: String get() = name ?: getPlayerNameOrDefault(uuid!!)
val uuid: UUID? get() = _uuid ?: if (isReal) throw IllegalStateException("This PlayerProfile must be resolved first") else null
companion object {
// below uuid is just a randomly generated one (version 4). Hopefully no minecraft player will ever have it :)
val star = PlayerProfile(UUID.fromString("7d09c4c6-117d-4f36-9778-c4d24618cee1"), "*", true)
fun nameless(player: OfflinePlayer): PlayerProfile {
if (!player.isValid) throw IllegalArgumentException("The given OfflinePlayer is not valid")
return PlayerProfile(player.uuid)
}
fun fromNameAndUuid(name: String?, uuid: UUID?): PlayerProfile? {
if (name == null && uuid == null) return null
if (star.name == name && star._uuid == uuid) return star
return PlayerProfile(uuid, name)
}
fun realPlayerByName(name: String): PlayerProfile {
return fromString(name, allowReal = true, allowFake = false)
}
fun fromString(input: String, allowReal: Boolean = true, allowFake: Boolean = false): PlayerProfile {
if (!allowReal) {
if (!allowFake) throw IllegalArgumentException("at least one of allowReal and allowFake must be true")
return PlayerProfile(input)
}
if (input == star.name) return star
return Bukkit.getOfflinePlayer(input).takeIf { it.isValid }?.let { PlayerProfile(it) }
?: PlayerProfile(null, input, !allowFake)
}
operator fun invoke(name: String): PlayerProfile {
if (name == star.name) return star
return PlayerProfile(null, name)
}
operator fun invoke(uuid: UUID): PlayerProfile {
if (uuid == star.uuid) return star
return PlayerProfile(uuid, null)
}
operator fun invoke(player: OfflinePlayer): PlayerProfile {
// avoid UUID comparison against STAR
return if (player.isValid) PlayerProfile(player.uuid, player.name) else invoke(player.name)
}
}
val isStar: Boolean get() = this === star || (name == star.name && _uuid == star._uuid)
val hasUUID: Boolean get() = _uuid != null
val mustBeResolved: Boolean get() = isReal && _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 {
if (isStar) return true
return uuid?.let { it == player.uniqueId } ?: false
|| (allowNameMatch && name?.let { it == player.name } ?: false)
}
fun equals(other: PlayerProfile): Boolean {
return if (_uuid != null) _uuid == other._uuid
else other._uuid == null && isReal == other.isReal && name == other.name
}
override fun equals(other: Any?): Boolean {
return other is PlayerProfile && equals(other)
}
override fun hashCode(): Int {
return _uuid?.hashCode() ?: name!!.hashCode()
}
/**
* resolve the uuid of this player profile if [mustBeResolved], using specified [storage].
* returns true if the PlayerProfile has a uuid after this call.
*/
suspend fun resolve(storage: Storage): Boolean {
if (mustBeResolved) {
val uuid = storage.getPlayerUuidForName(name!!).await()
_uuid = uuid
return uuid != null
}
return _uuid != null
}
fun resolve(uuid: UUID) {
if (isReal && _uuid == null) {
_uuid = uuid
}
}
}
*/

View File

@@ -3,7 +3,7 @@ package io.dico.parcels2.command
import io.dico.dicore.command.CommandException import io.dico.dicore.command.CommandException
import io.dico.dicore.command.ExecutionContext import io.dico.dicore.command.ExecutionContext
import io.dico.dicore.command.ICommandReceiver import io.dico.dicore.command.ICommandReceiver
import io.dico.parcels2.ParcelOwner import io.dico.parcels2.PlayerProfile
import io.dico.parcels2.ParcelWorld import io.dico.parcels2.ParcelWorld
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.util.hasAdminManage import io.dico.parcels2.util.hasAdminManage
@@ -31,7 +31,7 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei
protected suspend fun checkParcelLimit(player: Player, world: ParcelWorld) { protected suspend fun checkParcelLimit(player: Player, world: ParcelWorld) {
if (player.hasAdminManage) return if (player.hasAdminManage) return
val numOwnedParcels = plugin.storage.getOwnedParcels(ParcelOwner(player)).await() val numOwnedParcels = plugin.storage.getOwnedParcels(PlayerProfile(player)).await()
.filter { it.worldId.equals(world.id) }.size .filter { it.worldId.equals(world.id) }.size
val limit = player.parcelLimit val limit = player.parcelLimit

View File

@@ -5,7 +5,7 @@ import io.dico.dicore.command.annotation.Cmd
import io.dico.dicore.command.annotation.Desc import io.dico.dicore.command.annotation.Desc
import io.dico.parcels2.GlobalAddedData import io.dico.parcels2.GlobalAddedData
import io.dico.parcels2.GlobalAddedDataManager import io.dico.parcels2.GlobalAddedDataManager
import io.dico.parcels2.ParcelOwner import io.dico.parcels2.PlayerProfile
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.ParcelsPlugin
import org.bukkit.OfflinePlayer import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player import org.bukkit.entity.Player
@@ -13,7 +13,7 @@ import org.bukkit.entity.Player
class CommandsAddedStatusGlobal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { class CommandsAddedStatusGlobal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
private inline val data get() = plugin.globalAddedData private inline val data get() = plugin.globalAddedData
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
private inline operator fun GlobalAddedDataManager.get(player: OfflinePlayer): GlobalAddedData = data[ParcelOwner(player)] private inline operator fun GlobalAddedDataManager.get(player: OfflinePlayer): GlobalAddedData = data[PlayerProfile(player)]
@Cmd("allow", aliases = ["add", "permit"]) @Cmd("allow", aliases = ["add", "permit"])
@Desc("Globally allows a player to build on all", @Desc("Globally allows a player to build on all",

View File

@@ -33,15 +33,18 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
fun ParcelScope.cmdMakeMess(context: ExecutionContext) { fun ParcelScope.cmdMakeMess(context: ExecutionContext) {
val server = plugin.server val server = plugin.server
val blockDatas = arrayOf( val blockDatas = arrayOf(
server.createBlockData(Material.STICKY_PISTON), server.createBlockData(Material.BLUE_WOOL),
server.createBlockData(Material.LIME_WOOL),
server.createBlockData(Material.GLASS), server.createBlockData(Material.GLASS),
server.createBlockData(Material.STONE_SLAB), server.createBlockData(Material.STONE_SLAB),
server.createBlockData(Material.QUARTZ_BLOCK) server.createBlockData(Material.STONE),
server.createBlockData(Material.QUARTZ_BLOCK),
server.createBlockData(Material.BROWN_CONCRETE)
) )
val random = Random() val random = Random()
world.doBlockOperation(parcel.id, direction = RegionTraversal.UPWARD) { block -> world.doBlockOperation(parcel.id, direction = RegionTraversal.UPWARD) { block ->
block.blockData = blockDatas[random.nextInt(4)] block.blockData = blockDatas[random.nextInt(7)]
}.onProgressUpdate(1000, 1000) { progress, elapsedTime -> }.onProgressUpdate(1000, 1000) { progress, elapsedTime ->
context.sendMessage(EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed" context.sendMessage(EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed"
.format(progress * 100, elapsedTime / 1000.0)) .format(progress * 100, elapsedTime / 1000.0))

View File

@@ -6,7 +6,7 @@ import io.dico.dicore.command.annotation.Cmd
import io.dico.dicore.command.annotation.Desc import io.dico.dicore.command.annotation.Desc
import io.dico.dicore.command.annotation.Flag import io.dico.dicore.command.annotation.Flag
import io.dico.dicore.command.annotation.RequireParameters import io.dico.dicore.command.annotation.RequireParameters
import io.dico.parcels2.ParcelOwner import io.dico.parcels2.PlayerProfile
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.util.hasAdminManage import io.dico.parcels2.util.hasAdminManage
import io.dico.parcels2.util.hasParcelHomeOthers import io.dico.parcels2.util.hasParcelHomeOthers
@@ -25,7 +25,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
val parcel = world.nextEmptyParcel() val parcel = world.nextEmptyParcel()
?: error("This world is full, please ask an admin to upsize it") ?: error("This world is full, please ask an admin to upsize it")
parcel.owner = ParcelOwner(uuid = player.uuid) parcel.owner = PlayerProfile(uuid = player.uuid)
player.teleport(parcel.world.getHomeLocation(parcel.id)) player.teleport(parcel.world.getHomeLocation(parcel.id))
return "Enjoy your new parcel!" return "Enjoy your new parcel!"
} }
@@ -72,7 +72,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
} }
checkParcelLimit(player, world) checkParcelLimit(player, world)
parcel.owner = ParcelOwner(player) parcel.owner = PlayerProfile(player)
return "Enjoy your new parcel!" return "Enjoy your new parcel!"
} }

View File

@@ -7,14 +7,14 @@ import io.dico.dicore.command.parameter.type.ParameterType
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.Vec2i
import io.dico.parcels2.util.floor import io.dico.parcels2.util.floor
import io.dico.parcels2.util.isValid
import kotlinx.coroutines.experimental.Deferred import kotlinx.coroutines.experimental.Deferred
import org.bukkit.Bukkit
import org.bukkit.command.CommandSender import org.bukkit.command.CommandSender
import org.bukkit.entity.Player import org.bukkit.entity.Player
sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) { sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
abstract suspend fun ParcelsPlugin.getParcelSuspend(): Parcel? abstract suspend fun ParcelsPlugin.getParcelSuspend(): Parcel?
fun ParcelsPlugin.getParcelDeferred(): Deferred<Parcel?> = functionHelper.deferUndispatchedOnMainThread { getParcelSuspend() } fun ParcelsPlugin.getParcelDeferred(): Deferred<Parcel?> = functionHelper.deferUndispatchedOnMainThread { getParcelSuspend() }
class ByID(world: ParcelWorld, val id: Vec2i?, isDefault: Boolean) : ParcelTarget(world, isDefault) { class ByID(world: ParcelWorld, val id: Vec2i?, isDefault: Boolean) : ParcelTarget(world, isDefault) {
@@ -23,16 +23,35 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
val isPath: Boolean get() = id == null val isPath: Boolean get() = id == null
} }
class ByOwner(world: ParcelWorld, val owner: ParcelOwner, val index: Int, isDefault: Boolean) : ParcelTarget(world, isDefault) { class ByOwner(world: ParcelWorld,
owner: PlayerProfile,
val index: Int,
isDefault: Boolean,
val onResolveFailure: (() -> Unit)? = null) : ParcelTarget(world, isDefault) {
init { init {
if (index < 0) throw IllegalArgumentException("Invalid parcel home index: $index") if (index < 0) throw IllegalArgumentException("Invalid parcel home index: $index")
} }
var owner = owner; private set
override suspend fun ParcelsPlugin.getParcelSuspend(): Parcel? { override suspend fun ParcelsPlugin.getParcelSuspend(): Parcel? {
onResolveFailure?.let { onFail ->
val owner = owner
if (owner is PlayerProfile.Unresolved) {
val new = owner.tryResolveSuspendedly(storage)
if (new == null) {
onFail()
return@let
}
this@ByOwner.owner = new
}
}
val ownedParcelsSerialized = storage.getOwnedParcels(owner).await() val ownedParcelsSerialized = storage.getOwnedParcels(owner).await()
val ownedParcels = ownedParcelsSerialized val ownedParcels = ownedParcelsSerialized
.map { parcelProvider.getParcelById(it) } .map { parcelProvider.getParcelById(it) }
.filter { it != null && world == it.world && owner == it.owner } .filter { it != null && world == it.world && owner == it.owner }
return ownedParcels.getOrNull(index) return ownedParcels.getOrNull(index)
} }
} }
@@ -80,8 +99,8 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
} }
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") if (kind and OWNER == 0) invalidInput(parameter, "You must specify a parcel by ID, that is, the x and z component separated by a comma")
val (owner, index) = getHomeIndex(parameter, sender, input) val (owner, index) = getHomeIndex(parameter, kind, sender, input)
return ByOwner(world, owner, index, false) return ByOwner(world, owner, index, false, onResolveFailure = { invalidInput(parameter, "The player $input does not exist") })
} }
private fun getId(parameter: Parameter<*, *>, input: String): Vec2i { private fun getId(parameter: Parameter<*, *>, input: String): Vec2i {
@@ -94,7 +113,7 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
return Vec2i(x, z) return Vec2i(x, z)
} }
private fun getHomeIndex(parameter: Parameter<*, Int>, sender: CommandSender, input: String): Pair<ParcelOwner, Int> { private fun getHomeIndex(parameter: Parameter<*, *>, kind: Int, sender: CommandSender, input: String): Pair<PlayerProfile, Int> {
val splitIdx = input.indexOf(':') val splitIdx = input.indexOf(':')
val ownerString: String val ownerString: String
val indexString: String val indexString: String
@@ -109,9 +128,9 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
} }
val owner = if (ownerString.isEmpty()) val owner = if (ownerString.isEmpty())
ParcelOwner(requirePlayer(sender, parameter, "the player")) PlayerProfile(requirePlayer(sender, parameter, "the player"))
else else
inputAsOwner(parameter, ownerString) PlayerProfile.byName(ownerString, allowReal = kind and OWNER_REAL != 0, allowFake = kind and OWNER_FAKE != 0)
val index = if (indexString.isEmpty()) 0 else indexString.toIntOrNull() val index = if (indexString.isEmpty()) 0 else indexString.toIntOrNull()
?: invalidInput(parameter, "The home index must be an integer, $indexString is not an integer") ?: invalidInput(parameter, "The home index must be an integer, $indexString is not an integer")
@@ -124,22 +143,6 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
return sender 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? { override fun getDefaultValue(parameter: Parameter<ParcelTarget, Int>, sender: CommandSender, buffer: ArgumentBuffer): ParcelTarget? {
val kind = parameter.paramInfo ?: DEFAULT_KIND val kind = parameter.paramInfo ?: DEFAULT_KIND
val useLocation = when { val useLocation = when {
@@ -156,7 +159,8 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
return ByID(world, id, true) return ByID(world, id, true)
} }
return ByOwner(world, ParcelOwner(player), 0, true) return ByOwner(world, PlayerProfile(player), 0, true)
} }
} }
} }

View File

@@ -4,6 +4,7 @@ import io.dico.parcels2.*
import io.dico.parcels2.blockvisitor.RegionTraversal import io.dico.parcels2.blockvisitor.RegionTraversal
import io.dico.parcels2.blockvisitor.Worker import io.dico.parcels2.blockvisitor.Worker
import io.dico.parcels2.blockvisitor.WorktimeLimiter import io.dico.parcels2.blockvisitor.WorktimeLimiter
import io.dico.parcels2.options.DefaultGeneratorOptions
import io.dico.parcels2.util.* import io.dico.parcels2.util.*
import org.bukkit.* import org.bukkit.*
import org.bukkit.block.Biome import org.bukkit.block.Biome
@@ -17,21 +18,6 @@ import java.util.Random
private val airType = Bukkit.createBlockData(Material.AIR) 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() { class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOptions) : ParcelGenerator() {
private var _world: World? = null private var _world: World? = null
override val world: World override val world: World
@@ -44,15 +30,6 @@ class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOp
} }
private var maxHeight = 0 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 sectionSize = o.parcelSize + o.pathSize
val pathOffset = (if (o.pathSize % 2 == 0) o.pathSize + 2 else o.pathSize + 1) / 2 val pathOffset = (if (o.pathSize % 2 == 0) o.pathSize + 2 else o.pathSize + 1) / 2
val makePathMain = o.pathSize > 2 val makePathMain = o.pathSize > 2
@@ -142,7 +119,7 @@ class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOp
val modX = absX umod sectionSize val modX = absX umod sectionSize
val modZ = absZ umod sectionSize val modZ = absZ umod sectionSize
if (modX in 0 until parcelSize && modZ in 0 until parcelSize) { if (modX in 0 until parcelSize && modZ in 0 until parcelSize) {
return mapper((absX - modX) / sectionSize, (absZ - modZ) / sectionSize) return mapper((absX - modX) / sectionSize + 1, (absZ - modZ) / sectionSize + 1)
} }
return null return null
} }
@@ -163,16 +140,18 @@ class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOp
override val world: World = this@DefaultParcelGenerator.world override val world: World = this@DefaultParcelGenerator.world
override fun getBottomBlock(parcel: ParcelId): Vec2i = Vec2i( override fun getBottomBlock(parcel: ParcelId): Vec2i = Vec2i(
sectionSize * parcel.pos.x + pathOffset + o.offsetX, sectionSize * (parcel.x - 1) + pathOffset + o.offsetX,
sectionSize * parcel.pos.z + pathOffset + o.offsetZ sectionSize * (parcel.z - 1) + pathOffset + o.offsetZ
) )
override fun getHomeLocation(parcel: ParcelId): Location { override fun getHomeLocation(parcel: ParcelId): Location {
val bottom = getBottomBlock(parcel) val bottom = getBottomBlock(parcel)
return Location(world, bottom.x.toDouble(), o.floorHeight + 1.0, bottom.z + (o.parcelSize - 1) / 2.0, -90F, 0F) val x = bottom.x + (o.parcelSize - 1) / 2.0
val z = bottom.z - 2
return Location(world, x + 0.5, o.floorHeight + 1.0, z + 0.5, 0F, 0F)
} }
override fun setOwnerBlock(parcel: ParcelId, owner: ParcelOwner?) { override fun setOwnerBlock(parcel: ParcelId, owner: PlayerProfile?) {
val b = getBottomBlock(parcel) val b = getBottomBlock(parcel)
val wallBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 1) val wallBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 1)
@@ -201,8 +180,8 @@ class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOp
skullBlock.type = Material.PLAYER_HEAD skullBlock.type = Material.PLAYER_HEAD
val skull = skullBlock.state as Skull val skull = skullBlock.state as Skull
if (owner.uuid != null) { if (owner is PlayerProfile.Real) {
skull.owningPlayer = owner.offlinePlayer skull.owningPlayer = owner.playerUnchecked
} else { } else {
skull.owner = owner.name skull.owner = owner.name
} }

View File

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

View File

@@ -3,6 +3,7 @@ package io.dico.parcels2.defaultimpl
import io.dico.dicore.Formatting import io.dico.dicore.Formatting
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.Vec2i
import io.dico.parcels2.util.alsoIfTrue
import io.dico.parcels2.util.getPlayerName import io.dico.parcels2.util.getPlayerName
import org.bukkit.OfflinePlayer import org.bukkit.OfflinePlayer
import org.joda.time.DateTime import org.joda.time.DateTime
@@ -33,20 +34,24 @@ class ParcelImpl(override val world: ParcelWorld,
world.storage.setParcelData(this, null) world.storage.setParcelData(this, null)
} }
override val addedMap: Map<UUID, AddedStatus> get() = data.addedMap override val addedMap: AddedDataMap get() = data.addedMap
override fun getAddedStatus(uuid: UUID) = data.getAddedStatus(uuid) override fun getAddedStatus(key: StatusKey) = data.getAddedStatus(key)
override fun isBanned(uuid: UUID) = data.isBanned(uuid) override fun isBanned(key: StatusKey) = data.isBanned(key)
override fun isAllowed(uuid: UUID) = data.isAllowed(uuid) override fun isAllowed(key: StatusKey) = data.isAllowed(key)
override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean): Boolean { override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean): Boolean {
return (data.canBuild(player, checkAdmin, false)) return (data.canBuild(player, checkAdmin, false))
|| checkGlobal && world.globalAddedData[owner ?: return false].isAllowed(player) || checkGlobal && world.globalAddedData[owner ?: return false].isAllowed(player)
} }
val globalAddedMap: Map<UUID, AddedStatus>? get() = owner?.let { world.globalAddedData[it].addedMap } override var addedStatusOfStar: AddedStatus
get() = data.addedStatusOfStar
set(value) = run { setAddedStatus(PlayerProfile.Star, value) }
val globalAddedMap: AddedDataMap? get() = owner?.let { world.globalAddedData[it].addedMap }
override val since: DateTime? get() = data.since override val since: DateTime? get() = data.since
override var owner: ParcelOwner? override var owner: PlayerProfile?
get() = data.owner get() = data.owner
set(value) { set(value) {
if (data.owner != value) { if (data.owner != value) {
@@ -55,9 +60,9 @@ class ParcelImpl(override val world: ParcelWorld,
} }
} }
override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean { override fun setAddedStatus(key: StatusKey, status: AddedStatus): Boolean {
return data.setAddedStatus(uuid, status).also { return data.setAddedStatus(key, status).alsoIfTrue {
if (it) world.storage.setParcelPlayerStatus(this, uuid, status) world.storage.setParcelPlayerStatus(this, key, status)
} }
} }
@@ -102,10 +107,10 @@ private object ParcelInfoStringComputer {
append(' ') append(' ')
} }
private fun StringBuilder.appendAddedList(local: Map<UUID, AddedStatus>, global: Map<UUID, AddedStatus>, status: AddedStatus, fieldName: String) { private fun StringBuilder.appendAddedList(local: AddedDataMap, global: AddedDataMap, status: AddedStatus, fieldName: String) {
val globalSet = global.filterValues { it == status }.keys val globalSet = global.filterValues { it == status }.keys
val localList = local.filterValues { it == status }.keys.filter { it !in globalSet } val localList = local.filterValues { it == status }.keys.filter { it !in globalSet }
val stringList = globalSet.map(::getPlayerName).map { "(G)$it" } + localList.map(::getPlayerName) val stringList = globalSet.map(StatusKey::notNullName).map { "(G)$it" } + localList.map(StatusKey::notNullName)
if (stringList.isEmpty()) return if (stringList.isEmpty()) return
appendField({ appendField({

View File

@@ -28,7 +28,7 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
override fun getWorldGenerator(worldName: String): ParcelGenerator? { override fun getWorldGenerator(worldName: String): ParcelGenerator? {
return _worlds[worldName]?.generator return _worlds[worldName]?.generator
?: _generators[worldName] ?: _generators[worldName]
?: options.worlds[worldName]?.generator?.newGenerator(worldName)?.also { _generators[worldName] = it } ?: options.worlds[worldName]?.generator?.newInstance(worldName)?.also { _generators[worldName] = it }
} }
override fun loadWorlds() { override fun loadWorlds() {
@@ -60,13 +60,29 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
private fun loadStoredData() { private fun loadStoredData() {
plugin.functionHelper.launchLazilyOnMainThread { plugin.functionHelper.launchLazilyOnMainThread {
val channel = plugin.storage.readAllParcelData() val migration = plugin.options.migration
if (migration.enabled) {
migration.instance?.newInstance()?.apply {
logger.warn("Migrating database now...")
migrateTo(plugin.storage).join()
logger.warn("Migration completed")
if (migration.disableWhenComplete) {
migration.enabled = false
plugin.saveOptions()
}
}
}
logger.info("Loading all parcel data...")
val channel = plugin.storage.transmitAllParcelData()
do { do {
val pair = channel.receiveOrNull() ?: break val pair = channel.receiveOrNull() ?: break
val parcel = getParcelById(pair.first) ?: continue val parcel = getParcelById(pair.first) ?: continue
pair.second?.let { parcel.copyDataIgnoringDatabase(it) } pair.second?.let { parcel.copyDataIgnoringDatabase(it) }
} while (true) } while (true)
logger.info("Loading data completed")
_dataIsLoaded = true _dataIsLoaded = true
}.start() }.start()
} }
@@ -103,7 +119,7 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
} }
} }
val channel = plugin.storage.readAllParcelData() val channel = plugin.storage.transmitAllParcelData()
val job = plugin.functionHelper.launchLazilyOnMainThread { val job = plugin.functionHelper.launchLazilyOnMainThread {
do { do {
val pair = channel.receiveOrNull() ?: break val pair = channel.receiveOrNull() ?: break

View File

@@ -4,6 +4,7 @@ package io.dico.parcels2.defaultimpl
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.blockvisitor.WorktimeLimiter import io.dico.parcels2.blockvisitor.WorktimeLimiter
import io.dico.parcels2.options.RuntimeWorldOptions
import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.Storage
import org.bukkit.World import org.bukkit.World
import java.util.UUID import java.util.UUID
@@ -11,7 +12,7 @@ import java.util.UUID
class ParcelWorldImpl private class ParcelWorldImpl private
constructor(override val world: World, constructor(override val world: World,
override val generator: ParcelGenerator, override val generator: ParcelGenerator,
override var options: WorldOptions, override var options: RuntimeWorldOptions,
override val storage: Storage, override val storage: Storage,
override val globalAddedData: GlobalAddedDataManager, override val globalAddedData: GlobalAddedDataManager,
containerFactory: ParcelContainerFactory, containerFactory: ParcelContainerFactory,
@@ -62,7 +63,7 @@ constructor(override val world: World,
// Use this to be able to delegate blockManager and assign it to a property too, at least. // Use this to be able to delegate blockManager and assign it to a property too, at least.
operator fun invoke(world: World, operator fun invoke(world: World,
generator: ParcelGenerator, generator: ParcelGenerator,
options: WorldOptions, options: RuntimeWorldOptions,
storage: Storage, storage: Storage,
globalAddedData: GlobalAddedDataManager, globalAddedData: GlobalAddedDataManager,
containerFactory: ParcelContainerFactory, containerFactory: ParcelContainerFactory,

View File

@@ -26,7 +26,7 @@ class ParcelEntityTracker(val parcelProvider: ParcelProvider) {
*/ */
fun tick() { fun tick() {
map.editLoop { entity, parcel -> map.editLoop { entity, parcel ->
if (entity.isDead || entity.isOnGround) { if (entity.isDead) {
remove(); return@editLoop remove(); return@editLoop
} }
if (parcel.isPresentAnd { hasBlockVisitors }) { if (parcel.isPresentAnd { hasBlockVisitors }) {

View File

@@ -6,6 +6,7 @@ import io.dico.dicore.RegistratorListener
import io.dico.parcels2.Parcel import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelProvider import io.dico.parcels2.ParcelProvider
import io.dico.parcels2.ParcelWorld import io.dico.parcels2.ParcelWorld
import io.dico.parcels2.statusKey
import io.dico.parcels2.util.* import io.dico.parcels2.util.*
import org.bukkit.Material.* import org.bukkit.Material.*
import org.bukkit.World import org.bukkit.World
@@ -51,7 +52,7 @@ class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: Par
val user = event.player val user = event.player
if (user.hasBanBypass) return@l if (user.hasBanBypass) return@l
val parcel = parcelProvider.getParcelAt(event.to) ?: return@l val parcel = parcelProvider.getParcelAt(event.to) ?: return@l
if (parcel.isBanned(user.uuid)) { if (parcel.isBanned(user.statusKey)) {
parcelProvider.getParcelAt(event.from)?.also { parcelProvider.getParcelAt(event.from)?.also {
user.teleport(it.world.getHomeLocation(it.id)) user.teleport(it.world.getHomeLocation(it.id))
user.sendParcelMessage(nopermit = true, message = "You are banned from this parcel") user.sendParcelMessage(nopermit = true, message = "You are banned from this parcel")
@@ -83,9 +84,9 @@ class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: Par
* Prevents players from placing blocks outside of their parcels * Prevents players from placing blocks outside of their parcels
*/ */
@field:ListenerMarker(priority = NORMAL) @field:ListenerMarker(priority = NORMAL)
val onBlockPlaceEvent = RegistratorListener<BlockBreakEvent> l@{ event -> val onBlockPlaceEvent = RegistratorListener<BlockPlaceEvent> l@{ event ->
val (wo, ppa) = getWoAndPPa(event.block) ?: return@l val (wo, ppa) = getWoAndPPa(event.block) ?: return@l
if (!event.player.hasBuildAnywhere && !ppa.isNullOr { !canBuild(event.player) }) { if (!event.player.hasBuildAnywhere && ppa.isNullOr { !canBuild(event.player) }) {
event.isCancelled = true event.isCancelled = true
} }
} }
@@ -178,72 +179,79 @@ class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: Par
val clickedBlock = event.clickedBlock val clickedBlock = event.clickedBlock
val parcel = clickedBlock?.let { world.getParcelAt(it) } val parcel = clickedBlock?.let { world.getParcelAt(it) }
if (!user.hasBuildAnywhere && parcel.isPresentAnd { isBanned(user.uuid) }) { if (!user.hasBuildAnywhere && parcel.isPresentAnd { isBanned(user.statusKey) }) {
user.sendParcelMessage(nopermit = true, message = "You cannot interact with parcels you're banned from") user.sendParcelMessage(nopermit = true, message = "You cannot interact with parcels you're banned from")
event.isCancelled = true; return@l event.isCancelled = true; return@l
} }
when (event.action) { when (event.action) {
Action.RIGHT_CLICK_BLOCK -> when (clickedBlock.type) { Action.RIGHT_CLICK_BLOCK -> run {
REPEATER, when (clickedBlock.type) {
COMPARATOR -> run { REPEATER,
if (!parcel.canBuildN(user)) { COMPARATOR -> run {
event.isCancelled = true; return@l if (!parcel.canBuildN(user)) {
event.isCancelled = true; return@l
}
} }
} LEVER,
LEVER, STONE_BUTTON,
STONE_BUTTON, ANVIL,
ANVIL, TRAPPED_CHEST,
TRAPPED_CHEST, OAK_BUTTON, BIRCH_BUTTON, SPRUCE_BUTTON, JUNGLE_BUTTON, ACACIA_BUTTON, DARK_OAK_BUTTON,
OAK_BUTTON, BIRCH_BUTTON, SPRUCE_BUTTON, JUNGLE_BUTTON, ACACIA_BUTTON, DARK_OAK_BUTTON, OAK_FENCE_GATE, BIRCH_FENCE_GATE, SPRUCE_FENCE_GATE, JUNGLE_FENCE_GATE, ACACIA_FENCE_GATE, DARK_OAK_FENCE_GATE,
OAK_FENCE_GATE, BIRCH_FENCE_GATE, SPRUCE_FENCE_GATE, JUNGLE_FENCE_GATE, ACACIA_FENCE_GATE, DARK_OAK_FENCE_GATE, OAK_DOOR, BIRCH_DOOR, SPRUCE_DOOR, JUNGLE_DOOR, ACACIA_DOOR, DARK_OAK_DOOR,
OAK_DOOR, BIRCH_DOOR, SPRUCE_DOOR, JUNGLE_DOOR, ACACIA_DOOR, DARK_OAK_DOOR, OAK_TRAPDOOR, BIRCH_TRAPDOOR, SPRUCE_TRAPDOOR, JUNGLE_TRAPDOOR, ACACIA_TRAPDOOR, DARK_OAK_TRAPDOOR
OAK_TRAPDOOR, BIRCH_TRAPDOOR, SPRUCE_TRAPDOOR, JUNGLE_TRAPDOOR, ACACIA_TRAPDOOR, DARK_OAK_TRAPDOOR -> run {
-> run { if (!user.hasBuildAnywhere && !parcel.isNullOr { canBuild(user) || allowInteractInputs }) {
if (!user.hasBuildAnywhere && !parcel.isNullOr { canBuild(user) || allowInteractInputs }) { user.sendParcelMessage(nopermit = true, message = "You cannot use inputs in this parcel")
user.sendParcelMessage(nopermit = true, message = "You cannot use inputs in this parcel") event.isCancelled = true; return@l
event.isCancelled = true; return@l }
} }
}
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 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
-> run { -> run {
if (world.options.disableExplosions) { if (world.options.disableExplosions) {
val bed = clickedBlock.blockData as Bed val bed = clickedBlock.blockData as Bed
val head = if (bed == Bed.Part.FOOT) clickedBlock.getRelative(bed.facing) else clickedBlock val head = if (bed == Bed.Part.FOOT) clickedBlock.getRelative(bed.facing) else clickedBlock
when (head.biome) { when (head.biome) {
Biome.NETHER, Biome.THE_END -> run { Biome.NETHER, Biome.THE_END -> run {
user.sendParcelMessage(nopermit = true, message = "You cannot use this bed because it would explode") user.sendParcelMessage(nopermit = true, message = "You cannot use this bed because it would explode")
event.isCancelled = true; return@l event.isCancelled = true; return@l
}
} }
} }
} }
} }
onPlayerInteractEvent_RightClick(event, world, parcel)
} }
Action.RIGHT_CLICK_AIR -> if (event.hasItem()) { Action.RIGHT_CLICK_AIR -> onPlayerInteractEvent_RightClick(event, world, parcel)
val item = event.item.type
if (world.options.blockedItems.contains(item)) {
user.sendParcelMessage(nopermit = true, message = "You cannot use this bed because it would explode")
event.isCancelled = true; return@l
}
if (!parcel.canBuildN(user)) {
when (item) {
LAVA_BUCKET, WATER_BUCKET, BUCKET, FLINT_AND_STEEL -> event.isCancelled = true
}
}
}
Action.PHYSICAL -> if (!user.hasBuildAnywhere && !parcel.isPresentAnd { canBuild(user) || allowInteractInputs }) { Action.PHYSICAL -> if (!user.hasBuildAnywhere && !parcel.isPresentAnd { canBuild(user) || allowInteractInputs }) {
user.sendParcelMessage(nopermit = true, message = "You cannot use inputs in this parcel")
event.isCancelled = true; return@l event.isCancelled = true; return@l
} }
} }
} }
@Suppress("NON_EXHAUSTIVE_WHEN")
private fun onPlayerInteractEvent_RightClick(event: PlayerInteractEvent, world: ParcelWorld, parcel: Parcel?) {
if (event.hasItem()) {
val item = event.item.type
if (world.options.blockedItems.contains(item)) {
event.player.sendParcelMessage(nopermit = true, message = "You cannot use this item because it is disabled in this world")
event.isCancelled = true; return
}
if (!parcel.canBuildN(event.player)) {
when (item) {
LAVA_BUCKET, WATER_BUCKET, BUCKET, FLINT_AND_STEEL -> event.isCancelled = true
}
}
}
}
/* /*
* Prevents players from breeding mobs, entering or opening boats/minecarts, * Prevents players from breeding mobs, entering or opening boats/minecarts,
* rotating item frames, doing stuff with leashes, and putting stuff on armor stands. * rotating item frames, doing stuff with leashes, and putting stuff on armor stands.
@@ -352,7 +360,7 @@ class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: Par
world.weatherDuration = Int.MAX_VALUE world.weatherDuration = Int.MAX_VALUE
} }
// TODO: BlockFormEvent, BlockSpreadEvent, BlockFadeEvent // TODO: BlockFormEvent, BlockSpreadEvent, BlockFadeEvent, Fireworks
/* /*
* Prevents natural blocks forming * Prevents natural blocks forming
@@ -370,10 +378,10 @@ class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: Par
val hasEntity = event is EntityBlockFormEvent val hasEntity = event is EntityBlockFormEvent
val player = (event as? EntityBlockFormEvent)?.entity as? Player val player = (event as? EntityBlockFormEvent)?.entity as? Player
val cancel: Boolean = when (block.type) { val cancel: Boolean = when (event.newState.type) {
// prevent ice generation from Frost Walkers enchantment // prevent ice generation from Frost Walkers enchantment
ICE -> player != null && !ppa.canBuild(player) FROSTED_ICE -> player != null && !ppa.canBuild(player)
// prevent snow generation from weather // prevent snow generation from weather
SNOW -> !hasEntity && wo.options.preventWeatherBlockChanges SNOW -> !hasEntity && wo.options.preventWeatherBlockChanges
@@ -406,12 +414,13 @@ class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: Par
val onVehicleMoveEvent = RegistratorListener<VehicleMoveEvent> l@{ event -> val onVehicleMoveEvent = RegistratorListener<VehicleMoveEvent> l@{ event ->
val (wo, ppa) = getWoAndPPa(event.to.block) ?: return@l val (wo, ppa) = getWoAndPPa(event.to.block) ?: return@l
if (ppa == null) { if (ppa == null) {
event.vehicle.eject()
event.vehicle.passengers.forEach { event.vehicle.passengers.forEach {
if (it.type == EntityType.PLAYER) { if (it.type == EntityType.PLAYER) {
(it as Player).sendParcelMessage(except = true, message = "Your ride ends here") (it as Player).sendParcelMessage(except = true, message = "Your ride ends here")
} else it.remove() } else it.remove()
} }
event.vehicle.eject()
event.vehicle.remove()
} else if (ppa.hasBlockVisitors) { } else if (ppa.hasBlockVisitors) {
event.to.subtract(event.to).add(event.from) event.to.subtract(event.to).add(event.from)
} }

View File

@@ -0,0 +1,36 @@
package io.dico.parcels2.options
import io.dico.parcels2.ParcelGenerator
import io.dico.parcels2.defaultimpl.DefaultParcelGenerator
import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.block.Biome
import org.bukkit.block.data.BlockData
import kotlin.reflect.KClass
object GeneratorOptionsFactories : PolymorphicOptionsFactories<ParcelGenerator>("name", GeneratorOptions::class, DefaultGeneratorOptionsFactory())
class GeneratorOptions (name: String = "default", options: Any = DefaultGeneratorOptions()) : PolymorphicOptions<ParcelGenerator>(name, options, GeneratorOptionsFactories) {
fun newInstance(worldName: String) = factory.newInstance(key, options, worldName)
}
private class DefaultGeneratorOptionsFactory : PolymorphicOptionsFactory<ParcelGenerator> {
override val supportedKeys: List<String> = listOf("default")
override val optionsClass: KClass<out Any> get() = DefaultGeneratorOptions::class
override fun newInstance(key: String, options: Any, vararg extra: Any?): ParcelGenerator {
return DefaultParcelGenerator(extra.first() as String, options as DefaultGeneratorOptions)
}
}
class DefaultGeneratorOptions(val defaultBiome: Biome = Biome.JUNGLE,
val wallType: BlockData = Bukkit.createBlockData(Material.STONE_SLAB),
val floorType: BlockData = Bukkit.createBlockData(Material.QUARTZ_BLOCK),
val fillType: BlockData = Bukkit.createBlockData(Material.QUARTZ_BLOCK),
val pathMainType: BlockData = Bukkit.createBlockData(Material.SANDSTONE),
val pathAltType: BlockData = Bukkit.createBlockData(Material.REDSTONE_BLOCK),
val parcelSize: Int = 101,
val pathSize: Int = 9,
val floorHeight: Int = 64,
val offsetX: Int = 0,
val offsetZ: Int = 0)

View File

@@ -0,0 +1,21 @@
package io.dico.parcels2.options
import io.dico.parcels2.storage.migration.Migration
import io.dico.parcels2.storage.migration.plotme.PlotmeMigration
import kotlin.reflect.KClass
object MigrationOptionsFactories : PolymorphicOptionsFactories<Migration>("kind", MigrationOptions::class, PlotmeMigrationFactory())
class MigrationOptions(kind: String = "plotme-0.17", options: Any = PlotmeMigrationOptions()) : SimplePolymorphicOptions<Migration>(kind, options, MigrationOptionsFactories)
private class PlotmeMigrationFactory : PolymorphicOptionsFactory<Migration> {
override val supportedKeys = listOf("plotme-0.17")
override val optionsClass: KClass<out Any> get() = PlotmeMigrationOptions::class
override fun newInstance(key: String, options: Any, vararg extra: Any?): Migration {
return PlotmeMigration(options as PlotmeMigrationOptions)
}
}
class PlotmeMigrationOptions(val worldsFromTo: Map<String, String> = mapOf("plotworld" to "parcels"),
val storage: StorageOptions = StorageOptions(options = DataConnectionOptions(database = "plotme")))

View File

@@ -0,0 +1,58 @@
package io.dico.parcels2.options
import io.dico.parcels2.blockvisitor.TickWorktimeOptions
import org.bukkit.GameMode
import org.bukkit.Material
import java.io.Reader
import java.io.Writer
import java.util.EnumSet
class Options {
var worlds: Map<String, WorldOptions> = hashMapOf()
private set
var storage: StorageOptions = StorageOptions()
var tickWorktime: TickWorktimeOptions = TickWorktimeOptions(20, 1)
var migration = MigrationOptionsHolder()
fun addWorld(name: String,
generatorOptions: GeneratorOptions? = null,
worldOptions: RuntimeWorldOptions? = null) {
val optionsHolder = WorldOptions(
generatorOptions ?: GeneratorOptions(),
worldOptions ?: RuntimeWorldOptions()
)
(worlds as MutableMap).put(name, optionsHolder)
}
fun writeTo(writer: Writer) = optionsMapper.writeValue(writer, this)
fun mergeFrom(reader: Reader) = optionsMapper.readerForUpdating(this).readValue<Options>(reader)
override fun toString(): String = optionsMapper.writeValueAsString(this)
}
class WorldOptions(val generator: GeneratorOptions,
var runtime: RuntimeWorldOptions = RuntimeWorldOptions())
class RuntimeWorldOptions(var gameMode: GameMode? = GameMode.CREATIVE,
var dayTime: Boolean = true,
var noWeather: Boolean = true,
var preventWeatherBlockChanges: Boolean = true,
var preventBlockSpread: Boolean = true, // TODO
var dropEntityItems: Boolean = true,
var doTileDrops: Boolean = false,
var disableExplosions: Boolean = true,
var blockPortalCreation: Boolean = true,
var blockMobSpawning: Boolean = true,
var blockedItems: Set<Material> = EnumSet.of(Material.FLINT_AND_STEEL, Material.SNOWBALL),
var axisLimit: Int = 10)
class DataFileOptions(val location: String = "/flatfile-storage/")
class MigrationOptionsHolder {
var enabled = false
var disableWhenComplete = true
var instance: MigrationOptions? = MigrationOptions()
}

View File

@@ -0,0 +1,66 @@
package io.dico.parcels2.options
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.PropertyNamingStrategy
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.KotlinModule
import org.bukkit.Bukkit
import org.bukkit.block.data.BlockData
val optionsMapper = ObjectMapper(YAMLFactory()).apply {
propertyNamingStrategy = PropertyNamingStrategy.KEBAB_CASE
val kotlinModule = KotlinModule()
with(kotlinModule) {
/*
setSerializerModifier(object : BeanSerializerModifier() {
@Suppress("UNCHECKED_CAST")
override fun modifySerializer(config: SerializationConfig?, beanDesc: BeanDescription, serializer: JsonSerializer<*>): JsonSerializer<*> {
val newSerializer = if (GeneratorOptions::class.isSuperclassOf(beanDesc.beanClass.kotlin)) {
GeneratorOptionsSerializer(serializer as JsonSerializer<GeneratorOptions>)
} else {
serializer
}
return super.modifySerializer(config, beanDesc, newSerializer)
}
})*/
addSerializer(BlockDataSerializer())
addDeserializer(BlockData::class.java, BlockDataDeserializer())
GeneratorOptionsFactories.registerSerialization(this)
StorageOptionsFactories.registerSerialization(this)
MigrationOptionsFactories.registerSerialization(this)
}
registerModule(kotlinModule)
}
private class BlockDataSerializer : StdSerializer<BlockData>(BlockData::class.java) {
override fun serialize(value: BlockData, gen: JsonGenerator, provider: SerializerProvider) {
gen.writeString(value.asString)
}
}
private class BlockDataDeserializer : StdDeserializer<BlockData>(BlockData::class.java) {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): BlockData? {
try {
return Bukkit.createBlockData(p.valueAsString)
} catch (ex: Exception) {
throw RuntimeException("Exception occurred at ${p.currentLocation}", ex)
}
}
}

View File

@@ -0,0 +1,89 @@
package io.dico.parcels2.options
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import io.dico.parcels2.logger
import kotlin.reflect.KClass
abstract class PolymorphicOptions<T : Any>(val key: String,
val options: Any,
factories: PolymorphicOptionsFactories<T>) {
val factory = factories.getFactory(key)!!
}
abstract class SimplePolymorphicOptions<T : Any>(key: String, options: Any, factories: PolymorphicOptionsFactories<T>)
: PolymorphicOptions<T>(key, options, factories) {
fun newInstance(): T = factory.newInstance(key, options)
}
interface PolymorphicOptionsFactory<T : Any> {
val supportedKeys: List<String>
val optionsClass: KClass<out Any>
fun newInstance(key: String, options: Any, vararg extra: Any?): T
}
@Suppress("UNCHECKED_CAST")
abstract class PolymorphicOptionsFactories<T : Any>(val serializeKeyAs: String,
rootClass: KClass<out PolymorphicOptions<T>>,
vararg defaultFactories: PolymorphicOptionsFactory<T>) {
val rootClass = rootClass as KClass<PolymorphicOptions<T>>
private val map: MutableMap<String, PolymorphicOptionsFactory<T>> = linkedMapOf()
val availableKeys: Collection<String> get() = map.keys
fun registerFactory(factory: PolymorphicOptionsFactory<T>) = factory.supportedKeys.forEach { map.putIfAbsent(it.toLowerCase(), factory) }
fun getFactory(key: String): PolymorphicOptionsFactory<T>? = map[key.toLowerCase()]
fun registerSerialization(module: SimpleModule) {
module.addSerializer(PolymorphicOptionsSerializer(this))
module.addDeserializer(rootClass.java, PolymorphicOptionsDeserializer(this))
}
init {
defaultFactories.forEach { registerFactory(it) }
}
}
private class PolymorphicOptionsDeserializer<T : Any>(val factories: PolymorphicOptionsFactories<T>) : JsonDeserializer<PolymorphicOptions<T>>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext?): PolymorphicOptions<T> {
val node = p.readValueAsTree<JsonNode>()
val key = node.get(factories.serializeKeyAs).asText()
val factory = getFactory(key)
val optionsNode = node.get("options")
val options = p.codec.treeToValue(optionsNode, factory.optionsClass.java)
return factories.rootClass.constructors.first().call(key, options)
}
private fun getFactory(key: String): PolymorphicOptionsFactory<T> {
factories.getFactory(key)?.let { return it }
logger.warn("Unknown ${factories.rootClass.simpleName} ${factories.serializeKeyAs}: $key. " +
"\nAvailable options: ${factories.availableKeys}")
val default = factories.getFactory(factories.availableKeys.first())
?: throw IllegalStateException("No default ${factories.rootClass.simpleName} factory registered.")
return default
}
}
private class PolymorphicOptionsSerializer<T : Any>(val factories: PolymorphicOptionsFactories<T>) : StdSerializer<PolymorphicOptions<T>>(factories.rootClass.java) {
override fun serialize(value: PolymorphicOptions<T>, gen: JsonGenerator, sp: SerializerProvider?) {
with(gen) {
writeStartObject()
writeStringField(factories.serializeKeyAs, value.key)
writeFieldName("options")
writeObject(value.options)
writeEndObject()
}
}
}

View File

@@ -0,0 +1,59 @@
package io.dico.parcels2.options
import com.zaxxer.hikari.HikariDataSource
import io.dico.parcels2.logger
import io.dico.parcels2.storage.Storage
import io.dico.parcels2.storage.BackedStorage
import io.dico.parcels2.storage.exposed.ExposedBacking
import io.dico.parcels2.storage.getHikariConfig
import javax.sql.DataSource
object StorageOptionsFactories : PolymorphicOptionsFactories<Storage>("dialect", StorageOptions::class, ConnectionStorageFactory())
class StorageOptions(dialect: String = "mariadb", options: Any = DataConnectionOptions()) : SimplePolymorphicOptions<Storage>(dialect, options, StorageOptionsFactories) {
fun getDataSourceFactory(): DataSourceFactory? {
return when (factory) {
is ConnectionStorageFactory -> factory.getDataSourceFactory(key, options)
else -> return null
}
}
}
typealias DataSourceFactory = () -> DataSource
private class ConnectionStorageFactory : PolymorphicOptionsFactory<Storage> {
override val optionsClass = DataConnectionOptions::class
override val supportedKeys: List<String> = listOf("postgresql", "mariadb")
fun getDataSourceFactory(key: String, options: Any): DataSourceFactory {
val hikariConfig = getHikariConfig(key, options as DataConnectionOptions)
return { HikariDataSource(hikariConfig) }
}
override fun newInstance(key: String, options: Any, vararg extra: Any?): Storage {
return BackedStorage(ExposedBacking(getDataSourceFactory(key, options), (options as DataConnectionOptions).poolSize))
}
}
data class DataConnectionOptions(val address: String = "localhost",
val database: String = "parcels",
val username: String = "root",
val password: String = "",
val poolSize: Int = 4) {
fun splitAddressAndPort(defaultPort: Int = 3306): Pair<String, Int>? {
val idx = address.indexOf(":").takeUnless { it == -1 } ?: return Pair(address, defaultPort)
val addressName = address.substring(0, idx).takeUnless { it.isBlank() } ?: return null.also {
logger.error("(Invalidly) blank address in data storage options")
}
val port = address.substring(idx + 1).toIntOrNull() ?: return null.also {
logger.error("Invalid port number in data storage options: $it, using $defaultPort as default")
}
return Pair(addressName, port)
}
}

View File

@@ -1,6 +1,10 @@
package io.dico.parcels2.storage package io.dico.parcels2.storage
import io.dico.parcels2.* import io.dico.parcels2.*
import kotlinx.coroutines.experimental.CoroutineDispatcher
import kotlinx.coroutines.experimental.Deferred
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.channels.ReceiveChannel
import kotlinx.coroutines.experimental.channels.SendChannel import kotlinx.coroutines.experimental.channels.SendChannel
import java.util.UUID import java.util.UUID
@@ -10,41 +14,49 @@ interface Backing {
val isConnected: Boolean val isConnected: Boolean
suspend fun init() val dispatcher: CoroutineDispatcher
suspend fun shutdown() fun launchJob(job: Backing.() -> Unit): Job
fun <T> launchFuture(future: Backing.() -> T): Deferred<T>
fun <T> openChannel(future: Backing.(SendChannel<T>) -> Unit): ReceiveChannel<T>
fun <T> openChannelForWriting(future: Backing.(T) -> Unit): SendChannel<T>
/** fun init()
* 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 produceParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>)
suspend fun produceAllParcelData(channel: SendChannel<DataPair>) fun shutdown()
suspend fun readParcelData(parcel: ParcelId): ParcelData?
suspend fun getOwnedParcels(user: ParcelOwner): List<ParcelId>
suspend fun getNumParcels(user: ParcelOwner): Int = getOwnedParcels(user).size
suspend fun setParcelData(parcel: ParcelId, data: ParcelData?) fun getPlayerUuidForName(name: String): UUID?
suspend fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?) fun transmitParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>)
suspend fun setLocalPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus) fun transmitAllParcelData(channel: SendChannel<DataPair>)
suspend fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) fun readParcelData(parcel: ParcelId): ParcelData?
suspend fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean) fun getOwnedParcels(user: PlayerProfile): List<ParcelId>
fun getNumParcels(user: PlayerProfile): Int = getOwnedParcels(user).size
suspend fun produceAllGlobalAddedData(channel: SendChannel<AddedDataPair<ParcelOwner>>) fun setParcelData(parcel: ParcelId, data: ParcelData?)
suspend fun readGlobalAddedData(owner: ParcelOwner): MutableAddedDataMap fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?)
suspend fun setGlobalPlayerStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) fun setLocalPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus)
} fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean)
fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean)
fun transmitAllGlobalAddedData(channel: SendChannel<AddedDataPair<PlayerProfile>>)
fun readGlobalAddedData(owner: PlayerProfile): MutableAddedDataMap
fun setGlobalPlayerStatus(owner: PlayerProfile, player: PlayerProfile, status: AddedStatus)
}

View File

@@ -1,7 +1,7 @@
package io.dico.parcels2.storage package io.dico.parcels2.storage
import com.zaxxer.hikari.HikariConfig import com.zaxxer.hikari.HikariConfig
import io.dico.parcels2.DataConnectionOptions import io.dico.parcels2.options.DataConnectionOptions
fun getHikariConfig(dialectName: String, fun getHikariConfig(dialectName: String,
dco: DataConnectionOptions): HikariConfig = HikariConfig().apply { dco: DataConnectionOptions): HikariConfig = HikariConfig().apply {

View File

@@ -1,122 +0,0 @@
package io.dico.parcels2.storage
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
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.GeneratorFactories
import io.dico.parcels2.GeneratorOptions
import io.dico.parcels2.StorageOptions
import org.bukkit.Bukkit
import org.bukkit.block.data.BlockData
import kotlin.reflect.full.isSuperclassOf
val yamlObjectMapper = ObjectMapper(YAMLFactory()).apply {
propertyNamingStrategy = PropertyNamingStrategy.KEBAB_CASE
val kotlinModule = KotlinModule()
with(kotlinModule) {
setSerializerModifier(object : BeanSerializerModifier() {
@Suppress("UNCHECKED_CAST")
override fun modifySerializer(config: SerializationConfig?, beanDesc: BeanDescription, serializer: JsonSerializer<*>): JsonSerializer<*> {
val newSerializer = if (GeneratorOptions::class.isSuperclassOf(beanDesc.beanClass.kotlin)) {
GeneratorOptionsSerializer(serializer as JsonSerializer<GeneratorOptions>)
} else {
serializer
}
return super.modifySerializer(config, beanDesc, newSerializer)
}
})
addSerializer(BlockDataSerializer())
addDeserializer(BlockData::class.java, BlockDataDeserializer())
addSerializer(StorageOptionsSerializer())
addDeserializer(StorageOptions::class.java, StorageOptionsDeserializer())
addDeserializer(GeneratorOptions::class.java, GeneratorOptionsDeserializer())
}
registerModule(kotlinModule)
}
private class BlockDataSerializer : StdSerializer<BlockData>(BlockData::class.java) {
override fun serialize(value: BlockData, gen: JsonGenerator, provider: SerializerProvider) {
gen.writeString(value.asString)
}
}
private class BlockDataDeserializer : StdDeserializer<BlockData>(BlockData::class.java) {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): BlockData? {
try {
return Bukkit.createBlockData(p.valueAsString)
} catch (ex: Exception) {
throw RuntimeException("Exception occurred at ${p.currentLocation}", ex)
}
}
}
class StorageOptionsDeserializer : JsonDeserializer<StorageOptions>() {
override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): StorageOptions {
val node = p!!.readValueAsTree<JsonNode>()
val dialect = node.get("dialect").asText()
val optionsNode = node.get("options")
val factory = StorageFactory.getFactory(dialect) ?: throw IllegalStateException("Unknown storage dialect: $dialect")
val options = p.codec.treeToValue(optionsNode, factory.optionsClass.java)
return StorageOptions(dialect, options)
}
}
class StorageOptionsSerializer : StdSerializer<StorageOptions>(StorageOptions::class.java) {
override fun serialize(value: StorageOptions?, gen: JsonGenerator?, serializers: SerializerProvider?) {
with(gen!!) {
writeStartObject()
writeStringField("dialect", value!!.dialect)
writeFieldName("options")
writeObject(value.options)
writeEndObject()
}
}
}
class GeneratorOptionsDeserializer : JsonDeserializer<GeneratorOptions>() {
override fun deserialize(parser: JsonParser?, ctx: DeserializationContext?): GeneratorOptions? {
val node = parser!!.readValueAsTree<JsonNode>()
val name = node.get("name").asText()
val optionsNode = node.get("options")
val factory = GeneratorFactories.getFactory(name) ?: throw IllegalStateException("Unknown generator: $name")
return parser.codec.treeToValue(optionsNode, factory.optionsClass.java)
}
}
class GeneratorOptionsSerializer(private val defaultSerializer: JsonSerializer<GeneratorOptions>) : JsonSerializer<GeneratorOptions>() {
override fun serialize(input: GeneratorOptions?, generator: JsonGenerator?, provider: SerializerProvider?) {
with(generator!!) {
writeStartObject()
writeStringField("name", input!!.generatorFactory().name)
writeFieldName("options")
defaultSerializer.serialize(input, generator, provider)
writeEndObject()
}
}
}

View File

@@ -3,21 +3,18 @@
package io.dico.parcels2.storage package io.dico.parcels2.storage
import io.dico.parcels2.* import io.dico.parcels2.*
import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.Deferred
import kotlinx.coroutines.experimental.channels.ProducerScope import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.channels.ReceiveChannel import kotlinx.coroutines.experimental.channels.ReceiveChannel
import kotlinx.coroutines.experimental.channels.produce import kotlinx.coroutines.experimental.channels.SendChannel
import kotlinx.coroutines.experimental.launch
import java.util.UUID import java.util.UUID
import java.util.concurrent.Executor
import java.util.concurrent.Executors
typealias DataPair = Pair<ParcelId, ParcelData?> typealias DataPair = Pair<ParcelId, ParcelData?>
typealias AddedDataPair<TAttach> = Pair<TAttach, MutableAddedDataMap> typealias AddedDataPair<TAttach> = Pair<TAttach, MutableAddedDataMap>
interface Storage { interface Storage {
val name: String val name: String
val syncDispatcher: CoroutineDispatcher
val asyncDispatcher: CoroutineDispatcher
val isConnected: Boolean val isConnected: Boolean
fun init(): Job fun init(): Job
@@ -25,84 +22,77 @@ interface Storage {
fun shutdown(): Job fun shutdown(): Job
fun getPlayerUuidForName(name: String): Deferred<UUID?>
fun readParcelData(parcel: ParcelId): Deferred<ParcelData?> fun readParcelData(parcel: ParcelId): Deferred<ParcelData?>
fun readParcelData(parcels: Sequence<ParcelId>): ReceiveChannel<DataPair> fun transmitParcelData(parcels: Sequence<ParcelId>): ReceiveChannel<DataPair>
fun readAllParcelData(): ReceiveChannel<DataPair> fun transmitAllParcelData(): ReceiveChannel<DataPair>
fun getOwnedParcels(user: ParcelOwner): Deferred<List<ParcelId>> fun getOwnedParcels(user: PlayerProfile): Deferred<List<ParcelId>>
fun getNumParcels(user: ParcelOwner): Deferred<Int> fun getNumParcels(user: PlayerProfile): Deferred<Int>
fun setParcelData(parcel: ParcelId, data: ParcelData?): Job fun setParcelData(parcel: ParcelId, data: ParcelData?): Job
fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?): Job fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?): Job
fun setParcelPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus): Job fun setParcelPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus): Job
fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean): Job fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean): Job
fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean): Job fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean): Job
fun readAllGlobalAddedData(): ReceiveChannel<AddedDataPair<ParcelOwner>> fun transmitAllGlobalAddedData(): ReceiveChannel<AddedDataPair<PlayerProfile>>
fun readGlobalAddedData(owner: ParcelOwner): Deferred<MutableAddedDataMap?> fun readGlobalAddedData(owner: PlayerProfile): Deferred<MutableAddedDataMap?>
fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus): Job fun setGlobalAddedStatus(owner: PlayerProfile, player: PlayerProfile, status: AddedStatus): Job
fun getChannelToUpdateParcelData(): SendChannel<Pair<ParcelId, ParcelData>>
} }
class StorageWithCoroutineBacking internal constructor(val backing: Backing) : Storage { class BackedStorage internal constructor(val b: Backing) : Storage {
override val name get() = backing.name override val name get() = b.name
override val syncDispatcher = Executor { it.run() }.asCoroutineDispatcher() override val isConnected get() = b.isConnected
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
private inline fun <T> defer(noinline block: suspend CoroutineScope.() -> T): Deferred<T> { override fun init() = launch(b.dispatcher) { b.init() }
return async(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block)
}
private inline fun job(noinline block: suspend CoroutineScope.() -> Unit): Job { override fun shutdown() = launch(b.dispatcher) { b.shutdown() }
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(parcel: ParcelId) = defer { backing.readParcelData(parcel) } override fun getPlayerUuidForName(name: String): Deferred<UUID?> = b.launchFuture { b.getPlayerUuidForName(name) }
override fun readParcelData(parcels: Sequence<ParcelId>) = openChannel<DataPair> { backing.produceParcelData(channel, parcels) } override fun readParcelData(parcel: ParcelId) = b.launchFuture { b.readParcelData(parcel) }
override fun readAllParcelData() = openChannel<DataPair> { backing.produceAllParcelData(channel) } override fun transmitParcelData(parcels: Sequence<ParcelId>) = b.openChannel<DataPair> { b.transmitParcelData(it, parcels) }
override fun getOwnedParcels(user: ParcelOwner) = defer { backing.getOwnedParcels(user) } override fun transmitAllParcelData() = b.openChannel<DataPair> { b.transmitAllParcelData(it) }
override fun getNumParcels(user: ParcelOwner) = defer { backing.getNumParcels(user) } override fun getOwnedParcels(user: PlayerProfile) = b.launchFuture { b.getOwnedParcels(user) }
override fun setParcelData(parcel: ParcelId, data: ParcelData?) = job { backing.setParcelData(parcel, data) } override fun getNumParcels(user: PlayerProfile) = b.launchFuture { b.getNumParcels(user) }
override fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?) = job { backing.setParcelOwner(parcel, owner) } override fun setParcelData(parcel: ParcelId, data: ParcelData?) = b.launchJob { b.setParcelData(parcel, data) }
override fun setParcelPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus) = job { backing.setLocalPlayerStatus(parcel, player, status) } override fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) = b.launchJob { b.setParcelOwner(parcel, owner) }
override fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) = job { backing.setParcelAllowsInteractInventory(parcel, value) } override fun setParcelPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus) = b.launchJob { b.setLocalPlayerStatus(parcel, player, status) }
override fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean) = job { backing.setParcelAllowsInteractInputs(parcel, value) } override fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) = b.launchJob { b.setParcelAllowsInteractInventory(parcel, value) }
override fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean) = b.launchJob { b.setParcelAllowsInteractInputs(parcel, value) }
override fun readAllGlobalAddedData(): ReceiveChannel<AddedDataPair<ParcelOwner>> = openChannel { backing.produceAllGlobalAddedData(channel) } override fun transmitAllGlobalAddedData(): ReceiveChannel<AddedDataPair<PlayerProfile>> = b.openChannel { b.transmitAllGlobalAddedData(it) }
override fun readGlobalAddedData(owner: ParcelOwner): Deferred<MutableAddedDataMap?> = defer { backing.readGlobalAddedData(owner) } override fun readGlobalAddedData(owner: PlayerProfile): Deferred<MutableAddedDataMap?> = b.launchFuture { b.readGlobalAddedData(owner) }
override fun setGlobalAddedStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) = job { backing.setGlobalPlayerStatus(owner, player, status) } override fun setGlobalAddedStatus(owner: PlayerProfile, player: PlayerProfile, status: AddedStatus) = b.launchJob { b.setGlobalPlayerStatus(owner, player, status) }
override fun getChannelToUpdateParcelData(): SendChannel<Pair<ParcelId, ParcelData>> = b.openChannelForWriting { b.setParcelData(it.first, it.second) }
} }

View File

@@ -1,43 +0,0 @@
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 {
companion object StorageFactories {
private val map: MutableMap<String, StorageFactory> = HashMap()
fun registerFactory(dialect: String, generator: StorageFactory): Boolean = map.putIfAbsent(dialect.toLowerCase(), generator) == null
fun getFactory(dialect: String): StorageFactory? = map[dialect.toLowerCase()]
init {
// have to write the code like this in kotlin.
// This code is absolutely disgusting
ConnectionStorageFactory().register(this)
}
}
val optionsClass: KClass<out Any>
fun newStorageInstance(dialect: String, options: Any): Storage
}
class ConnectionStorageFactory : StorageFactory {
override val optionsClass = DataConnectionOptions::class
private val types: List<String> = listOf("postgresql", "mariadb")
fun register(companion: StorageFactory.StorageFactories) {
types.forEach { companion.registerFactory(it, this) }
}
override fun newStorageInstance(dialect: String, options: Any): Storage {
val hikariConfig = getHikariConfig(dialect, options as DataConnectionOptions)
val dataSourceFactory = suspend { HikariDataSource(hikariConfig) }
return StorageWithCoroutineBacking(ExposedBacking(dataSourceFactory))
}
}

View File

@@ -1,16 +1,19 @@
@file:Suppress("NOTHING_TO_INLINE", "PARAMETER_NAME_CHANGED_ON_OVERRIDE", "LocalVariableName") @file:Suppress("NOTHING_TO_INLINE", "PARAMETER_NAME_CHANGED_ON_OVERRIDE", "LocalVariableName", "UNUSED_EXPRESSION")
package io.dico.parcels2.storage.exposed package io.dico.parcels2.storage.exposed
import com.zaxxer.hikari.HikariDataSource import com.zaxxer.hikari.HikariDataSource
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.storage.AddedDataPair
import io.dico.parcels2.storage.Backing import io.dico.parcels2.storage.Backing
import io.dico.parcels2.storage.DataPair import io.dico.parcels2.storage.DataPair
import io.dico.parcels2.util.synchronized
import io.dico.parcels2.util.toUUID import io.dico.parcels2.util.toUUID
import kotlinx.coroutines.experimental.CoroutineStart import kotlinx.coroutines.experimental.*
import kotlinx.coroutines.experimental.Unconfined import kotlinx.coroutines.experimental.channels.ArrayChannel
import kotlinx.coroutines.experimental.channels.LinkedListChannel
import kotlinx.coroutines.experimental.channels.ReceiveChannel
import kotlinx.coroutines.experimental.channels.SendChannel import kotlinx.coroutines.experimental.channels.SendChannel
import kotlinx.coroutines.experimental.launch
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SchemaUtils.create import org.jetbrains.exposed.sql.SchemaUtils.create
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
@@ -21,14 +24,44 @@ import javax.sql.DataSource
class ExposedDatabaseException(message: String? = null) : Exception(message) class ExposedDatabaseException(message: String? = null) : Exception(message)
class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) : Backing { class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSize: Int) : Backing {
override val name get() = "Exposed" override val name get() = "Exposed"
override val dispatcher: ThreadPoolDispatcher = newFixedThreadPoolContext(poolSize, "Parcels StorageThread")
private var dataSource: DataSource? = null private var dataSource: DataSource? = null
private var database: Database? = null private var database: Database? = null
private var isShutdown: Boolean = false private var isShutdown: Boolean = false
override val isConnected get() = database != null override val isConnected get() = database != null
override fun launchJob(job: Backing.() -> Unit): Job = launch(dispatcher) { transaction { job() } }
override fun <T> launchFuture(future: Backing.() -> T): Deferred<T> = async(dispatcher) { transaction { future() } }
override fun <T> openChannel(future: Backing.(SendChannel<T>) -> Unit): ReceiveChannel<T> {
val channel = LinkedListChannel<T>()
launchJob { future(channel) }
return channel
}
override fun <T> openChannelForWriting(action: Backing.(T) -> Unit): SendChannel<T> {
val channel = ArrayChannel<T>(poolSize * 2)
repeat(poolSize) {
launch(dispatcher) {
try {
while (true) {
action(channel.receive())
}
} catch (ex: Exception) {
// channel closed
}
}
}
return channel
}
private fun <T> transaction(statement: Transaction.() -> T) = transaction(database!!, statement)
companion object { companion object {
init { init {
Database.registerDialect("mariadb") { Database.registerDialect("mariadb") {
@@ -37,63 +70,85 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) :
} }
} }
private fun <T> transaction(statement: Transaction.() -> T) = transaction(database!!, statement) override fun init() {
synchronized {
private suspend fun transactionLaunch(statement: suspend Transaction.() -> Unit): Unit = transaction(database!!) { if (isShutdown || isConnected) throw IllegalStateException()
launch(context = Unconfined, start = CoroutineStart.UNDISPATCHED) { dataSource = dataSourceFactory()
statement(this@transaction) database = Database.connect(dataSource!!)
transaction(database!!) {
create(WorldsT, ProfilesT, ParcelsT, ParcelOptionsT, AddedLocalT, AddedGlobalT)
}
} }
} }
override suspend fun init() { override fun shutdown() {
if (isShutdown) throw IllegalStateException() synchronized {
dataSource = dataSourceFactory() if (isShutdown) throw IllegalStateException()
database = Database.connect(dataSource!!) dataSource?.let {
transaction(database) { (it as? HikariDataSource)?.close()
create(WorldsT, OwnersT, ParcelsT, ParcelOptionsT, AddedLocalT, AddedGlobalT) }
database = null
isShutdown = true
} }
} }
override suspend fun shutdown() { private fun PlayerProfile.toOwnerProfile(): PlayerProfile {
if (isShutdown) throw IllegalStateException() if (this is PlayerProfile.Star) return PlayerProfile.Fake(PlayerProfile.Star.name)
dataSource?.let { return this
(it as? HikariDataSource)?.close()
}
database = null
isShutdown = true
} }
override suspend fun produceParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>) { private fun PlayerProfile.Unresolved.toResolvedProfile(): PlayerProfile.Real {
return resolve(getPlayerUuidForName(name) ?: throwException())
}
private fun PlayerProfile.toResolvedProfile(): PlayerProfile {
if (this is PlayerProfile.Unresolved) return toResolvedProfile()
return this
}
private fun PlayerProfile.toRealProfile(): PlayerProfile.Real = when (this) {
is PlayerProfile.Real -> this
is PlayerProfile.Fake -> throw IllegalArgumentException("Fake profiles are not accepted")
is PlayerProfile.Unresolved -> toResolvedProfile()
else -> throw InternalError("Case should not be reached")
}
override fun getPlayerUuidForName(name: String): UUID? {
return ProfilesT.slice(ProfilesT.uuid).select { ProfilesT.name.upperCase() eq name.toUpperCase() }
.firstOrNull()?.let { it[ProfilesT.uuid]?.toUUID() }
}
override fun transmitParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>) {
for (parcel in parcels) { for (parcel in parcels) {
val data = readParcelData(parcel) val data = readParcelData(parcel)
channel.send(parcel to data) channel.offer(parcel to data)
} }
channel.close() channel.close()
} }
override suspend fun produceAllParcelData(channel: SendChannel<Pair<ParcelId, ParcelData?>>) = transactionLaunch { override fun transmitAllParcelData(channel: SendChannel<DataPair>) {
ParcelsT.selectAll().forEach { row -> ParcelsT.selectAll().forEach { row ->
val parcel = ParcelsT.getId(row) ?: return@forEach val parcel = ParcelsT.getItem(row) ?: return@forEach
val data = rowToParcelData(row) val data = rowToParcelData(row)
channel.send(parcel to data) channel.offer(parcel to data)
} }
channel.close() channel.close()
} }
override suspend fun readParcelData(parcel: ParcelId): ParcelData? = transaction { override fun readParcelData(parcel: ParcelId): ParcelData? {
val row = ParcelsT.getRow(parcel) ?: return@transaction null val row = ParcelsT.getRow(parcel) ?: return null
rowToParcelData(row) return rowToParcelData(row)
} }
override suspend fun getOwnedParcels(user: ParcelOwner): List<ParcelId> = transaction { override fun getOwnedParcels(user: PlayerProfile): List<ParcelId> {
val user_id = OwnersT.getId(user) ?: return@transaction emptyList() val user_id = ProfilesT.getId(user.toOwnerProfile()) ?: return emptyList()
ParcelsT.select { ParcelsT.owner_id eq user_id } return ParcelsT.select { ParcelsT.owner_id eq user_id }
.orderBy(ParcelsT.claim_time, isAsc = true) .orderBy(ParcelsT.claim_time, isAsc = true)
.mapNotNull(ParcelsT::getId) .mapNotNull(ParcelsT::getItem)
.toList() .toList()
} }
override suspend fun setParcelData(parcel: ParcelId, data: ParcelData?) { override fun setParcelData(parcel: ParcelId, data: ParcelData?) {
if (data == null) { if (data == null) {
transaction { transaction {
ParcelsT.getId(parcel)?.let { id -> ParcelsT.getId(parcel)?.let { id ->
@@ -117,21 +172,21 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) :
setParcelOwner(parcel, data.owner) setParcelOwner(parcel, data.owner)
for ((uuid, status) in data.addedMap) { for ((profile, status) in data.addedMap) {
setLocalPlayerStatus(parcel, uuid, status) AddedLocalT.setPlayerStatus(parcel, profile, status)
} }
setParcelAllowsInteractInputs(parcel, data.allowInteractInputs) setParcelAllowsInteractInputs(parcel, data.allowInteractInputs)
setParcelAllowsInteractInventory(parcel, data.allowInteractInventory) setParcelAllowsInteractInventory(parcel, data.allowInteractInventory)
} }
override suspend fun setParcelOwner(parcel: ParcelId, owner: ParcelOwner?) = transaction { override fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) {
val id = if (owner == null) val id = if (owner == null)
ParcelsT.getId(parcel) ?: return@transaction ParcelsT.getId(parcel) ?: return
else else
ParcelsT.getOrInitId(parcel) ParcelsT.getOrInitId(parcel)
val owner_id = owner?.let { OwnersT.getOrInitId(it) } val owner_id = owner?.let { ProfilesT.getOrInitId(it.toOwnerProfile()) }
val time = owner?.let { DateTime.now() } val time = owner?.let { DateTime.now() }
ParcelsT.update({ ParcelsT.id eq id }) { ParcelsT.update({ ParcelsT.id eq id }) {
@@ -140,11 +195,11 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) :
} }
} }
override suspend fun setLocalPlayerStatus(parcel: ParcelId, player: UUID, status: AddedStatus) = transaction { override fun setLocalPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus) {
AddedLocalT.setPlayerStatus(parcel, player, status) AddedLocalT.setPlayerStatus(parcel, player.toRealProfile(), status)
} }
override suspend fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean): Unit = transaction { override fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) {
val id = ParcelsT.getOrInitId(parcel) val id = ParcelsT.getOrInitId(parcel)
ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
it[ParcelOptionsT.parcel_id] = id it[ParcelOptionsT.parcel_id] = id
@@ -152,7 +207,7 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) :
} }
} }
override suspend fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean): Unit = transaction { override fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean) {
val id = ParcelsT.getOrInitId(parcel) val id = ParcelsT.getOrInitId(parcel)
ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
it[ParcelOptionsT.parcel_id] = id it[ParcelOptionsT.parcel_id] = id
@@ -160,36 +215,30 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) :
} }
} }
override suspend fun produceAllGlobalAddedData(channel: SendChannel<Pair<ParcelOwner, MutableMap<UUID, AddedStatus>>>) = transactionLaunch { override fun transmitAllGlobalAddedData(channel: SendChannel<AddedDataPair<PlayerProfile>>) {
AddedGlobalT.sendAllAddedData(channel) AddedGlobalT.sendAllAddedData(channel)
channel.close() channel.close()
} }
override suspend fun readGlobalAddedData(owner: ParcelOwner): MutableMap<UUID, AddedStatus> = transaction { override fun readGlobalAddedData(owner: PlayerProfile): MutableAddedDataMap {
return@transaction AddedGlobalT.readAddedData(OwnersT.getId(owner) ?: return@transaction hashMapOf()) return AddedGlobalT.readAddedData(ProfilesT.getId(owner.toOwnerProfile()) ?: return hashMapOf())
} }
override suspend fun setGlobalPlayerStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) = transaction { override fun setGlobalPlayerStatus(owner: PlayerProfile, player: PlayerProfile, status: AddedStatus) {
AddedGlobalT.setPlayerStatus(owner, player, status) AddedGlobalT.setPlayerStatus(owner, player.toRealProfile(), status)
} }
private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply { private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply {
owner = row[ParcelsT.owner_id]?.let { OwnersT.getId(it) } owner = row[ParcelsT.owner_id]?.let { ProfilesT.getItem(it) }
since = row[ParcelsT.claim_time] since = row[ParcelsT.claim_time]
val parcelId = row[ParcelsT.id] val id = row[ParcelsT.id]
addedMap = AddedLocalT.readAddedData(parcelId) ParcelOptionsT.select { ParcelOptionsT.parcel_id eq id }.firstOrNull()?.let { optrow ->
allowInteractInputs = optrow[ParcelOptionsT.interact_inputs]
AddedLocalT.select { AddedLocalT.attach_id eq parcelId }.forEach { allowInteractInventory = optrow[ParcelOptionsT.interact_inventory]
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 { addedMap = AddedLocalT.readAddedData(id)
allowInteractInputs = it[ParcelOptionsT.interact_inputs]
allowInteractInventory = it[ParcelOptionsT.interact_inventory]
}
} }
} }

View File

@@ -1,9 +1,7 @@
package io.dico.parcels2.storage.exposed package io.dico.parcels2.storage.exposed
import org.jetbrains.exposed.sql.Column import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.Index import org.jetbrains.exposed.sql.Function
import org.jetbrains.exposed.sql.Table
import org.jetbrains.exposed.sql.Transaction
import org.jetbrains.exposed.sql.statements.InsertStatement import org.jetbrains.exposed.sql.statements.InsertStatement
import org.jetbrains.exposed.sql.transactions.TransactionManager import org.jetbrains.exposed.sql.transactions.TransactionManager
@@ -61,3 +59,16 @@ fun Table.indexR(customIndexName: String? = null, isUnique: Boolean = false, var
} }
fun Table.uniqueIndexR(customIndexName: String? = null, vararg columns: Column<*>): Index = indexR(customIndexName, true, *columns) fun Table.uniqueIndexR(customIndexName: String? = null, vararg columns: Column<*>): Index = indexR(customIndexName, true, *columns)
fun <T : Int?> ExpressionWithColumnType<T>.abs(): Function<T> = Abs(this)
class Abs<T : Int?>(val expr: Expression<T>) : Function<T>(IntegerColumnType()) {
override fun toSQL(queryBuilder: QueryBuilder): String = "ABS(${expr.toSQL(queryBuilder)})"
}
fun <T : Comparable<T>> SqlExpressionBuilder.greater(col1: ExpressionWithColumnType<T>, col2: ExpressionWithColumnType<T>): Expression<T> {
return case(col1)
.When(col1.greater(col2), col1)
.Else(col2)
}

View File

@@ -3,12 +3,12 @@
package io.dico.parcels2.storage.exposed package io.dico.parcels2.storage.exposed
import io.dico.parcels2.ParcelId import io.dico.parcels2.ParcelId
import io.dico.parcels2.ParcelOwner
import io.dico.parcels2.ParcelWorldId import io.dico.parcels2.ParcelWorldId
import io.dico.parcels2.PlayerProfile
import io.dico.parcels2.util.toByteArray import io.dico.parcels2.util.toByteArray
import io.dico.parcels2.util.toUUID import io.dico.parcels2.util.toUUID
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.InsertStatement import org.jetbrains.exposed.sql.statements.UpdateBuilder
import java.util.UUID import java.util.UUID
sealed class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj>, QueryObj>(tableName: String, columnName: String) sealed class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj>, QueryObj>(tableName: String, columnName: String)
@@ -23,16 +23,16 @@ sealed class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj>,
return select { where(table) }.firstOrNull()?.let { it[id] } return select { where(table) }.firstOrNull()?.let { it[id] }
} }
internal inline fun insertAndGetId(objName: String, noinline body: TableT.(InsertStatement<Number>) -> Unit): Int { internal inline fun getOrInitId(getId: () -> Int?, noinline body: TableT.(UpdateBuilder<*>) -> Unit, objName: () -> String): Int {
return table.insert(body)[id] ?: insertError(objName) return getId() ?: table.insertIgnore(body)[id] ?: getId() ?: throw ExposedDatabaseException("This should not happen - failed to insert ${objName()} and get its id")
} }
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 getId(obj: QueryObj): Int?
abstract fun getOrInitId(obj: QueryObj): Int abstract fun getOrInitId(obj: QueryObj): Int
fun getId(id: Int): QueryObj? = select { this@IdTransactionsTable.id eq id }.firstOrNull()?.let { getId(it) } fun getItem(id: Int): QueryObj? = select { this@IdTransactionsTable.id eq id }.firstOrNull()?.let { getItem(it) }
abstract fun getId(row: ResultRow): QueryObj? abstract fun getItem(row: ResultRow): QueryObj?
fun getId(obj: QueryObj, init: Boolean): Int? = if (init) getOrInitId(obj) else getId(obj)
} }
object WorldsT : IdTransactionsTable<WorldsT, ParcelWorldId>("parcel_worlds", "world_id") { object WorldsT : IdTransactionsTable<WorldsT, ParcelWorldId>("parcel_worlds", "world_id") {
@@ -44,14 +44,16 @@ object WorldsT : IdTransactionsTable<WorldsT, ParcelWorldId>("parcel_worlds", "w
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, 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 getId(worldName: String, uid: UUID?): Int? = getId(worldName, uid?.toByteArray())
internal inline fun getOrInitId(worldName: String, worldUid: UUID?): Int = worldUid?.toByteArray().let { binaryUid -> internal inline fun getOrInitId(worldName: String, worldUid: UUID?): Int = worldUid?.toByteArray().let { binaryUid ->
getId(worldName, binaryUid) return getOrInitId(
?: insertAndGetId("world named $worldName") { it[name] = worldName; binaryUid?.let { buid -> it[uid] = buid } } { getId(worldName, binaryUid) },
{ it[name] = worldName; it[uid] = binaryUid },
{ "world named $worldName" })
} }
override fun getId(world: ParcelWorldId): Int? = getId(world.name, world.uid) override fun getId(world: ParcelWorldId): Int? = getId(world.name, world.uid)
override fun getOrInitId(world: ParcelWorldId): Int = getOrInitId(world.name, world.uid) override fun getOrInitId(world: ParcelWorldId): Int = getOrInitId(world.name, world.uid)
override fun getId(row: ResultRow): ParcelWorldId { override fun getItem(row: ResultRow): ParcelWorldId {
return ParcelWorldId(row[name], row[uid]?.toUUID()) return ParcelWorldId(row[name], row[uid]?.toUUID())
} }
} }
@@ -60,7 +62,7 @@ object ParcelsT : IdTransactionsTable<ParcelsT, ParcelId>("parcels", "parcel_id"
val world_id = integer("world_id").references(WorldsT.id) val world_id = integer("world_id").references(WorldsT.id)
val px = integer("px") val px = integer("px")
val pz = integer("pz") val pz = integer("pz")
val owner_id = integer("owner_id").references(OwnersT.id).nullable() val owner_id = integer("owner_id").references(ProfilesT.id).nullable()
val claim_time = datetime("claim_time").nullable() val claim_time = datetime("claim_time").nullable()
val index_location = uniqueIndexR("index_location", world_id, px, pz) val index_location = uniqueIndexR("index_location", world_id, px, pz)
@@ -68,8 +70,10 @@ object ParcelsT : IdTransactionsTable<ParcelsT, ParcelId>("parcels", "parcel_id"
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 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 { private inline fun getOrInitId(worldName: String, worldUid: UUID?, parcelX: Int, parcelZ: Int): Int {
val worldId = WorldsT.getOrInitId(worldName, worldUid) val worldId = WorldsT.getOrInitId(worldName, worldUid)
return getId(worldId, parcelX, parcelZ) return getOrInitId(
?: insertAndGetId("parcel at $worldName($parcelX, $parcelZ)") { it[world_id] = worldId; it[px] = parcelX; it[pz] = parcelZ } { getId(worldId, parcelX, parcelZ) },
{ it[world_id] = worldId; it[px] = parcelX; it[pz] = parcelZ },
{ "parcel at $worldName($parcelX, $parcelZ)" })
} }
override fun getId(parcel: ParcelId): Int? = getId(parcel.worldId.name, parcel.worldId.uid, parcel.x, parcel.z) override fun getId(parcel: ParcelId): Int? = getId(parcel.worldId.name, parcel.worldId.uid, parcel.x, parcel.z)
@@ -78,41 +82,56 @@ object ParcelsT : IdTransactionsTable<ParcelsT, ParcelId>("parcels", "parcel_id"
private inline fun getRow(id: Int): ResultRow? = select { ParcelsT.id eq id }.firstOrNull() private inline fun getRow(id: Int): ResultRow? = select { ParcelsT.id eq id }.firstOrNull()
fun getRow(parcel: ParcelId): ResultRow? = getId(parcel)?.let { getRow(it) } fun getRow(parcel: ParcelId): ResultRow? = getId(parcel)?.let { getRow(it) }
override fun getId(row: ResultRow): ParcelId? { override fun getItem(row: ResultRow): ParcelId? {
val worldId = row[world_id] val worldId = row[world_id]
val world = WorldsT.getId(worldId) ?: return null val world = WorldsT.getItem(worldId) ?: return null
return ParcelId(world, row[px], row[pz]) return ParcelId(world, row[px], row[pz])
} }
} }
object OwnersT : IdTransactionsTable<OwnersT, ParcelOwner>("parcel_owners", "owner_id") { object ProfilesT : IdTransactionsTable<ProfilesT, PlayerProfile>("parcel_profiles", "owner_id") {
val uuid = binary("uuid", 16).nullable() val uuid = binary("uuid", 16).nullable()
val name = varchar("name", 32) val name = varchar("name", 32)
val index_pair = uniqueIndexR("index_pair", uuid, name) val index_pair = uniqueIndexR("index_pair", uuid, name)
private inline fun getId(binaryUuid: ByteArray) = getId { uuid eq binaryUuid } private inline fun getId(binaryUuid: ByteArray) = getId { uuid eq binaryUuid }
private inline fun getId(uuid: UUID) = getId(uuid.toByteArray()) 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 getId(nameIn: String) = getId { uuid.isNull() and (name.lowerCase() eq nameIn.toLowerCase()) }
private inline fun getRealId(nameIn: String) = getId { uuid.isNotNull() and (name.lowerCase() eq nameIn.toLowerCase()) }
private inline fun getOrInitId(uuid: UUID, name: String) = uuid.toByteArray().let { binaryUuid -> private inline fun getOrInitId(uuid: UUID, name: String) = uuid.toByteArray().let { binaryUuid -> getOrInitId(
getId(binaryUuid) ?: insertAndGetId("owner(uuid = $uuid)") { { getId(binaryUuid) },
it[this@OwnersT.uuid] = binaryUuid { it[this@ProfilesT.uuid] = binaryUuid; it[this@ProfilesT.name] = name },
it[this@OwnersT.name] = name { "profile(uuid = $uuid, name = $name)" })
}
} }
private inline fun getOrInitId(name: String) = private inline fun getOrInitId(name: String) = getOrInitId(
getId(name) ?: insertAndGetId("owner(name = $name)") { it[OwnersT.name] = name } { getId(name) },
{ it[ProfilesT.name] = name },
{ "owner(name = $name)" })
override fun getId(owner: ParcelOwner): Int? =
if (owner.hasUUID) getId(owner.uuid!!)
else getId(owner.name!!)
override fun getOrInitId(owner: ParcelOwner): Int = override fun getId(profile: PlayerProfile): Int? = when (profile) {
if (owner.hasUUID) getOrInitId(owner.uuid!!, owner.notNullName) is PlayerProfile.Real -> getId(profile.uuid)
else getOrInitId(owner.name!!) is PlayerProfile.Fake -> getId(profile.name)
is PlayerProfile.Unresolved -> getRealId(profile.name)
override fun getId(row: ResultRow): ParcelOwner { else -> throw IllegalArgumentException()
return row[uuid]?.toUUID()?.let { ParcelOwner(it) } ?: ParcelOwner(row[name])
} }
override fun getOrInitId(profile: PlayerProfile): Int = when (profile) {
is PlayerProfile.Real -> getOrInitId(profile.uuid, profile.notNullName)
is PlayerProfile.Fake -> getOrInitId(profile.name)
else -> throw IllegalArgumentException()
}
override fun getItem(row: ResultRow): PlayerProfile {
return PlayerProfile(row[uuid]?.toUUID(), row[name])
}
fun getRealItem(id: Int): PlayerProfile.Real? {
return getItem(id) as? PlayerProfile.Real
}
} }
// val ParcelsWithOptionsT = ParcelsT.join(ParcelOptionsT, JoinType.INNER, onColumn = ParcelsT.id, otherColumn = ParcelOptionsT.parcel_id)

View File

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

View File

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

View File

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

View File

@@ -1,37 +1,35 @@
@file:Suppress("RedundantSuspendModifier", "DEPRECATION")
package io.dico.parcels2.storage.migration.plotme package io.dico.parcels2.storage.migration.plotme
import com.zaxxer.hikari.HikariDataSource import com.zaxxer.hikari.HikariDataSource
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.options.PlotmeMigrationOptions
import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.Storage
import io.dico.parcels2.storage.exposed.abs
import io.dico.parcels2.storage.exposed.greater
import io.dico.parcels2.storage.migration.Migration 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.toUUID
import io.dico.parcels2.util.uuid import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.asCoroutineDispatcher
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.launch
import org.bukkit.Bukkit import kotlinx.coroutines.experimental.newFixedThreadPoolContext
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.ByteArrayOutputStream
import java.sql.Blob import java.sql.Blob
import java.util.UUID import java.util.UUID
import java.util.concurrent.Executors
import javax.sql.DataSource import javax.sql.DataSource
class PlotmeMigration(val parcelProvider: ParcelProvider, class PlotmeMigration(val options: PlotmeMigrationOptions) : Migration {
val worldMapper: Map<String, String>,
val dataSourceFactory: () -> DataSource) : Migration {
private var dataSource: DataSource? = null private var dataSource: DataSource? = null
private var database: Database? = null private var database: Database? = null
private var isShutdown: Boolean = false private var isShutdown: Boolean = false
private val dispatcher = Executors.newSingleThreadExecutor { Thread(it, "PlotMe Migration Thread") }.asCoroutineDispatcher()
private val mlogger = LoggerFactory.getLogger("PlotMe Migrator") private val mlogger = LoggerFactory.getLogger("PlotMe Migrator")
val dispatcher = newFixedThreadPoolContext(1, "PlotMe Migration Thread")
private fun <T> transaction(statement: Transaction.() -> T) = org.jetbrains.exposed.sql.transactions.transaction(database!!, statement) private fun <T> transaction(statement: Transaction.() -> T) = org.jetbrains.exposed.sql.transactions.transaction(database!!, statement)
override fun migrateTo(storage: Storage) { override fun migrateTo(storage: Storage): Job {
launch(context = dispatcher) { return launch(dispatcher) {
init() init()
doWork(storage) doWork(storage)
shutdown() shutdown()
@@ -39,8 +37,8 @@ class PlotmeMigration(val parcelProvider: ParcelProvider,
} }
fun init() { fun init() {
if (isShutdown) throw IllegalStateException() if (isShutdown || database != null) throw IllegalStateException()
dataSource = dataSourceFactory() dataSource = options.storage.getDataSourceFactory()!!()
database = Database.connect(dataSource!!) database = Database.connect(dataSource!!)
} }
@@ -53,66 +51,61 @@ class PlotmeMigration(val parcelProvider: ParcelProvider,
isShutdown = true isShutdown = true
} }
val parcelsCache = hashMapOf<String, MutableMap<Vec2i, ParcelData>>() suspend fun doWork(target: Storage) {
val exit = transaction {
private fun getMap(worldName: String): MutableMap<Vec2i, ParcelData>? { (!PlotmePlotsT.exists()).also {
val mapped = worldMapper[worldName] ?: return null if (it) mlogger.warn("Plotme tables don't appear to exist. Exiting.")
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() if (exit) return
iterPlotmeTable(PlotmePlotsT) { data, row -> val worldCache = options.worldsFromTo.mapValues { ParcelWorldId(it.value) }
// 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]) fun getParcelId(table: PlotmeTable, row: ResultRow): ParcelId? {
val world = worldCache[row[table.world_name]] ?: return null
return ParcelId(world, row[table.px], row[table.pz])
} }
iterPlotmeTable(PlotmeAllowedT) { data, row -> fun PlotmePlotPlayerMap.transmitPlotmeAddedTable(kind: AddedStatus) {
val uuid = row[player_uuid]?.toUUID() selectAll().forEach { row ->
?: Bukkit.getOfflinePlayer(row[player_name]).takeIf { it.isValid }?.uuid val parcel = getParcelId(this, row) ?: return@forEach
?: return@iterPlotmeTable val profile = StatusKey.safe(row[player_uuid]?.toUUID(), row[player_name]) ?: return@forEach
target.setParcelPlayerStatus(parcel, profile, kind)
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)
} }
} }
} mlogger.info("Transmitting data from plotmeplots table")
transaction {
private fun Blob.toUUID(): UUID { PlotmePlotsT.selectAll()
val out = ByteArrayOutputStream(16) .orderBy(PlotmePlotsT.world_name)
binaryStream.copyTo(out, bufferSize = 16) .orderBy(with(SqlExpressionBuilder) { greater(PlotmePlotsT.px.abs(), PlotmePlotsT.pz.abs()) })
return out.toByteArray().toUUID() .forEach { row ->
} val parcel = getParcelId(PlotmePlotsT, row) ?: return@forEach
val owner = PlayerProfile.safe(row[PlotmePlotsT.owner_uuid]?.toUUID(), row[PlotmePlotsT.owner_name])
private inline fun <T : PlotmeTable> iterPlotmeTable(table: T, block: T.(ParcelData, ResultRow) -> Unit) { target.setParcelOwner(parcel, owner)
table.selectAll().forEach { row -> }
val data = getData(row[table.world_name], Vec2i(row[table.px], row[table.pz])) ?: return@forEach
table.block(data, row)
} }
mlogger.info("Transmitting data from plotmeallowed table")
transaction {
PlotmeAllowedT.transmitPlotmeAddedTable(AddedStatus.ALLOWED)
}
mlogger.info("Transmitting data from plotmedenied table")
transaction {
PlotmeDeniedT.transmitPlotmeAddedTable(AddedStatus.BANNED)
}
mlogger.warn("Data has been **transmitted**.")
mlogger.warn("Loading parcel data might take a while as enqueued transactions from this migration are completed.")
} }
private fun Blob.toUUID(): UUID? {
val ba = ByteArray(16)
val count = binaryStream.read(ba, 0, 16)
if (count < 16) return null
return ba.toUUID()
}
} }

View File

@@ -7,9 +7,9 @@ const val uppercase: Boolean = false
fun String.toCorrectCase() = if (uppercase) this else toLowerCase() fun String.toCorrectCase() = if (uppercase) this else toLowerCase()
sealed class PlotmeTable(name: String) : Table(name) { sealed class PlotmeTable(name: String) : Table(name) {
val px = PlotmePlotsT.integer("idX").primaryKey() val px = integer("idX").primaryKey()
val pz = PlotmePlotsT.integer("idZ").primaryKey() val pz = integer("idZ").primaryKey()
val world_name = PlotmePlotsT.varchar("world", 32).primaryKey() val world_name = varchar("world", 32).primaryKey()
} }
object PlotmePlotsT : PlotmeTable("plotmePlots".toCorrectCase()) { object PlotmePlotsT : PlotmeTable("plotmePlots".toCorrectCase()) {
@@ -18,8 +18,8 @@ object PlotmePlotsT : PlotmeTable("plotmePlots".toCorrectCase()) {
} }
sealed class PlotmePlotPlayerMap(name: String) : PlotmeTable(name) { sealed class PlotmePlotPlayerMap(name: String) : PlotmeTable(name) {
val player_name = PlotmePlotsT.varchar("player", 32) val player_name = varchar("player", 32)
val player_uuid = PlotmePlotsT.blob("playerid").nullable() val player_uuid = blob("playerid").nullable()
} }
object PlotmeAllowedT : PlotmePlotPlayerMap("plotmeAllowed".toCorrectCase()) object PlotmeAllowedT : PlotmePlotPlayerMap("plotmeAllowed".toCorrectCase())

View File

@@ -4,14 +4,20 @@ import io.dico.parcels2.logger
import java.io.File import java.io.File
fun File.tryCreate(): Boolean { fun File.tryCreate(): Boolean {
if (exists()) {
return !isDirectory
}
val parent = parentFile val parent = parentFile
if (parent == null || !(parent.exists() || parent.mkdirs()) || !createNewFile()) { if (parent == null || !(parent.exists() || parent.mkdirs()) || !createNewFile()) {
logger.warn("Failed to create file ${canonicalPath}") logger.warn("Failed to create file $canonicalPath")
return false return false
} }
return true return true
} }
inline fun Boolean.alsoIfTrue(block: () -> Unit): Boolean = also { if (it) block() }
inline fun Boolean.alsoIfFalse(block: () -> Unit): Boolean = also { if (!it) block() }
inline fun <R> Any.synchronized(block: () -> R): R = synchronized(this, block) inline fun <R> Any.synchronized(block: () -> R): R = synchronized(this, block)
inline fun <T> T?.isNullOr(condition: T.() -> Boolean): Boolean = this == null || condition() inline fun <T> T?.isNullOr(condition: T.() -> Boolean): Boolean = this == null || condition()

View File

@@ -2,12 +2,14 @@ package io.dico.parcels2.util
import io.dico.dicore.Formatting import io.dico.dicore.Formatting
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.PlayerProfile
import io.dico.parcels2.logger import io.dico.parcels2.logger
import org.bukkit.OfflinePlayer import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.bukkit.plugin.java.JavaPlugin import org.bukkit.plugin.java.JavaPlugin
inline val OfflinePlayer.uuid get() = uniqueId inline val OfflinePlayer.uuid get() = uniqueId
@Suppress("UsePropertyAccessSyntax") @Suppress("UsePropertyAccessSyntax")
inline val OfflinePlayer.isValid inline val OfflinePlayer.isValid
get() = isOnline() || hasPlayedBefore() get() = isOnline() || hasPlayedBefore()

View File

@@ -1,11 +1,17 @@
<configuration debug="true"> <configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder> <encoder>
<pattern>%d{HH:mm:ss.SSS} %magenta(%-8.-8(%thread)) %highlight(%-5level) %boldCyan(%32.-32logger{32}) - %msg</pattern> <!-- old pattern <pattern>%d{HH:mm:ss.SSS} %magenta(%-8.-8(%thread)) %highlight(%-5level) %boldCyan(%8.-32logger{32}) - %msg\n</pattern>-->
<pattern>%magenta(%-16.-16(%thread)) %highlight(%-5level) %boldCyan(%6.-32logger{32}) - %msg</pattern>
</encoder> </encoder>
</appender> </appender>
<root level="info"> <root level="debug">
<appender-ref ref="STDOUT" /> <appender-ref ref="STDOUT" />
</root> </root>
<logger name="com.zaxxer.hikari.pool.HikariPool" level="info"/>
<logger name="com.zaxxer.hikari.pool.PoolBase" level="info"/>
<logger name="com.zaxxer.hikari.HikariConfig" level="info"/>
<logger name="Exposed" level="info"/>
</configuration> </configuration>