Merge branch 'dev'
This commit is contained in:
@@ -72,7 +72,8 @@ dependencies {
|
||||
compile("org.jetbrains.exposed:exposed:0.10.3") { isTransitive = false }
|
||||
compile("joda-time:joda-time:2.10")
|
||||
compile("com.zaxxer:HikariCP:3.2.0")
|
||||
compile("ch.qos.logback:logback-classic:1.2.3")
|
||||
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"
|
||||
compile("com.fasterxml.jackson.core:jackson-core:$jacksonVersion")
|
||||
|
||||
@@ -1,41 +1,50 @@
|
||||
package io.dico.parcels2
|
||||
|
||||
import io.dico.parcels2.util.uuid
|
||||
import org.bukkit.OfflinePlayer
|
||||
import java.util.UUID
|
||||
|
||||
typealias MutableAddedDataMap = MutableMap<UUID, AddedStatus>
|
||||
typealias AddedDataMap = Map<UUID, AddedStatus>
|
||||
typealias StatusKey = PlayerProfile.Real
|
||||
typealias MutableAddedDataMap = MutableMap<StatusKey, AddedStatus>
|
||||
typealias AddedDataMap = Map<StatusKey, AddedStatus>
|
||||
|
||||
@Suppress("FunctionName")
|
||||
fun MutableAddedDataMap(): MutableAddedDataMap = hashMapOf()
|
||||
|
||||
interface AddedData {
|
||||
val addedMap: AddedDataMap
|
||||
var addedStatusOfStar: AddedStatus
|
||||
|
||||
fun getAddedStatus(uuid: UUID): AddedStatus
|
||||
fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean
|
||||
fun getAddedStatus(key: StatusKey): AddedStatus
|
||||
fun setAddedStatus(key: StatusKey, status: AddedStatus): Boolean
|
||||
|
||||
fun compareAndSetAddedStatus(uuid: UUID, expect: AddedStatus, status: AddedStatus): Boolean =
|
||||
(getAddedStatus(uuid) == expect).also { if (it) setAddedStatus(uuid, status) }
|
||||
fun compareAndSetAddedStatus(key: StatusKey, expect: AddedStatus, status: AddedStatus): Boolean =
|
||||
(getAddedStatus(key) == expect).also { if (it) setAddedStatus(key, status) }
|
||||
|
||||
fun isAllowed(uuid: UUID) = getAddedStatus(uuid) == AddedStatus.ALLOWED
|
||||
fun allow(uuid: UUID) = setAddedStatus(uuid, AddedStatus.ALLOWED)
|
||||
fun disallow(uuid: UUID) = compareAndSetAddedStatus(uuid, AddedStatus.ALLOWED, AddedStatus.DEFAULT)
|
||||
fun isBanned(uuid: UUID) = getAddedStatus(uuid) == AddedStatus.BANNED
|
||||
fun ban(uuid: UUID) = setAddedStatus(uuid, AddedStatus.BANNED)
|
||||
fun unban(uuid: UUID) = compareAndSetAddedStatus(uuid, AddedStatus.BANNED, AddedStatus.DEFAULT)
|
||||
fun isAllowed(key: StatusKey) = getAddedStatus(key) == AddedStatus.ALLOWED
|
||||
fun allow(key: StatusKey) = setAddedStatus(key, AddedStatus.ALLOWED)
|
||||
fun disallow(key: StatusKey) = compareAndSetAddedStatus(key, AddedStatus.ALLOWED, AddedStatus.DEFAULT)
|
||||
fun isBanned(key: StatusKey) = getAddedStatus(key) == AddedStatus.BANNED
|
||||
fun ban(key: StatusKey) = setAddedStatus(key, AddedStatus.BANNED)
|
||||
fun unban(key: StatusKey) = compareAndSetAddedStatus(key, AddedStatus.BANNED, AddedStatus.DEFAULT)
|
||||
|
||||
fun isAllowed(player: OfflinePlayer) = isAllowed(player.uuid)
|
||||
fun allow(player: OfflinePlayer) = allow(player.uuid)
|
||||
fun disallow(player: OfflinePlayer) = disallow(player.uuid)
|
||||
fun isBanned(player: OfflinePlayer) = isBanned(player.uuid)
|
||||
fun ban(player: OfflinePlayer) = ban(player.uuid)
|
||||
fun unban(player: OfflinePlayer) = unban(player.uuid)
|
||||
fun isAllowed(player: OfflinePlayer) = isAllowed(player.statusKey)
|
||||
fun allow(player: OfflinePlayer) = allow(player.statusKey)
|
||||
fun disallow(player: OfflinePlayer) = disallow(player.statusKey)
|
||||
fun isBanned(player: OfflinePlayer) = isBanned(player.statusKey)
|
||||
fun ban(player: OfflinePlayer) = ban(player.statusKey)
|
||||
fun unban(player: OfflinePlayer) = unban(player.statusKey)
|
||||
}
|
||||
|
||||
inline val OfflinePlayer.statusKey get() = PlayerProfile.nameless(this)
|
||||
|
||||
open class AddedDataHolder(override var addedMap: MutableAddedDataMap = mutableMapOf()) : AddedData {
|
||||
override fun getAddedStatus(uuid: UUID): AddedStatus = addedMap.getOrDefault(uuid, AddedStatus.DEFAULT)
|
||||
override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean = status.takeIf { it != AddedStatus.DEFAULT }
|
||||
?.let { addedMap.put(uuid, it) != it }
|
||||
?: addedMap.remove(uuid) != null
|
||||
override var addedStatusOfStar: AddedStatus = AddedStatus.DEFAULT
|
||||
|
||||
override fun getAddedStatus(key: StatusKey): AddedStatus = addedMap.getOrDefault(key, addedStatusOfStar)
|
||||
|
||||
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 {
|
||||
@@ -43,15 +52,15 @@ enum class AddedStatus {
|
||||
ALLOWED,
|
||||
BANNED;
|
||||
|
||||
val isDefault get() = this == DEFAULT
|
||||
val isAllowed get() = this == ALLOWED
|
||||
val isBanned get() = this == BANNED
|
||||
inline val isDefault get() = this == DEFAULT
|
||||
inline val isAllowed get() = this == ALLOWED
|
||||
inline val isBanned get() = this == BANNED
|
||||
}
|
||||
|
||||
interface GlobalAddedData : AddedData {
|
||||
val owner: ParcelOwner
|
||||
val owner: PlayerProfile
|
||||
}
|
||||
|
||||
interface GlobalAddedDataManager {
|
||||
operator fun get(owner: ParcelOwner): GlobalAddedData
|
||||
operator fun get(owner: PlayerProfile): GlobalAddedData
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
|
||||
}
|
||||
@@ -33,7 +33,7 @@ interface Parcel : ParcelData {
|
||||
}
|
||||
|
||||
interface ParcelData : AddedData {
|
||||
var owner: ParcelOwner?
|
||||
var owner: PlayerProfile?
|
||||
val since: DateTime?
|
||||
|
||||
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 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) }
|
||||
|| (checkAdmin && player is Player && player.hasBuildAnywhere)
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package io.dico.parcels2
|
||||
import io.dico.parcels2.blockvisitor.RegionTraversal
|
||||
import io.dico.parcels2.blockvisitor.Worker
|
||||
import io.dico.parcels2.blockvisitor.WorktimeLimiter
|
||||
import io.dico.parcels2.defaultimpl.DefaultParcelGenerator
|
||||
import io.dico.parcels2.util.Vec2i
|
||||
import org.bukkit.Chunk
|
||||
import org.bukkit.Location
|
||||
@@ -13,29 +12,7 @@ import org.bukkit.block.Block
|
||||
import org.bukkit.entity.Entity
|
||||
import org.bukkit.generator.BlockPopulator
|
||||
import org.bukkit.generator.ChunkGenerator
|
||||
import java.util.HashMap
|
||||
import java.util.Random
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
object GeneratorFactories {
|
||||
private val map: MutableMap<String, GeneratorFactory> = HashMap()
|
||||
|
||||
fun registerFactory(generator: GeneratorFactory): Boolean = map.putIfAbsent(generator.name, generator) == null
|
||||
|
||||
fun getFactory(name: String): GeneratorFactory? = map.get(name)
|
||||
|
||||
init {
|
||||
registerFactory(DefaultParcelGenerator.Factory)
|
||||
}
|
||||
}
|
||||
|
||||
interface GeneratorFactory {
|
||||
val name: String
|
||||
|
||||
val optionsClass: KClass<out GeneratorOptions>
|
||||
|
||||
fun newParcelGenerator(worldName: String, options: GeneratorOptions): ParcelGenerator
|
||||
}
|
||||
|
||||
abstract class ParcelGenerator : ChunkGenerator() {
|
||||
abstract val world: World
|
||||
@@ -59,6 +36,7 @@ abstract class ParcelGenerator : ChunkGenerator() {
|
||||
abstract fun makeParcelLocator(container: ParcelContainer): ParcelLocator
|
||||
}
|
||||
|
||||
@Suppress("DeprecatedCallableAddReplaceWith")
|
||||
interface ParcelBlockManager {
|
||||
val world: World
|
||||
val worktimeLimiter: WorktimeLimiter
|
||||
@@ -67,7 +45,7 @@ interface ParcelBlockManager {
|
||||
|
||||
fun getHomeLocation(parcel: ParcelId): Location
|
||||
|
||||
fun setOwnerBlock(parcel: ParcelId, owner: ParcelOwner?)
|
||||
fun setOwnerBlock(parcel: ParcelId, owner: PlayerProfile?)
|
||||
|
||||
@Deprecated("")
|
||||
fun getEntities(parcel: ParcelId): Collection<Entity> = TODO()
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.dico.parcels2
|
||||
|
||||
import io.dico.parcels2.options.RuntimeWorldOptions
|
||||
import io.dico.parcels2.storage.Storage
|
||||
import io.dico.parcels2.util.Vec2i
|
||||
import io.dico.parcels2.util.floor
|
||||
@@ -76,7 +77,7 @@ interface ParcelWorld : ParcelLocator, ParcelContainer, ParcelBlockManager {
|
||||
val id: ParcelWorldId
|
||||
val name: String
|
||||
val uid: UUID?
|
||||
val options: WorldOptions
|
||||
val options: RuntimeWorldOptions
|
||||
val generator: ParcelGenerator
|
||||
val storage: Storage
|
||||
val container: ParcelContainer
|
||||
|
||||
@@ -10,8 +10,9 @@ import io.dico.parcels2.defaultimpl.GlobalAddedDataManagerImpl
|
||||
import io.dico.parcels2.defaultimpl.ParcelProviderImpl
|
||||
import io.dico.parcels2.listener.ParcelEntityTracker
|
||||
import io.dico.parcels2.listener.ParcelListeners
|
||||
import io.dico.parcels2.options.Options
|
||||
import io.dico.parcels2.options.optionsMapper
|
||||
import io.dico.parcels2.storage.Storage
|
||||
import io.dico.parcels2.storage.yamlObjectMapper
|
||||
import io.dico.parcels2.util.FunctionHelper
|
||||
import io.dico.parcels2.util.tryCreate
|
||||
import org.bukkit.Bukkit
|
||||
@@ -41,6 +42,7 @@ class ParcelsPlugin : JavaPlugin() {
|
||||
|
||||
override fun onEnable() {
|
||||
plogger.info("Debug enabled: ${plogger.isDebugEnabled}")
|
||||
plogger.debug(System.getProperty("user.dir"))
|
||||
if (!init()) {
|
||||
Bukkit.getPluginManager().disablePlugin(this)
|
||||
}
|
||||
@@ -60,7 +62,7 @@ class ParcelsPlugin : JavaPlugin() {
|
||||
if (!loadOptions()) return false
|
||||
|
||||
try {
|
||||
storage = options.storage.newStorageInstance()
|
||||
storage = options.storage.newInstance()
|
||||
storage.init()
|
||||
} catch (ex: Exception) {
|
||||
plogger.error("Failed to connect to database", ex)
|
||||
@@ -83,26 +85,33 @@ class ParcelsPlugin : JavaPlugin() {
|
||||
|
||||
fun loadOptions(): Boolean {
|
||||
when {
|
||||
optionsFile.exists() -> yamlObjectMapper.readerForUpdating(options).readValue<Options>(optionsFile)
|
||||
optionsFile.tryCreate() -> {
|
||||
optionsFile.exists() -> optionsMapper.readerForUpdating(options).readValue<Options>(optionsFile)
|
||||
else -> run {
|
||||
options.addWorld("parcels")
|
||||
try {
|
||||
yamlObjectMapper.writeValue(optionsFile, options)
|
||||
} catch (ex: Throwable) {
|
||||
optionsFile.delete()
|
||||
throw ex
|
||||
if (saveOptions()) {
|
||||
plogger.warn("Created options file with a world template. Please review it before next start.")
|
||||
} else {
|
||||
plogger.error("Failed to save options file ${optionsFile.canonicalPath}")
|
||||
}
|
||||
plogger.warn("Created options file with a world template. Please review it before next start.")
|
||||
return false
|
||||
}
|
||||
else -> {
|
||||
plogger.error("Failed to save options file ${optionsFile.canonicalPath}")
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
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? {
|
||||
return parcelProvider.getWorldGenerator(worldName)
|
||||
}
|
||||
@@ -118,6 +127,8 @@ class ParcelsPlugin : JavaPlugin() {
|
||||
listeners = ParcelListeners(parcelProvider, entityTracker)
|
||||
registrator.registerListeners(listeners!!)
|
||||
}
|
||||
|
||||
functionHelper.scheduleRepeating(100, 5, entityTracker::tick)
|
||||
}
|
||||
|
||||
}
|
||||
287
src/main/kotlin/io/dico/parcels2/PlayerProfile.kt
Normal file
287
src/main/kotlin/io/dico/parcels2/PlayerProfile.kt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -3,7 +3,7 @@ package io.dico.parcels2.command
|
||||
import io.dico.dicore.command.CommandException
|
||||
import io.dico.dicore.command.ExecutionContext
|
||||
import io.dico.dicore.command.ICommandReceiver
|
||||
import io.dico.parcels2.ParcelOwner
|
||||
import io.dico.parcels2.PlayerProfile
|
||||
import io.dico.parcels2.ParcelWorld
|
||||
import io.dico.parcels2.ParcelsPlugin
|
||||
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) {
|
||||
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
|
||||
|
||||
val limit = player.parcelLimit
|
||||
|
||||
@@ -5,7 +5,7 @@ import io.dico.dicore.command.annotation.Cmd
|
||||
import io.dico.dicore.command.annotation.Desc
|
||||
import io.dico.parcels2.GlobalAddedData
|
||||
import io.dico.parcels2.GlobalAddedDataManager
|
||||
import io.dico.parcels2.ParcelOwner
|
||||
import io.dico.parcels2.PlayerProfile
|
||||
import io.dico.parcels2.ParcelsPlugin
|
||||
import org.bukkit.OfflinePlayer
|
||||
import org.bukkit.entity.Player
|
||||
@@ -13,7 +13,7 @@ import org.bukkit.entity.Player
|
||||
class CommandsAddedStatusGlobal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
|
||||
private inline val data get() = plugin.globalAddedData
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
private inline operator fun GlobalAddedDataManager.get(player: OfflinePlayer): GlobalAddedData = data[ParcelOwner(player)]
|
||||
private inline operator fun GlobalAddedDataManager.get(player: OfflinePlayer): GlobalAddedData = data[PlayerProfile(player)]
|
||||
|
||||
@Cmd("allow", aliases = ["add", "permit"])
|
||||
@Desc("Globally allows a player to build on all",
|
||||
|
||||
@@ -33,15 +33,18 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
|
||||
fun ParcelScope.cmdMakeMess(context: ExecutionContext) {
|
||||
val server = plugin.server
|
||||
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.STONE_SLAB),
|
||||
server.createBlockData(Material.QUARTZ_BLOCK)
|
||||
server.createBlockData(Material.STONE),
|
||||
server.createBlockData(Material.QUARTZ_BLOCK),
|
||||
server.createBlockData(Material.BROWN_CONCRETE)
|
||||
)
|
||||
val random = Random()
|
||||
|
||||
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 ->
|
||||
context.sendMessage(EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed"
|
||||
.format(progress * 100, elapsedTime / 1000.0))
|
||||
|
||||
@@ -6,7 +6,7 @@ import io.dico.dicore.command.annotation.Cmd
|
||||
import io.dico.dicore.command.annotation.Desc
|
||||
import io.dico.dicore.command.annotation.Flag
|
||||
import io.dico.dicore.command.annotation.RequireParameters
|
||||
import io.dico.parcels2.ParcelOwner
|
||||
import io.dico.parcels2.PlayerProfile
|
||||
import io.dico.parcels2.ParcelsPlugin
|
||||
import io.dico.parcels2.util.hasAdminManage
|
||||
import io.dico.parcels2.util.hasParcelHomeOthers
|
||||
@@ -25,7 +25,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
|
||||
|
||||
val parcel = world.nextEmptyParcel()
|
||||
?: 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))
|
||||
return "Enjoy your new parcel!"
|
||||
}
|
||||
@@ -72,7 +72,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
|
||||
}
|
||||
|
||||
checkParcelLimit(player, world)
|
||||
parcel.owner = ParcelOwner(player)
|
||||
parcel.owner = PlayerProfile(player)
|
||||
return "Enjoy your new parcel!"
|
||||
}
|
||||
|
||||
|
||||
@@ -7,14 +7,14 @@ import io.dico.dicore.command.parameter.type.ParameterType
|
||||
import io.dico.parcels2.*
|
||||
import io.dico.parcels2.util.Vec2i
|
||||
import io.dico.parcels2.util.floor
|
||||
import io.dico.parcels2.util.isValid
|
||||
import kotlinx.coroutines.experimental.Deferred
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.command.CommandSender
|
||||
import org.bukkit.entity.Player
|
||||
|
||||
sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
|
||||
|
||||
abstract suspend fun ParcelsPlugin.getParcelSuspend(): Parcel?
|
||||
|
||||
fun ParcelsPlugin.getParcelDeferred(): Deferred<Parcel?> = functionHelper.deferUndispatchedOnMainThread { getParcelSuspend() }
|
||||
|
||||
class ByID(world: ParcelWorld, val id: Vec2i?, isDefault: Boolean) : ParcelTarget(world, isDefault) {
|
||||
@@ -23,16 +23,35 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
|
||||
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 {
|
||||
if (index < 0) throw IllegalArgumentException("Invalid parcel home index: $index")
|
||||
}
|
||||
|
||||
var owner = owner; private set
|
||||
|
||||
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 ownedParcels = ownedParcelsSerialized
|
||||
.map { parcelProvider.getParcelById(it) }
|
||||
.filter { it != null && world == it.world && owner == it.owner }
|
||||
|
||||
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")
|
||||
val (owner, index) = getHomeIndex(parameter, sender, input)
|
||||
return ByOwner(world, owner, index, false)
|
||||
val (owner, index) = getHomeIndex(parameter, kind, sender, input)
|
||||
return ByOwner(world, owner, index, false, onResolveFailure = { invalidInput(parameter, "The player $input does not exist") })
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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 ownerString: String
|
||||
val indexString: String
|
||||
@@ -109,9 +128,9 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
|
||||
}
|
||||
|
||||
val owner = if (ownerString.isEmpty())
|
||||
ParcelOwner(requirePlayer(sender, parameter, "the player"))
|
||||
PlayerProfile(requirePlayer(sender, parameter, "the player"))
|
||||
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()
|
||||
?: 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
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun inputAsOwner(parameter: Parameter<*, Int>, input: String): ParcelOwner {
|
||||
val kind = parameter.paramInfo ?: DEFAULT_KIND
|
||||
if (kind and OWNER_REAL == 0) {
|
||||
return ParcelOwner(input)
|
||||
}
|
||||
|
||||
val player = Bukkit.getOfflinePlayer(input).takeIf { it.isValid }
|
||||
if (player == null) {
|
||||
if (kind and OWNER_FAKE == 0) invalidInput(parameter, "The player $input does not exist")
|
||||
return ParcelOwner(input)
|
||||
}
|
||||
|
||||
return ParcelOwner(player)
|
||||
}
|
||||
|
||||
override fun getDefaultValue(parameter: Parameter<ParcelTarget, Int>, sender: CommandSender, buffer: ArgumentBuffer): ParcelTarget? {
|
||||
val kind = parameter.paramInfo ?: DEFAULT_KIND
|
||||
val useLocation = when {
|
||||
@@ -156,7 +159,8 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
|
||||
return ByID(world, id, true)
|
||||
}
|
||||
|
||||
return ByOwner(world, ParcelOwner(player), 0, true)
|
||||
return ByOwner(world, PlayerProfile(player), 0, true)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import io.dico.parcels2.*
|
||||
import io.dico.parcels2.blockvisitor.RegionTraversal
|
||||
import io.dico.parcels2.blockvisitor.Worker
|
||||
import io.dico.parcels2.blockvisitor.WorktimeLimiter
|
||||
import io.dico.parcels2.options.DefaultGeneratorOptions
|
||||
import io.dico.parcels2.util.*
|
||||
import org.bukkit.*
|
||||
import org.bukkit.block.Biome
|
||||
@@ -17,21 +18,6 @@ import java.util.Random
|
||||
|
||||
private val airType = Bukkit.createBlockData(Material.AIR)
|
||||
|
||||
data class DefaultGeneratorOptions(var defaultBiome: Biome = Biome.JUNGLE,
|
||||
var wallType: BlockData = Bukkit.createBlockData(Material.STONE_SLAB),
|
||||
var floorType: BlockData = Bukkit.createBlockData(Material.QUARTZ_BLOCK),
|
||||
var fillType: BlockData = Bukkit.createBlockData(Material.QUARTZ_BLOCK),
|
||||
var pathMainType: BlockData = Bukkit.createBlockData(Material.SANDSTONE),
|
||||
var pathAltType: BlockData = Bukkit.createBlockData(Material.REDSTONE_BLOCK),
|
||||
var parcelSize: Int = 101,
|
||||
var pathSize: Int = 9,
|
||||
var floorHeight: Int = 64,
|
||||
var offsetX: Int = 0,
|
||||
var offsetZ: Int = 0) : GeneratorOptions() {
|
||||
|
||||
override fun generatorFactory(): GeneratorFactory = DefaultParcelGenerator.Factory
|
||||
}
|
||||
|
||||
class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOptions) : ParcelGenerator() {
|
||||
private var _world: World? = null
|
||||
override val world: World
|
||||
@@ -44,15 +30,6 @@ class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOp
|
||||
}
|
||||
|
||||
private var maxHeight = 0
|
||||
|
||||
companion object Factory : GeneratorFactory {
|
||||
override val name get() = "default"
|
||||
override val optionsClass get() = DefaultGeneratorOptions::class
|
||||
override fun newParcelGenerator(worldName: String, options: GeneratorOptions): ParcelGenerator {
|
||||
return DefaultParcelGenerator(worldName, options as DefaultGeneratorOptions)
|
||||
}
|
||||
}
|
||||
|
||||
val sectionSize = o.parcelSize + o.pathSize
|
||||
val pathOffset = (if (o.pathSize % 2 == 0) o.pathSize + 2 else o.pathSize + 1) / 2
|
||||
val makePathMain = o.pathSize > 2
|
||||
@@ -142,7 +119,7 @@ class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOp
|
||||
val modX = absX umod sectionSize
|
||||
val modZ = absZ umod sectionSize
|
||||
if (modX in 0 until parcelSize && modZ in 0 until parcelSize) {
|
||||
return mapper((absX - modX) / sectionSize, (absZ - modZ) / sectionSize)
|
||||
return mapper((absX - modX) / sectionSize + 1, (absZ - modZ) / sectionSize + 1)
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -163,16 +140,18 @@ class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOp
|
||||
override val world: World = this@DefaultParcelGenerator.world
|
||||
|
||||
override fun getBottomBlock(parcel: ParcelId): Vec2i = Vec2i(
|
||||
sectionSize * parcel.pos.x + pathOffset + o.offsetX,
|
||||
sectionSize * parcel.pos.z + pathOffset + o.offsetZ
|
||||
sectionSize * (parcel.x - 1) + pathOffset + o.offsetX,
|
||||
sectionSize * (parcel.z - 1) + pathOffset + o.offsetZ
|
||||
)
|
||||
|
||||
override fun getHomeLocation(parcel: ParcelId): Location {
|
||||
val bottom = getBottomBlock(parcel)
|
||||
return Location(world, bottom.x.toDouble(), o.floorHeight + 1.0, bottom.z + (o.parcelSize - 1) / 2.0, -90F, 0F)
|
||||
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 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
|
||||
val skull = skullBlock.state as Skull
|
||||
if (owner.uuid != null) {
|
||||
skull.owningPlayer = owner.offlinePlayer
|
||||
if (owner is PlayerProfile.Real) {
|
||||
skull.owningPlayer = owner.playerUnchecked
|
||||
} else {
|
||||
skull.owner = owner.name
|
||||
}
|
||||
|
||||
@@ -3,33 +3,32 @@
|
||||
package io.dico.parcels2.defaultimpl
|
||||
|
||||
import io.dico.parcels2.*
|
||||
import io.dico.parcels2.util.alsoIfTrue
|
||||
import java.util.Collections
|
||||
import java.util.UUID
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
private inner class GlobalAddedDataImpl(override val owner: ParcelOwner,
|
||||
private inner class GlobalAddedDataImpl(override val owner: PlayerProfile,
|
||||
data: MutableAddedDataMap = emptyData)
|
||||
: AddedDataHolder(data), GlobalAddedData {
|
||||
|
||||
private inline var data get() = addedMap; set(value) = run { addedMap = value }
|
||||
private inline val isEmpty get() = data === emptyData
|
||||
|
||||
override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean {
|
||||
override fun setAddedStatus(key: StatusKey, status: AddedStatus): Boolean {
|
||||
if (isEmpty) {
|
||||
if (status == AddedStatus.DEFAULT) return false
|
||||
data = mutableMapOf()
|
||||
}
|
||||
return super.setAddedStatus(uuid, status).also {
|
||||
if (it) plugin.storage.setGlobalAddedStatus(owner, uuid, status)
|
||||
return super.setAddedStatus(key, status).alsoIfTrue {
|
||||
plugin.storage.setGlobalAddedStatus(owner, key, status)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
@@ -3,6 +3,7 @@ package io.dico.parcels2.defaultimpl
|
||||
import io.dico.dicore.Formatting
|
||||
import io.dico.parcels2.*
|
||||
import io.dico.parcels2.util.Vec2i
|
||||
import io.dico.parcels2.util.alsoIfTrue
|
||||
import io.dico.parcels2.util.getPlayerName
|
||||
import org.bukkit.OfflinePlayer
|
||||
import org.joda.time.DateTime
|
||||
@@ -33,20 +34,24 @@ class ParcelImpl(override val world: ParcelWorld,
|
||||
world.storage.setParcelData(this, null)
|
||||
}
|
||||
|
||||
override val addedMap: Map<UUID, AddedStatus> get() = data.addedMap
|
||||
override fun getAddedStatus(uuid: UUID) = data.getAddedStatus(uuid)
|
||||
override fun isBanned(uuid: UUID) = data.isBanned(uuid)
|
||||
override fun isAllowed(uuid: UUID) = data.isAllowed(uuid)
|
||||
override val addedMap: AddedDataMap get() = data.addedMap
|
||||
override fun getAddedStatus(key: StatusKey) = data.getAddedStatus(key)
|
||||
override fun isBanned(key: StatusKey) = data.isBanned(key)
|
||||
override fun isAllowed(key: StatusKey) = data.isAllowed(key)
|
||||
override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean): Boolean {
|
||||
return (data.canBuild(player, checkAdmin, false))
|
||||
|| checkGlobal && world.globalAddedData[owner ?: return false].isAllowed(player)
|
||||
}
|
||||
|
||||
val globalAddedMap: Map<UUID, AddedStatus>? get() = owner?.let { world.globalAddedData[it].addedMap }
|
||||
override 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 var owner: ParcelOwner?
|
||||
override var owner: PlayerProfile?
|
||||
get() = data.owner
|
||||
set(value) {
|
||||
if (data.owner != value) {
|
||||
@@ -55,9 +60,9 @@ class ParcelImpl(override val world: ParcelWorld,
|
||||
}
|
||||
}
|
||||
|
||||
override fun setAddedStatus(uuid: UUID, status: AddedStatus): Boolean {
|
||||
return data.setAddedStatus(uuid, status).also {
|
||||
if (it) world.storage.setParcelPlayerStatus(this, uuid, status)
|
||||
override fun setAddedStatus(key: StatusKey, status: AddedStatus): Boolean {
|
||||
return data.setAddedStatus(key, status).alsoIfTrue {
|
||||
world.storage.setParcelPlayerStatus(this, key, status)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,10 +107,10 @@ private object ParcelInfoStringComputer {
|
||||
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 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
|
||||
|
||||
appendField({
|
||||
|
||||
@@ -28,7 +28,7 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
|
||||
override fun getWorldGenerator(worldName: String): ParcelGenerator? {
|
||||
return _worlds[worldName]?.generator
|
||||
?: _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() {
|
||||
@@ -60,13 +60,29 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
|
||||
|
||||
private fun loadStoredData() {
|
||||
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 {
|
||||
val pair = channel.receiveOrNull() ?: break
|
||||
val parcel = getParcelById(pair.first) ?: continue
|
||||
pair.second?.let { parcel.copyDataIgnoringDatabase(it) }
|
||||
} while (true)
|
||||
|
||||
logger.info("Loading data completed")
|
||||
_dataIsLoaded = true
|
||||
}.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 {
|
||||
do {
|
||||
val pair = channel.receiveOrNull() ?: break
|
||||
|
||||
@@ -4,6 +4,7 @@ package io.dico.parcels2.defaultimpl
|
||||
|
||||
import io.dico.parcels2.*
|
||||
import io.dico.parcels2.blockvisitor.WorktimeLimiter
|
||||
import io.dico.parcels2.options.RuntimeWorldOptions
|
||||
import io.dico.parcels2.storage.Storage
|
||||
import org.bukkit.World
|
||||
import java.util.UUID
|
||||
@@ -11,7 +12,7 @@ import java.util.UUID
|
||||
class ParcelWorldImpl private
|
||||
constructor(override val world: World,
|
||||
override val generator: ParcelGenerator,
|
||||
override var options: WorldOptions,
|
||||
override var options: RuntimeWorldOptions,
|
||||
override val storage: Storage,
|
||||
override val globalAddedData: GlobalAddedDataManager,
|
||||
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.
|
||||
operator fun invoke(world: World,
|
||||
generator: ParcelGenerator,
|
||||
options: WorldOptions,
|
||||
options: RuntimeWorldOptions,
|
||||
storage: Storage,
|
||||
globalAddedData: GlobalAddedDataManager,
|
||||
containerFactory: ParcelContainerFactory,
|
||||
|
||||
@@ -26,7 +26,7 @@ class ParcelEntityTracker(val parcelProvider: ParcelProvider) {
|
||||
*/
|
||||
fun tick() {
|
||||
map.editLoop { entity, parcel ->
|
||||
if (entity.isDead || entity.isOnGround) {
|
||||
if (entity.isDead) {
|
||||
remove(); return@editLoop
|
||||
}
|
||||
if (parcel.isPresentAnd { hasBlockVisitors }) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import io.dico.dicore.RegistratorListener
|
||||
import io.dico.parcels2.Parcel
|
||||
import io.dico.parcels2.ParcelProvider
|
||||
import io.dico.parcels2.ParcelWorld
|
||||
import io.dico.parcels2.statusKey
|
||||
import io.dico.parcels2.util.*
|
||||
import org.bukkit.Material.*
|
||||
import org.bukkit.World
|
||||
@@ -51,7 +52,7 @@ class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: Par
|
||||
val user = event.player
|
||||
if (user.hasBanBypass) 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 {
|
||||
user.teleport(it.world.getHomeLocation(it.id))
|
||||
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
|
||||
*/
|
||||
@field:ListenerMarker(priority = NORMAL)
|
||||
val onBlockPlaceEvent = RegistratorListener<BlockBreakEvent> l@{ event ->
|
||||
val onBlockPlaceEvent = RegistratorListener<BlockPlaceEvent> l@{ event ->
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -178,72 +179,79 @@ class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: Par
|
||||
val clickedBlock = event.clickedBlock
|
||||
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")
|
||||
event.isCancelled = true; return@l
|
||||
}
|
||||
|
||||
when (event.action) {
|
||||
Action.RIGHT_CLICK_BLOCK -> when (clickedBlock.type) {
|
||||
REPEATER,
|
||||
COMPARATOR -> run {
|
||||
if (!parcel.canBuildN(user)) {
|
||||
event.isCancelled = true; return@l
|
||||
Action.RIGHT_CLICK_BLOCK -> run {
|
||||
when (clickedBlock.type) {
|
||||
REPEATER,
|
||||
COMPARATOR -> run {
|
||||
if (!parcel.canBuildN(user)) {
|
||||
event.isCancelled = true; return@l
|
||||
}
|
||||
}
|
||||
}
|
||||
LEVER,
|
||||
STONE_BUTTON,
|
||||
ANVIL,
|
||||
TRAPPED_CHEST,
|
||||
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_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
|
||||
-> run {
|
||||
if (!user.hasBuildAnywhere && !parcel.isNullOr { canBuild(user) || allowInteractInputs }) {
|
||||
user.sendParcelMessage(nopermit = true, message = "You cannot use inputs in this parcel")
|
||||
event.isCancelled = true; return@l
|
||||
LEVER,
|
||||
STONE_BUTTON,
|
||||
ANVIL,
|
||||
TRAPPED_CHEST,
|
||||
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_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
|
||||
-> run {
|
||||
if (!user.hasBuildAnywhere && !parcel.isNullOr { canBuild(user) || allowInteractInputs }) {
|
||||
user.sendParcelMessage(nopermit = true, message = "You cannot use inputs in this parcel")
|
||||
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
|
||||
-> run {
|
||||
if (world.options.disableExplosions) {
|
||||
val bed = clickedBlock.blockData as Bed
|
||||
val head = if (bed == Bed.Part.FOOT) clickedBlock.getRelative(bed.facing) else clickedBlock
|
||||
when (head.biome) {
|
||||
Biome.NETHER, Biome.THE_END -> run {
|
||||
user.sendParcelMessage(nopermit = true, message = "You cannot use this bed because it would explode")
|
||||
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
|
||||
-> run {
|
||||
if (world.options.disableExplosions) {
|
||||
val bed = clickedBlock.blockData as Bed
|
||||
val head = if (bed == Bed.Part.FOOT) clickedBlock.getRelative(bed.facing) else clickedBlock
|
||||
when (head.biome) {
|
||||
Biome.NETHER, Biome.THE_END -> run {
|
||||
user.sendParcelMessage(nopermit = true, message = "You cannot use this bed because it would explode")
|
||||
event.isCancelled = true; return@l
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
onPlayerInteractEvent_RightClick(event, world, parcel)
|
||||
}
|
||||
|
||||
Action.RIGHT_CLICK_AIR -> if (event.hasItem()) {
|
||||
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.RIGHT_CLICK_AIR -> onPlayerInteractEvent_RightClick(event, world, parcel)
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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,
|
||||
* 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
|
||||
}
|
||||
|
||||
// TODO: BlockFormEvent, BlockSpreadEvent, BlockFadeEvent
|
||||
// TODO: BlockFormEvent, BlockSpreadEvent, BlockFadeEvent, Fireworks
|
||||
|
||||
/*
|
||||
* Prevents natural blocks forming
|
||||
@@ -370,10 +378,10 @@ class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: Par
|
||||
val hasEntity = event is EntityBlockFormEvent
|
||||
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
|
||||
ICE -> player != null && !ppa.canBuild(player)
|
||||
FROSTED_ICE -> player != null && !ppa.canBuild(player)
|
||||
|
||||
// prevent snow generation from weather
|
||||
SNOW -> !hasEntity && wo.options.preventWeatherBlockChanges
|
||||
@@ -406,12 +414,13 @@ class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: Par
|
||||
val onVehicleMoveEvent = RegistratorListener<VehicleMoveEvent> l@{ event ->
|
||||
val (wo, ppa) = getWoAndPPa(event.to.block) ?: return@l
|
||||
if (ppa == null) {
|
||||
event.vehicle.eject()
|
||||
event.vehicle.passengers.forEach {
|
||||
if (it.type == EntityType.PLAYER) {
|
||||
(it as Player).sendParcelMessage(except = true, message = "Your ride ends here")
|
||||
} else it.remove()
|
||||
}
|
||||
event.vehicle.eject()
|
||||
event.vehicle.remove()
|
||||
} else if (ppa.hasBlockVisitors) {
|
||||
event.to.subtract(event.to).add(event.from)
|
||||
}
|
||||
|
||||
36
src/main/kotlin/io/dico/parcels2/options/GeneratorOptions.kt
Normal file
36
src/main/kotlin/io/dico/parcels2/options/GeneratorOptions.kt
Normal 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)
|
||||
21
src/main/kotlin/io/dico/parcels2/options/MigrationOptions.kt
Normal file
21
src/main/kotlin/io/dico/parcels2/options/MigrationOptions.kt
Normal 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")))
|
||||
58
src/main/kotlin/io/dico/parcels2/options/Options.kt
Normal file
58
src/main/kotlin/io/dico/parcels2/options/Options.kt
Normal 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()
|
||||
}
|
||||
66
src/main/kotlin/io/dico/parcels2/options/OptionsMapper.kt
Normal file
66
src/main/kotlin/io/dico/parcels2/options/OptionsMapper.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
59
src/main/kotlin/io/dico/parcels2/options/StorageOptions.kt
Normal file
59
src/main/kotlin/io/dico/parcels2/options/StorageOptions.kt
Normal 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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
package io.dico.parcels2.storage
|
||||
|
||||
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 java.util.UUID
|
||||
|
||||
@@ -10,41 +14,49 @@ interface Backing {
|
||||
|
||||
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>
|
||||
|
||||
|
||||
/**
|
||||
* 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>)
|
||||
fun init()
|
||||
|
||||
suspend fun produceAllParcelData(channel: SendChannel<DataPair>)
|
||||
|
||||
suspend fun readParcelData(parcel: ParcelId): ParcelData?
|
||||
|
||||
suspend fun getOwnedParcels(user: ParcelOwner): List<ParcelId>
|
||||
|
||||
suspend fun getNumParcels(user: ParcelOwner): Int = getOwnedParcels(user).size
|
||||
fun shutdown()
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.dico.parcels2.storage
|
||||
|
||||
import com.zaxxer.hikari.HikariConfig
|
||||
import io.dico.parcels2.DataConnectionOptions
|
||||
import io.dico.parcels2.options.DataConnectionOptions
|
||||
|
||||
fun getHikariConfig(dialectName: String,
|
||||
dco: DataConnectionOptions): HikariConfig = HikariConfig().apply {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,21 +3,18 @@
|
||||
package io.dico.parcels2.storage
|
||||
|
||||
import io.dico.parcels2.*
|
||||
import kotlinx.coroutines.experimental.*
|
||||
import kotlinx.coroutines.experimental.channels.ProducerScope
|
||||
import kotlinx.coroutines.experimental.Deferred
|
||||
import kotlinx.coroutines.experimental.Job
|
||||
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.concurrent.Executor
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
typealias DataPair = Pair<ParcelId, ParcelData?>
|
||||
typealias AddedDataPair<TAttach> = Pair<TAttach, MutableAddedDataMap>
|
||||
|
||||
interface Storage {
|
||||
val name: String
|
||||
val syncDispatcher: CoroutineDispatcher
|
||||
val asyncDispatcher: CoroutineDispatcher
|
||||
val isConnected: Boolean
|
||||
|
||||
fun init(): Job
|
||||
@@ -25,84 +22,77 @@ interface Storage {
|
||||
fun shutdown(): Job
|
||||
|
||||
|
||||
fun getPlayerUuidForName(name: String): Deferred<UUID?>
|
||||
|
||||
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 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 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 {
|
||||
override val name get() = backing.name
|
||||
override val syncDispatcher = Executor { it.run() }.asCoroutineDispatcher()
|
||||
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
|
||||
class BackedStorage internal constructor(val b: Backing) : Storage {
|
||||
override val name get() = b.name
|
||||
override val isConnected get() = b.isConnected
|
||||
|
||||
private inline fun <T> defer(noinline block: suspend CoroutineScope.() -> T): Deferred<T> {
|
||||
return async(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block)
|
||||
}
|
||||
override fun init() = launch(b.dispatcher) { b.init() }
|
||||
|
||||
private inline fun job(noinline block: suspend CoroutineScope.() -> Unit): Job {
|
||||
return launch(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block)
|
||||
}
|
||||
|
||||
private inline fun <T> openChannel(noinline block: suspend ProducerScope<T>.() -> Unit): ReceiveChannel<T> {
|
||||
return produce(asyncDispatcher, capacity = channelCapacity, block = block)
|
||||
}
|
||||
|
||||
override fun init() = job { backing.init() }
|
||||
|
||||
override fun shutdown() = job { backing.shutdown() }
|
||||
override fun shutdown() = launch(b.dispatcher) { b.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) }
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource
|
||||
import io.dico.parcels2.*
|
||||
import io.dico.parcels2.storage.AddedDataPair
|
||||
import io.dico.parcels2.storage.Backing
|
||||
import io.dico.parcels2.storage.DataPair
|
||||
import io.dico.parcels2.util.synchronized
|
||||
import io.dico.parcels2.util.toUUID
|
||||
import kotlinx.coroutines.experimental.CoroutineStart
|
||||
import kotlinx.coroutines.experimental.Unconfined
|
||||
import kotlinx.coroutines.experimental.*
|
||||
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.launch
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.SchemaUtils.create
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
@@ -21,14 +24,44 @@ import javax.sql.DataSource
|
||||
|
||||
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 dispatcher: ThreadPoolDispatcher = newFixedThreadPoolContext(poolSize, "Parcels StorageThread")
|
||||
|
||||
private var dataSource: DataSource? = null
|
||||
private var database: Database? = null
|
||||
private var isShutdown: Boolean = false
|
||||
|
||||
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 {
|
||||
init {
|
||||
Database.registerDialect("mariadb") {
|
||||
@@ -37,63 +70,85 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) :
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> transaction(statement: Transaction.() -> T) = transaction(database!!, statement)
|
||||
|
||||
private suspend fun transactionLaunch(statement: suspend Transaction.() -> Unit): Unit = transaction(database!!) {
|
||||
launch(context = Unconfined, start = CoroutineStart.UNDISPATCHED) {
|
||||
statement(this@transaction)
|
||||
override fun init() {
|
||||
synchronized {
|
||||
if (isShutdown || isConnected) throw IllegalStateException()
|
||||
dataSource = dataSourceFactory()
|
||||
database = Database.connect(dataSource!!)
|
||||
transaction(database!!) {
|
||||
create(WorldsT, ProfilesT, ParcelsT, ParcelOptionsT, AddedLocalT, AddedGlobalT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun init() {
|
||||
if (isShutdown) throw IllegalStateException()
|
||||
dataSource = dataSourceFactory()
|
||||
database = Database.connect(dataSource!!)
|
||||
transaction(database) {
|
||||
create(WorldsT, OwnersT, ParcelsT, ParcelOptionsT, AddedLocalT, AddedGlobalT)
|
||||
override fun shutdown() {
|
||||
synchronized {
|
||||
if (isShutdown) throw IllegalStateException()
|
||||
dataSource?.let {
|
||||
(it as? HikariDataSource)?.close()
|
||||
}
|
||||
database = null
|
||||
isShutdown = true
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun shutdown() {
|
||||
if (isShutdown) throw IllegalStateException()
|
||||
dataSource?.let {
|
||||
(it as? HikariDataSource)?.close()
|
||||
}
|
||||
database = null
|
||||
isShutdown = true
|
||||
private fun PlayerProfile.toOwnerProfile(): PlayerProfile {
|
||||
if (this is PlayerProfile.Star) return PlayerProfile.Fake(PlayerProfile.Star.name)
|
||||
return this
|
||||
}
|
||||
|
||||
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) {
|
||||
val data = readParcelData(parcel)
|
||||
channel.send(parcel to data)
|
||||
channel.offer(parcel to data)
|
||||
}
|
||||
channel.close()
|
||||
}
|
||||
|
||||
override suspend fun produceAllParcelData(channel: SendChannel<Pair<ParcelId, ParcelData?>>) = transactionLaunch {
|
||||
override fun transmitAllParcelData(channel: SendChannel<DataPair>) {
|
||||
ParcelsT.selectAll().forEach { row ->
|
||||
val parcel = ParcelsT.getId(row) ?: return@forEach
|
||||
val parcel = ParcelsT.getItem(row) ?: return@forEach
|
||||
val data = rowToParcelData(row)
|
||||
channel.send(parcel to data)
|
||||
channel.offer(parcel to data)
|
||||
}
|
||||
channel.close()
|
||||
}
|
||||
|
||||
override suspend fun readParcelData(parcel: ParcelId): ParcelData? = transaction {
|
||||
val row = ParcelsT.getRow(parcel) ?: return@transaction null
|
||||
rowToParcelData(row)
|
||||
override fun readParcelData(parcel: ParcelId): ParcelData? {
|
||||
val row = ParcelsT.getRow(parcel) ?: return null
|
||||
return rowToParcelData(row)
|
||||
}
|
||||
|
||||
override suspend fun getOwnedParcels(user: ParcelOwner): List<ParcelId> = transaction {
|
||||
val user_id = OwnersT.getId(user) ?: return@transaction emptyList()
|
||||
ParcelsT.select { ParcelsT.owner_id eq user_id }
|
||||
override fun getOwnedParcels(user: PlayerProfile): List<ParcelId> {
|
||||
val user_id = ProfilesT.getId(user.toOwnerProfile()) ?: return emptyList()
|
||||
return ParcelsT.select { ParcelsT.owner_id eq user_id }
|
||||
.orderBy(ParcelsT.claim_time, isAsc = true)
|
||||
.mapNotNull(ParcelsT::getId)
|
||||
.mapNotNull(ParcelsT::getItem)
|
||||
.toList()
|
||||
}
|
||||
|
||||
override suspend fun setParcelData(parcel: ParcelId, data: ParcelData?) {
|
||||
override fun setParcelData(parcel: ParcelId, data: ParcelData?) {
|
||||
if (data == null) {
|
||||
transaction {
|
||||
ParcelsT.getId(parcel)?.let { id ->
|
||||
@@ -117,21 +172,21 @@ class ExposedBacking(private val dataSourceFactory: suspend () -> DataSource) :
|
||||
|
||||
setParcelOwner(parcel, data.owner)
|
||||
|
||||
for ((uuid, status) in data.addedMap) {
|
||||
setLocalPlayerStatus(parcel, uuid, status)
|
||||
for ((profile, status) in data.addedMap) {
|
||||
AddedLocalT.setPlayerStatus(parcel, profile, status)
|
||||
}
|
||||
|
||||
setParcelAllowsInteractInputs(parcel, data.allowInteractInputs)
|
||||
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)
|
||||
ParcelsT.getId(parcel) ?: return@transaction
|
||||
ParcelsT.getId(parcel) ?: return
|
||||
else
|
||||
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() }
|
||||
|
||||
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 {
|
||||
AddedLocalT.setPlayerStatus(parcel, player, status)
|
||||
override fun setLocalPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus) {
|
||||
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)
|
||||
ParcelOptionsT.upsert(ParcelOptionsT.parcel_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)
|
||||
ParcelOptionsT.upsert(ParcelOptionsT.parcel_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)
|
||||
channel.close()
|
||||
}
|
||||
|
||||
override suspend fun readGlobalAddedData(owner: ParcelOwner): MutableMap<UUID, AddedStatus> = transaction {
|
||||
return@transaction AddedGlobalT.readAddedData(OwnersT.getId(owner) ?: return@transaction hashMapOf())
|
||||
override fun readGlobalAddedData(owner: PlayerProfile): MutableAddedDataMap {
|
||||
return AddedGlobalT.readAddedData(ProfilesT.getId(owner.toOwnerProfile()) ?: return hashMapOf())
|
||||
}
|
||||
|
||||
override suspend fun setGlobalPlayerStatus(owner: ParcelOwner, player: UUID, status: AddedStatus) = transaction {
|
||||
AddedGlobalT.setPlayerStatus(owner, player, status)
|
||||
override fun setGlobalPlayerStatus(owner: PlayerProfile, player: PlayerProfile, status: AddedStatus) {
|
||||
AddedGlobalT.setPlayerStatus(owner, player.toRealProfile(), status)
|
||||
}
|
||||
|
||||
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]
|
||||
|
||||
val parcelId = row[ParcelsT.id]
|
||||
addedMap = AddedLocalT.readAddedData(parcelId)
|
||||
|
||||
AddedLocalT.select { AddedLocalT.attach_id eq parcelId }.forEach {
|
||||
val uuid = it[AddedLocalT.player_uuid].toUUID()
|
||||
val status = if (it[AddedLocalT.allowed_flag]) AddedStatus.ALLOWED else AddedStatus.BANNED
|
||||
setAddedStatus(uuid, status)
|
||||
val id = row[ParcelsT.id]
|
||||
ParcelOptionsT.select { ParcelOptionsT.parcel_id eq id }.firstOrNull()?.let { optrow ->
|
||||
allowInteractInputs = optrow[ParcelOptionsT.interact_inputs]
|
||||
allowInteractInventory = optrow[ParcelOptionsT.interact_inventory]
|
||||
}
|
||||
|
||||
ParcelOptionsT.select { ParcelOptionsT.parcel_id eq parcelId }.firstOrNull()?.let {
|
||||
allowInteractInputs = it[ParcelOptionsT.interact_inputs]
|
||||
allowInteractInventory = it[ParcelOptionsT.interact_inventory]
|
||||
}
|
||||
addedMap = AddedLocalT.readAddedData(id)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package io.dico.parcels2.storage.exposed
|
||||
|
||||
import org.jetbrains.exposed.sql.Column
|
||||
import org.jetbrains.exposed.sql.Index
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
import org.jetbrains.exposed.sql.Transaction
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.Function
|
||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||
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 <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)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
package io.dico.parcels2.storage.exposed
|
||||
|
||||
import io.dico.parcels2.ParcelId
|
||||
import io.dico.parcels2.ParcelOwner
|
||||
import io.dico.parcels2.ParcelWorldId
|
||||
import io.dico.parcels2.PlayerProfile
|
||||
import io.dico.parcels2.util.toByteArray
|
||||
import io.dico.parcels2.util.toUUID
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||
import org.jetbrains.exposed.sql.statements.UpdateBuilder
|
||||
import java.util.UUID
|
||||
|
||||
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] }
|
||||
}
|
||||
|
||||
internal inline fun insertAndGetId(objName: String, noinline body: TableT.(InsertStatement<Number>) -> Unit): Int {
|
||||
return table.insert(body)[id] ?: insertError(objName)
|
||||
internal inline fun getOrInitId(getId: () -> Int?, noinline body: TableT.(UpdateBuilder<*>) -> Unit, objName: () -> String): Int {
|
||||
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 getOrInitId(obj: QueryObj): Int
|
||||
fun getId(id: Int): QueryObj? = select { this@IdTransactionsTable.id eq id }.firstOrNull()?.let { getId(it) }
|
||||
abstract fun getId(row: ResultRow): QueryObj?
|
||||
fun getItem(id: Int): QueryObj? = select { this@IdTransactionsTable.id eq id }.firstOrNull()?.let { getItem(it) }
|
||||
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") {
|
||||
@@ -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, uid: UUID?): Int? = getId(worldName, uid?.toByteArray())
|
||||
internal inline fun getOrInitId(worldName: String, worldUid: UUID?): Int = worldUid?.toByteArray().let { binaryUid ->
|
||||
getId(worldName, binaryUid)
|
||||
?: insertAndGetId("world named $worldName") { it[name] = worldName; binaryUid?.let { buid -> it[uid] = buid } }
|
||||
return getOrInitId(
|
||||
{ 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 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())
|
||||
}
|
||||
}
|
||||
@@ -60,7 +62,7 @@ object ParcelsT : IdTransactionsTable<ParcelsT, ParcelId>("parcels", "parcel_id"
|
||||
val world_id = integer("world_id").references(WorldsT.id)
|
||||
val px = integer("px")
|
||||
val pz = integer("pz")
|
||||
val owner_id = integer("owner_id").references(OwnersT.id).nullable()
|
||||
val owner_id = integer("owner_id").references(ProfilesT.id).nullable()
|
||||
val claim_time = datetime("claim_time").nullable()
|
||||
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 getOrInitId(worldName: String, worldUid: UUID?, parcelX: Int, parcelZ: Int): Int {
|
||||
val worldId = WorldsT.getOrInitId(worldName, worldUid)
|
||||
return getId(worldId, parcelX, parcelZ)
|
||||
?: insertAndGetId("parcel at $worldName($parcelX, $parcelZ)") { it[world_id] = worldId; it[px] = parcelX; it[pz] = parcelZ }
|
||||
return getOrInitId(
|
||||
{ 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)
|
||||
@@ -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()
|
||||
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 world = WorldsT.getId(worldId) ?: return null
|
||||
val world = WorldsT.getItem(worldId) ?: return null
|
||||
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 name = varchar("name", 32)
|
||||
val index_pair = uniqueIndexR("index_pair", uuid, name)
|
||||
|
||||
private inline fun getId(binaryUuid: ByteArray) = getId { uuid eq binaryUuid }
|
||||
private inline fun getId(uuid: UUID) = getId(uuid.toByteArray())
|
||||
private inline fun getId(nameIn: String) = getId { uuid.isNull() and (name eq nameIn) }
|
||||
private inline fun 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 ->
|
||||
getId(binaryUuid) ?: insertAndGetId("owner(uuid = $uuid)") {
|
||||
it[this@OwnersT.uuid] = binaryUuid
|
||||
it[this@OwnersT.name] = name
|
||||
}
|
||||
private inline fun getOrInitId(uuid: UUID, name: String) = uuid.toByteArray().let { binaryUuid -> getOrInitId(
|
||||
{ getId(binaryUuid) },
|
||||
{ it[this@ProfilesT.uuid] = binaryUuid; it[this@ProfilesT.name] = name },
|
||||
{ "profile(uuid = $uuid, name = $name)" })
|
||||
}
|
||||
|
||||
private inline fun getOrInitId(name: String) =
|
||||
getId(name) ?: insertAndGetId("owner(name = $name)") { it[OwnersT.name] = name }
|
||||
private inline fun getOrInitId(name: String) = getOrInitId(
|
||||
{ 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 =
|
||||
if (owner.hasUUID) getOrInitId(owner.uuid!!, owner.notNullName)
|
||||
else getOrInitId(owner.name!!)
|
||||
|
||||
override fun getId(row: ResultRow): ParcelOwner {
|
||||
return row[uuid]?.toUUID()?.let { ParcelOwner(it) } ?: ParcelOwner(row[name])
|
||||
override fun getId(profile: PlayerProfile): Int? = when (profile) {
|
||||
is PlayerProfile.Real -> getId(profile.uuid)
|
||||
is PlayerProfile.Fake -> getId(profile.name)
|
||||
is PlayerProfile.Unresolved -> getRealId(profile.name)
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -2,17 +2,13 @@
|
||||
|
||||
package io.dico.parcels2.storage.exposed
|
||||
|
||||
import io.dico.parcels2.AddedStatus
|
||||
import io.dico.parcels2.ParcelId
|
||||
import io.dico.parcels2.ParcelOwner
|
||||
import io.dico.parcels2.util.toByteArray
|
||||
import io.dico.parcels2.util.toUUID
|
||||
import io.dico.parcels2.*
|
||||
import kotlinx.coroutines.experimental.channels.SendChannel
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import java.util.UUID
|
||||
|
||||
object AddedLocalT : AddedTable<ParcelId>("parcels_added_local", ParcelsT)
|
||||
object AddedGlobalT : AddedTable<ParcelOwner>("parcels_added_global", OwnersT)
|
||||
object AddedGlobalT : AddedTable<PlayerProfile>("parcels_added_global", ProfilesT)
|
||||
|
||||
object ParcelOptionsT : Table("parcel_options") {
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
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 index_pair = uniqueIndexR("index_pair", attach_id, player_uuid)
|
||||
|
||||
fun setPlayerStatus(attachedOn: AttachT, player: UUID, status: AddedStatus) {
|
||||
val binaryUuid = player.toByteArray()
|
||||
val index_pair = uniqueIndexR("index_pair", attach_id, profile_id)
|
||||
|
||||
fun setPlayerStatus(attachedOn: AttachT, player: PlayerProfile.Real, status: AddedStatus) {
|
||||
if (status.isDefault) {
|
||||
idTable.getId(attachedOn)?.let { id ->
|
||||
deleteWhere { (attach_id eq id) and (player_uuid eq binaryUuid) }
|
||||
val player_id = ProfilesT.getId(player) ?: return
|
||||
idTable.getId(attachedOn)?.let { holder ->
|
||||
deleteWhere { (attach_id eq holder) and (profile_id eq player_id) }
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val id = idTable.getOrInitId(attachedOn)
|
||||
val holder = idTable.getOrInitId(attachedOn)
|
||||
val player_id = ProfilesT.getOrInitId(player)
|
||||
upsert(conflictIndex = index_pair) {
|
||||
it[attach_id] = id
|
||||
it[player_uuid] = binaryUuid
|
||||
it[attach_id] = holder
|
||||
it[profile_id] = player_id
|
||||
it[allowed_flag] = status.isAllowed
|
||||
}
|
||||
}
|
||||
|
||||
fun readAddedData(id: Int): MutableMap<UUID, AddedStatus> {
|
||||
return slice(player_uuid, allowed_flag).select { attach_id eq id }
|
||||
.associateByTo(hashMapOf(), { it[player_uuid].toUUID() }, { it[allowed_flag].asAddedStatus() })
|
||||
fun readAddedData(id: Int): MutableAddedDataMap {
|
||||
val list = slice(profile_id, allowed_flag).select { attach_id eq id }
|
||||
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()
|
||||
|
||||
if (iterator.hasNext()) {
|
||||
val firstRow = iterator.next()
|
||||
var id: Int = firstRow[attach_id]
|
||||
var attach: SerializableT? = null
|
||||
var map: MutableMap<UUID, AddedStatus>? = null
|
||||
var attach: AttachT? = null
|
||||
var map: MutableAddedDataMap? = null
|
||||
|
||||
fun initAttachAndMap() {
|
||||
attach = idTable.getId(id)
|
||||
attach = idTable.getItem(id)
|
||||
map = attach?.let { mutableMapOf() }
|
||||
}
|
||||
|
||||
suspend fun sendIfPresent() {
|
||||
fun sendIfPresent() {
|
||||
if (attach != null && map != null && map!!.isNotEmpty()) {
|
||||
channel.send(attach!! to map!!)
|
||||
channel.offer(attach!! to map!!)
|
||||
}
|
||||
attach = null
|
||||
map = null
|
||||
@@ -88,13 +88,13 @@ sealed class AddedTable<AttachT>(name: String, val idTable: IdTransactionsTable<
|
||||
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()
|
||||
map!![player_uuid] = status
|
||||
map!![profile] = status
|
||||
}
|
||||
|
||||
sendIfPresent()
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun Boolean?.asAddedStatus() = if (this == null) AddedStatus.DEFAULT else if (this) AddedStatus.ALLOWED else AddedStatus.BANNED
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package io.dico.parcels2.storage.migration
|
||||
|
||||
import io.dico.parcels2.storage.Storage
|
||||
import kotlinx.coroutines.experimental.Job
|
||||
|
||||
interface Migration {
|
||||
fun migrateTo(storage: Storage)
|
||||
fun migrateTo(storage: Storage): Job
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package io.dico.parcels2.storage.migration
|
||||
|
||||
interface MigrationFactory {
|
||||
fun getMigration()
|
||||
}
|
||||
@@ -1,37 +1,35 @@
|
||||
@file:Suppress("RedundantSuspendModifier", "DEPRECATION")
|
||||
|
||||
package io.dico.parcels2.storage.migration.plotme
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource
|
||||
import io.dico.parcels2.*
|
||||
import io.dico.parcels2.options.PlotmeMigrationOptions
|
||||
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.util.Vec2i
|
||||
import io.dico.parcels2.util.isValid
|
||||
import io.dico.parcels2.util.toUUID
|
||||
import io.dico.parcels2.util.uuid
|
||||
import kotlinx.coroutines.experimental.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.experimental.Job
|
||||
import kotlinx.coroutines.experimental.launch
|
||||
import org.bukkit.Bukkit
|
||||
import kotlinx.coroutines.experimental.newFixedThreadPoolContext
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.sql.Blob
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.Executors
|
||||
import javax.sql.DataSource
|
||||
|
||||
class PlotmeMigration(val parcelProvider: ParcelProvider,
|
||||
val worldMapper: Map<String, String>,
|
||||
val dataSourceFactory: () -> DataSource) : Migration {
|
||||
class PlotmeMigration(val options: PlotmeMigrationOptions) : Migration {
|
||||
private var dataSource: DataSource? = null
|
||||
private var database: Database? = null
|
||||
private var isShutdown: Boolean = false
|
||||
private val dispatcher = Executors.newSingleThreadExecutor { Thread(it, "PlotMe Migration Thread") }.asCoroutineDispatcher()
|
||||
private val mlogger = LoggerFactory.getLogger("PlotMe Migrator")
|
||||
val dispatcher = newFixedThreadPoolContext(1, "PlotMe Migration Thread")
|
||||
|
||||
private fun <T> transaction(statement: Transaction.() -> T) = org.jetbrains.exposed.sql.transactions.transaction(database!!, statement)
|
||||
|
||||
override fun migrateTo(storage: Storage) {
|
||||
launch(context = dispatcher) {
|
||||
override fun migrateTo(storage: Storage): Job {
|
||||
return launch(dispatcher) {
|
||||
init()
|
||||
doWork(storage)
|
||||
shutdown()
|
||||
@@ -39,8 +37,8 @@ class PlotmeMigration(val parcelProvider: ParcelProvider,
|
||||
}
|
||||
|
||||
fun init() {
|
||||
if (isShutdown) throw IllegalStateException()
|
||||
dataSource = dataSourceFactory()
|
||||
if (isShutdown || database != null) throw IllegalStateException()
|
||||
dataSource = options.storage.getDataSourceFactory()!!()
|
||||
database = Database.connect(dataSource!!)
|
||||
}
|
||||
|
||||
@@ -53,66 +51,61 @@ class PlotmeMigration(val parcelProvider: ParcelProvider,
|
||||
isShutdown = true
|
||||
}
|
||||
|
||||
val parcelsCache = hashMapOf<String, MutableMap<Vec2i, ParcelData>>()
|
||||
|
||||
private fun getMap(worldName: String): MutableMap<Vec2i, ParcelData>? {
|
||||
val mapped = worldMapper[worldName] ?: return null
|
||||
return parcelsCache.computeIfAbsent(mapped) { mutableMapOf() }
|
||||
}
|
||||
|
||||
private fun getData(worldName: String, position: Vec2i): ParcelData? {
|
||||
return getMap(worldName)?.computeIfAbsent(position) { ParcelDataHolder() }
|
||||
}
|
||||
|
||||
fun doWork(target: Storage): Unit = transaction {
|
||||
if (!PlotmePlotsT.exists()) {
|
||||
mlogger.warn("Plotme tables don't appear to exist. Exiting.")
|
||||
return@transaction
|
||||
suspend fun doWork(target: Storage) {
|
||||
val exit = transaction {
|
||||
(!PlotmePlotsT.exists()).also {
|
||||
if (it) mlogger.warn("Plotme tables don't appear to exist. Exiting.")
|
||||
}
|
||||
}
|
||||
parcelsCache.clear()
|
||||
if (exit) return
|
||||
|
||||
iterPlotmeTable(PlotmePlotsT) { data, row ->
|
||||
// in practice, owner_uuid is not null for any plot currently. It will convert well.
|
||||
data.owner = ParcelOwner(row[owner_uuid]?.toUUID(), row[owner_name])
|
||||
val worldCache = options.worldsFromTo.mapValues { ParcelWorldId(it.value) }
|
||||
|
||||
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 ->
|
||||
val uuid = row[player_uuid]?.toUUID()
|
||||
?: Bukkit.getOfflinePlayer(row[player_name]).takeIf { it.isValid }?.uuid
|
||||
?: return@iterPlotmeTable
|
||||
|
||||
data.setAddedStatus(uuid, AddedStatus.ALLOWED)
|
||||
}
|
||||
|
||||
iterPlotmeTable(PlotmeDeniedT) { data, row ->
|
||||
val uuid = row[PlotmeAllowedT.player_uuid]?.toUUID()
|
||||
?: Bukkit.getOfflinePlayer(row[PlotmeAllowedT.player_name]).takeIf { it.isValid }?.uuid
|
||||
?: return@iterPlotmeTable
|
||||
|
||||
data.setAddedStatus(uuid, AddedStatus.BANNED)
|
||||
}
|
||||
|
||||
for ((worldName, map) in parcelsCache) {
|
||||
val world = ParcelWorldId(worldName)
|
||||
for ((pos, data) in map) {
|
||||
val parcel = ParcelId(world, pos)
|
||||
target.setParcelData(parcel, data)
|
||||
fun PlotmePlotPlayerMap.transmitPlotmeAddedTable(kind: AddedStatus) {
|
||||
selectAll().forEach { row ->
|
||||
val parcel = getParcelId(this, row) ?: return@forEach
|
||||
val profile = StatusKey.safe(row[player_uuid]?.toUUID(), row[player_name]) ?: return@forEach
|
||||
target.setParcelPlayerStatus(parcel, profile, kind)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun Blob.toUUID(): UUID {
|
||||
val out = ByteArrayOutputStream(16)
|
||||
binaryStream.copyTo(out, bufferSize = 16)
|
||||
return out.toByteArray().toUUID()
|
||||
}
|
||||
|
||||
private inline fun <T : PlotmeTable> iterPlotmeTable(table: T, block: T.(ParcelData, ResultRow) -> Unit) {
|
||||
table.selectAll().forEach { row ->
|
||||
val data = getData(row[table.world_name], Vec2i(row[table.px], row[table.pz])) ?: return@forEach
|
||||
table.block(data, row)
|
||||
mlogger.info("Transmitting data from plotmeplots table")
|
||||
transaction {
|
||||
PlotmePlotsT.selectAll()
|
||||
.orderBy(PlotmePlotsT.world_name)
|
||||
.orderBy(with(SqlExpressionBuilder) { greater(PlotmePlotsT.px.abs(), PlotmePlotsT.pz.abs()) })
|
||||
.forEach { row ->
|
||||
val parcel = getParcelId(PlotmePlotsT, row) ?: return@forEach
|
||||
val owner = PlayerProfile.safe(row[PlotmePlotsT.owner_uuid]?.toUUID(), row[PlotmePlotsT.owner_name])
|
||||
target.setParcelOwner(parcel, owner)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -7,9 +7,9 @@ const val uppercase: Boolean = false
|
||||
fun String.toCorrectCase() = if (uppercase) this else toLowerCase()
|
||||
|
||||
sealed class PlotmeTable(name: String) : Table(name) {
|
||||
val px = PlotmePlotsT.integer("idX").primaryKey()
|
||||
val pz = PlotmePlotsT.integer("idZ").primaryKey()
|
||||
val world_name = PlotmePlotsT.varchar("world", 32).primaryKey()
|
||||
val px = integer("idX").primaryKey()
|
||||
val pz = integer("idZ").primaryKey()
|
||||
val world_name = varchar("world", 32).primaryKey()
|
||||
}
|
||||
|
||||
object PlotmePlotsT : PlotmeTable("plotmePlots".toCorrectCase()) {
|
||||
@@ -18,8 +18,8 @@ object PlotmePlotsT : PlotmeTable("plotmePlots".toCorrectCase()) {
|
||||
}
|
||||
|
||||
sealed class PlotmePlotPlayerMap(name: String) : PlotmeTable(name) {
|
||||
val player_name = PlotmePlotsT.varchar("player", 32)
|
||||
val player_uuid = PlotmePlotsT.blob("playerid").nullable()
|
||||
val player_name = varchar("player", 32)
|
||||
val player_uuid = blob("playerid").nullable()
|
||||
}
|
||||
|
||||
object PlotmeAllowedT : PlotmePlotPlayerMap("plotmeAllowed".toCorrectCase())
|
||||
|
||||
@@ -4,14 +4,20 @@ import io.dico.parcels2.logger
|
||||
import java.io.File
|
||||
|
||||
fun File.tryCreate(): Boolean {
|
||||
if (exists()) {
|
||||
return !isDirectory
|
||||
}
|
||||
val parent = parentFile
|
||||
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 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 <T> T?.isNullOr(condition: T.() -> Boolean): Boolean = this == null || condition()
|
||||
|
||||
@@ -2,12 +2,14 @@ package io.dico.parcels2.util
|
||||
|
||||
import io.dico.dicore.Formatting
|
||||
import io.dico.parcels2.ParcelsPlugin
|
||||
import io.dico.parcels2.PlayerProfile
|
||||
import io.dico.parcels2.logger
|
||||
import org.bukkit.OfflinePlayer
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.plugin.java.JavaPlugin
|
||||
|
||||
inline val OfflinePlayer.uuid get() = uniqueId
|
||||
|
||||
@Suppress("UsePropertyAccessSyntax")
|
||||
inline val OfflinePlayer.isValid
|
||||
get() = isOnline() || hasPlayedBefore()
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
<configuration debug="true">
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<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>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<root level="debug">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</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>
|
||||
Reference in New Issue
Block a user