Archived
0

Replace AddedData API with Privileges API, adding CAN_MANAGE and required changes

This commit is contained in:
Dico
2018-09-24 02:45:41 +01:00
parent e0bf8249bd
commit 1a440767b3
33 changed files with 552 additions and 413 deletions

View File

@@ -87,10 +87,11 @@ dependencies {
tasks { tasks {
removeIf { it is ShadowJar } removeIf { it is ShadowJar }
val compileKotlin by getting(KotlinCompile::class) { tasks.withType<KotlinCompile> {
kotlinOptions { kotlinOptions {
javaParameters = true javaParameters = true
suppressWarnings = true suppressWarnings = true
jvmTarget = "1.8"
//freeCompilerArgs = listOf("-XXLanguage:+InlineClasses", "-Xuse-experimental=kotlin.Experimental") //freeCompilerArgs = listOf("-XXLanguage:+InlineClasses", "-Xuse-experimental=kotlin.Experimental")
} }
} }

View File

@@ -1,62 +0,0 @@
package io.dico.parcels2
import io.dico.parcels2.AddedStatus.*
import org.bukkit.OfflinePlayer
enum class AddedStatus {
DEFAULT, ALLOWED, BANNED;
}
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 statusOfStar: AddedStatus
fun getStatus(key: StatusKey): AddedStatus
fun setStatus(key: StatusKey, status: AddedStatus): Boolean
fun casStatus(key: StatusKey, expect: AddedStatus, status: AddedStatus): Boolean =
getStatus(key) == expect && setStatus(key, status)
fun isAllowed(key: StatusKey) = getStatus(key) == ALLOWED
fun allow(key: StatusKey) = setStatus(key, ALLOWED)
fun disallow(key: StatusKey) = casStatus(key, ALLOWED, DEFAULT)
fun isBanned(key: StatusKey) = getStatus(key) == BANNED
fun ban(key: StatusKey) = setStatus(key, BANNED)
fun unban(key: StatusKey) = casStatus(key, BANNED, DEFAULT)
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: StatusKey
get() = PlayerProfile.nameless(this)
open class AddedDataHolder(override var addedMap: MutableAddedDataMap = MutableAddedDataMap()) : AddedData {
override var statusOfStar: AddedStatus = DEFAULT
override fun getStatus(key: StatusKey): AddedStatus = addedMap.getOrDefault(key, statusOfStar)
override fun setStatus(key: StatusKey, status: AddedStatus): Boolean {
return if (status == DEFAULT) addedMap.remove(key) != null
else addedMap.put(key, status) != status
}
}
interface GlobalAddedData : AddedData {
val owner: PlayerProfile
}
interface GlobalAddedDataManager {
operator fun get(owner: PlayerProfile): GlobalAddedData
}

View File

@@ -2,7 +2,6 @@ package io.dico.parcels2
import io.dico.parcels2.util.ext.getMaterialsWithWoodTypePrefix import io.dico.parcels2.util.ext.getMaterialsWithWoodTypePrefix
import org.bukkit.Material import org.bukkit.Material
import java.lang.IllegalArgumentException
import java.util.EnumMap import java.util.EnumMap
class Interactables class Interactables
@@ -113,13 +112,14 @@ val pathInteractableConfig: InteractableConfiguration = run {
interface InteractableConfiguration { interface InteractableConfiguration {
val interactableClasses: List<Interactables> get() = Interactables.classesById.filter { isInteractable(it) } val interactableClasses: List<Interactables> get() = Interactables.classesById.filter { isInteractable(it) }
fun isInteractable(material: Material): Boolean fun isInteractable(material: Material): Boolean
fun isInteractable(clazz: Interactables): Boolean fun isInteractable(clazz: Interactables): Boolean
fun setInteractable(clazz: Interactables, interactable: Boolean): Boolean fun setInteractable(clazz: Interactables, interactable: Boolean): Boolean
fun clear(): Boolean fun clear(): Boolean
fun copyFrom(other: InteractableConfiguration) {
Interactables.classesById.forEach { setInteractable(it, other.isInteractable(it)) } fun copyFrom(other: InteractableConfiguration) =
} Interactables.classesById.fold(false) { cur, elem -> setInteractable(elem, other.isInteractable(elem) || cur) }
operator fun invoke(material: Material) = isInteractable(material) operator fun invoke(material: Material) = isInteractable(material)
operator fun invoke(className: String) = isInteractable(Interactables[className]) operator fun invoke(className: String) = isInteractable(Interactables[className])

View File

@@ -1,7 +1,7 @@
package io.dico.parcels2 package io.dico.parcels2
import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.Vec2i
import io.dico.parcels2.util.ext.hasBuildAnywhere import io.dico.parcels2.util.ext.hasPermBuildAnywhere
import org.bukkit.Location import org.bukkit.Location
import org.bukkit.OfflinePlayer import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player import org.bukkit.entity.Player
@@ -37,7 +37,7 @@ interface Parcel : ParcelData {
val homeLocation: Location get() = world.blockManager.getHomeLocation(id) val homeLocation: Location get() = world.blockManager.getHomeLocation(id)
} }
interface ParcelData : AddedData { interface ParcelData : Privileges {
var owner: PlayerProfile? var owner: PlayerProfile?
val lastClaimTime: DateTime? val lastClaimTime: DateTime?
var ownerSignOutdated: Boolean var ownerSignOutdated: Boolean
@@ -54,14 +54,15 @@ interface ParcelData : AddedData {
} }
} }
class ParcelDataHolder(addedMap: MutableAddedDataMap = mutableMapOf()) class ParcelDataHolder(addedMap: MutablePrivilegeMap = mutableMapOf())
: ParcelData, AddedDataHolder(addedMap) { : ParcelData, PrivilegesHolder(addedMap) {
override var owner: PlayerProfile? = null override var owner: PlayerProfile? = null
override var lastClaimTime: DateTime? = null override var lastClaimTime: DateTime? = null
override var ownerSignOutdated = false override var ownerSignOutdated = false
override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean) = isAllowed(player.statusKey) override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean) =
hasPrivilegeToBuild(player)
|| owner.let { it != null && it.matches(player, allowNameMatch = false) } || owner.let { it != null && it.matches(player, allowNameMatch = false) }
|| (checkAdmin && player is Player && player.hasBuildAnywhere) || (checkAdmin && player is Player && player.hasPermBuildAnywhere)
override var interactableConfig: InteractableConfiguration = BitmaskInteractableConfiguration() override var interactableConfig: InteractableConfiguration = BitmaskInteractableConfiguration()
} }

View File

@@ -85,7 +85,7 @@ interface ParcelWorld : ParcelLocator, ParcelContainer {
val container: ParcelContainer val container: ParcelContainer
val locator: ParcelLocator val locator: ParcelLocator
val blockManager: ParcelBlockManager val blockManager: ParcelBlockManager
val globalAddedData: GlobalAddedDataManager val globalPrivileges: GlobalPrivilegesManager
val creationTime: DateTime? val creationTime: DateTime?

View File

@@ -6,7 +6,7 @@ import io.dico.dicore.command.ICommandDispatcher
import io.dico.parcels2.blockvisitor.TickWorktimeLimiter import io.dico.parcels2.blockvisitor.TickWorktimeLimiter
import io.dico.parcels2.blockvisitor.WorktimeLimiter import io.dico.parcels2.blockvisitor.WorktimeLimiter
import io.dico.parcels2.command.getParcelCommands import io.dico.parcels2.command.getParcelCommands
import io.dico.parcels2.defaultimpl.GlobalAddedDataManagerImpl import io.dico.parcels2.defaultimpl.GlobalPrivilegesManagerImpl
import io.dico.parcels2.defaultimpl.ParcelProviderImpl import io.dico.parcels2.defaultimpl.ParcelProviderImpl
import io.dico.parcels2.listener.ParcelEntityTracker import io.dico.parcels2.listener.ParcelEntityTracker
import io.dico.parcels2.listener.ParcelListeners import io.dico.parcels2.listener.ParcelListeners
@@ -35,7 +35,7 @@ class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginScheduler {
lateinit var options: Options; private set lateinit var options: Options; private set
lateinit var parcelProvider: ParcelProvider; private set lateinit var parcelProvider: ParcelProvider; private set
lateinit var storage: Storage; private set lateinit var storage: Storage; private set
lateinit var globalAddedData: GlobalAddedDataManager; private set lateinit var globalPrivileges: GlobalPrivilegesManager; private set
val registrator = Registrator(this) val registrator = Registrator(this)
lateinit var entityTracker: ParcelEntityTracker; private set lateinit var entityTracker: ParcelEntityTracker; private set
@@ -83,7 +83,7 @@ class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginScheduler {
return false return false
} }
globalAddedData = GlobalAddedDataManagerImpl(this) globalPrivileges = GlobalPrivilegesManagerImpl(this)
entityTracker = ParcelEntityTracker(parcelProvider) entityTracker = ParcelEntityTracker(parcelProvider)
} catch (ex: Exception) { } catch (ex: Exception) {
plogger.error("Error loading options", ex) plogger.error("Error loading options", ex)

View File

@@ -0,0 +1,134 @@
package io.dico.parcels2
import io.dico.parcels2.Privilege.*
import org.bukkit.OfflinePlayer
enum class Privilege(
val number: Int,
val transient: Boolean = false
) {
BANNED(1),
DEFAULT(2),
CAN_BUILD(3),
CAN_MANAGE(4),
OWNER(-1, transient = true),
ADMIN(-1, transient = true);
fun requireNonTransient(): Privilege {
if (transient) {
throw IllegalArgumentException("Transient privilege $this is invalid")
}
return this
}
/*
fun canEnter() = this >= BANNED
fun canBuild() = this >= CAN_BUILD
fun canManage() = this >= CAN_MANAGE
*/
companion object {
fun getByNumber(number: Int) = safeGetByNumber(number)
?: throw IllegalArgumentException(
if (number == -1) "Transient privileges are not stored"
else "Privilege with number $number doesn't exist"
)
fun safeGetByNumber(id: Int) =
when (id) {
1 -> BANNED
2 -> DEFAULT
3 -> CAN_BUILD
4 -> CAN_MANAGE
else -> null
}
}
}
typealias PrivilegeKey = PlayerProfile.Real
typealias MutablePrivilegeMap = MutableMap<PrivilegeKey, Privilege>
typealias PrivilegeMap = Map<PrivilegeKey, Privilege>
@Suppress("FunctionName")
fun MutablePrivilegeMap(): MutablePrivilegeMap = hashMapOf()
/**
* Privileges object never returns a transient privilege.
*/
interface Privileges {
val map: PrivilegeMap
var privilegeOfStar: Privilege
fun privilege(key: PrivilegeKey): Privilege
fun privilege(player: OfflinePlayer) = privilege(player.privilegeKey)
fun setPrivilege(key: PrivilegeKey, privilege: Privilege): Boolean
fun setPrivilege(player: OfflinePlayer, privilege: Privilege) = setPrivilege(player.privilegeKey, privilege)
fun changePrivilege(key: PrivilegeKey, expect: Privilege, update: Privilege): Boolean =
(when { // if CAN_BUILD is expected, CAN_MANAGE is valid.
expect > DEFAULT -> privilege(key) >= expect
expect == DEFAULT -> privilege(key) == expect
else -> privilege(key) <= expect
})
&& setPrivilege(key, update)
fun hasPrivilegeToManage(key: PrivilegeKey) = privilege(key) >= CAN_MANAGE
fun allowManage(key: PrivilegeKey) = setPrivilege(key, CAN_MANAGE)
fun disallowManage(key: PrivilegeKey) = changePrivilege(key, CAN_MANAGE, CAN_BUILD)
fun hasPrivilegeToBuild(key: PrivilegeKey) = privilege(key) >= CAN_BUILD
fun allowBuild(key: PrivilegeKey) = setPrivilege(key, CAN_BUILD)
fun disallowBuild(key: PrivilegeKey) = changePrivilege(key, CAN_BUILD, DEFAULT)
fun isBanned(key: PrivilegeKey) = privilege(key) == BANNED
fun ban(key: PrivilegeKey) = setPrivilege(key, BANNED)
fun unban(key: PrivilegeKey) = changePrivilege(key, BANNED, DEFAULT)
/* OfflinePlayer overloads */
fun hasPrivilegeToManage(player: OfflinePlayer) = hasPrivilegeToManage(player.privilegeKey)
fun allowManage(player: OfflinePlayer) = allowManage(player.privilegeKey)
fun disallowManage(player: OfflinePlayer) = disallowManage(player.privilegeKey)
fun hasPrivilegeToBuild(player: OfflinePlayer) = hasPrivilegeToBuild(player.privilegeKey)
fun allowBuild(player: OfflinePlayer) = allowBuild(player.privilegeKey)
fun disallowBuild(player: OfflinePlayer) = disallowBuild(player.privilegeKey)
fun isBanned(player: OfflinePlayer) = isBanned(player.privilegeKey)
fun ban(player: OfflinePlayer) = ban(player.privilegeKey)
fun unban(player: OfflinePlayer) = unban(player.privilegeKey)
}
inline val OfflinePlayer.privilegeKey: PrivilegeKey
get() = PlayerProfile.nameless(this)
open class PrivilegesHolder(override var map: MutablePrivilegeMap = MutablePrivilegeMap()) : Privileges {
override var privilegeOfStar: Privilege = DEFAULT
set(value) = run { field = value.requireNonTransient() }
override fun privilege(key: PrivilegeKey): Privilege = map.getOrDefault(key, privilegeOfStar)
override fun setPrivilege(key: PrivilegeKey, privilege: Privilege): Boolean {
privilege.requireNonTransient()
if (key.isStar) {
if (privilegeOfStar == privilege) return false
privilegeOfStar = privilege
return true
}
return if (privilege == DEFAULT) map.remove(key) != null
else map.put(key, privilege) != privilege
}
}
interface GlobalPrivileges : Privileges {
val owner: PlayerProfile
}
interface GlobalPrivilegesManager {
operator fun get(owner: PlayerProfile): GlobalPrivileges
}

View File

@@ -4,7 +4,7 @@ import io.dico.dicore.command.*
import io.dico.parcels2.ParcelWorld import io.dico.parcels2.ParcelWorld
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.PlayerProfile import io.dico.parcels2.PlayerProfile
import io.dico.parcels2.util.ext.hasAdminManage import io.dico.parcels2.util.ext.hasPermAdminManage
import io.dico.parcels2.util.ext.parcelLimit import io.dico.parcels2.util.ext.parcelLimit
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.bukkit.plugin.Plugin import org.bukkit.plugin.Plugin
@@ -26,7 +26,7 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei
} }
protected suspend fun checkParcelLimit(player: Player, world: ParcelWorld) { protected suspend fun checkParcelLimit(player: Player, world: ParcelWorld) {
if (player.hasAdminManage) return if (player.hasPermAdminManage) return
val numOwnedParcels = plugin.storage.getOwnedParcels(PlayerProfile(player)).await() val numOwnedParcels = plugin.storage.getOwnedParcels(PlayerProfile(player)).await()
.filter { it.worldId.equals(world.id) }.size .filter { it.worldId.equals(world.id) }.size

View File

@@ -1,56 +0,0 @@
package io.dico.parcels2.command
import io.dico.dicore.command.Validate
import io.dico.dicore.command.annotation.Cmd
import io.dico.dicore.command.annotation.Desc
import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.util.ext.hasAdminManage
import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player
class CommandsAddedStatusLocal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@Cmd("allow", aliases = ["add", "permit"])
@Desc("Allows a player to build on this parcel",
shortVersion = "allows a player to build on this parcel")
@ParcelRequire(owner = true)
fun ParcelScope.cmdAllow(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(parcel.owner != null || sender.hasAdminManage, "This parcel is unowned")
Validate.isTrue(!parcel.owner!!.matches(player), "The target already owns the parcel")
Validate.isTrue(parcel.allow(player), "${player.name} is already allowed to build on this parcel")
return "${player.name} is now allowed to build on this parcel"
}
@Cmd("disallow", aliases = ["remove", "forbid"])
@Desc("Disallows a player to build on this parcel,",
"they won't be allowed to anymore",
shortVersion = "disallows a player to build on this parcel")
@ParcelRequire(owner = true)
fun ParcelScope.cmdDisallow(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(parcel.disallow(player), "${player.name} is not currently allowed to build on this parcel")
return "${player.name} is not allowed to build on this parcel anymore"
}
@Cmd("ban", aliases = ["deny"])
@Desc("Bans a player from this parcel,",
"making them unable to enter",
shortVersion = "bans a player from this parcel")
@ParcelRequire(owner = true)
fun ParcelScope.cmdBan(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(parcel.owner != null || sender.hasAdminManage, "This parcel is unowned")
Validate.isTrue(!parcel.owner!!.matches(player), "The owner cannot be banned from the parcel")
Validate.isTrue(parcel.ban(player), "${player.name} is already banned from this parcel")
return "${player.name} is now banned from this parcel"
}
@Cmd("unban", aliases = ["undeny"])
@Desc("Unbans a player from this parcel,",
"they will be able to enter it again",
shortVersion = "unbans a player from this parcel")
@ParcelRequire(owner = true)
fun ParcelScope.cmdUnban(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(parcel.unban(player), "${player.name} is not currently banned from this parcel")
return "${player.name} is not banned from this parcel anymore"
}
}

View File

@@ -5,11 +5,12 @@ import io.dico.dicore.command.annotation.Cmd
import io.dico.dicore.command.annotation.Flag import io.dico.dicore.command.annotation.Flag
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.PlayerProfile import io.dico.parcels2.PlayerProfile
import io.dico.parcels2.Privilege
class CommandsAdmin(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { class CommandsAdmin(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@Cmd("setowner") @Cmd("setowner")
@ParcelRequire(admin = true) @RequireParcelPrivilege(Privilege.ADMIN)
fun ParcelScope.cmdSetowner(target: PlayerProfile): Any? { fun ParcelScope.cmdSetowner(target: PlayerProfile): Any? {
parcel.owner = target parcel.owner = target
@@ -18,14 +19,14 @@ class CommandsAdmin(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
} }
@Cmd("dispose") @Cmd("dispose")
@ParcelRequire(admin = true) @RequireParcelPrivilege(Privilege.ADMIN)
fun ParcelScope.cmdDispose(): Any? { fun ParcelScope.cmdDispose(): Any? {
parcel.dispose() parcel.dispose()
return "Data of (${parcel.id.idString}) has been disposed" return "Data of (${parcel.id.idString}) has been disposed"
} }
@Cmd("reset") @Cmd("reset")
@ParcelRequire(admin = true) @RequireParcelPrivilege(Privilege.ADMIN)
fun ParcelScope.cmdReset(context: ExecutionContext, @Flag sure: Boolean): Any? { fun ParcelScope.cmdReset(context: ExecutionContext, @Flag sure: Boolean): Any? {
if (!sure) return areYouSureMessage(context) if (!sure) return areYouSureMessage(context)
parcel.dispose() parcel.dispose()
@@ -34,9 +35,10 @@ class CommandsAdmin(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
} }
@Cmd("swap") @Cmd("swap")
@RequireParcelPrivilege(Privilege.ADMIN)
fun ParcelScope.cmdSwap(context: ExecutionContext, @Flag sure: Boolean): Any? { fun ParcelScope.cmdSwap(context: ExecutionContext, @Flag sure: Boolean): Any? {
if (!sure) return areYouSureMessage(context) if (!sure) return areYouSureMessage(context)
TODO() TODO("implement swap")
} }

View File

@@ -5,6 +5,7 @@ import io.dico.dicore.command.EMessageType
import io.dico.dicore.command.ExecutionContext import io.dico.dicore.command.ExecutionContext
import io.dico.dicore.command.annotation.Cmd import io.dico.dicore.command.annotation.Cmd
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.Privilege
import io.dico.parcels2.blockvisitor.RegionTraverser import io.dico.parcels2.blockvisitor.RegionTraverser
import io.dico.parcels2.doBlockOperation import io.dico.parcels2.doBlockOperation
import org.bukkit.Bukkit import org.bukkit.Bukkit
@@ -30,7 +31,7 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
} }
@Cmd("make_mess") @Cmd("make_mess")
@ParcelRequire(owner = true) @RequireParcelPrivilege(Privilege.OWNER)
fun ParcelScope.cmdMakeMess(context: ExecutionContext) { fun ParcelScope.cmdMakeMess(context: ExecutionContext) {
val server = plugin.server val server = plugin.server
val blockDatas = arrayOf( val blockDatas = arrayOf(
@@ -47,8 +48,10 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
world.blockManager.doBlockOperation(parcel.id, traverser = RegionTraverser.upward) { block -> world.blockManager.doBlockOperation(parcel.id, traverser = RegionTraverser.upward) { block ->
block.blockData = blockDatas[random.nextInt(7)] block.blockData = blockDatas[random.nextInt(7)]
}.onProgressUpdate(1000, 1000) { progress, elapsedTime -> }.onProgressUpdate(1000, 1000) { progress, elapsedTime ->
context.sendMessage(EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed" context.sendMessage(
.format(progress * 100, elapsedTime / 1000.0)) EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed"
.format(progress * 100, elapsedTime / 1000.0)
)
} }
} }

View File

@@ -9,8 +9,8 @@ import io.dico.dicore.command.annotation.RequireParameters
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.PlayerProfile import io.dico.parcels2.PlayerProfile
import io.dico.parcels2.command.ParcelTarget.Kind import io.dico.parcels2.command.ParcelTarget.Kind
import io.dico.parcels2.util.ext.hasAdminManage
import io.dico.parcels2.util.ext.hasParcelHomeOthers import io.dico.parcels2.util.ext.hasParcelHomeOthers
import io.dico.parcels2.util.ext.hasPermAdminManage
import io.dico.parcels2.util.ext.uuid import io.dico.parcels2.util.ext.uuid
import org.bukkit.block.Biome import org.bukkit.block.Biome
import org.bukkit.entity.Player import org.bukkit.entity.Player
@@ -99,7 +99,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
) )
suspend fun ParcelScope.cmdClaim(player: Player): Any? { suspend fun ParcelScope.cmdClaim(player: Player): Any? {
checkConnected("be claimed") checkConnected("be claimed")
parcel.owner.takeIf { !player.hasAdminManage }?.let { parcel.owner.takeIf { !player.hasPermAdminManage }?.let {
error(if (it.matches(player)) "You already own this parcel" else "This parcel is not available") error(if (it.matches(player)) "You already own this parcel" else "This parcel is not available")
} }
@@ -110,14 +110,14 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@Cmd("unclaim") @Cmd("unclaim")
@Desc("Unclaims this parcel") @Desc("Unclaims this parcel")
@ParcelRequire(owner = true) @RequireParcelPrivilege(Privilege.OWNER)
fun ParcelScope.cmdUnclaim(player: Player): Any? { fun ParcelScope.cmdUnclaim(player: Player): Any? {
parcel.dispose() parcel.dispose()
return "Your parcel has been disposed" return "Your parcel has been disposed"
} }
@Cmd("clear") @Cmd("clear")
@ParcelRequire(owner = true) @RequireParcelPrivilege(Privilege.OWNER)
fun ParcelScope.cmdClear(context: ExecutionContext, @Flag sure: Boolean): Any? { fun ParcelScope.cmdClear(context: ExecutionContext, @Flag sure: Boolean): Any? {
if (!sure) return areYouSureMessage(context) if (!sure) return areYouSureMessage(context)
@@ -126,7 +126,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
} }
@Cmd("setbiome") @Cmd("setbiome")
@ParcelRequire(owner = true) @RequireParcelPrivilege(Privilege.OWNER)
fun ParcelScope.cmdSetbiome(context: ExecutionContext, biome: Biome): Any? { fun ParcelScope.cmdSetbiome(context: ExecutionContext, biome: Biome): Any? {
Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel") Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel")
world.blockManager.setBiome(parcel.id, biome) world.blockManager.setBiome(parcel.id, biome)

View File

@@ -1,61 +0,0 @@
package io.dico.parcels2.command
import io.dico.dicore.command.CommandBuilder
import io.dico.dicore.command.Validate
import io.dico.dicore.command.annotation.Cmd
import io.dico.dicore.command.annotation.Desc
import io.dico.dicore.command.annotation.RequireParameters
import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelsPlugin
import org.bukkit.entity.Player
import kotlin.reflect.KMutableProperty
class CommandsParcelOptions(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
/* TODO options
@Cmd("inputs")
@Desc("Sets whether players who are not allowed to",
"build here can use levers, buttons,",
"pressure plates, tripwire or redstone ore",
shortVersion = "allows using inputs")
@RequireParameters(0)
fun ParcelScope.cmdInputs(player: Player, enabled: Boolean?): Any? {
return runOptionCommand(player, Parcel::allowInteractInputs, enabled, "using levers, buttons, etc.")
}
@Cmd("inventory")
@Desc("Sets whether players who are not allowed to",
"build here can interact with inventories",
shortVersion = "allows editing inventories")
@RequireParameters(0)
fun ParcelScope.cmdInventory(player: Player, enabled: Boolean?): Any? {
return runOptionCommand(player, Parcel::allowInteractInventory, enabled, "interaction with inventories")
}*/
private inline val Boolean.enabledWord get() = if (this) "enabled" else "disabled"
private fun ParcelScope.runOptionCommand(player: Player,
property: KMutableProperty<Boolean>,
enabled: Boolean?,
desc: String): Any? {
checkConnected("have their options changed")
val current = property.getter.call(parcel)
if (enabled == null) {
val word = if (current) "" else "not "
return "This parcel does ${word}allow $desc"
}
checkCanManage(player, "change its options")
Validate.isTrue(current != enabled, "That option was already ${enabled.enabledWord}")
property.setter.call(parcel, enabled)
return "That option is now ${enabled.enabledWord}"
}
companion object {
private const val descShort = "changes interaction options for this parcel"
private val desc = arrayOf("Sets whether players who are not allowed to", "build here can interact with certain things.")
fun setGroupDescription(builder: CommandBuilder) {
builder.setGroupDescription(descShort, *desc)
}
}
}

View File

@@ -3,47 +3,72 @@ package io.dico.parcels2.command
import io.dico.dicore.command.Validate import io.dico.dicore.command.Validate
import io.dico.dicore.command.annotation.Cmd import io.dico.dicore.command.annotation.Cmd
import io.dico.dicore.command.annotation.Desc import io.dico.dicore.command.annotation.Desc
import io.dico.parcels2.GlobalAddedData import io.dico.parcels2.GlobalPrivileges
import io.dico.parcels2.GlobalAddedDataManager import io.dico.parcels2.GlobalPrivilegesManager
import io.dico.parcels2.PlayerProfile
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.PlayerProfile
import org.bukkit.OfflinePlayer import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player import org.bukkit.entity.Player
class CommandsAddedStatusGlobal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { class CommandsPrivilegesGlobal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
private inline val data get() = plugin.globalAddedData private inline val data get() = plugin.globalPrivileges
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
private inline operator fun GlobalAddedDataManager.get(player: OfflinePlayer): GlobalAddedData = data[PlayerProfile(player)] private inline operator fun GlobalPrivilegesManager.get(player: OfflinePlayer): GlobalPrivileges = this[PlayerProfile(player)]
@Cmd("entrust")
@Desc(
"Allows a player to manage this parcel",
shortVersion = "allows a player to manage this parcel"
)
fun cmdEntrust(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(player != sender, "The target cannot be yourself")
Validate.isTrue(data[sender].allowManage(player), "${player.name} is already allowed to manage globally")
return "${player.name} is now allowed to manage globally"
}
@Cmd("distrust")
@Desc(
"Disallows a player to manage globally,",
"they will still be able to build",
shortVersion = "disallows a player to manage globally"
)
fun cmdDistrust(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(data[sender].disallowManage(player), "${player.name} is not currently allowed to manage globally")
return "${player.name} is not allowed to manage globally anymore"
}
@Cmd("allow", aliases = ["add", "permit"]) @Cmd("allow", aliases = ["add", "permit"])
@Desc("Globally allows a player to build on all", @Desc(
"Globally allows a player to build on all",
"the parcels that you own.", "the parcels that you own.",
shortVersion = "globally allows a player to build on your parcels") shortVersion = "globally allows a player to build on your parcels"
@ParcelRequire(owner = true) )
fun cmdAllow(sender: Player, player: OfflinePlayer): Any? { fun cmdAllow(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(player != sender, "The target cannot be yourself") Validate.isTrue(player != sender, "The target cannot be yourself")
Validate.isTrue(data[sender].allow(player), "${player.name} is already allowed globally") Validate.isTrue(data[sender].allowBuild(player), "${player.name} is already allowed globally")
return "${player.name} is now allowed to build on all your parcels" return "${player.name} is now allowed to build on all your parcels"
} }
@Cmd("disallow", aliases = ["remove", "forbid"]) @Cmd("disallow", aliases = ["remove", "forbid"])
@Desc("Globally disallows a player to build on", @Desc(
"Globally disallows a player to build on",
"the parcels that you own.", "the parcels that you own.",
"If the player is allowed to build on specific", "If the player is allowed to build on specific",
"parcels, they can still build there.", "parcels, they can still build there.",
shortVersion = "globally disallows a player to build on your parcels") shortVersion = "globally disallows a player to build on your parcels"
@ParcelRequire(owner = true) )
fun cmdDisallow(sender: Player, player: OfflinePlayer): Any? { fun cmdDisallow(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(player != sender, "The target cannot be yourself") Validate.isTrue(player != sender, "The target cannot be yourself")
Validate.isTrue(data[sender].disallow(player), "${player.name} is not currently allowed globally") Validate.isTrue(data[sender].disallowBuild(player), "${player.name} is not currently allowed globally")
return "${player.name} is not allowed to build on all your parcels anymore" return "${player.name} is not allowed to build on all your parcels anymore"
} }
@Cmd("ban", aliases = ["deny"]) @Cmd("ban", aliases = ["deny"])
@Desc("Globally bans a player from all the parcels", @Desc(
"Globally bans a player from all the parcels",
"that you own, making them unable to enter.", "that you own, making them unable to enter.",
shortVersion = "globally bans a player from your parcels") shortVersion = "globally bans a player from your parcels"
@ParcelRequire(owner = true) )
fun cmdBan(sender: Player, player: OfflinePlayer): Any? { fun cmdBan(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(player != sender, "The target cannot be yourself") Validate.isTrue(player != sender, "The target cannot be yourself")
Validate.isTrue(data[sender].ban(player), "${player.name} is already banned from all your parcels") Validate.isTrue(data[sender].ban(player), "${player.name} is already banned from all your parcels")
@@ -51,12 +76,13 @@ class CommandsAddedStatusGlobal(plugin: ParcelsPlugin) : AbstractParcelCommands(
} }
@Cmd("unban", aliases = ["undeny"]) @Cmd("unban", aliases = ["undeny"])
@Desc("Globally unbans a player from all the parcels", @Desc(
"Globally unbans a player from all the parcels",
"that you own, they can enter again.", "that you own, they can enter again.",
"If the player is banned from specific parcels,", "If the player is banned from specific parcels,",
"they will still be banned there.", "they will still be banned there.",
shortVersion = "globally unbans a player from your parcels") shortVersion = "globally unbans a player from your parcels"
@ParcelRequire(owner = true) )
fun cmdUnban(sender: Player, player: OfflinePlayer): Any? { fun cmdUnban(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(data[sender].unban(player), "${player.name} is not currently banned from all your parcels") Validate.isTrue(data[sender].unban(player), "${player.name} is not currently banned from all your parcels")
return "${player.name} is not banned from all your parcels anymore" return "${player.name} is not banned from all your parcels anymore"

View File

@@ -0,0 +1,90 @@
package io.dico.parcels2.command
import io.dico.dicore.command.Validate
import io.dico.dicore.command.annotation.Cmd
import io.dico.dicore.command.annotation.Desc
import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.Privilege
import io.dico.parcels2.util.ext.hasPermAdminManage
import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player
class CommandsPrivilegesLocal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@Cmd("entrust")
@Desc(
"Allows a player to manage this parcel",
shortVersion = "allows a player to manage this parcel"
)
@RequireParcelPrivilege(Privilege.OWNER)
fun ParcelScope.cmdEntrust(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(parcel.owner != null || sender.hasPermAdminManage, "This parcel is unowned")
Validate.isTrue(!parcel.owner!!.matches(player), "The target already owns the parcel")
Validate.isTrue(parcel.allowManage(player), "${player.name} is already allowed to manage this parcel")
return "${player.name} is now allowed to manage this parcel"
}
@Cmd("distrust")
@Desc(
"Disallows a player to manage this parcel,",
"they will still be able to build",
shortVersion = "disallows a player to manage this parcel"
)
@RequireParcelPrivilege(Privilege.OWNER)
fun ParcelScope.cmdDistrust(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(parcel.disallowManage(player), "${player.name} is not currently allowed to manage this parcel")
return "${player.name} is not allowed to manage this parcel anymore"
}
@Cmd("allow", aliases = ["add", "permit"])
@Desc(
"Allows a player to build on this parcel",
shortVersion = "allows a player to build on this parcel"
)
@RequireParcelPrivilege(Privilege.CAN_MANAGE)
fun ParcelScope.cmdAllow(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(parcel.owner != null || sender.hasPermAdminManage, "This parcel is unowned")
Validate.isTrue(!parcel.owner!!.matches(player), "The target already owns the parcel")
Validate.isTrue(parcel.allowBuild(player), "${player.name} is already allowed to build on this parcel")
return "${player.name} is now allowed to build on this parcel"
}
@Cmd("disallow", aliases = ["remove", "forbid"])
@Desc(
"Disallows a player to build on this parcel,",
"they won't be allowed to anymore",
shortVersion = "disallows a player to build on this parcel"
)
@RequireParcelPrivilege(Privilege.CAN_MANAGE)
fun ParcelScope.cmdDisallow(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(parcel.disallowBuild(player), "${player.name} is not currently allowed to build on this parcel")
return "${player.name} is not allowed to build on this parcel anymore"
}
@Cmd("ban", aliases = ["deny"])
@Desc(
"Bans a player from this parcel,",
"making them unable to enter",
shortVersion = "bans a player from this parcel"
)
@RequireParcelPrivilege(Privilege.CAN_MANAGE)
fun ParcelScope.cmdBan(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(parcel.owner != null || sender.hasPermAdminManage, "This parcel is unowned")
Validate.isTrue(!parcel.owner!!.matches(player), "The owner cannot be banned from the parcel")
Validate.isTrue(parcel.ban(player), "${player.name} is already banned from this parcel")
return "${player.name} is now banned from this parcel"
}
@Cmd("unban", aliases = ["undeny"])
@Desc(
"Unbans a player from this parcel,",
"they will be able to enter it again",
shortVersion = "unbans a player from this parcel"
)
@RequireParcelPrivilege(Privilege.CAN_MANAGE)
fun ParcelScope.cmdUnban(sender: Player, player: OfflinePlayer): Any? {
Validate.isTrue(parcel.unban(player), "${player.name} is not currently banned from this parcel")
return "${player.name} is not banned from this parcel anymore"
}
}

View File

@@ -4,6 +4,7 @@ import io.dico.dicore.command.CommandBuilder
import io.dico.dicore.command.ICommandAddress import io.dico.dicore.command.ICommandAddress
import io.dico.dicore.command.ICommandDispatcher import io.dico.dicore.command.ICommandDispatcher
import io.dico.dicore.command.registration.reflect.ReflectiveRegistration import io.dico.dicore.command.registration.reflect.ReflectiveRegistration
import io.dico.parcels2.Interactables
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.logger import io.dico.parcels2.logger
import java.util.LinkedList import java.util.LinkedList
@@ -20,15 +21,25 @@ fun getParcelCommands(plugin: ParcelsPlugin): ICommandDispatcher =
group("parcel", "plot", "plots", "p") { group("parcel", "plot", "plots", "p") {
addRequiredPermission("parcels.command") addRequiredPermission("parcels.command")
registerCommands(CommandsGeneral(plugin)) registerCommands(CommandsGeneral(plugin))
registerCommands(CommandsAddedStatusLocal(plugin)) registerCommands(CommandsPrivilegesLocal(plugin))
group("option", "opt", "o") { group("option", "opt", "o") {
CommandsParcelOptions.setGroupDescription(this) setGroupDescription(
registerCommands(CommandsParcelOptions(plugin)) "changes interaction options for this parcel",
"Sets whether players who are not allowed to",
"build here can interact with certain things."
)
group("interact", "i") {
val command = ParcelOptionsInteractCommand(plugin.parcelProvider)
Interactables.classesById.forEach {
addSubCommand(it.name, command)
}
}
} }
group("global", "g") { group("global", "g") {
registerCommands(CommandsAddedStatusGlobal(plugin)) registerCommands(CommandsPrivilegesGlobal(plugin))
} }
group("admin", "a") { group("admin", "a") {

View File

@@ -7,7 +7,9 @@ import io.dico.dicore.command.Validate
import io.dico.parcels2.Parcel import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelProvider import io.dico.parcels2.ParcelProvider
import io.dico.parcels2.ParcelWorld import io.dico.parcels2.ParcelWorld
import io.dico.parcels2.util.ext.hasAdminManage import io.dico.parcels2.Privilege
import io.dico.parcels2.Privilege.*
import io.dico.parcels2.util.ext.hasPermAdminManage
import io.dico.parcels2.util.ext.uuid import io.dico.parcels2.util.ext.uuid
import org.bukkit.entity.Player import org.bukkit.entity.Player
import java.lang.reflect.Method import java.lang.reflect.Method
@@ -18,44 +20,55 @@ import kotlin.reflect.jvm.kotlinFunction
@Target(AnnotationTarget.FUNCTION) @Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
annotation class ParcelRequire(val admin: Boolean = false, val owner: Boolean = false) annotation class RequireParcelPrivilege(val privilege: Privilege)
/*
@Target(AnnotationTarget.FUNCTION) @Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
annotation class SuspensionTimeout(val millis: Int) annotation class SuspensionTimeout(val millis: Int)
*/
open class WorldScope(val world: ParcelWorld) : ICommandReceiver open class WorldScope(val world: ParcelWorld) : ICommandReceiver
open class ParcelScope(val parcel: Parcel) : WorldScope(parcel.world) { open class ParcelScope(val parcel: Parcel) : WorldScope(parcel.world) {
fun checkCanManage(player: Player, action: String) = Validate.isTrue(player.hasAdminManage || parcel.isOwner(player.uuid), fun checkCanManage(player: Player, action: String) = Validate.isTrue(
"You must own this parcel to $action") player.hasPermAdminManage || parcel.hasPrivilegeToManage(player),
"You must own this parcel to $action"
)
} }
fun getParcelCommandReceiver(parcelProvider: ParcelProvider, context: ExecutionContext, method: Method, cmdName: String): ICommandReceiver { fun getParcelCommandReceiver(parcelProvider: ParcelProvider, context: ExecutionContext, method: Method, cmdName: String): ICommandReceiver {
val player = context.sender as Player val player = context.sender as Player
val function = method.kotlinFunction!! val function = method.kotlinFunction!!
val receiverType = function.extensionReceiverParameter!!.type val receiverType = function.extensionReceiverParameter!!.type
val require = function.findAnnotation<ParcelRequire>() val require = function.findAnnotation<RequireParcelPrivilege>()
val admin = require?.admin == true
val owner = require?.owner == true
return when (receiverType.jvmErasure) { return when (receiverType.jvmErasure) {
ParcelScope::class -> ParcelScope(parcelProvider.getParcelRequired(player, admin, owner)) ParcelScope::class -> ParcelScope(parcelProvider.getParcelRequired(player, require?.privilege))
WorldScope::class -> WorldScope(parcelProvider.getWorldRequired(player, admin)) WorldScope::class -> WorldScope(parcelProvider.getWorldRequired(player, require?.privilege == ADMIN))
else -> throw InternalError("Invalid command receiver type") else -> throw InternalError("Invalid command receiver type")
} }
} }
fun ParcelProvider.getWorldRequired(player: Player, admin: Boolean = false): ParcelWorld { fun ParcelProvider.getWorldRequired(player: Player, admin: Boolean = false): ParcelWorld {
if (admin) Validate.isTrue(player.hasAdminManage, "You must have admin rights to use that command") if (admin) Validate.isTrue(player.hasPermAdminManage, "You must have admin rights to use that command")
return getWorld(player.world) return getWorld(player.world)
?: throw CommandException("You must be in a parcel world to use that command") ?: throw CommandException("You must be in a parcel world to use that command")
} }
fun ParcelProvider.getParcelRequired(player: Player, admin: Boolean = false, own: Boolean = false): Parcel { fun ParcelProvider.getParcelRequired(player: Player, privilege: Privilege? = null): Parcel {
val parcel = getWorldRequired(player, admin = admin).getParcelAt(player) val parcel = getWorldRequired(player, admin = privilege == ADMIN).getParcelAt(player)
?: throw CommandException("You must be in a parcel to use that command") ?: throw CommandException("You must be in a parcel to use that command")
if (own) Validate.isTrue(parcel.isOwner(player.uuid) || player.hasAdminManage,
"You must own this parcel to use that command") if (!player.hasPermAdminManage) {
@Suppress("NON_EXHAUSTIVE_WHEN")
when (privilege) {
OWNER ->
Validate.isTrue(parcel.isOwner(player.uuid), "You must own this parcel to use that command")
CAN_MANAGE ->
Validate.isTrue(parcel.hasPrivilegeToManage(player), "You must have management privileges on this parcel to use that command")
}
}
return parcel return parcel
} }

View File

@@ -0,0 +1,36 @@
package io.dico.parcels2.command
import io.dico.dicore.command.Command
import io.dico.dicore.command.CommandException
import io.dico.dicore.command.ExecutionContext
import io.dico.dicore.command.IContextFilter
import io.dico.dicore.command.parameter.type.ParameterTypes
import io.dico.parcels2.Interactables
import io.dico.parcels2.ParcelProvider
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
class ParcelOptionsInteractCommand(val parcelProvider: ParcelProvider) : Command() {
init {
addContextFilter(IContextFilter.PLAYER_ONLY)
addParameter("allowed", "allowed", ParameterTypes.BOOLEAN)
}
override fun execute(sender: CommandSender, context: ExecutionContext): String? {
val parcel = parcelProvider.getParcelRequired(sender as Player, owner = true)
val interactableClassName = context.address.mainKey
val allowed: Boolean = context.get("allowed")
val change = parcel.interactableConfig.setInteractable(Interactables[interactableClassName], allowed)
return when {
allowed && change -> "Other players can now interact with $interactableClassName"
allowed && !change -> err("Other players could already interact with $interactableClassName")
change -> "Other players can not interact with $interactableClassName anymore"
else -> err("Other players were not allowed to interact with $interactableClassName")
}
}
}
private fun err(message: String): Nothing = throw CommandException(message)

View File

@@ -8,7 +8,7 @@ import io.dico.parcels2.*
import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.Storage
import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.Vec2i
import io.dico.parcels2.util.ext.floor import io.dico.parcels2.util.ext.floor
import kotlinx.coroutines.CoroutineStart.* import kotlinx.coroutines.CoroutineStart.UNDISPATCHED
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async import kotlinx.coroutines.async
import org.bukkit.command.CommandSender import org.bukkit.command.CommandSender
@@ -26,12 +26,14 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
val isPath: Boolean get() = id == null val isPath: Boolean get() = id == null
} }
class ByOwner(world: ParcelWorld, class ByOwner(
owner: PlayerProfile, world: ParcelWorld,
val index: Int, owner: PlayerProfile,
parsedKind: Int, val index: Int,
isDefault: Boolean, parsedKind: Int,
val onResolveFailure: (() -> Unit)? = null) : ParcelTarget(world, parsedKind, isDefault) { isDefault: Boolean,
val onResolveFailure: (() -> Unit)? = null
) : ParcelTarget(world, parsedKind, isDefault) {
init { init {
if (index < 0) throw IllegalArgumentException("Invalid parcel home index: $index") if (index < 0) throw IllegalArgumentException("Invalid parcel home index: $index")
} }

View File

@@ -1,38 +0,0 @@
@file:Suppress("UNCHECKED_CAST")
package io.dico.parcels2.defaultimpl
import io.dico.parcels2.*
import io.dico.parcels2.util.ext.alsoIfTrue
import java.util.Collections
class GlobalAddedDataManagerImpl(val plugin: ParcelsPlugin) : GlobalAddedDataManager {
private val map = mutableMapOf<PlayerProfile, GlobalAddedData>()
override fun get(owner: PlayerProfile): GlobalAddedData {
return map[owner] ?: GlobalAddedDataImpl(owner).also { map[owner] = it }
}
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 setStatus(key: StatusKey, status: AddedStatus): Boolean {
if (isEmpty) {
if (status == AddedStatus.DEFAULT) return false
data = mutableMapOf()
}
return super.setStatus(key, status).alsoIfTrue {
plugin.storage.setGlobalAddedStatus(owner, key, status)
}
}
}
private companion object {
val emptyData = Collections.emptyMap<Any, Any>() as MutableAddedDataMap
}
}

View File

@@ -0,0 +1,38 @@
@file:Suppress("UNCHECKED_CAST")
package io.dico.parcels2.defaultimpl
import io.dico.parcels2.*
import io.dico.parcels2.util.ext.alsoIfTrue
import java.util.Collections
class GlobalPrivilegesManagerImpl(val plugin: ParcelsPlugin) : GlobalPrivilegesManager {
private val map = mutableMapOf<PlayerProfile, GlobalPrivileges>()
override fun get(owner: PlayerProfile): GlobalPrivileges {
return map[owner] ?: GlobalPrivilegesImpl(owner).also { map[owner] = it }
}
private inner class GlobalPrivilegesImpl(override val owner: PlayerProfile,
data: MutablePrivilegeMap = emptyData)
: PrivilegesHolder(data), GlobalPrivileges {
private inline var data get() = map; set(value) = run { map = value }
private inline val isEmpty get() = data === emptyData
override fun setPrivilege(key: PrivilegeKey, privilege: Privilege): Boolean {
if (isEmpty) {
if (privilege == Privilege.DEFAULT) return false
data = mutableMapOf()
}
return super.set(key, privilege).alsoIfTrue {
plugin.storage.setGlobalPrivilege(owner, key, privilege)
}
}
}
private companion object {
val emptyData = Collections.emptyMap<Any, Any>() as MutablePrivilegeMap
}
}

View File

@@ -35,20 +35,20 @@ class ParcelImpl(
world.storage.setParcelData(this, null) world.storage.setParcelData(this, null)
} }
override val addedMap: AddedDataMap get() = data.addedMap override val map: PrivilegeMap get() = data.map
override fun getStatus(key: StatusKey) = data.getStatus(key) override fun privilege(key: PrivilegeKey) = data.privilege(key)
override fun isBanned(key: StatusKey) = data.isBanned(key) override fun isBanned(key: PrivilegeKey) = data.isBanned(key)
override fun isAllowed(key: StatusKey) = data.isAllowed(key) override fun hasPrivilegeToBuild(key: PrivilegeKey) = data.hasPrivilegeToBuild(key)
override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean): Boolean { override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean): Boolean {
return (data.canBuild(player, checkAdmin, false)) return (data.canBuild(player, checkAdmin, false))
|| checkGlobal && world.globalAddedData[owner ?: return false].isAllowed(player) || checkGlobal && world.globalPrivileges[owner ?: return false].hasPrivilegeToBuild(player)
} }
override var statusOfStar: AddedStatus override var privilegeOfStar: Privilege
get() = data.statusOfStar get() = data.privilegeOfStar
set(value) = run { setStatus(PlayerProfile.Star, value) } set(value) = run { setPrivilege(PlayerProfile.Star, value) }
val globalAddedMap: AddedDataMap? get() = owner?.let { world.globalAddedData[it].addedMap } val globalAddedMap: PrivilegeMap? get() = owner?.let { world.globalPrivileges[it].map }
override val lastClaimTime: DateTime? get() = data.lastClaimTime override val lastClaimTime: DateTime? get() = data.lastClaimTime
@@ -71,12 +71,16 @@ class ParcelImpl(
} }
} }
override fun setStatus(key: StatusKey, status: AddedStatus): Boolean { override fun setPrivilege(key: PrivilegeKey, privilege: Privilege): Boolean {
return data.setStatus(key, status).alsoIfTrue { return data.setPrivilege(key, privilege).alsoIfTrue {
world.storage.setParcelPlayerStatus(this, key, status) world.storage.setLocalPrivilege(this, key, privilege)
} }
} }
private fun updateInteractableConfigStorage() {
world.storage.setParcelOptionsInteractConfig(this, data.interactableConfig)
}
private var _interactableConfig: InteractableConfiguration? = null private var _interactableConfig: InteractableConfiguration? = null
override var interactableConfig: InteractableConfiguration override var interactableConfig: InteractableConfiguration
get() { get() {
@@ -86,21 +90,18 @@ class ParcelImpl(
override fun isInteractable(clazz: Interactables): Boolean = data.interactableConfig.isInteractable(clazz) override fun isInteractable(clazz: Interactables): Boolean = data.interactableConfig.isInteractable(clazz)
override fun setInteractable(clazz: Interactables, interactable: Boolean): Boolean = override fun setInteractable(clazz: Interactables, interactable: Boolean): Boolean =
data.interactableConfig.setInteractable(clazz, interactable).alsoIfTrue { data.interactableConfig.setInteractable(clazz, interactable).alsoIfTrue { updateInteractableConfigStorage() }
// TODO update storage
}
override fun clear(): Boolean = override fun clear(): Boolean =
data.interactableConfig.clear().alsoIfTrue { data.interactableConfig.clear().alsoIfTrue { updateInteractableConfigStorage() }
// TODO update storage
}
} }
} }
return _interactableConfig!! return _interactableConfig!!
} }
set(value) { set(value) {
data.interactableConfig.copyFrom(value) if (data.interactableConfig.copyFrom(value)) {
// TODO update storage updateInteractableConfigStorage()
}
} }
private var blockVisitors = AtomicInteger(0) private var blockVisitors = AtomicInteger(0)
@@ -139,10 +140,10 @@ private object ParcelInfoStringComputer {
append(' ') append(' ')
} }
private fun StringBuilder.appendAddedList(local: AddedDataMap, global: AddedDataMap, status: AddedStatus, fieldName: String) { private fun StringBuilder.appendAddedList(local: PrivilegeMap, global: PrivilegeMap, status: Privilege, fieldName: String) {
val globalSet = global.filterValues { it == status }.keys val globalSet = global.filterValues { it == status }.keys
val localList = local.filterValues { it == status }.keys.filter { it !in globalSet } val localList = local.filterValues { it == status }.keys.filter { it !in globalSet }
val stringList = globalSet.map(StatusKey::notNullName).map { "(G)$it" } + localList.map(StatusKey::notNullName) val stringList = globalSet.map(PrivilegeKey::notNullName).map { "(G)$it" } + localList.map(PrivilegeKey::notNullName)
if (stringList.isEmpty()) return if (stringList.isEmpty()) return
appendField({ appendField({
@@ -182,11 +183,11 @@ private object ParcelInfoStringComputer {
append('\n') append('\n')
val global = owner?.let { parcel.world.globalAddedData[owner].addedMap } ?: emptyMap() val global = owner?.let { parcel.world.globalPrivileges[owner].map } ?: emptyMap()
val local = parcel.addedMap val local = parcel.map
appendAddedList(local, global, AddedStatus.ALLOWED, "Allowed") appendAddedList(local, global, Privilege.CAN_BUILD, "Allowed")
append('\n') append('\n')
appendAddedList(local, global, AddedStatus.BANNED, "Banned") appendAddedList(local, global, Privilege.BANNED, "Banned")
/* TODO options /* TODO options
if (!parcel.allowInteractInputs || !parcel.allowInteractInventory) { if (!parcel.allowInteractInputs || !parcel.allowInteractInventory) {

View File

@@ -2,8 +2,6 @@ package io.dico.parcels2.defaultimpl
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.util.schedule import io.dico.parcels2.util.schedule
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.CoroutineStart.*
import kotlinx.coroutines.Unconfined import kotlinx.coroutines.Unconfined
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.bukkit.Bukkit import org.bukkit.Bukkit
@@ -61,7 +59,7 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
else WorldCreator(worldName).generator(generator).createWorld().also { logger.info("Creating world $worldName") } else WorldCreator(worldName).generator(generator).createWorld().also { logger.info("Creating world $worldName") }
parcelWorld = ParcelWorldImpl(bukkitWorld, generator, worldOptions.runtime, plugin.storage, parcelWorld = ParcelWorldImpl(bukkitWorld, generator, worldOptions.runtime, plugin.storage,
plugin.globalAddedData, ::DefaultParcelContainer, plugin, plugin.worktimeLimiter) plugin.globalPrivileges, ::DefaultParcelContainer, plugin, plugin.worktimeLimiter)
if (!worldExists) { if (!worldExists) {
val time = DateTime.now() val time = DateTime.now()
@@ -119,7 +117,7 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
worldOptions, worldOptions,
worldOptions.generator.newGenerator(this, worldName), worldOptions.generator.newGenerator(this, worldName),
plugin.storage, plugin.storage,
plugin.globalAddedData, plugin.globalPrivileges,
::DefaultParcelContainer) ::DefaultParcelContainer)
} catch (ex: Exception) { } catch (ex: Exception) {

View File

@@ -15,7 +15,7 @@ class ParcelWorldImpl(override val world: World,
override val generator: ParcelGenerator, override val generator: ParcelGenerator,
override var options: RuntimeWorldOptions, override var options: RuntimeWorldOptions,
override val storage: Storage, override val storage: Storage,
override val globalAddedData: GlobalAddedDataManager, val globalPrivileges: GlobalPrivilegesManager,
containerFactory: ParcelContainerFactory, containerFactory: ParcelContainerFactory,
coroutineScope: CoroutineScope, coroutineScope: CoroutineScope,
worktimeLimiter: WorktimeLimiter) worktimeLimiter: WorktimeLimiter)

View File

@@ -36,7 +36,7 @@ class ParcelListeners(
val entityTracker: ParcelEntityTracker, val entityTracker: ParcelEntityTracker,
val storage: Storage val storage: Storage
) { ) {
private inline fun Parcel?.canBuildN(user: Player) = isPresentAnd { canBuild(user) } || user.hasBuildAnywhere private inline fun Parcel?.canBuildN(user: Player) = isPresentAnd { canBuild(user) } || user.hasPermBuildAnywhere
/** /**
* Get the world and parcel that the block resides in * Get the world and parcel that the block resides in
@@ -55,9 +55,9 @@ class ParcelListeners(
@field:ListenerMarker(priority = NORMAL) @field:ListenerMarker(priority = NORMAL)
val onPlayerMoveEvent = RegistratorListener<PlayerMoveEvent> l@{ event -> val onPlayerMoveEvent = RegistratorListener<PlayerMoveEvent> l@{ event ->
val user = event.player val user = event.player
if (user.hasBanBypass) return@l if (user.hasPermBanBypass) return@l
val parcel = parcelProvider.getParcelAt(event.to) ?: return@l val parcel = parcelProvider.getParcelAt(event.to) ?: return@l
if (parcel.isBanned(user.statusKey)) { if (parcel.isBanned(user.privilegeKey)) {
parcelProvider.getParcelAt(event.from)?.also { parcelProvider.getParcelAt(event.from)?.also {
user.teleport(it.homeLocation) user.teleport(it.homeLocation)
user.sendParcelMessage(nopermit = true, message = "You are banned from this parcel") user.sendParcelMessage(nopermit = true, message = "You are banned from this parcel")
@@ -72,7 +72,7 @@ class ParcelListeners(
@field:ListenerMarker(priority = NORMAL) @field:ListenerMarker(priority = NORMAL)
val onBlockBreakEvent = RegistratorListener<BlockBreakEvent> l@{ event -> val onBlockBreakEvent = RegistratorListener<BlockBreakEvent> l@{ event ->
val (wo, ppa) = getWoAndPPa(event.block) ?: return@l val (wo, ppa) = getWoAndPPa(event.block) ?: return@l
if (!event.player.hasBuildAnywhere && ppa.isNullOr { !canBuild(event.player) }) { if (!event.player.hasPermBuildAnywhere && ppa.isNullOr { !canBuild(event.player) }) {
event.isCancelled = true; return@l event.isCancelled = true; return@l
} }
@@ -91,7 +91,7 @@ class ParcelListeners(
@field:ListenerMarker(priority = NORMAL) @field:ListenerMarker(priority = NORMAL)
val onBlockPlaceEvent = RegistratorListener<BlockPlaceEvent> l@{ event -> val onBlockPlaceEvent = RegistratorListener<BlockPlaceEvent> l@{ event ->
val (wo, ppa) = getWoAndPPa(event.block) ?: return@l val (wo, ppa) = getWoAndPPa(event.block) ?: return@l
if (!event.player.hasBuildAnywhere && ppa.isNullOr { !canBuild(event.player) }) { if (!event.player.hasPermBuildAnywhere && ppa.isNullOr { !canBuild(event.player) }) {
event.isCancelled = true event.isCancelled = true
} }
} }
@@ -185,7 +185,7 @@ class ParcelListeners(
val clickedBlock = event.clickedBlock val clickedBlock = event.clickedBlock
val parcel = clickedBlock?.let { world.getParcelAt(it) } val parcel = clickedBlock?.let { world.getParcelAt(it) }
if (!user.hasBuildAnywhere && parcel.isPresentAnd { isBanned(user.statusKey) }) { if (!user.hasPermBuildAnywhere && parcel.isPresentAnd { isBanned(user.privilegeKey) }) {
user.sendParcelMessage(nopermit = true, message = "You cannot interact with parcels you're banned from") user.sendParcelMessage(nopermit = true, message = "You cannot interact with parcels you're banned from")
event.isCancelled = true; return@l event.isCancelled = true; return@l
} }
@@ -218,7 +218,7 @@ class ParcelListeners(
} }
Action.RIGHT_CLICK_AIR -> onPlayerInteractEvent_RightClick(event, world, parcel) Action.RIGHT_CLICK_AIR -> onPlayerInteractEvent_RightClick(event, world, parcel)
Action.PHYSICAL -> if (!user.hasBuildAnywhere && !parcel.isPresentAnd { canBuild(user) || interactableConfig("pressure_plates") }) { Action.PHYSICAL -> if (!user.hasPermBuildAnywhere && !parcel.isPresentAnd { canBuild(user) || interactableConfig("pressure_plates") }) {
user.sendParcelMessage(nopermit = true, message = "You cannot use inputs in this parcel") user.sendParcelMessage(nopermit = true, message = "You cannot use inputs in this parcel")
event.isCancelled = true; return@l event.isCancelled = true; return@l
} }
@@ -484,7 +484,7 @@ class ParcelListeners(
event.isCancelled = true; return@l event.isCancelled = true; return@l
} }
if (!event.player.hasBuildAnywhere && !ppa.canBuild(event.player)) { if (!event.player.hasPermBuildAnywhere && !ppa.canBuild(event.player)) {
event.isCancelled = true; return@l event.isCancelled = true; return@l
} }
@@ -560,7 +560,7 @@ class ParcelListeners(
@field:ListenerMarker(priority = NORMAL) @field:ListenerMarker(priority = NORMAL)
val onPlayerChangedWorldEvent = RegistratorListener<PlayerChangedWorldEvent> l@{ event -> val onPlayerChangedWorldEvent = RegistratorListener<PlayerChangedWorldEvent> l@{ event ->
val world = parcelProvider.getWorld(event.player.world) ?: return@l val world = parcelProvider.getWorld(event.player.world) ?: return@l
if (world.options.gameMode != null && !event.player.hasGamemodeBypass) { if (world.options.gameMode != null && !event.player.hasPermGamemodeBypass) {
event.player.gameMode = world.options.gameMode event.player.gameMode = world.options.gameMode
} }
} }

View File

@@ -4,7 +4,6 @@ import com.sk89q.worldedit.EditSession.Stage.BEFORE_REORDER
import com.sk89q.worldedit.Vector import com.sk89q.worldedit.Vector
import com.sk89q.worldedit.Vector2D import com.sk89q.worldedit.Vector2D
import com.sk89q.worldedit.WorldEdit import com.sk89q.worldedit.WorldEdit
import com.sk89q.worldedit.WorldEditException
import com.sk89q.worldedit.bukkit.WorldEditPlugin import com.sk89q.worldedit.bukkit.WorldEditPlugin
import com.sk89q.worldedit.event.extent.EditSessionEvent import com.sk89q.worldedit.event.extent.EditSessionEvent
import com.sk89q.worldedit.extent.AbstractDelegateExtent import com.sk89q.worldedit.extent.AbstractDelegateExtent
@@ -12,11 +11,10 @@ import com.sk89q.worldedit.extent.Extent
import com.sk89q.worldedit.util.eventbus.EventHandler.Priority.VERY_EARLY import com.sk89q.worldedit.util.eventbus.EventHandler.Priority.VERY_EARLY
import com.sk89q.worldedit.util.eventbus.Subscribe import com.sk89q.worldedit.util.eventbus.Subscribe
import com.sk89q.worldedit.world.biome.BaseBiome import com.sk89q.worldedit.world.biome.BaseBiome
import com.sk89q.worldedit.world.block.BaseBlock
import com.sk89q.worldedit.world.block.BlockStateHolder import com.sk89q.worldedit.world.block.BlockStateHolder
import io.dico.parcels2.ParcelWorld import io.dico.parcels2.ParcelWorld
import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.ParcelsPlugin
import io.dico.parcels2.util.ext.hasBuildAnywhere import io.dico.parcels2.util.ext.hasPermBuildAnywhere
import io.dico.parcels2.util.ext.sendParcelMessage import io.dico.parcels2.util.ext.sendParcelMessage
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.bukkit.plugin.Plugin import org.bukkit.plugin.Plugin
@@ -33,7 +31,7 @@ class WorldEditListener(val parcels: ParcelsPlugin, val worldEdit: WorldEdit) {
if (actor == null || !actor.isPlayer) return if (actor == null || !actor.isPlayer) return
val player = parcels.server.getPlayer(actor.uniqueId) val player = parcels.server.getPlayer(actor.uniqueId)
if (player.hasBuildAnywhere) return if (player.hasPermBuildAnywhere) return
event.extent = ParcelsExtent(event.extent, world, player) event.extent = ParcelsExtent(event.extent, world, player)
} }

View File

@@ -1,13 +1,13 @@
package io.dico.parcels2.storage package io.dico.parcels2.storage
import io.dico.parcels2.* import io.dico.parcels2.*
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.channels.SendChannel
import org.joda.time.DateTime import org.joda.time.DateTime
import java.util.UUID import java.util.UUID
import kotlin.coroutines.CoroutineContext
interface Backing { interface Backing {
@@ -15,7 +15,7 @@ interface Backing {
val isConnected: Boolean val isConnected: Boolean
val dispatcher: CoroutineDispatcher val coroutineContext: CoroutineContext
fun launchJob(job: Backing.() -> Unit): Job fun launchJob(job: Backing.() -> Unit): Job
@@ -56,9 +56,9 @@ interface Backing {
fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean) fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean)
fun setLocalPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus) fun setLocalPrivilege(parcel: ParcelId, player: PlayerProfile, status: Privilege)
fun setParcelOptionsInteractBitmask(parcel: ParcelId, bitmask: IntArray?) fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration)
/* /*
fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean)
@@ -67,7 +67,7 @@ interface Backing {
fun transmitAllGlobalAddedData(channel: SendChannel<AddedDataPair<PlayerProfile>>) fun transmitAllGlobalAddedData(channel: SendChannel<AddedDataPair<PlayerProfile>>)
fun readGlobalAddedData(owner: PlayerProfile): MutableAddedDataMap fun readGlobalPrivileges(owner: PlayerProfile): MutablePrivilegeMap
fun setGlobalPlayerStatus(owner: PlayerProfile, player: PlayerProfile, status: AddedStatus) fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, status: Privilege)
} }

View File

@@ -3,6 +3,7 @@
package io.dico.parcels2.storage package io.dico.parcels2.storage
import io.dico.parcels2.* import io.dico.parcels2.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.channels.ReceiveChannel
@@ -10,9 +11,10 @@ import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.joda.time.DateTime import org.joda.time.DateTime
import java.util.UUID import java.util.UUID
import kotlin.coroutines.CoroutineContext
typealias DataPair = Pair<ParcelId, ParcelData?> typealias DataPair = Pair<ParcelId, ParcelData?>
typealias AddedDataPair<TAttach> = Pair<TAttach, MutableAddedDataMap> typealias AddedDataPair<TAttach> = Pair<TAttach, MutablePrivilegeMap>
interface Storage { interface Storage {
val name: String val name: String
@@ -48,28 +50,29 @@ interface Storage {
fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean): Job fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean): Job
fun setParcelPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus): Job fun setLocalPrivilege(parcel: ParcelId, player: PlayerProfile, privilege: Privilege): Job
fun setParcelOptionsInteractBitmask(parcel: ParcelId, bitmask: IntArray): Job fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration): Job
fun transmitAllGlobalAddedData(): ReceiveChannel<AddedDataPair<PlayerProfile>> fun transmitAllGlobalAddedData(): ReceiveChannel<AddedDataPair<PlayerProfile>>
fun readGlobalAddedData(owner: PlayerProfile): Deferred<MutableAddedDataMap?> fun readGlobalPrivileges(owner: PlayerProfile): Deferred<MutablePrivilegeMap?>
fun setGlobalAddedStatus(owner: PlayerProfile, player: PlayerProfile, status: AddedStatus): Job fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, status: Privilege): Job
fun getChannelToUpdateParcelData(): SendChannel<Pair<ParcelId, ParcelData>> fun getChannelToUpdateParcelData(): SendChannel<Pair<ParcelId, ParcelData>>
} }
class BackedStorage internal constructor(val b: Backing) : Storage { class BackedStorage internal constructor(val b: Backing) : Storage, CoroutineScope {
override val name get() = b.name override val name get() = b.name
override val isConnected get() = b.isConnected override val isConnected get() = b.isConnected
override val coroutineContext: CoroutineContext get() = b.coroutineContext
override fun init() = launch(b.dispatcher) { b.init() } override fun init() = launch { b.init() }
override fun shutdown() = launch(b.dispatcher) { b.shutdown() } override fun shutdown() = launch { b.shutdown() }
override fun getWorldCreationTime(worldId: ParcelWorldId): Deferred<DateTime?> = b.launchFuture { b.getWorldCreationTime(worldId) } override fun getWorldCreationTime(worldId: ParcelWorldId): Deferred<DateTime?> = b.launchFuture { b.getWorldCreationTime(worldId) }
@@ -96,16 +99,16 @@ class BackedStorage internal constructor(val b: Backing) : Storage {
override fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean): Job = b.launchJob { b.setParcelOwnerSignOutdated(parcel, outdated) } override fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean): Job = b.launchJob { b.setParcelOwnerSignOutdated(parcel, outdated) }
override fun setParcelPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus) = b.launchJob { b.setLocalPlayerStatus(parcel, player, status) } override fun setLocalPrivilege(parcel: ParcelId, player: PlayerProfile, privilege: Privilege) = b.launchJob { b.setLocalPrivilege(parcel, player, privilege) }
override fun setParcelOptionsInteractBitmask(parcel: ParcelId, bitmask: IntArray) = b.launchJob { b.setParcelOptionsInteractBitmask(parcel, bitmask) } override fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration) = b.launchJob { b.setParcelOptionsInteractConfig(parcel, config) }
override fun transmitAllGlobalAddedData(): ReceiveChannel<AddedDataPair<PlayerProfile>> = b.openChannel { b.transmitAllGlobalAddedData(it) } override fun transmitAllGlobalAddedData(): ReceiveChannel<AddedDataPair<PlayerProfile>> = b.openChannel { b.transmitAllGlobalAddedData(it) }
override fun readGlobalAddedData(owner: PlayerProfile): Deferred<MutableAddedDataMap?> = b.launchFuture { b.readGlobalAddedData(owner) } override fun readGlobalPrivileges(owner: PlayerProfile): Deferred<MutablePrivilegeMap?> = b.launchFuture { b.readGlobalPrivileges(owner) }
override fun setGlobalAddedStatus(owner: PlayerProfile, player: PlayerProfile, status: AddedStatus) = b.launchJob { b.setGlobalPlayerStatus(owner, player, status) } override fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, status: Privilege) = b.launchJob { b.setGlobalPrivilege(owner, player, status) }
override fun getChannelToUpdateParcelData(): SendChannel<Pair<ParcelId, ParcelData>> = b.openChannelForWriting { b.setParcelData(it.first, it.second) } override fun getChannelToUpdateParcelData(): SendChannel<Pair<ParcelId, ParcelData>> = b.openChannelForWriting { b.setParcelData(it.first, it.second) }
} }

View File

@@ -23,17 +23,16 @@ import javax.sql.DataSource
class ExposedDatabaseException(message: String? = null) : Exception(message) class ExposedDatabaseException(message: String? = null) : Exception(message)
class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSize: Int) : Backing { class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSize: Int) : Backing, CoroutineScope {
override val name get() = "Exposed" override val name get() = "Exposed"
override val dispatcher: ThreadPoolDispatcher = newFixedThreadPoolContext(poolSize, "Parcels StorageThread") override val coroutineContext = Job() + newFixedThreadPoolContext(poolSize, "Parcels StorageThread")
private var dataSource: DataSource? = null private var dataSource: DataSource? = null
private var database: Database? = null private var database: Database? = null
private var isShutdown: Boolean = false private var isShutdown: Boolean = false
override val isConnected get() = database != null override val isConnected get() = database != null
override fun launchJob(job: Backing.() -> Unit): Job = launch(dispatcher) { transaction { job() } } override fun launchJob(job: Backing.() -> Unit): Job = launch { transaction { job() } }
override fun <T> launchFuture(future: Backing.() -> T): Deferred<T> = async(dispatcher) { transaction { future() } } override fun <T> launchFuture(future: Backing.() -> T): Deferred<T> = async { transaction { future() } }
override fun <T> openChannel(future: Backing.(SendChannel<T>) -> Unit): ReceiveChannel<T> { override fun <T> openChannel(future: Backing.(SendChannel<T>) -> Unit): ReceiveChannel<T> {
val channel = LinkedListChannel<T>() val channel = LinkedListChannel<T>()
@@ -44,8 +43,8 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
override fun <T> openChannelForWriting(action: Backing.(T) -> Unit): SendChannel<T> { override fun <T> openChannelForWriting(action: Backing.(T) -> Unit): SendChannel<T> {
val channel = ArrayChannel<T>(poolSize * 2) val channel = ArrayChannel<T>(poolSize * 2)
repeat(poolSize) { repeat(poolSize.clampMax(3)) {
launch(dispatcher) { launch {
try { try {
while (true) { while (true) {
action(channel.receive()) action(channel.receive())
@@ -75,7 +74,7 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
dataSource = dataSourceFactory() dataSource = dataSourceFactory()
database = Database.connect(dataSource!!) database = Database.connect(dataSource!!)
transaction(database!!) { transaction(database!!) {
create(WorldsT, ProfilesT, ParcelsT, ParcelOptionsT, AddedLocalT, AddedGlobalT) create(WorldsT, ProfilesT, ParcelsT, ParcelOptionsT, PrivilegesLocalT, PrivilegesGlobalT)
} }
} }
} }
@@ -83,11 +82,12 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
override fun shutdown() { override fun shutdown() {
synchronized { synchronized {
if (isShutdown) throw IllegalStateException() if (isShutdown) throw IllegalStateException()
isShutdown = true
coroutineContext[Job]!!.cancel(CancellationException("ExposedBacking shutdown"))
dataSource?.let { dataSource?.let {
(it as? HikariDataSource)?.close() (it as? HikariDataSource)?.close()
} }
database = null database = null
isShutdown = true
} }
} }
@@ -173,7 +173,7 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
// Below should cascade automatically // Below should cascade automatically
/* /*
AddedLocalT.deleteIgnoreWhere { AddedLocalT.parcel_id eq id } PrivilegesLocalT.deleteIgnoreWhere { PrivilegesLocalT.parcel_id eq id }
ParcelOptionsT.deleteIgnoreWhere(limit = 1) { ParcelOptionsT.parcel_id eq id } ParcelOptionsT.deleteIgnoreWhere(limit = 1) { ParcelOptionsT.parcel_id eq id }
*/ */
} }
@@ -184,18 +184,16 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
transaction { transaction {
val id = ParcelsT.getOrInitId(parcel) val id = ParcelsT.getOrInitId(parcel)
AddedLocalT.deleteIgnoreWhere { AddedLocalT.attach_id eq id } PrivilegesLocalT.deleteIgnoreWhere { PrivilegesLocalT.attach_id eq id }
} }
setParcelOwner(parcel, data.owner) setParcelOwner(parcel, data.owner)
for ((profile, status) in data.addedMap) { for ((profile, privilege) in data.map) {
AddedLocalT.setPlayerStatus(parcel, profile, status) PrivilegesLocalT.setPrivilege(parcel, profile, privilege)
} }
val bitmaskArray = (data.interactableConfig as? BitmaskInteractableConfiguration ?: return).bitmaskArray setParcelOptionsInteractConfig(parcel, data.interactableConfig)
val isAllZero = bitmaskArray.fold(false) { cur, elem -> cur || elem != 0 }
setParcelOptionsInteractBitmask(parcel, if (isAllZero) null else bitmaskArray)
} }
override fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) { override fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) {
@@ -221,19 +219,22 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
} }
} }
override fun setLocalPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus) { override fun setLocalPrivilege(parcel: ParcelId, player: PlayerProfile, privilege: Privilege) {
AddedLocalT.setPlayerStatus(parcel, player.toRealProfile(), status) PrivilegesLocalT.setPrivilege(parcel, player.toRealProfile(), privilege)
} }
override fun setParcelOptionsInteractBitmask(parcel: ParcelId, bitmask: IntArray?) { override fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration) {
if (bitmask == null) { val bitmaskArray = (config as? BitmaskInteractableConfiguration ?: return).bitmaskArray
val isAllZero = !bitmaskArray.fold(false) { cur, elem -> cur || elem != 0 }
if (isAllZero) {
val id = ParcelsT.getId(parcel) ?: return val id = ParcelsT.getId(parcel) ?: return
ParcelOptionsT.deleteWhere { ParcelOptionsT.parcel_id eq id } ParcelOptionsT.deleteWhere { ParcelOptionsT.parcel_id eq id }
return return
} }
if (bitmask.size != 1) throw IllegalArgumentException() if (bitmaskArray.size != 1) throw IllegalArgumentException()
val array = bitmask.toByteArray() val array = bitmaskArray.toByteArray()
val id = ParcelsT.getOrInitId(parcel) val id = ParcelsT.getOrInitId(parcel)
ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
it[parcel_id] = id it[parcel_id] = id
@@ -242,16 +243,16 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
} }
override fun transmitAllGlobalAddedData(channel: SendChannel<AddedDataPair<PlayerProfile>>) { override fun transmitAllGlobalAddedData(channel: SendChannel<AddedDataPair<PlayerProfile>>) {
AddedGlobalT.sendAllAddedData(channel) PrivilegesGlobalT.sendAllAddedData(channel)
channel.close() channel.close()
} }
override fun readGlobalAddedData(owner: PlayerProfile): MutableAddedDataMap { override fun readGlobalPrivileges(owner: PlayerProfile): MutablePrivilegeMap {
return AddedGlobalT.readAddedData(ProfilesT.getId(owner.toOwnerProfile()) ?: return hashMapOf()) return PrivilegesGlobalT.readPrivileges(ProfilesT.getId(owner.toOwnerProfile()) ?: return hashMapOf())
} }
override fun setGlobalPlayerStatus(owner: PlayerProfile, player: PlayerProfile, status: AddedStatus) { override fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege) {
AddedGlobalT.setPlayerStatus(owner, player.toRealProfile(), status) PrivilegesGlobalT.setPrivilege(owner, player.toRealProfile(), privilege)
} }
private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply { private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply {
@@ -266,7 +267,7 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
System.arraycopy(source, 0, target, 0, source.size.clampMax(target.size)) System.arraycopy(source, 0, target, 0, source.size.clampMax(target.size))
} }
addedMap = AddedLocalT.readAddedData(id) map = PrivilegesLocalT.readPrivileges(id)
} }
} }

View File

@@ -26,7 +26,7 @@ abstract class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj
internal inline fun getOrInitId(getId: () -> Int?, noinline body: TableT.(UpdateBuilder<*>) -> Unit, objName: () -> String): Int { internal inline fun getOrInitId(getId: () -> Int?, noinline body: TableT.(UpdateBuilder<*>) -> Unit, objName: () -> String): Int {
return getId() ?: table.insertIgnore(body)[id] ?: getId() return getId() ?: table.insertIgnore(body)[id] ?: getId()
?: throw ExposedDatabaseException("This should not happen - failed to insert ${objName()} and get its id") ?: throw ExposedDatabaseException("This should not happen - failed to insert ${objName()} and get its number")
} }
abstract fun getId(obj: QueryObj): Int? abstract fun getId(obj: QueryObj): Int?

View File

@@ -3,30 +3,30 @@
package io.dico.parcels2.storage.exposed package io.dico.parcels2.storage.exposed
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.AddedStatus.ALLOWED import io.dico.parcels2.Privilege.DEFAULT
import io.dico.parcels2.AddedStatus.DEFAULT
import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.channels.SendChannel
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import java.util.UUID
object AddedLocalT : AddedTable<ParcelId>("parcels_added_local", ParcelsT) object PrivilegesLocalT : PrivilegesTable<ParcelId>("parcels_added_local", ParcelsT)
object AddedGlobalT : AddedTable<PlayerProfile>("parcels_added_global", ProfilesT) object PrivilegesGlobalT : PrivilegesTable<PlayerProfile>("parcels_added_global", ProfilesT)
object ParcelOptionsT : Table("parcel_options") { object ParcelOptionsT : Table("parcel_options") {
val parcel_id = integer("parcel_id").primaryKey().references(ParcelsT.id, ReferenceOption.CASCADE) val parcel_id = integer("parcel_id").primaryKey().references(ParcelsT.id, ReferenceOption.CASCADE)
val interact_bitmask = binary("interact_bitmask", 4).default(ByteArray(4) { 0 }) // all zero by default val interact_bitmask = binary("interact_bitmask", 4)
} }
typealias AddedStatusSendChannel<AttachT> = SendChannel<Pair<AttachT, MutableAddedDataMap>> typealias PrivilegesSendChannel<AttachT> = SendChannel<Pair<AttachT, MutablePrivilegeMap>>
sealed class AddedTable<AttachT>(name: String, val idTable: IdTransactionsTable<*, AttachT>) : Table(name) { sealed class PrivilegesTable<AttachT>(name: String, val idTable: IdTransactionsTable<*, AttachT>) : Table(name) {
val attach_id = integer("attach_id").references(idTable.id, ReferenceOption.CASCADE) val attach_id = integer("attach_id").references(idTable.id, ReferenceOption.CASCADE)
val profile_id = integer("profile_id").references(ProfilesT.id, ReferenceOption.CASCADE) val profile_id = integer("profile_id").references(ProfilesT.id, ReferenceOption.CASCADE)
val allowed_flag = bool("allowed_flag") val privilege = integer("privilege")
val index_pair = uniqueIndexR("index_pair", attach_id, profile_id) val index_pair = uniqueIndexR("index_pair", attach_id, profile_id)
fun setPlayerStatus(attachedOn: AttachT, player: PlayerProfile.Real, status: AddedStatus) { fun setPrivilege(attachedOn: AttachT, player: PlayerProfile.Real, privilege: Privilege) {
if (status == DEFAULT) { privilege.requireNonTransient()
if (privilege == DEFAULT) {
val player_id = ProfilesT.getId(player) ?: return val player_id = ProfilesT.getId(player) ?: return
idTable.getId(attachedOn)?.let { holder -> idTable.getId(attachedOn)?.let { holder ->
deleteWhere { (attach_id eq holder) and (profile_id eq player_id) } deleteWhere { (attach_id eq holder) and (profile_id eq player_id) }
@@ -39,28 +39,28 @@ sealed class AddedTable<AttachT>(name: String, val idTable: IdTransactionsTable<
upsert(conflictIndex = index_pair) { upsert(conflictIndex = index_pair) {
it[attach_id] = holder it[attach_id] = holder
it[profile_id] = player_id it[profile_id] = player_id
it[allowed_flag] = status == ALLOWED it[this.privilege] = privilege.number
} }
} }
fun readAddedData(id: Int): MutableAddedDataMap { fun readPrivileges(id: Int): MutablePrivilegeMap {
val list = slice(profile_id, allowed_flag).select { attach_id eq id } val list = slice(profile_id, privilege).select { attach_id eq id }
val result = MutableAddedDataMap() val result = MutablePrivilegeMap()
for (row in list) { for (row in list) {
val profile = ProfilesT.getRealItem(row[profile_id]) ?: continue val profile = ProfilesT.getRealItem(row[profile_id]) ?: continue
result[profile] = row[allowed_flag].asAddedStatus() result[profile] = Privilege.safeGetByNumber(row[privilege]) ?: continue
} }
return result return result
} }
fun sendAllAddedData(channel: AddedStatusSendChannel<AttachT>) { fun sendAllAddedData(channel: PrivilegesSendChannel<AttachT>) {
val iterator = selectAll().orderBy(attach_id).iterator() val iterator = selectAll().orderBy(attach_id).iterator()
if (iterator.hasNext()) { if (iterator.hasNext()) {
val firstRow = iterator.next() val firstRow = iterator.next()
var id: Int = firstRow[attach_id] var id: Int = firstRow[attach_id]
var attach: AttachT? = null var attach: AttachT? = null
var map: MutableAddedDataMap? = null var map: MutablePrivilegeMap? = null
fun initAttachAndMap() { fun initAttachAndMap() {
attach = idTable.getItem(id) attach = idTable.getItem(id)
@@ -90,14 +90,12 @@ sealed class AddedTable<AttachT>(name: String, val idTable: IdTransactionsTable<
} }
val profile = ProfilesT.getRealItem(row[profile_id]) ?: continue val profile = ProfilesT.getRealItem(row[profile_id]) ?: continue
val status = row[allowed_flag].asAddedStatus() val privilege = Privilege.safeGetByNumber(row[privilege]) ?: continue
map!![profile] = status map!![profile] = privilege
} }
sendIfPresent() sendIfPresent()
} }
} }
private inline fun Boolean?.asAddedStatus() = if (this == null) AddedStatus.DEFAULT else if (this) ALLOWED else AddedStatus.BANNED
} }

View File

@@ -66,11 +66,11 @@ class PlotmeMigration(val options: PlotmeMigrationOptions) : Migration {
return ParcelId(world, row[table.px], row[table.pz]) return ParcelId(world, row[table.px], row[table.pz])
} }
fun PlotmePlotPlayerMap.transmitPlotmeAddedTable(kind: AddedStatus) { fun PlotmePlotPlayerMap.transmitPlotmeAddedTable(kind: Privilege) {
selectAll().forEach { row -> selectAll().forEach { row ->
val parcel = getParcelId(this, row) ?: return@forEach val parcel = getParcelId(this, row) ?: return@forEach
val profile = StatusKey.safe(row[player_uuid]?.toUUID(), row[player_name]) ?: return@forEach val profile = StatusKey.safe(row[player_uuid]?.toUUID(), row[player_name]) ?: return@forEach
target.setParcelPlayerStatus(parcel, profile, kind) target.setLocalPrivilege(parcel, profile, kind)
} }
} }
@@ -89,12 +89,12 @@ class PlotmeMigration(val options: PlotmeMigrationOptions) : Migration {
mlogger.info("Transmitting data from plotmeallowed table") mlogger.info("Transmitting data from plotmeallowed table")
transaction { transaction {
PlotmeAllowedT.transmitPlotmeAddedTable(AddedStatus.ALLOWED) PlotmeAllowedT.transmitPlotmeAddedTable(Privilege.CAN_BUILD)
} }
mlogger.info("Transmitting data from plotmedenied table") mlogger.info("Transmitting data from plotmedenied table")
transaction { transaction {
PlotmeDeniedT.transmitPlotmeAddedTable(AddedStatus.BANNED) PlotmeDeniedT.transmitPlotmeAddedTable(Privilege.BANNED)
} }
mlogger.warn("Data has been **transmitted**.") mlogger.warn("Data has been **transmitted**.")

View File

@@ -13,12 +13,12 @@ inline val OfflinePlayer.uuid get() = uniqueId
inline val OfflinePlayer.isValid inline val OfflinePlayer.isValid
get() = isOnline() || hasPlayedBefore() get() = isOnline() || hasPlayedBefore()
inline val Player.hasBanBypass get() = hasPermission("parcels.admin.bypass.ban") inline val Player.hasPermBanBypass get() = hasPermission("parcels.admin.bypass.ban")
inline val Player.hasGamemodeBypass get() = hasPermission("parcels.admin.bypass.gamemode") inline val Player.hasPermGamemodeBypass get() = hasPermission("parcels.admin.bypass.gamemode")
inline val Player.hasBuildAnywhere get() = hasPermission("parcels.admin.bypass.build") inline val Player.hasPermBuildAnywhere get() = hasPermission("parcels.admin.bypass.build")
inline val Player.hasAdminManage get() = hasPermission("parcels.admin.manage") inline val Player.hasPermAdminManage get() = hasPermission("parcels.admin.manage")
inline val Player.hasParcelHomeOthers get() = hasPermission("parcels.command.home.others") inline val Player.hasParcelHomeOthers get() = hasPermission("parcels.command.home.others")
inline val Player.hasRandomSpecific get() = hasPermission("parcels.command.random.specific") inline val Player.hasPermRandomSpecific get() = hasPermission("parcels.command.random.specific")
val Player.parcelLimit: Int val Player.parcelLimit: Int
get() { get() {
for (info in effectivePermissions) { for (info in effectivePermissions) {