From b0d1fab486e04a415f971bb02f5ba76b83a62351 Mon Sep 17 00:00:00 2001 From: Dico Date: Sun, 23 Sep 2018 20:42:14 +0100 Subject: Do some work on interactables --- src/main/kotlin/io/dico/parcels2/AddedData.kt | 48 ++++++----- src/main/kotlin/io/dico/parcels2/Interactable.kt | 92 ++++++++++++++++++---- src/main/kotlin/io/dico/parcels2/Parcel.kt | 5 -- .../dico/parcels2/command/CommandsParcelOptions.kt | 3 +- .../defaultimpl/GlobalAddedDataManagerImpl.kt | 4 +- .../io/dico/parcels2/defaultimpl/ParcelImpl.kt | 59 ++++++-------- .../io/dico/parcels2/listener/ParcelListeners.kt | 75 +++++++----------- .../kotlin/io/dico/parcels2/storage/Backing.kt | 4 +- .../io/dico/parcels2/storage/DataConverters.kt | 38 +++++++++ .../kotlin/io/dico/parcels2/storage/Storage.kt | 8 +- .../parcels2/storage/exposed/ExposedBacking.kt | 33 ++++---- .../io/dico/parcels2/storage/exposed/IdTables.kt | 4 +- .../io/dico/parcels2/storage/exposed/ListTables.kt | 11 +-- .../storage/migration/plotme/PlotmeMigration.kt | 2 +- src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt | 6 -- .../kotlin/io/dico/parcels2/util/ext/Material.kt | 4 +- src/main/kotlin/io/dico/parcels2/util/ext/Math.kt | 5 +- todo.md | 2 + 18 files changed, 232 insertions(+), 171 deletions(-) create mode 100644 src/main/kotlin/io/dico/parcels2/storage/DataConverters.kt diff --git a/src/main/kotlin/io/dico/parcels2/AddedData.kt b/src/main/kotlin/io/dico/parcels2/AddedData.kt index 9835950..a23b36e 100644 --- a/src/main/kotlin/io/dico/parcels2/AddedData.kt +++ b/src/main/kotlin/io/dico/parcels2/AddedData.kt @@ -1,7 +1,12 @@ 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 @@ -11,20 +16,20 @@ fun MutableAddedDataMap(): MutableAddedDataMap = hashMapOf() interface AddedData { val addedMap: AddedDataMap - var addedStatusOfStar: AddedStatus + var statusOfStar: AddedStatus - fun getAddedStatus(key: StatusKey): AddedStatus - fun setAddedStatus(key: StatusKey, status: AddedStatus): Boolean + fun getStatus(key: StatusKey): AddedStatus + fun setStatus(key: StatusKey, status: AddedStatus): Boolean - fun compareAndSetAddedStatus(key: StatusKey, expect: AddedStatus, status: AddedStatus): Boolean = - getAddedStatus(key) == expect && setAddedStatus(key, status) + fun casStatus(key: StatusKey, expect: AddedStatus, status: AddedStatus): Boolean = + getStatus(key) == expect && setStatus(key, status) - fun isAllowed(key: StatusKey) = getAddedStatus(key) == AddedStatus.ALLOWED - fun allow(key: StatusKey) = setAddedStatus(key, AddedStatus.ALLOWED) - fun disallow(key: StatusKey) = compareAndSetAddedStatus(key, AddedStatus.ALLOWED, AddedStatus.DEFAULT) - fun isBanned(key: StatusKey) = getAddedStatus(key) == AddedStatus.BANNED - fun ban(key: StatusKey) = setAddedStatus(key, AddedStatus.BANNED) - fun unban(key: StatusKey) = compareAndSetAddedStatus(key, AddedStatus.BANNED, AddedStatus.DEFAULT) + fun isAllowed(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) @@ -34,29 +39,20 @@ interface AddedData { fun unban(player: OfflinePlayer) = unban(player.statusKey) } -inline val OfflinePlayer.statusKey get() = PlayerProfile.nameless(this) +inline val OfflinePlayer.statusKey: StatusKey + get() = PlayerProfile.nameless(this) open class AddedDataHolder(override var addedMap: MutableAddedDataMap = MutableAddedDataMap()) : AddedData { - override var addedStatusOfStar: AddedStatus = AddedStatus.DEFAULT + override var statusOfStar: AddedStatus = DEFAULT - override fun getAddedStatus(key: StatusKey): AddedStatus = addedMap.getOrDefault(key, addedStatusOfStar) + override fun getStatus(key: StatusKey): AddedStatus = addedMap.getOrDefault(key, statusOfStar) - override fun setAddedStatus(key: StatusKey, status: AddedStatus): Boolean { - return if (status.isDefault) addedMap.remove(key) != null + override fun setStatus(key: StatusKey, status: AddedStatus): Boolean { + return if (status == DEFAULT) addedMap.remove(key) != null else addedMap.put(key, status) != status } } -enum class AddedStatus { - DEFAULT, - ALLOWED, - BANNED; - - inline val isDefault get() = this == DEFAULT - inline val isAllowed get() = this == ALLOWED - inline val isBanned get() = this == BANNED -} - interface GlobalAddedData : AddedData { val owner: PlayerProfile } diff --git a/src/main/kotlin/io/dico/parcels2/Interactable.kt b/src/main/kotlin/io/dico/parcels2/Interactable.kt index f1beac5..60539aa 100644 --- a/src/main/kotlin/io/dico/parcels2/Interactable.kt +++ b/src/main/kotlin/io/dico/parcels2/Interactable.kt @@ -1,14 +1,17 @@ package io.dico.parcels2 -import io.dico.parcels2.util.ext.findWoodKindPrefixedMaterials +import io.dico.parcels2.util.ext.getMaterialsWithWoodTypePrefix import org.bukkit.Material +import java.lang.IllegalArgumentException import java.util.EnumMap class Interactables -private constructor(val id: Int, - val name: String, - val interactableByDefault: Boolean, - vararg val materials: Material) { +private constructor( + val id: Int, + val name: String, + val interactableByDefault: Boolean, + vararg val materials: Material +) { companion object { val classesById: List @@ -22,35 +25,64 @@ private constructor(val id: Int, listedMaterials = EnumMap(mapOf(*array.flatMap { clazz -> clazz.materials.map { it to clazz.id } }.toTypedArray())) } + operator fun get(material: Material): Interactables? { + val id = listedMaterials[material] ?: return null + return classesById[id] + } + + operator fun get(name: String): Interactables { + return classesByName[name] ?: throw IllegalArgumentException("Interactables class does not exist: $name") + } + + operator fun get(id: Int): Interactables { + return classesById[id] + } + private fun getClassesArray() = run { var id = 0 @Suppress("UNUSED_CHANGED_VALUE") arrayOf( - Interactables(id++, "button", true, + Interactables( + id++, "buttons", true, Material.STONE_BUTTON, - *findWoodKindPrefixedMaterials("BUTTON") + *getMaterialsWithWoodTypePrefix("BUTTON") ), - Interactables(id++, "lever", true, - Material.LEVER), + Interactables( + id++, "levers", true, + Material.LEVER + ), - Interactables(id++, "pressure_plate", true, + Interactables( + id++, "pressure_plates", true, Material.STONE_PRESSURE_PLATE, - *findWoodKindPrefixedMaterials("PRESSURE_PLATE"), + *getMaterialsWithWoodTypePrefix("PRESSURE_PLATE"), Material.HEAVY_WEIGHTED_PRESSURE_PLATE, - Material.LIGHT_WEIGHTED_PRESSURE_PLATE), + Material.LIGHT_WEIGHTED_PRESSURE_PLATE + ), - Interactables(id++, "redstone_components", false, + Interactables( + id++, "redstone", false, Material.COMPARATOR, - Material.REPEATER), + Material.REPEATER + ), - Interactables(id++, "containers", false, + Interactables( + id++, "containers", false, Material.CHEST, Material.TRAPPED_CHEST, Material.DISPENSER, Material.DROPPER, Material.HOPPER, - Material.FURNACE) + Material.FURNACE + ), + + Interactables( + id++, "gates", true, + *getMaterialsWithWoodTypePrefix("DOOR"), + *getMaterialsWithWoodTypePrefix("TRAPDOOR"), + *getMaterialsWithWoodTypePrefix("FENCE_GATE") + ) ) } @@ -58,6 +90,27 @@ private constructor(val id: Int, } +val Parcel?.effectiveInteractableConfig: InteractableConfiguration + get() = this?.interactableConfig ?: pathInteractableConfig + +val pathInteractableConfig: InteractableConfiguration = run { + val data = BitmaskInteractableConfiguration().apply { + Interactables.classesById.forEach { + setInteractable(it, false) + } + } + object : InteractableConfiguration by data { + override fun setInteractable(clazz: Interactables, interactable: Boolean) = + throw IllegalStateException("pathInteractableConfig is immutable") + + override fun clear() = + throw IllegalStateException("pathInteractableConfig is immutable") + + override fun copyFrom(other: InteractableConfiguration) = + throw IllegalStateException("pathInteractableConfig is immutable") + } +} + interface InteractableConfiguration { val interactableClasses: List get() = Interactables.classesById.filter { isInteractable(it) } fun isInteractable(material: Material): Boolean @@ -67,8 +120,13 @@ interface InteractableConfiguration { fun copyFrom(other: InteractableConfiguration) { Interactables.classesById.forEach { setInteractable(it, other.isInteractable(it)) } } + + operator fun invoke(material: Material) = isInteractable(material) + operator fun invoke(className: String) = isInteractable(Interactables[className]) } +fun InteractableConfiguration.isInteractable(clazz: Interactables?) = clazz != null && isInteractable(clazz) + class BitmaskInteractableConfiguration : InteractableConfiguration { val bitmaskArray = IntArray((Interactables.classesById.size + 31) / 32) @@ -98,7 +156,7 @@ class BitmaskInteractableConfiguration : InteractableConfiguration { override fun clear(): Boolean { var change = false for (i in bitmaskArray.indices) { - change = change || bitmaskArray[i] != 0 + if (!change && bitmaskArray[i] != 0) change = true bitmaskArray[i] = 0 } return change diff --git a/src/main/kotlin/io/dico/parcels2/Parcel.kt b/src/main/kotlin/io/dico/parcels2/Parcel.kt index bdf4ff9..2c56627 100644 --- a/src/main/kotlin/io/dico/parcels2/Parcel.kt +++ b/src/main/kotlin/io/dico/parcels2/Parcel.kt @@ -45,9 +45,6 @@ interface ParcelData : AddedData { fun canBuild(player: OfflinePlayer, checkAdmin: Boolean = true, checkGlobal: Boolean = true): Boolean - var allowInteractInputs: Boolean - var allowInteractInventory: Boolean - fun isOwner(uuid: UUID): Boolean { return owner?.uuid == uuid } @@ -66,8 +63,6 @@ class ParcelDataHolder(addedMap: MutableAddedDataMap = mutableMapOf()) || owner.let { it != null && it.matches(player, allowNameMatch = false) } || (checkAdmin && player is Player && player.hasBuildAnywhere) - override var allowInteractInputs = true - override var allowInteractInventory = true override var interactableConfig: InteractableConfiguration = BitmaskInteractableConfiguration() } diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsParcelOptions.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsParcelOptions.kt index c77686e..b46e972 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsParcelOptions.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsParcelOptions.kt @@ -11,6 +11,7 @@ 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,", @@ -28,7 +29,7 @@ class CommandsParcelOptions(plugin: ParcelsPlugin) : AbstractParcelCommands(plug @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, diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalAddedDataManagerImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalAddedDataManagerImpl.kt index 49ab71f..ad7048c 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalAddedDataManagerImpl.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalAddedDataManagerImpl.kt @@ -20,12 +20,12 @@ class GlobalAddedDataManagerImpl(val plugin: ParcelsPlugin) : GlobalAddedDataMan private inline var data get() = addedMap; set(value) = run { addedMap = value } private inline val isEmpty get() = data === emptyData - override fun setAddedStatus(key: StatusKey, status: AddedStatus): Boolean { + override fun setStatus(key: StatusKey, status: AddedStatus): Boolean { if (isEmpty) { if (status == AddedStatus.DEFAULT) return false data = mutableMapOf() } - return super.setAddedStatus(key, status).alsoIfTrue { + return super.setStatus(key, status).alsoIfTrue { plugin.storage.setGlobalAddedStatus(owner, key, status) } } diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt index 59e84f4..d392a8f 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt @@ -9,9 +9,11 @@ import org.bukkit.OfflinePlayer import org.joda.time.DateTime import java.util.concurrent.atomic.AtomicInteger -class ParcelImpl(override val world: ParcelWorld, - override val x: Int, - override val z: Int) : Parcel, ParcelId { +class ParcelImpl( + override val world: ParcelWorld, + override val x: Int, + override val z: Int +) : Parcel, ParcelId { override val id: ParcelId = this override val pos get() = Vec2i(x, z) override var data: ParcelDataHolder = ParcelDataHolder(); private set @@ -34,7 +36,7 @@ class ParcelImpl(override val world: ParcelWorld, } override val addedMap: AddedDataMap get() = data.addedMap - override fun getAddedStatus(key: StatusKey) = data.getAddedStatus(key) + 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 fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean): Boolean { @@ -42,9 +44,9 @@ class ParcelImpl(override val world: ParcelWorld, || checkGlobal && world.globalAddedData[owner ?: return false].isAllowed(player) } - override var addedStatusOfStar: AddedStatus - get() = data.addedStatusOfStar - set(value) = run { setAddedStatus(PlayerProfile.Star, value) } + override var statusOfStar: AddedStatus + get() = data.statusOfStar + set(value) = run { setStatus(PlayerProfile.Star, value) } val globalAddedMap: AddedDataMap? get() = owner?.let { world.globalAddedData[it].addedMap } @@ -69,28 +71,12 @@ class ParcelImpl(override val world: ParcelWorld, } } - override fun setAddedStatus(key: StatusKey, status: AddedStatus): Boolean { - return data.setAddedStatus(key, status).alsoIfTrue { + override fun setStatus(key: StatusKey, status: AddedStatus): Boolean { + return data.setStatus(key, status).alsoIfTrue { world.storage.setParcelPlayerStatus(this, key, status) } } - override var allowInteractInputs: Boolean - get() = data.allowInteractInputs - set(value) { - if (data.allowInteractInputs == value) return - world.storage.setParcelAllowsInteractInputs(this, value) - data.allowInteractInputs = value - } - - override var allowInteractInventory: Boolean - get() = data.allowInteractInventory - set(value) { - if (data.allowInteractInventory == value) return - world.storage.setParcelAllowsInteractInventory(this, value) - data.allowInteractInventory = value - } - private var _interactableConfig: InteractableConfiguration? = null override var interactableConfig: InteractableConfiguration get() { @@ -99,13 +85,15 @@ class ParcelImpl(override val world: ParcelWorld, override fun isInteractable(material: Material): Boolean = data.interactableConfig.isInteractable(material) 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 - } + override fun setInteractable(clazz: Interactables, interactable: Boolean): Boolean = + data.interactableConfig.setInteractable(clazz, interactable).alsoIfTrue { + // TODO update storage + } - override fun clear(): Boolean = data.interactableConfig.clear().alsoIfTrue { - // TODO update storage - } + override fun clear(): Boolean = + data.interactableConfig.clear().alsoIfTrue { + // TODO update storage + } } } return _interactableConfig!! @@ -165,9 +153,11 @@ private object ParcelInfoStringComputer { append(infoStringColor1) append(')') }) { - stringList.joinTo(this, + stringList.joinTo( + this, separator = infoStringColor1.toString() + ", " + infoStringColor2, - limit = 150) + limit = 150 + ) } } @@ -198,6 +188,7 @@ private object ParcelInfoStringComputer { append('\n') appendAddedList(local, global, AddedStatus.BANNED, "Banned") + /* TODO options if (!parcel.allowInteractInputs || !parcel.allowInteractInventory) { appendField("Options") { append("(") @@ -206,7 +197,7 @@ private object ParcelInfoStringComputer { appendField("inventory") { append(parcel.allowInteractInventory) } append(")") } - } + }*/ } } \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt b/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt index 6d89ee6..e39583c 100644 --- a/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt +++ b/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt @@ -3,10 +3,7 @@ package io.dico.parcels2.listener import gnu.trove.TLongCollection import io.dico.dicore.ListenerMarker import io.dico.dicore.RegistratorListener -import io.dico.parcels2.Parcel -import io.dico.parcels2.ParcelProvider -import io.dico.parcels2.ParcelWorld -import io.dico.parcels2.statusKey +import io.dico.parcels2.* import io.dico.parcels2.storage.Storage import io.dico.parcels2.util.ext.* import org.bukkit.Material.* @@ -31,11 +28,14 @@ import org.bukkit.event.weather.WeatherChangeEvent import org.bukkit.event.world.ChunkLoadEvent import org.bukkit.event.world.StructureGrowEvent import org.bukkit.inventory.InventoryHolder +import java.util.EnumSet @Suppress("NOTHING_TO_INLINE") -class ParcelListeners(val parcelProvider: ParcelProvider, - val entityTracker: ParcelEntityTracker, - val storage: Storage) { +class ParcelListeners( + val parcelProvider: ParcelProvider, + val entityTracker: ParcelEntityTracker, + val storage: Storage +) { private inline fun Parcel?.canBuildN(user: Player) = isPresentAnd { canBuild(user) } || user.hasBuildAnywhere /** @@ -170,6 +170,7 @@ class ParcelListeners(val parcelProvider: ParcelProvider, if (ppa.isNullOr { hasBlockVisitors }) event.isCancelled = true } + private val bedTypes = EnumSet.copyOf(getMaterialsWithWoodTypePrefix("BED").toList()) /* * Prevents players from placing liquids, using flint and steel, changing redstone components, * using inputs (unless allowed by the plot), @@ -191,49 +192,33 @@ class ParcelListeners(val parcelProvider: ParcelProvider, when (event.action) { Action.RIGHT_CLICK_BLOCK -> run { - when (clickedBlock.type) { - REPEATER, - COMPARATOR -> run { - if (!parcel.canBuildN(user)) { - event.isCancelled = true; return@l - } - } - LEVER, - STONE_BUTTON, - ANVIL, - TRAPPED_CHEST, - OAK_BUTTON, BIRCH_BUTTON, SPRUCE_BUTTON, JUNGLE_BUTTON, ACACIA_BUTTON, DARK_OAK_BUTTON, - OAK_FENCE_GATE, BIRCH_FENCE_GATE, SPRUCE_FENCE_GATE, JUNGLE_FENCE_GATE, ACACIA_FENCE_GATE, DARK_OAK_FENCE_GATE, - OAK_DOOR, BIRCH_DOOR, SPRUCE_DOOR, JUNGLE_DOOR, ACACIA_DOOR, DARK_OAK_DOOR, - OAK_TRAPDOOR, BIRCH_TRAPDOOR, SPRUCE_TRAPDOOR, JUNGLE_TRAPDOOR, ACACIA_TRAPDOOR, DARK_OAK_TRAPDOOR - -> run { - if (!user.hasBuildAnywhere && !parcel.isNullOr { canBuild(user) || allowInteractInputs }) { - user.sendParcelMessage(nopermit = true, message = "You cannot use inputs in this parcel") - event.isCancelled = true; return@l - } - } + val type = clickedBlock.type + val interactable = parcel.effectiveInteractableConfig.isInteractable(type) || parcel.isPresentAnd { canBuild(user) } + if (!interactable) { + val interactableClassName = Interactables[type]!!.name + user.sendParcelMessage(nopermit = true, message = "You cannot interact with $interactableClassName in this parcel") + event.isCancelled = true + return@l + } - WHITE_BED, ORANGE_BED, MAGENTA_BED, LIGHT_BLUE_BED, YELLOW_BED, LIME_BED, PINK_BED, GRAY_BED, LIGHT_GRAY_BED, CYAN_BED, PURPLE_BED, BLUE_BED, BROWN_BED, GREEN_BED, RED_BED, BLACK_BED - -> run { - if (world.options.disableExplosions) { - val bed = clickedBlock.blockData as Bed - val head = if (bed == Bed.Part.FOOT) clickedBlock.getRelative(bed.facing) else clickedBlock - when (head.biome) { - Biome.NETHER, Biome.THE_END -> run { - user.sendParcelMessage(nopermit = true, message = "You cannot use this bed because it would explode") - event.isCancelled = true; return@l - } + if (bedTypes.contains(type)) { + val bed = clickedBlock.blockData as Bed + val head = if (bed == Bed.Part.FOOT) clickedBlock.getRelative(bed.facing) else clickedBlock + when (head.biome) { + Biome.NETHER, Biome.THE_END -> { + if (world.options.disableExplosions || parcel.isNullOr { !canBuild(user) }) { + user.sendParcelMessage(nopermit = true, message = "You cannot use this bed because it would explode") + event.isCancelled = true; return@l } - } - } } + onPlayerInteractEvent_RightClick(event, world, parcel) } Action.RIGHT_CLICK_AIR -> onPlayerInteractEvent_RightClick(event, world, parcel) - Action.PHYSICAL -> if (!user.hasBuildAnywhere && !parcel.isPresentAnd { canBuild(user) || allowInteractInputs }) { + Action.PHYSICAL -> if (!user.hasBuildAnywhere && !parcel.isPresentAnd { canBuild(user) || interactableConfig("pressure_plates") }) { user.sendParcelMessage(nopermit = true, message = "You cannot use inputs in this parcel") event.isCancelled = true; return@l } @@ -322,7 +307,7 @@ class ParcelListeners(val parcelProvider: ParcelProvider, @field:ListenerMarker(priority = NORMAL) val onPlayerDropItemEvent = RegistratorListener l@{ event -> val (wo, ppa) = getWoAndPPa(event.itemDrop.location.block) ?: return@l - if (!ppa.canBuildN(event.player) && !ppa.isPresentAnd { allowInteractInventory }) event.isCancelled = true + if (!ppa.canBuildN(event.player) && !ppa.isPresentAnd { interactableConfig("containers") }) event.isCancelled = true } /* @@ -343,7 +328,7 @@ class ParcelListeners(val parcelProvider: ParcelProvider, val user = event.whoClicked as? Player ?: return@l if ((event.inventory ?: return@l).holder === user) return@l // inventory null: hotbar val (wo, ppa) = getWoAndPPa(event.inventory.location.block) ?: return@l - if (ppa.isNullOr { !canBuild(user) && !allowInteractInventory }) { + if (ppa.isNullOr { !canBuild(user) && !interactableConfig("containers") }) { event.isCancelled = true } } @@ -385,10 +370,10 @@ class ParcelListeners(val parcelProvider: ParcelProvider, val cancel: Boolean = when (event.newState.type) { - // prevent ice generation from Frost Walkers enchantment + // prevent ice generation from Frost Walkers enchantment FROSTED_ICE -> player != null && !ppa.canBuild(player) - // prevent snow generation from weather + // prevent snow generation from weather SNOW -> !hasEntity && wo.options.preventWeatherBlockChanges else -> false diff --git a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt index 3875467..6c91714 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt @@ -58,10 +58,12 @@ interface Backing { fun setLocalPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus) + fun setParcelOptionsInteractBitmask(parcel: ParcelId, bitmask: IntArray?) +/* fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean) - +*/ fun transmitAllGlobalAddedData(channel: SendChannel>) diff --git a/src/main/kotlin/io/dico/parcels2/storage/DataConverters.kt b/src/main/kotlin/io/dico/parcels2/storage/DataConverters.kt new file mode 100644 index 0000000..80f41b2 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/storage/DataConverters.kt @@ -0,0 +1,38 @@ +package io.dico.parcels2.storage + +import java.lang.IllegalArgumentException +import java.nio.ByteBuffer +import java.util.UUID + +/* For putting it into the database */ +fun UUID.toByteArray(): ByteArray = + ByteBuffer.allocate(16).apply { + putLong(mostSignificantBits) + putLong(leastSignificantBits) + }.array() + +/* For getting it out of the database */ +fun ByteArray.toUUID(): UUID = + ByteBuffer.wrap(this).run { + val mostSignificantBits = getLong() + val leastSignificantBits = getLong() + UUID(mostSignificantBits, leastSignificantBits) + } + +/* For putting it into the database */ +fun IntArray.toByteArray(): ByteArray = + ByteBuffer.allocate(size * Int.SIZE_BYTES).also { buf -> + buf.asIntBuffer().put(this) + }.array() + +/* For getting it out of the database */ +fun ByteArray.toIntArray(): IntArray { + if (this.size % Int.SIZE_BYTES != 0) + throw IllegalArgumentException("Size must be divisible by ${Int.SIZE_BYTES}") + + return ByteBuffer.wrap(this).run { + IntArray(remaining() / 4).also { array -> + asIntBuffer().get(array) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt index 7b04bce..c9c0d4a 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt @@ -50,9 +50,7 @@ interface Storage { fun setParcelPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus): Job - fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean): Job - - fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean): Job + fun setParcelOptionsInteractBitmask(parcel: ParcelId, bitmask: IntArray): Job fun transmitAllGlobalAddedData(): ReceiveChannel> @@ -100,9 +98,7 @@ class BackedStorage internal constructor(val b: Backing) : Storage { override fun setParcelPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus) = b.launchJob { b.setLocalPlayerStatus(parcel, player, status) } - override fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) = b.launchJob { b.setParcelAllowsInteractInventory(parcel, value) } - - override fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean) = b.launchJob { b.setParcelAllowsInteractInputs(parcel, value) } + override fun setParcelOptionsInteractBitmask(parcel: ParcelId, bitmask: IntArray) = b.launchJob { b.setParcelOptionsInteractBitmask(parcel, bitmask) } override fun transmitAllGlobalAddedData(): ReceiveChannel> = b.openChannel { b.transmitAllGlobalAddedData(it) } 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 39a32e0..b7f9f82 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt @@ -5,12 +5,9 @@ package io.dico.parcels2.storage.exposed import com.zaxxer.hikari.HikariDataSource import io.dico.parcels2.* import io.dico.parcels2.PlayerProfile.Star.name -import io.dico.parcels2.storage.AddedDataPair -import io.dico.parcels2.storage.Backing -import io.dico.parcels2.storage.DataPair +import io.dico.parcels2.storage.* +import io.dico.parcels2.util.ext.clampMax import io.dico.parcels2.util.ext.synchronized -import io.dico.parcels2.util.toByteArray -import io.dico.parcels2.util.toUUID import kotlinx.coroutines.* import kotlinx.coroutines.channels.ArrayChannel import kotlinx.coroutines.channels.LinkedListChannel @@ -196,8 +193,9 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi AddedLocalT.setPlayerStatus(parcel, profile, status) } - setParcelAllowsInteractInputs(parcel, data.allowInteractInputs) - setParcelAllowsInteractInventory(parcel, data.allowInteractInventory) + 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) } override fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) { @@ -227,19 +225,19 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi AddedLocalT.setPlayerStatus(parcel, player.toRealProfile(), status) } - override fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) { - val id = ParcelsT.getOrInitId(parcel) - ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { - it[parcel_id] = id - it[interact_inventory] = value + override fun setParcelOptionsInteractBitmask(parcel: ParcelId, bitmask: IntArray?) { + if (bitmask == null) { + val id = ParcelsT.getId(parcel) ?: return + ParcelOptionsT.deleteWhere { ParcelOptionsT.parcel_id eq id } + return } - } - override fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean) { + if (bitmask.size != 1) throw IllegalArgumentException() + val array = bitmask.toByteArray() val id = ParcelsT.getOrInitId(parcel) ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { it[parcel_id] = id - it[interact_inputs] = value + it[interact_bitmask] = array } } @@ -263,8 +261,9 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi val id = row[ParcelsT.id] ParcelOptionsT.select { ParcelOptionsT.parcel_id eq id }.firstOrNull()?.let { optrow -> - allowInteractInputs = optrow[ParcelOptionsT.interact_inputs] - allowInteractInventory = optrow[ParcelOptionsT.interact_inventory] + val source = optrow[ParcelOptionsT.interact_bitmask].toIntArray() + val target = (interactableConfig as? BitmaskInteractableConfiguration ?: return@let).bitmaskArray + System.arraycopy(source, 0, target, 0, source.size.clampMax(target.size)) } addedMap = AddedLocalT.readAddedData(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 afbaa6e..745895e 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt @@ -5,8 +5,8 @@ package io.dico.parcels2.storage.exposed import io.dico.parcels2.ParcelId import io.dico.parcels2.ParcelWorldId import io.dico.parcels2.PlayerProfile -import io.dico.parcels2.util.toByteArray -import io.dico.parcels2.util.toUUID +import io.dico.parcels2.storage.toByteArray +import io.dico.parcels2.storage.toUUID import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.statements.UpdateBuilder import org.joda.time.DateTime 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 c976f69..f41d545 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt @@ -3,6 +3,8 @@ package io.dico.parcels2.storage.exposed import io.dico.parcels2.* +import io.dico.parcels2.AddedStatus.ALLOWED +import io.dico.parcels2.AddedStatus.DEFAULT import kotlinx.coroutines.channels.SendChannel import org.jetbrains.exposed.sql.* import java.util.UUID @@ -12,8 +14,7 @@ object AddedGlobalT : AddedTable("parcels_added_global", Profiles object ParcelOptionsT : Table("parcel_options") { val parcel_id = integer("parcel_id").primaryKey().references(ParcelsT.id, ReferenceOption.CASCADE) - val interact_inventory = bool("interact_inventory").default(true) - val interact_inputs = bool("interact_inputs").default(true) + val interact_bitmask = binary("interact_bitmask", 4).default(ByteArray(4) { 0 }) // all zero by default } typealias AddedStatusSendChannel = SendChannel> @@ -25,7 +26,7 @@ sealed class AddedTable(name: String, val idTable: IdTransactionsTable< val index_pair = uniqueIndexR("index_pair", attach_id, profile_id) fun setPlayerStatus(attachedOn: AttachT, player: PlayerProfile.Real, status: AddedStatus) { - if (status.isDefault) { + if (status == DEFAULT) { val player_id = ProfilesT.getId(player) ?: return idTable.getId(attachedOn)?.let { holder -> deleteWhere { (attach_id eq holder) and (profile_id eq player_id) } @@ -38,7 +39,7 @@ 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.isAllowed + it[allowed_flag] = status == ALLOWED } } @@ -97,6 +98,6 @@ sealed class AddedTable(name: String, val idTable: IdTransactionsTable< } } - private inline fun Boolean?.asAddedStatus() = if (this == null) AddedStatus.DEFAULT else if (this) AddedStatus.ALLOWED else AddedStatus.BANNED + private inline fun Boolean?.asAddedStatus() = if (this == null) AddedStatus.DEFAULT else if (this) 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 3fae57f..0dcf36d 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 @@ -9,7 +9,7 @@ import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.exposed.abs import io.dico.parcels2.storage.exposed.greater import io.dico.parcels2.storage.migration.Migration -import io.dico.parcels2.util.toUUID +import io.dico.parcels2.storage.toUUID import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.newFixedThreadPoolContext diff --git a/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt b/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt index 268c1b9..1398037 100644 --- a/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt +++ b/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt @@ -11,10 +11,4 @@ fun getPlayerName(uuid: UUID): String? { return Bukkit.getOfflinePlayer(uuid)?.takeIf { it.isValid }?.name } -fun UUID.toByteArray(): ByteArray = - ByteBuffer.allocate(16).apply { - putLong(mostSignificantBits) - putLong(leastSignificantBits) - }.array() -fun ByteArray.toUUID(): UUID = ByteBuffer.wrap(this).run { UUID(long, long) } diff --git a/src/main/kotlin/io/dico/parcels2/util/ext/Material.kt b/src/main/kotlin/io/dico/parcels2/util/ext/Material.kt index c375cb2..e160e55 100644 --- a/src/main/kotlin/io/dico/parcels2/util/ext/Material.kt +++ b/src/main/kotlin/io/dico/parcels2/util/ext/Material.kt @@ -79,7 +79,7 @@ private fun getMaterialPrefixed(prefix: String, name: String): Material { return Material.getMaterial("${prefix}_$name") ?: throw IllegalArgumentException("Material ${prefix}_$name doesn't exist") } -fun findWoodKindPrefixedMaterials(name: String) = arrayOf( +fun getMaterialsWithWoodTypePrefix(name: String) = arrayOf( getMaterialPrefixed("OAK", name), getMaterialPrefixed("BIRCH", name), getMaterialPrefixed("SPRUCE", name), @@ -88,7 +88,7 @@ fun findWoodKindPrefixedMaterials(name: String) = arrayOf( getMaterialPrefixed("DARK_OAK", name) ) -fun findColorPrefixedMaterials(name: String) = arrayOf( +fun getMaterialsWithWoolColorPrefix(name: String) = arrayOf( getMaterialPrefixed("WHITE", name), getMaterialPrefixed("ORANGE", name), getMaterialPrefixed("MAGENTA", name), diff --git a/src/main/kotlin/io/dico/parcels2/util/ext/Math.kt b/src/main/kotlin/io/dico/parcels2/util/ext/Math.kt index 32540fd..62ee220 100644 --- a/src/main/kotlin/io/dico/parcels2/util/ext/Math.kt +++ b/src/main/kotlin/io/dico/parcels2/util/ext/Math.kt @@ -29,4 +29,7 @@ fun IntRange.clamp(min: Int, max: Int): IntRange { return IntRange(first, max) } return this -} \ No newline at end of file +} + +// the name coerceAtMost is bad +fun Int.clampMax(max: Int) = coerceAtMost(max) \ No newline at end of file diff --git a/todo.md b/todo.md index c04937a..93e9c02 100644 --- a/todo.md +++ b/todo.md @@ -79,4 +79,6 @@ Implement a container that doesn't require loading all parcel data on startup (C ~~Update player profiles in the database on join to account for name changes.~~ +Store player status on parcel (allowed, default banned) as a number to allow for future additions to this set of possibilities + -- cgit v1.2.3