From 1a440767b3d8d7b267ecfeeb37f8c315bae9d112 Mon Sep 17 00:00:00 2001 From: Dico Date: Mon, 24 Sep 2018 02:45:41 +0100 Subject: Replace AddedData API with Privileges API, adding CAN_MANAGE and required changes --- build.gradle.kts | 3 +- src/main/kotlin/io/dico/parcels2/AddedData.kt | 62 ---------- src/main/kotlin/io/dico/parcels2/Interactable.kt | 8 +- src/main/kotlin/io/dico/parcels2/Parcel.kt | 13 +- src/main/kotlin/io/dico/parcels2/ParcelWorld.kt | 2 +- src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt | 6 +- src/main/kotlin/io/dico/parcels2/Privileges.kt | 134 +++++++++++++++++++++ .../parcels2/command/AbstractParcelCommands.kt | 4 +- .../parcels2/command/CommandsAddedStatusGlobal.kt | 65 ---------- .../parcels2/command/CommandsAddedStatusLocal.kt | 56 --------- .../io/dico/parcels2/command/CommandsAdmin.kt | 10 +- .../io/dico/parcels2/command/CommandsDebug.kt | 9 +- .../io/dico/parcels2/command/CommandsGeneral.kt | 10 +- .../dico/parcels2/command/CommandsParcelOptions.kt | 61 ---------- .../parcels2/command/CommandsPrivilegesGlobal.kt | 91 ++++++++++++++ .../parcels2/command/CommandsPrivilegesLocal.kt | 90 ++++++++++++++ .../dico/parcels2/command/ParcelCommandBuilder.kt | 19 ++- .../parcels2/command/ParcelCommandReceivers.kt | 41 ++++--- .../command/ParcelOptionsInteractCommand.kt | 36 ++++++ .../io/dico/parcels2/command/ParcelTarget.kt | 16 +-- .../defaultimpl/GlobalAddedDataManagerImpl.kt | 38 ------ .../defaultimpl/GlobalPrivilegesManagerImpl.kt | 38 ++++++ .../io/dico/parcels2/defaultimpl/ParcelImpl.kt | 53 ++++---- .../parcels2/defaultimpl/ParcelProviderImpl.kt | 6 +- .../dico/parcels2/defaultimpl/ParcelWorldImpl.kt | 2 +- .../io/dico/parcels2/listener/ParcelListeners.kt | 18 +-- .../io/dico/parcels2/listener/WorldEditListener.kt | 6 +- .../kotlin/io/dico/parcels2/storage/Backing.kt | 12 +- .../kotlin/io/dico/parcels2/storage/Storage.kt | 27 +++-- .../parcels2/storage/exposed/ExposedBacking.kt | 57 ++++----- .../io/dico/parcels2/storage/exposed/IdTables.kt | 2 +- .../io/dico/parcels2/storage/exposed/ListTables.kt | 42 +++---- .../storage/migration/plotme/PlotmeMigration.kt | 8 +- .../kotlin/io/dico/parcels2/util/ext/Player.kt | 10 +- 34 files changed, 597 insertions(+), 458 deletions(-) delete mode 100644 src/main/kotlin/io/dico/parcels2/AddedData.kt create mode 100644 src/main/kotlin/io/dico/parcels2/Privileges.kt delete mode 100644 src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusGlobal.kt delete mode 100644 src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusLocal.kt delete mode 100644 src/main/kotlin/io/dico/parcels2/command/CommandsParcelOptions.kt create mode 100644 src/main/kotlin/io/dico/parcels2/command/CommandsPrivilegesGlobal.kt create mode 100644 src/main/kotlin/io/dico/parcels2/command/CommandsPrivilegesLocal.kt create mode 100644 src/main/kotlin/io/dico/parcels2/command/ParcelOptionsInteractCommand.kt delete mode 100644 src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalAddedDataManagerImpl.kt create mode 100644 src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalPrivilegesManagerImpl.kt diff --git a/build.gradle.kts b/build.gradle.kts index 34a9737..098f1af 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -87,10 +87,11 @@ dependencies { tasks { removeIf { it is ShadowJar } - val compileKotlin by getting(KotlinCompile::class) { + tasks.withType { kotlinOptions { javaParameters = true suppressWarnings = true + jvmTarget = "1.8" //freeCompilerArgs = listOf("-XXLanguage:+InlineClasses", "-Xuse-experimental=kotlin.Experimental") } } diff --git a/src/main/kotlin/io/dico/parcels2/AddedData.kt b/src/main/kotlin/io/dico/parcels2/AddedData.kt deleted file mode 100644 index a23b36e..0000000 --- a/src/main/kotlin/io/dico/parcels2/AddedData.kt +++ /dev/null @@ -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 -typealias AddedDataMap = Map - -@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 -} diff --git a/src/main/kotlin/io/dico/parcels2/Interactable.kt b/src/main/kotlin/io/dico/parcels2/Interactable.kt index 60539aa..cb83d6e 100644 --- a/src/main/kotlin/io/dico/parcels2/Interactable.kt +++ b/src/main/kotlin/io/dico/parcels2/Interactable.kt @@ -2,7 +2,6 @@ package io.dico.parcels2 import io.dico.parcels2.util.ext.getMaterialsWithWoodTypePrefix import org.bukkit.Material -import java.lang.IllegalArgumentException import java.util.EnumMap class Interactables @@ -113,13 +112,14 @@ val pathInteractableConfig: InteractableConfiguration = run { interface InteractableConfiguration { val interactableClasses: List get() = Interactables.classesById.filter { isInteractable(it) } + fun isInteractable(material: Material): Boolean fun isInteractable(clazz: Interactables): Boolean fun setInteractable(clazz: Interactables, interactable: Boolean): 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(className: String) = isInteractable(Interactables[className]) diff --git a/src/main/kotlin/io/dico/parcels2/Parcel.kt b/src/main/kotlin/io/dico/parcels2/Parcel.kt index 2c56627..6ec6121 100644 --- a/src/main/kotlin/io/dico/parcels2/Parcel.kt +++ b/src/main/kotlin/io/dico/parcels2/Parcel.kt @@ -1,7 +1,7 @@ package io.dico.parcels2 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.OfflinePlayer import org.bukkit.entity.Player @@ -37,7 +37,7 @@ interface Parcel : ParcelData { val homeLocation: Location get() = world.blockManager.getHomeLocation(id) } -interface ParcelData : AddedData { +interface ParcelData : Privileges { var owner: PlayerProfile? val lastClaimTime: DateTime? var ownerSignOutdated: Boolean @@ -54,14 +54,15 @@ interface ParcelData : AddedData { } } -class ParcelDataHolder(addedMap: MutableAddedDataMap = mutableMapOf()) - : ParcelData, AddedDataHolder(addedMap) { +class ParcelDataHolder(addedMap: MutablePrivilegeMap = mutableMapOf()) + : ParcelData, PrivilegesHolder(addedMap) { override var owner: PlayerProfile? = null override var lastClaimTime: DateTime? = null 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) } - || (checkAdmin && player is Player && player.hasBuildAnywhere) + || (checkAdmin && player is Player && player.hasPermBuildAnywhere) override var interactableConfig: InteractableConfiguration = BitmaskInteractableConfiguration() } diff --git a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt index d70368f..1e9dc44 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt @@ -85,7 +85,7 @@ interface ParcelWorld : ParcelLocator, ParcelContainer { val container: ParcelContainer val locator: ParcelLocator val blockManager: ParcelBlockManager - val globalAddedData: GlobalAddedDataManager + val globalPrivileges: GlobalPrivilegesManager val creationTime: DateTime? diff --git a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt index c863716..3e12ba5 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt @@ -6,7 +6,7 @@ import io.dico.dicore.command.ICommandDispatcher import io.dico.parcels2.blockvisitor.TickWorktimeLimiter import io.dico.parcels2.blockvisitor.WorktimeLimiter import io.dico.parcels2.command.getParcelCommands -import io.dico.parcels2.defaultimpl.GlobalAddedDataManagerImpl +import io.dico.parcels2.defaultimpl.GlobalPrivilegesManagerImpl import io.dico.parcels2.defaultimpl.ParcelProviderImpl import io.dico.parcels2.listener.ParcelEntityTracker import io.dico.parcels2.listener.ParcelListeners @@ -35,7 +35,7 @@ class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginScheduler { lateinit var options: Options; private set lateinit var parcelProvider: ParcelProvider; private set lateinit var storage: Storage; private set - lateinit var globalAddedData: GlobalAddedDataManager; private set + lateinit var globalPrivileges: GlobalPrivilegesManager; private set val registrator = Registrator(this) lateinit var entityTracker: ParcelEntityTracker; private set @@ -83,7 +83,7 @@ class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginScheduler { return false } - globalAddedData = GlobalAddedDataManagerImpl(this) + globalPrivileges = GlobalPrivilegesManagerImpl(this) entityTracker = ParcelEntityTracker(parcelProvider) } catch (ex: Exception) { plogger.error("Error loading options", ex) diff --git a/src/main/kotlin/io/dico/parcels2/Privileges.kt b/src/main/kotlin/io/dico/parcels2/Privileges.kt new file mode 100644 index 0000000..11a1f84 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/Privileges.kt @@ -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 +typealias PrivilegeMap = Map + +@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 +} diff --git a/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt b/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt index 2339309..0c0b47f 100644 --- a/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt +++ b/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt @@ -4,7 +4,7 @@ import io.dico.dicore.command.* import io.dico.parcels2.ParcelWorld import io.dico.parcels2.ParcelsPlugin 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 org.bukkit.entity.Player import org.bukkit.plugin.Plugin @@ -26,7 +26,7 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei } protected suspend fun checkParcelLimit(player: Player, world: ParcelWorld) { - if (player.hasAdminManage) return + if (player.hasPermAdminManage) return val numOwnedParcels = plugin.storage.getOwnedParcels(PlayerProfile(player)).await() .filter { it.worldId.equals(world.id) }.size diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusGlobal.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusGlobal.kt deleted file mode 100644 index eae6da2..0000000 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusGlobal.kt +++ /dev/null @@ -1,65 +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.GlobalAddedData -import io.dico.parcels2.GlobalAddedDataManager -import io.dico.parcels2.PlayerProfile -import io.dico.parcels2.ParcelsPlugin -import org.bukkit.OfflinePlayer -import org.bukkit.entity.Player - -class CommandsAddedStatusGlobal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { - private inline val data get() = plugin.globalAddedData - @Suppress("NOTHING_TO_INLINE") - private inline operator fun GlobalAddedDataManager.get(player: OfflinePlayer): GlobalAddedData = data[PlayerProfile(player)] - - @Cmd("allow", aliases = ["add", "permit"]) - @Desc("Globally allows a player to build on all", - "the parcels that you own.", - shortVersion = "globally allows a player to build on your parcels") - @ParcelRequire(owner = true) - fun cmdAllow(sender: Player, player: OfflinePlayer): Any? { - Validate.isTrue(player != sender, "The target cannot be yourself") - Validate.isTrue(data[sender].allow(player), "${player.name} is already allowed globally") - return "${player.name} is now allowed to build on all your parcels" - } - - @Cmd("disallow", aliases = ["remove", "forbid"]) - @Desc("Globally disallows a player to build on", - "the parcels that you own.", - "If the player is allowed to build on specific", - "parcels, they can still build there.", - shortVersion = "globally disallows a player to build on your parcels") - @ParcelRequire(owner = true) - fun cmdDisallow(sender: Player, player: OfflinePlayer): Any? { - Validate.isTrue(player != sender, "The target cannot be yourself") - Validate.isTrue(data[sender].disallow(player), "${player.name} is not currently allowed globally") - return "${player.name} is not allowed to build on all your parcels anymore" - } - - @Cmd("ban", aliases = ["deny"]) - @Desc("Globally bans a player from all the parcels", - "that you own, making them unable to enter.", - shortVersion = "globally bans a player from your parcels") - @ParcelRequire(owner = true) - fun cmdBan(sender: Player, player: OfflinePlayer): Any? { - Validate.isTrue(player != sender, "The target cannot be yourself") - Validate.isTrue(data[sender].ban(player), "${player.name} is already banned from all your parcels") - return "${player.name} is now banned from all your parcels" - } - - @Cmd("unban", aliases = ["undeny"]) - @Desc("Globally unbans a player from all the parcels", - "that you own, they can enter again.", - "If the player is banned from specific parcels,", - "they will still be banned there.", - shortVersion = "globally unbans a player from your parcels") - @ParcelRequire(owner = true) - fun cmdUnban(sender: Player, player: OfflinePlayer): Any? { - Validate.isTrue(data[sender].unban(player), "${player.name} is not currently banned from all your parcels") - return "${player.name} is not banned from all your parcels anymore" - } - -} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusLocal.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusLocal.kt deleted file mode 100644 index 223e504..0000000 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusLocal.kt +++ /dev/null @@ -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" - } - -} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsAdmin.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsAdmin.kt index 35ede71..0b155f2 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsAdmin.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsAdmin.kt @@ -5,11 +5,12 @@ import io.dico.dicore.command.annotation.Cmd import io.dico.dicore.command.annotation.Flag import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.PlayerProfile +import io.dico.parcels2.Privilege class CommandsAdmin(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { @Cmd("setowner") - @ParcelRequire(admin = true) + @RequireParcelPrivilege(Privilege.ADMIN) fun ParcelScope.cmdSetowner(target: PlayerProfile): Any? { parcel.owner = target @@ -18,14 +19,14 @@ class CommandsAdmin(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { } @Cmd("dispose") - @ParcelRequire(admin = true) + @RequireParcelPrivilege(Privilege.ADMIN) fun ParcelScope.cmdDispose(): Any? { parcel.dispose() return "Data of (${parcel.id.idString}) has been disposed" } @Cmd("reset") - @ParcelRequire(admin = true) + @RequireParcelPrivilege(Privilege.ADMIN) fun ParcelScope.cmdReset(context: ExecutionContext, @Flag sure: Boolean): Any? { if (!sure) return areYouSureMessage(context) parcel.dispose() @@ -34,9 +35,10 @@ class CommandsAdmin(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { } @Cmd("swap") + @RequireParcelPrivilege(Privilege.ADMIN) fun ParcelScope.cmdSwap(context: ExecutionContext, @Flag sure: Boolean): Any? { if (!sure) return areYouSureMessage(context) - TODO() + TODO("implement swap") } diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt index 3ae17f2..b646450 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt @@ -5,6 +5,7 @@ import io.dico.dicore.command.EMessageType import io.dico.dicore.command.ExecutionContext import io.dico.dicore.command.annotation.Cmd import io.dico.parcels2.ParcelsPlugin +import io.dico.parcels2.Privilege import io.dico.parcels2.blockvisitor.RegionTraverser import io.dico.parcels2.doBlockOperation import org.bukkit.Bukkit @@ -30,7 +31,7 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { } @Cmd("make_mess") - @ParcelRequire(owner = true) + @RequireParcelPrivilege(Privilege.OWNER) fun ParcelScope.cmdMakeMess(context: ExecutionContext) { val server = plugin.server val blockDatas = arrayOf( @@ -47,8 +48,10 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { world.blockManager.doBlockOperation(parcel.id, traverser = RegionTraverser.upward) { block -> 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)) + context.sendMessage( + EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed" + .format(progress * 100, elapsedTime / 1000.0) + ) } } diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt index eef07fd..33825fb 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt @@ -9,8 +9,8 @@ import io.dico.dicore.command.annotation.RequireParameters import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.PlayerProfile 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.hasPermAdminManage import io.dico.parcels2.util.ext.uuid import org.bukkit.block.Biome import org.bukkit.entity.Player @@ -99,7 +99,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { ) suspend fun ParcelScope.cmdClaim(player: Player): Any? { 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") } @@ -110,14 +110,14 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { @Cmd("unclaim") @Desc("Unclaims this parcel") - @ParcelRequire(owner = true) + @RequireParcelPrivilege(Privilege.OWNER) fun ParcelScope.cmdUnclaim(player: Player): Any? { parcel.dispose() return "Your parcel has been disposed" } @Cmd("clear") - @ParcelRequire(owner = true) + @RequireParcelPrivilege(Privilege.OWNER) fun ParcelScope.cmdClear(context: ExecutionContext, @Flag sure: Boolean): Any? { if (!sure) return areYouSureMessage(context) @@ -126,7 +126,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { } @Cmd("setbiome") - @ParcelRequire(owner = true) + @RequireParcelPrivilege(Privilege.OWNER) fun ParcelScope.cmdSetbiome(context: ExecutionContext, biome: Biome): Any? { Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel") world.blockManager.setBiome(parcel.id, biome) diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsParcelOptions.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsParcelOptions.kt deleted file mode 100644 index b46e972..0000000 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsParcelOptions.kt +++ /dev/null @@ -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, - 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) - } - } - -} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsPrivilegesGlobal.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsPrivilegesGlobal.kt new file mode 100644 index 0000000..91b8272 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsPrivilegesGlobal.kt @@ -0,0 +1,91 @@ +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.GlobalPrivileges +import io.dico.parcels2.GlobalPrivilegesManager +import io.dico.parcels2.ParcelsPlugin +import io.dico.parcels2.PlayerProfile +import org.bukkit.OfflinePlayer +import org.bukkit.entity.Player + +class CommandsPrivilegesGlobal(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { + private inline val data get() = plugin.globalPrivileges + @Suppress("NOTHING_TO_INLINE") + 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"]) + @Desc( + "Globally allows a player to build on all", + "the parcels that you own.", + shortVersion = "globally allows a player to build on your parcels" + ) + fun cmdAllow(sender: Player, player: OfflinePlayer): Any? { + Validate.isTrue(player != sender, "The target cannot be yourself") + Validate.isTrue(data[sender].allowBuild(player), "${player.name} is already allowed globally") + return "${player.name} is now allowed to build on all your parcels" + } + + @Cmd("disallow", aliases = ["remove", "forbid"]) + @Desc( + "Globally disallows a player to build on", + "the parcels that you own.", + "If the player is allowed to build on specific", + "parcels, they can still build there.", + shortVersion = "globally disallows a player to build on your parcels" + ) + fun cmdDisallow(sender: Player, player: OfflinePlayer): Any? { + Validate.isTrue(player != sender, "The target cannot be yourself") + 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" + } + + @Cmd("ban", aliases = ["deny"]) + @Desc( + "Globally bans a player from all the parcels", + "that you own, making them unable to enter.", + shortVersion = "globally bans a player from your parcels" + ) + fun cmdBan(sender: Player, player: OfflinePlayer): Any? { + Validate.isTrue(player != sender, "The target cannot be yourself") + Validate.isTrue(data[sender].ban(player), "${player.name} is already banned from all your parcels") + return "${player.name} is now banned from all your parcels" + } + + @Cmd("unban", aliases = ["undeny"]) + @Desc( + "Globally unbans a player from all the parcels", + "that you own, they can enter again.", + "If the player is banned from specific parcels,", + "they will still be banned there.", + shortVersion = "globally unbans a player from your parcels" + ) + fun cmdUnban(sender: Player, player: OfflinePlayer): Any? { + Validate.isTrue(data[sender].unban(player), "${player.name} is not currently banned from all your parcels") + return "${player.name} is not banned from all your parcels anymore" + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsPrivilegesLocal.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsPrivilegesLocal.kt new file mode 100644 index 0000000..16b99af --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsPrivilegesLocal.kt @@ -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" + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt index b9dfe1e..1eddf97 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt @@ -4,6 +4,7 @@ import io.dico.dicore.command.CommandBuilder import io.dico.dicore.command.ICommandAddress import io.dico.dicore.command.ICommandDispatcher import io.dico.dicore.command.registration.reflect.ReflectiveRegistration +import io.dico.parcels2.Interactables import io.dico.parcels2.ParcelsPlugin import io.dico.parcels2.logger import java.util.LinkedList @@ -20,15 +21,25 @@ fun getParcelCommands(plugin: ParcelsPlugin): ICommandDispatcher = group("parcel", "plot", "plots", "p") { addRequiredPermission("parcels.command") registerCommands(CommandsGeneral(plugin)) - registerCommands(CommandsAddedStatusLocal(plugin)) + registerCommands(CommandsPrivilegesLocal(plugin)) group("option", "opt", "o") { - CommandsParcelOptions.setGroupDescription(this) - registerCommands(CommandsParcelOptions(plugin)) + setGroupDescription( + "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") { - registerCommands(CommandsAddedStatusGlobal(plugin)) + registerCommands(CommandsPrivilegesGlobal(plugin)) } group("admin", "a") { diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt index 488148d..443adfb 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt @@ -7,7 +7,9 @@ import io.dico.dicore.command.Validate import io.dico.parcels2.Parcel import io.dico.parcels2.ParcelProvider import io.dico.parcels2.ParcelWorld -import io.dico.parcels2.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 org.bukkit.entity.Player import java.lang.reflect.Method @@ -18,44 +20,55 @@ import kotlin.reflect.jvm.kotlinFunction @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) -annotation class ParcelRequire(val admin: Boolean = false, val owner: Boolean = false) +annotation class RequireParcelPrivilege(val privilege: Privilege) +/* @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) annotation class SuspensionTimeout(val millis: Int) +*/ open class WorldScope(val world: ParcelWorld) : ICommandReceiver open class ParcelScope(val parcel: Parcel) : WorldScope(parcel.world) { - fun checkCanManage(player: Player, action: String) = Validate.isTrue(player.hasAdminManage || parcel.isOwner(player.uuid), - "You must own this parcel to $action") + fun checkCanManage(player: Player, action: String) = Validate.isTrue( + player.hasPermAdminManage || parcel.hasPrivilegeToManage(player), + "You must own this parcel to $action" + ) } fun getParcelCommandReceiver(parcelProvider: ParcelProvider, context: ExecutionContext, method: Method, cmdName: String): ICommandReceiver { val player = context.sender as Player val function = method.kotlinFunction!! val receiverType = function.extensionReceiverParameter!!.type - val require = function.findAnnotation() - val admin = require?.admin == true - val owner = require?.owner == true + val require = function.findAnnotation() return when (receiverType.jvmErasure) { - ParcelScope::class -> ParcelScope(parcelProvider.getParcelRequired(player, admin, owner)) - WorldScope::class -> WorldScope(parcelProvider.getWorldRequired(player, admin)) + ParcelScope::class -> ParcelScope(parcelProvider.getParcelRequired(player, require?.privilege)) + WorldScope::class -> WorldScope(parcelProvider.getWorldRequired(player, require?.privilege == ADMIN)) else -> throw InternalError("Invalid command receiver type") } } 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) ?: 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 { - val parcel = getWorldRequired(player, admin = admin).getParcelAt(player) +fun ParcelProvider.getParcelRequired(player: Player, privilege: Privilege? = null): Parcel { + val parcel = getWorldRequired(player, admin = privilege == ADMIN).getParcelAt(player) ?: throw CommandException("You must be in a parcel to use that command") - if (own) Validate.isTrue(parcel.isOwner(player.uuid) || player.hasAdminManage, - "You must own this parcel to use that command") + + 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 } diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelOptionsInteractCommand.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelOptionsInteractCommand.kt new file mode 100644 index 0000000..feba76e --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelOptionsInteractCommand.kt @@ -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) \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt index abf7d40..956da94 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt @@ -8,7 +8,7 @@ import io.dico.parcels2.* import io.dico.parcels2.storage.Storage import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.ext.floor -import kotlinx.coroutines.CoroutineStart.* +import kotlinx.coroutines.CoroutineStart.UNDISPATCHED import kotlinx.coroutines.Deferred import kotlinx.coroutines.async 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 } - class ByOwner(world: ParcelWorld, - owner: PlayerProfile, - val index: Int, - parsedKind: Int, - isDefault: Boolean, - val onResolveFailure: (() -> Unit)? = null) : ParcelTarget(world, parsedKind, isDefault) { + class ByOwner( + world: ParcelWorld, + owner: PlayerProfile, + val index: Int, + parsedKind: Int, + isDefault: Boolean, + val onResolveFailure: (() -> Unit)? = null + ) : ParcelTarget(world, parsedKind, isDefault) { init { if (index < 0) throw IllegalArgumentException("Invalid parcel home index: $index") } diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalAddedDataManagerImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalAddedDataManagerImpl.kt deleted file mode 100644 index ad7048c..0000000 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalAddedDataManagerImpl.kt +++ /dev/null @@ -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() - - 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() as MutableAddedDataMap - } - -} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalPrivilegesManagerImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalPrivilegesManagerImpl.kt new file mode 100644 index 0000000..239fe0a --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalPrivilegesManagerImpl.kt @@ -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() + + 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() as MutablePrivilegeMap + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt index d392a8f..1580cf1 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt @@ -35,20 +35,20 @@ class ParcelImpl( world.storage.setParcelData(this, null) } - override val addedMap: AddedDataMap get() = data.addedMap - override fun getStatus(key: StatusKey) = data.getStatus(key) - override fun isBanned(key: StatusKey) = data.isBanned(key) - override fun isAllowed(key: StatusKey) = data.isAllowed(key) + override val map: PrivilegeMap get() = data.map + override fun privilege(key: PrivilegeKey) = data.privilege(key) + override fun isBanned(key: PrivilegeKey) = data.isBanned(key) + override fun hasPrivilegeToBuild(key: PrivilegeKey) = data.hasPrivilegeToBuild(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) + || checkGlobal && world.globalPrivileges[owner ?: return false].hasPrivilegeToBuild(player) } - override var statusOfStar: AddedStatus - get() = data.statusOfStar - set(value) = run { setStatus(PlayerProfile.Star, value) } + override var privilegeOfStar: Privilege + get() = data.privilegeOfStar + 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 @@ -71,12 +71,16 @@ class ParcelImpl( } } - override fun setStatus(key: StatusKey, status: AddedStatus): Boolean { - return data.setStatus(key, status).alsoIfTrue { - world.storage.setParcelPlayerStatus(this, key, status) + override fun setPrivilege(key: PrivilegeKey, privilege: Privilege): Boolean { + return data.setPrivilege(key, privilege).alsoIfTrue { + world.storage.setLocalPrivilege(this, key, privilege) } } + private fun updateInteractableConfigStorage() { + world.storage.setParcelOptionsInteractConfig(this, data.interactableConfig) + } + private var _interactableConfig: InteractableConfiguration? = null override var interactableConfig: InteractableConfiguration get() { @@ -86,21 +90,18 @@ class ParcelImpl( override fun isInteractable(clazz: Interactables): Boolean = data.interactableConfig.isInteractable(clazz) override fun setInteractable(clazz: Interactables, interactable: Boolean): Boolean = - data.interactableConfig.setInteractable(clazz, interactable).alsoIfTrue { - // TODO update storage - } + data.interactableConfig.setInteractable(clazz, interactable).alsoIfTrue { updateInteractableConfigStorage() } override fun clear(): Boolean = - data.interactableConfig.clear().alsoIfTrue { - // TODO update storage - } + data.interactableConfig.clear().alsoIfTrue { updateInteractableConfigStorage() } } } return _interactableConfig!! } set(value) { - data.interactableConfig.copyFrom(value) - // TODO update storage + if (data.interactableConfig.copyFrom(value)) { + updateInteractableConfigStorage() + } } private var blockVisitors = AtomicInteger(0) @@ -139,10 +140,10 @@ private object ParcelInfoStringComputer { 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 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 appendField({ @@ -182,11 +183,11 @@ private object ParcelInfoStringComputer { append('\n') - val global = owner?.let { parcel.world.globalAddedData[owner].addedMap } ?: emptyMap() - val local = parcel.addedMap - appendAddedList(local, global, AddedStatus.ALLOWED, "Allowed") + val global = owner?.let { parcel.world.globalPrivileges[owner].map } ?: emptyMap() + val local = parcel.map + appendAddedList(local, global, Privilege.CAN_BUILD, "Allowed") append('\n') - appendAddedList(local, global, AddedStatus.BANNED, "Banned") + appendAddedList(local, global, Privilege.BANNED, "Banned") /* TODO options if (!parcel.allowInteractInputs || !parcel.allowInteractInventory) { diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt index 8920e2e..1112047 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt @@ -2,8 +2,6 @@ package io.dico.parcels2.defaultimpl import io.dico.parcels2.* import io.dico.parcels2.util.schedule -import kotlinx.coroutines.CoroutineStart -import kotlinx.coroutines.CoroutineStart.* import kotlinx.coroutines.Unconfined import kotlinx.coroutines.launch 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") } parcelWorld = ParcelWorldImpl(bukkitWorld, generator, worldOptions.runtime, plugin.storage, - plugin.globalAddedData, ::DefaultParcelContainer, plugin, plugin.worktimeLimiter) + plugin.globalPrivileges, ::DefaultParcelContainer, plugin, plugin.worktimeLimiter) if (!worldExists) { val time = DateTime.now() @@ -119,7 +117,7 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider { worldOptions, worldOptions.generator.newGenerator(this, worldName), plugin.storage, - plugin.globalAddedData, + plugin.globalPrivileges, ::DefaultParcelContainer) } catch (ex: Exception) { diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt index 9f96a8c..519008c 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt @@ -15,7 +15,7 @@ class ParcelWorldImpl(override val world: World, override val generator: ParcelGenerator, override var options: RuntimeWorldOptions, override val storage: Storage, - override val globalAddedData: GlobalAddedDataManager, + val globalPrivileges: GlobalPrivilegesManager, containerFactory: ParcelContainerFactory, coroutineScope: CoroutineScope, worktimeLimiter: WorktimeLimiter) diff --git a/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt b/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt index 1af3406..c8e64d3 100644 --- a/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt +++ b/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt @@ -36,7 +36,7 @@ class ParcelListeners( val entityTracker: ParcelEntityTracker, 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 @@ -55,9 +55,9 @@ class ParcelListeners( @field:ListenerMarker(priority = NORMAL) val onPlayerMoveEvent = RegistratorListener l@{ event -> val user = event.player - if (user.hasBanBypass) return@l + if (user.hasPermBanBypass) 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 { user.teleport(it.homeLocation) user.sendParcelMessage(nopermit = true, message = "You are banned from this parcel") @@ -72,7 +72,7 @@ class ParcelListeners( @field:ListenerMarker(priority = NORMAL) val onBlockBreakEvent = RegistratorListener l@{ event -> 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 } @@ -91,7 +91,7 @@ class ParcelListeners( @field:ListenerMarker(priority = NORMAL) val onBlockPlaceEvent = RegistratorListener l@{ event -> 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 } } @@ -185,7 +185,7 @@ class ParcelListeners( val clickedBlock = event.clickedBlock 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") event.isCancelled = true; return@l } @@ -218,7 +218,7 @@ class ParcelListeners( } 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") event.isCancelled = true; return@l } @@ -484,7 +484,7 @@ class ParcelListeners( 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 } @@ -560,7 +560,7 @@ class ParcelListeners( @field:ListenerMarker(priority = NORMAL) val onPlayerChangedWorldEvent = RegistratorListener l@{ event -> 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 } } diff --git a/src/main/kotlin/io/dico/parcels2/listener/WorldEditListener.kt b/src/main/kotlin/io/dico/parcels2/listener/WorldEditListener.kt index b68f7c2..b279d2d 100644 --- a/src/main/kotlin/io/dico/parcels2/listener/WorldEditListener.kt +++ b/src/main/kotlin/io/dico/parcels2/listener/WorldEditListener.kt @@ -4,7 +4,6 @@ import com.sk89q.worldedit.EditSession.Stage.BEFORE_REORDER import com.sk89q.worldedit.Vector import com.sk89q.worldedit.Vector2D import com.sk89q.worldedit.WorldEdit -import com.sk89q.worldedit.WorldEditException import com.sk89q.worldedit.bukkit.WorldEditPlugin import com.sk89q.worldedit.event.extent.EditSessionEvent 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.Subscribe import com.sk89q.worldedit.world.biome.BaseBiome -import com.sk89q.worldedit.world.block.BaseBlock import com.sk89q.worldedit.world.block.BlockStateHolder import io.dico.parcels2.ParcelWorld 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 org.bukkit.entity.Player import org.bukkit.plugin.Plugin @@ -33,7 +31,7 @@ class WorldEditListener(val parcels: ParcelsPlugin, val worldEdit: WorldEdit) { if (actor == null || !actor.isPlayer) return val player = parcels.server.getPlayer(actor.uniqueId) - if (player.hasBuildAnywhere) return + if (player.hasPermBuildAnywhere) return event.extent = ParcelsExtent(event.extent, world, player) } diff --git a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt index 6c91714..69c5b62 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt @@ -1,13 +1,13 @@ package io.dico.parcels2.storage import io.dico.parcels2.* -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Deferred import kotlinx.coroutines.Job import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.channels.SendChannel import org.joda.time.DateTime import java.util.UUID +import kotlin.coroutines.CoroutineContext interface Backing { @@ -15,7 +15,7 @@ interface Backing { val isConnected: Boolean - val dispatcher: CoroutineDispatcher + val coroutineContext: CoroutineContext fun launchJob(job: Backing.() -> Unit): Job @@ -56,9 +56,9 @@ interface Backing { 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) @@ -67,7 +67,7 @@ interface Backing { fun transmitAllGlobalAddedData(channel: SendChannel>) - 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) } diff --git a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt index c9c0d4a..fa63496 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt @@ -3,6 +3,7 @@ package io.dico.parcels2.storage import io.dico.parcels2.* +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred import kotlinx.coroutines.Job import kotlinx.coroutines.channels.ReceiveChannel @@ -10,9 +11,10 @@ import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.launch import org.joda.time.DateTime import java.util.UUID +import kotlin.coroutines.CoroutineContext typealias DataPair = Pair -typealias AddedDataPair = Pair +typealias AddedDataPair = Pair interface Storage { val name: String @@ -48,28 +50,29 @@ interface Storage { 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> - fun readGlobalAddedData(owner: PlayerProfile): Deferred + fun readGlobalPrivileges(owner: PlayerProfile): Deferred - fun setGlobalAddedStatus(owner: PlayerProfile, player: PlayerProfile, status: AddedStatus): Job + fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, status: Privilege): Job fun getChannelToUpdateParcelData(): SendChannel> } -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 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 = 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 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> = b.openChannel { b.transmitAllGlobalAddedData(it) } - override fun readGlobalAddedData(owner: PlayerProfile): Deferred = b.launchFuture { b.readGlobalAddedData(owner) } + override fun readGlobalPrivileges(owner: PlayerProfile): Deferred = 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> = b.openChannelForWriting { b.setParcelData(it.first, it.second) } } diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt index b7f9f82..4c88c11 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt @@ -23,17 +23,16 @@ import javax.sql.DataSource 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 dispatcher: ThreadPoolDispatcher = newFixedThreadPoolContext(poolSize, "Parcels StorageThread") - + override val coroutineContext = Job() + 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 launchFuture(future: Backing.() -> T): Deferred = async(dispatcher) { transaction { future() } } + override fun launchJob(job: Backing.() -> Unit): Job = launch { transaction { job() } } + override fun launchFuture(future: Backing.() -> T): Deferred = async { transaction { future() } } override fun openChannel(future: Backing.(SendChannel) -> Unit): ReceiveChannel { val channel = LinkedListChannel() @@ -44,8 +43,8 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi override fun openChannelForWriting(action: Backing.(T) -> Unit): SendChannel { val channel = ArrayChannel(poolSize * 2) - repeat(poolSize) { - launch(dispatcher) { + repeat(poolSize.clampMax(3)) { + launch { try { while (true) { action(channel.receive()) @@ -75,7 +74,7 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi dataSource = dataSourceFactory() database = Database.connect(dataSource!!) 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() { synchronized { if (isShutdown) throw IllegalStateException() + isShutdown = true + coroutineContext[Job]!!.cancel(CancellationException("ExposedBacking shutdown")) dataSource?.let { (it as? HikariDataSource)?.close() } database = null - isShutdown = true } } @@ -173,7 +173,7 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi // 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 } */ } @@ -184,18 +184,16 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi transaction { val id = ParcelsT.getOrInitId(parcel) - AddedLocalT.deleteIgnoreWhere { AddedLocalT.attach_id eq id } + PrivilegesLocalT.deleteIgnoreWhere { PrivilegesLocalT.attach_id eq id } } setParcelOwner(parcel, data.owner) - for ((profile, status) in data.addedMap) { - AddedLocalT.setPlayerStatus(parcel, profile, status) + for ((profile, privilege) in data.map) { + PrivilegesLocalT.setPrivilege(parcel, profile, privilege) } - val bitmaskArray = (data.interactableConfig as? BitmaskInteractableConfiguration ?: return).bitmaskArray - val isAllZero = bitmaskArray.fold(false) { cur, elem -> cur || elem != 0 } - setParcelOptionsInteractBitmask(parcel, if (isAllZero) null else bitmaskArray) + setParcelOptionsInteractConfig(parcel, data.interactableConfig) } 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) { - AddedLocalT.setPlayerStatus(parcel, player.toRealProfile(), status) + override fun setLocalPrivilege(parcel: ParcelId, player: PlayerProfile, privilege: Privilege) { + PrivilegesLocalT.setPrivilege(parcel, player.toRealProfile(), privilege) } - override fun setParcelOptionsInteractBitmask(parcel: ParcelId, bitmask: IntArray?) { - if (bitmask == null) { + override fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration) { + 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 ParcelOptionsT.deleteWhere { ParcelOptionsT.parcel_id eq id } return } - if (bitmask.size != 1) throw IllegalArgumentException() - val array = bitmask.toByteArray() + if (bitmaskArray.size != 1) throw IllegalArgumentException() + val array = bitmaskArray.toByteArray() val id = ParcelsT.getOrInitId(parcel) ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { it[parcel_id] = id @@ -242,16 +243,16 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi } override fun transmitAllGlobalAddedData(channel: SendChannel>) { - AddedGlobalT.sendAllAddedData(channel) + PrivilegesGlobalT.sendAllAddedData(channel) channel.close() } - override fun readGlobalAddedData(owner: PlayerProfile): MutableAddedDataMap { - return AddedGlobalT.readAddedData(ProfilesT.getId(owner.toOwnerProfile()) ?: return hashMapOf()) + override fun readGlobalPrivileges(owner: PlayerProfile): MutablePrivilegeMap { + return PrivilegesGlobalT.readPrivileges(ProfilesT.getId(owner.toOwnerProfile()) ?: return hashMapOf()) } - override fun setGlobalPlayerStatus(owner: PlayerProfile, player: PlayerProfile, status: AddedStatus) { - AddedGlobalT.setPlayerStatus(owner, player.toRealProfile(), status) + override fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege) { + PrivilegesGlobalT.setPrivilege(owner, player.toRealProfile(), privilege) } 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)) } - addedMap = AddedLocalT.readAddedData(id) + map = PrivilegesLocalT.readPrivileges(id) } } diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt index 696b84c..6f6ad6b 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt @@ -26,7 +26,7 @@ abstract class IdTransactionsTable 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") + ?: throw ExposedDatabaseException("This should not happen - failed to insert ${objName()} and get its number") } abstract fun getId(obj: QueryObj): Int? diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt index f41d545..600255e 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt @@ -3,30 +3,30 @@ package io.dico.parcels2.storage.exposed import io.dico.parcels2.* -import io.dico.parcels2.AddedStatus.ALLOWED -import io.dico.parcels2.AddedStatus.DEFAULT +import io.dico.parcels2.Privilege.DEFAULT import kotlinx.coroutines.channels.SendChannel import org.jetbrains.exposed.sql.* -import java.util.UUID -object AddedLocalT : AddedTable("parcels_added_local", ParcelsT) -object AddedGlobalT : AddedTable("parcels_added_global", ProfilesT) +object PrivilegesLocalT : PrivilegesTable("parcels_added_local", ParcelsT) +object PrivilegesGlobalT : PrivilegesTable("parcels_added_global", ProfilesT) object ParcelOptionsT : Table("parcel_options") { 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 = SendChannel> +typealias PrivilegesSendChannel = SendChannel> -sealed class AddedTable(name: String, val idTable: IdTransactionsTable<*, AttachT>) : Table(name) { +sealed class PrivilegesTable(name: String, val idTable: IdTransactionsTable<*, AttachT>) : Table(name) { val attach_id = integer("attach_id").references(idTable.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) - fun setPlayerStatus(attachedOn: AttachT, player: PlayerProfile.Real, status: AddedStatus) { - if (status == DEFAULT) { + fun setPrivilege(attachedOn: AttachT, player: PlayerProfile.Real, privilege: Privilege) { + privilege.requireNonTransient() + + if (privilege == DEFAULT) { val player_id = ProfilesT.getId(player) ?: return idTable.getId(attachedOn)?.let { holder -> deleteWhere { (attach_id eq holder) and (profile_id eq player_id) } @@ -39,28 +39,28 @@ sealed class AddedTable(name: String, val idTable: IdTransactionsTable< upsert(conflictIndex = index_pair) { it[attach_id] = holder it[profile_id] = player_id - it[allowed_flag] = status == ALLOWED + it[this.privilege] = privilege.number } } - fun readAddedData(id: Int): MutableAddedDataMap { - val list = slice(profile_id, allowed_flag).select { attach_id eq id } - val result = MutableAddedDataMap() + fun readPrivileges(id: Int): MutablePrivilegeMap { + val list = slice(profile_id, privilege).select { attach_id eq id } + val result = MutablePrivilegeMap() for (row in list) { val profile = ProfilesT.getRealItem(row[profile_id]) ?: continue - result[profile] = row[allowed_flag].asAddedStatus() + result[profile] = Privilege.safeGetByNumber(row[privilege]) ?: continue } return result } - fun sendAllAddedData(channel: AddedStatusSendChannel) { + fun sendAllAddedData(channel: PrivilegesSendChannel) { val iterator = selectAll().orderBy(attach_id).iterator() if (iterator.hasNext()) { val firstRow = iterator.next() var id: Int = firstRow[attach_id] var attach: AttachT? = null - var map: MutableAddedDataMap? = null + var map: MutablePrivilegeMap? = null fun initAttachAndMap() { attach = idTable.getItem(id) @@ -90,14 +90,12 @@ sealed class AddedTable(name: String, val idTable: IdTransactionsTable< } val profile = ProfilesT.getRealItem(row[profile_id]) ?: continue - val status = row[allowed_flag].asAddedStatus() - map!![profile] = status + val privilege = Privilege.safeGetByNumber(row[privilege]) ?: continue + map!![profile] = privilege } sendIfPresent() } } - private inline fun Boolean?.asAddedStatus() = if (this == null) AddedStatus.DEFAULT else if (this) ALLOWED else AddedStatus.BANNED - } diff --git a/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt index 0dcf36d..b514f87 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt @@ -66,11 +66,11 @@ class PlotmeMigration(val options: PlotmeMigrationOptions) : Migration { return ParcelId(world, row[table.px], row[table.pz]) } - fun PlotmePlotPlayerMap.transmitPlotmeAddedTable(kind: AddedStatus) { + fun PlotmePlotPlayerMap.transmitPlotmeAddedTable(kind: Privilege) { 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) + target.setLocalPrivilege(parcel, profile, kind) } } @@ -89,12 +89,12 @@ class PlotmeMigration(val options: PlotmeMigrationOptions) : Migration { mlogger.info("Transmitting data from plotmeallowed table") transaction { - PlotmeAllowedT.transmitPlotmeAddedTable(AddedStatus.ALLOWED) + PlotmeAllowedT.transmitPlotmeAddedTable(Privilege.CAN_BUILD) } mlogger.info("Transmitting data from plotmedenied table") transaction { - PlotmeDeniedT.transmitPlotmeAddedTable(AddedStatus.BANNED) + PlotmeDeniedT.transmitPlotmeAddedTable(Privilege.BANNED) } mlogger.warn("Data has been **transmitted**.") diff --git a/src/main/kotlin/io/dico/parcels2/util/ext/Player.kt b/src/main/kotlin/io/dico/parcels2/util/ext/Player.kt index 38402f0..83bcaf2 100644 --- a/src/main/kotlin/io/dico/parcels2/util/ext/Player.kt +++ b/src/main/kotlin/io/dico/parcels2/util/ext/Player.kt @@ -13,12 +13,12 @@ inline val OfflinePlayer.uuid get() = uniqueId inline val OfflinePlayer.isValid get() = isOnline() || hasPlayedBefore() -inline val Player.hasBanBypass get() = hasPermission("parcels.admin.bypass.ban") -inline val Player.hasGamemodeBypass get() = hasPermission("parcels.admin.bypass.gamemode") -inline val Player.hasBuildAnywhere get() = hasPermission("parcels.admin.bypass.build") -inline val Player.hasAdminManage get() = hasPermission("parcels.admin.manage") +inline val Player.hasPermBanBypass get() = hasPermission("parcels.admin.bypass.ban") +inline val Player.hasPermGamemodeBypass get() = hasPermission("parcels.admin.bypass.gamemode") +inline val Player.hasPermBuildAnywhere get() = hasPermission("parcels.admin.bypass.build") +inline val Player.hasPermAdminManage get() = hasPermission("parcels.admin.manage") 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 get() { for (info in effectivePermissions) { -- cgit v1.2.3