Archived
0

Do some work on interactables

This commit is contained in:
Dico
2018-09-23 20:42:14 +01:00
parent 535df42c54
commit b0d1fab486
18 changed files with 232 additions and 171 deletions

View File

@@ -1,7 +1,12 @@
package io.dico.parcels2 package io.dico.parcels2
import io.dico.parcels2.AddedStatus.*
import org.bukkit.OfflinePlayer import org.bukkit.OfflinePlayer
enum class AddedStatus {
DEFAULT, ALLOWED, BANNED;
}
typealias StatusKey = PlayerProfile.Real typealias StatusKey = PlayerProfile.Real
typealias MutableAddedDataMap = MutableMap<StatusKey, AddedStatus> typealias MutableAddedDataMap = MutableMap<StatusKey, AddedStatus>
typealias AddedDataMap = Map<StatusKey, AddedStatus> typealias AddedDataMap = Map<StatusKey, AddedStatus>
@@ -11,20 +16,20 @@ fun MutableAddedDataMap(): MutableAddedDataMap = hashMapOf()
interface AddedData { interface AddedData {
val addedMap: AddedDataMap val addedMap: AddedDataMap
var addedStatusOfStar: AddedStatus var statusOfStar: AddedStatus
fun getAddedStatus(key: StatusKey): AddedStatus fun getStatus(key: StatusKey): AddedStatus
fun setAddedStatus(key: StatusKey, status: AddedStatus): Boolean fun setStatus(key: StatusKey, status: AddedStatus): Boolean
fun compareAndSetAddedStatus(key: StatusKey, expect: AddedStatus, status: AddedStatus): Boolean = fun casStatus(key: StatusKey, expect: AddedStatus, status: AddedStatus): Boolean =
getAddedStatus(key) == expect && setAddedStatus(key, status) getStatus(key) == expect && setStatus(key, status)
fun isAllowed(key: StatusKey) = getAddedStatus(key) == AddedStatus.ALLOWED fun isAllowed(key: StatusKey) = getStatus(key) == ALLOWED
fun allow(key: StatusKey) = setAddedStatus(key, AddedStatus.ALLOWED) fun allow(key: StatusKey) = setStatus(key, ALLOWED)
fun disallow(key: StatusKey) = compareAndSetAddedStatus(key, AddedStatus.ALLOWED, AddedStatus.DEFAULT) fun disallow(key: StatusKey) = casStatus(key, ALLOWED, DEFAULT)
fun isBanned(key: StatusKey) = getAddedStatus(key) == AddedStatus.BANNED fun isBanned(key: StatusKey) = getStatus(key) == BANNED
fun ban(key: StatusKey) = setAddedStatus(key, AddedStatus.BANNED) fun ban(key: StatusKey) = setStatus(key, BANNED)
fun unban(key: StatusKey) = compareAndSetAddedStatus(key, AddedStatus.BANNED, AddedStatus.DEFAULT) fun unban(key: StatusKey) = casStatus(key, BANNED, DEFAULT)
fun isAllowed(player: OfflinePlayer) = isAllowed(player.statusKey) fun isAllowed(player: OfflinePlayer) = isAllowed(player.statusKey)
fun allow(player: OfflinePlayer) = allow(player.statusKey) fun allow(player: OfflinePlayer) = allow(player.statusKey)
@@ -34,29 +39,20 @@ interface AddedData {
fun unban(player: OfflinePlayer) = unban(player.statusKey) 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 { 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 { override fun setStatus(key: StatusKey, status: AddedStatus): Boolean {
return if (status.isDefault) addedMap.remove(key) != null return if (status == DEFAULT) addedMap.remove(key) != null
else addedMap.put(key, status) != status 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 { interface GlobalAddedData : AddedData {
val owner: PlayerProfile val owner: PlayerProfile
} }

View File

@@ -1,14 +1,17 @@
package io.dico.parcels2 package io.dico.parcels2
import io.dico.parcels2.util.ext.findWoodKindPrefixedMaterials import io.dico.parcels2.util.ext.getMaterialsWithWoodTypePrefix
import org.bukkit.Material import org.bukkit.Material
import java.lang.IllegalArgumentException
import java.util.EnumMap import java.util.EnumMap
class Interactables class Interactables
private constructor(val id: Int, private constructor(
val name: String, val id: Int,
val interactableByDefault: Boolean, val name: String,
vararg val materials: Material) { val interactableByDefault: Boolean,
vararg val materials: Material
) {
companion object { companion object {
val classesById: List<Interactables> val classesById: List<Interactables>
@@ -22,35 +25,64 @@ private constructor(val id: Int,
listedMaterials = EnumMap(mapOf(*array.flatMap { clazz -> clazz.materials.map { it to clazz.id } }.toTypedArray())) 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 { private fun getClassesArray() = run {
var id = 0 var id = 0
@Suppress("UNUSED_CHANGED_VALUE") @Suppress("UNUSED_CHANGED_VALUE")
arrayOf( arrayOf(
Interactables(id++, "button", true, Interactables(
id++, "buttons", true,
Material.STONE_BUTTON, Material.STONE_BUTTON,
*findWoodKindPrefixedMaterials("BUTTON") *getMaterialsWithWoodTypePrefix("BUTTON")
), ),
Interactables(id++, "lever", true, Interactables(
Material.LEVER), id++, "levers", true,
Material.LEVER
),
Interactables(id++, "pressure_plate", true, Interactables(
id++, "pressure_plates", true,
Material.STONE_PRESSURE_PLATE, Material.STONE_PRESSURE_PLATE,
*findWoodKindPrefixedMaterials("PRESSURE_PLATE"), *getMaterialsWithWoodTypePrefix("PRESSURE_PLATE"),
Material.HEAVY_WEIGHTED_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.COMPARATOR,
Material.REPEATER), Material.REPEATER
),
Interactables(id++, "containers", false, Interactables(
id++, "containers", false,
Material.CHEST, Material.CHEST,
Material.TRAPPED_CHEST, Material.TRAPPED_CHEST,
Material.DISPENSER, Material.DISPENSER,
Material.DROPPER, Material.DROPPER,
Material.HOPPER, 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 { interface InteractableConfiguration {
val interactableClasses: List<Interactables> get() = Interactables.classesById.filter { isInteractable(it) } val interactableClasses: List<Interactables> get() = Interactables.classesById.filter { isInteractable(it) }
fun isInteractable(material: Material): Boolean fun isInteractable(material: Material): Boolean
@@ -67,8 +120,13 @@ interface InteractableConfiguration {
fun copyFrom(other: InteractableConfiguration) { fun copyFrom(other: InteractableConfiguration) {
Interactables.classesById.forEach { setInteractable(it, other.isInteractable(it)) } 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 { class BitmaskInteractableConfiguration : InteractableConfiguration {
val bitmaskArray = IntArray((Interactables.classesById.size + 31) / 32) val bitmaskArray = IntArray((Interactables.classesById.size + 31) / 32)
@@ -98,7 +156,7 @@ class BitmaskInteractableConfiguration : InteractableConfiguration {
override fun clear(): Boolean { override fun clear(): Boolean {
var change = false var change = false
for (i in bitmaskArray.indices) { for (i in bitmaskArray.indices) {
change = change || bitmaskArray[i] != 0 if (!change && bitmaskArray[i] != 0) change = true
bitmaskArray[i] = 0 bitmaskArray[i] = 0
} }
return change return change

View File

@@ -45,9 +45,6 @@ interface ParcelData : AddedData {
fun canBuild(player: OfflinePlayer, checkAdmin: Boolean = true, checkGlobal: Boolean = true): Boolean fun canBuild(player: OfflinePlayer, checkAdmin: Boolean = true, checkGlobal: Boolean = true): Boolean
var allowInteractInputs: Boolean
var allowInteractInventory: Boolean
fun isOwner(uuid: UUID): Boolean { fun isOwner(uuid: UUID): Boolean {
return owner?.uuid == uuid return owner?.uuid == uuid
} }
@@ -66,8 +63,6 @@ class ParcelDataHolder(addedMap: MutableAddedDataMap = mutableMapOf())
|| owner.let { it != null && it.matches(player, allowNameMatch = false) } || owner.let { it != null && it.matches(player, allowNameMatch = false) }
|| (checkAdmin && player is Player && player.hasBuildAnywhere) || (checkAdmin && player is Player && player.hasBuildAnywhere)
override var allowInteractInputs = true
override var allowInteractInventory = true
override var interactableConfig: InteractableConfiguration = BitmaskInteractableConfiguration() override var interactableConfig: InteractableConfiguration = BitmaskInteractableConfiguration()
} }

View File

@@ -11,6 +11,7 @@ import org.bukkit.entity.Player
import kotlin.reflect.KMutableProperty import kotlin.reflect.KMutableProperty
class CommandsParcelOptions(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { class CommandsParcelOptions(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
/* TODO options
@Cmd("inputs") @Cmd("inputs")
@Desc("Sets whether players who are not allowed to", @Desc("Sets whether players who are not allowed to",
"build here can use levers, buttons,", "build here can use levers, buttons,",
@@ -28,7 +29,7 @@ class CommandsParcelOptions(plugin: ParcelsPlugin) : AbstractParcelCommands(plug
@RequireParameters(0) @RequireParameters(0)
fun ParcelScope.cmdInventory(player: Player, enabled: Boolean?): Any? { fun ParcelScope.cmdInventory(player: Player, enabled: Boolean?): Any? {
return runOptionCommand(player, Parcel::allowInteractInventory, enabled, "interaction with inventories") return runOptionCommand(player, Parcel::allowInteractInventory, enabled, "interaction with inventories")
} }*/
private inline val Boolean.enabledWord get() = if (this) "enabled" else "disabled" private inline val Boolean.enabledWord get() = if (this) "enabled" else "disabled"
private fun ParcelScope.runOptionCommand(player: Player, private fun ParcelScope.runOptionCommand(player: Player,

View File

@@ -20,12 +20,12 @@ class GlobalAddedDataManagerImpl(val plugin: ParcelsPlugin) : GlobalAddedDataMan
private inline var data get() = addedMap; set(value) = run { addedMap = value } private inline var data get() = addedMap; set(value) = run { addedMap = value }
private inline val isEmpty get() = data === emptyData private inline val isEmpty get() = data === emptyData
override fun setAddedStatus(key: StatusKey, status: AddedStatus): Boolean { override fun setStatus(key: StatusKey, status: AddedStatus): Boolean {
if (isEmpty) { if (isEmpty) {
if (status == AddedStatus.DEFAULT) return false if (status == AddedStatus.DEFAULT) return false
data = mutableMapOf() data = mutableMapOf()
} }
return super.setAddedStatus(key, status).alsoIfTrue { return super.setStatus(key, status).alsoIfTrue {
plugin.storage.setGlobalAddedStatus(owner, key, status) plugin.storage.setGlobalAddedStatus(owner, key, status)
} }
} }

View File

@@ -9,9 +9,11 @@ import org.bukkit.OfflinePlayer
import org.joda.time.DateTime import org.joda.time.DateTime
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
class ParcelImpl(override val world: ParcelWorld, class ParcelImpl(
override val x: Int, override val world: ParcelWorld,
override val z: Int) : Parcel, ParcelId { override val x: Int,
override val z: Int
) : Parcel, ParcelId {
override val id: ParcelId = this override val id: ParcelId = this
override val pos get() = Vec2i(x, z) override val pos get() = Vec2i(x, z)
override var data: ParcelDataHolder = ParcelDataHolder(); private set 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 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 isBanned(key: StatusKey) = data.isBanned(key)
override fun isAllowed(key: StatusKey) = data.isAllowed(key) override fun isAllowed(key: StatusKey) = data.isAllowed(key)
override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean): Boolean { override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean): Boolean {
@@ -42,9 +44,9 @@ class ParcelImpl(override val world: ParcelWorld,
|| checkGlobal && world.globalAddedData[owner ?: return false].isAllowed(player) || checkGlobal && world.globalAddedData[owner ?: return false].isAllowed(player)
} }
override var addedStatusOfStar: AddedStatus override var statusOfStar: AddedStatus
get() = data.addedStatusOfStar get() = data.statusOfStar
set(value) = run { setAddedStatus(PlayerProfile.Star, value) } set(value) = run { setStatus(PlayerProfile.Star, value) }
val globalAddedMap: AddedDataMap? get() = owner?.let { world.globalAddedData[it].addedMap } 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 { override fun setStatus(key: StatusKey, status: AddedStatus): Boolean {
return data.setAddedStatus(key, status).alsoIfTrue { return data.setStatus(key, status).alsoIfTrue {
world.storage.setParcelPlayerStatus(this, key, status) 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 private var _interactableConfig: InteractableConfiguration? = null
override var interactableConfig: InteractableConfiguration override var interactableConfig: InteractableConfiguration
get() { get() {
@@ -99,13 +85,15 @@ class ParcelImpl(override val world: ParcelWorld,
override fun isInteractable(material: Material): Boolean = data.interactableConfig.isInteractable(material) override fun isInteractable(material: Material): Boolean = data.interactableConfig.isInteractable(material)
override fun isInteractable(clazz: Interactables): Boolean = data.interactableConfig.isInteractable(clazz) override fun isInteractable(clazz: Interactables): Boolean = data.interactableConfig.isInteractable(clazz)
override fun setInteractable(clazz: Interactables, interactable: Boolean): Boolean = data.interactableConfig.setInteractable(clazz, interactable).alsoIfTrue { override fun setInteractable(clazz: Interactables, interactable: Boolean): Boolean =
// TODO update storage data.interactableConfig.setInteractable(clazz, interactable).alsoIfTrue {
} // TODO update storage
}
override fun clear(): Boolean = data.interactableConfig.clear().alsoIfTrue { override fun clear(): Boolean =
// TODO update storage data.interactableConfig.clear().alsoIfTrue {
} // TODO update storage
}
} }
} }
return _interactableConfig!! return _interactableConfig!!
@@ -165,9 +153,11 @@ private object ParcelInfoStringComputer {
append(infoStringColor1) append(infoStringColor1)
append(')') append(')')
}) { }) {
stringList.joinTo(this, stringList.joinTo(
this,
separator = infoStringColor1.toString() + ", " + infoStringColor2, separator = infoStringColor1.toString() + ", " + infoStringColor2,
limit = 150) limit = 150
)
} }
} }
@@ -198,6 +188,7 @@ private object ParcelInfoStringComputer {
append('\n') append('\n')
appendAddedList(local, global, AddedStatus.BANNED, "Banned") appendAddedList(local, global, AddedStatus.BANNED, "Banned")
/* TODO options
if (!parcel.allowInteractInputs || !parcel.allowInteractInventory) { if (!parcel.allowInteractInputs || !parcel.allowInteractInventory) {
appendField("Options") { appendField("Options") {
append("(") append("(")
@@ -206,7 +197,7 @@ private object ParcelInfoStringComputer {
appendField("inventory") { append(parcel.allowInteractInventory) } appendField("inventory") { append(parcel.allowInteractInventory) }
append(")") append(")")
} }
} }*/
} }
} }

View File

@@ -3,10 +3,7 @@ package io.dico.parcels2.listener
import gnu.trove.TLongCollection import gnu.trove.TLongCollection
import io.dico.dicore.ListenerMarker import io.dico.dicore.ListenerMarker
import io.dico.dicore.RegistratorListener import io.dico.dicore.RegistratorListener
import io.dico.parcels2.Parcel import io.dico.parcels2.*
import io.dico.parcels2.ParcelProvider
import io.dico.parcels2.ParcelWorld
import io.dico.parcels2.statusKey
import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.Storage
import io.dico.parcels2.util.ext.* import io.dico.parcels2.util.ext.*
import org.bukkit.Material.* 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.ChunkLoadEvent
import org.bukkit.event.world.StructureGrowEvent import org.bukkit.event.world.StructureGrowEvent
import org.bukkit.inventory.InventoryHolder import org.bukkit.inventory.InventoryHolder
import java.util.EnumSet
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
class ParcelListeners(val parcelProvider: ParcelProvider, class ParcelListeners(
val entityTracker: ParcelEntityTracker, val parcelProvider: ParcelProvider,
val storage: Storage) { 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.hasBuildAnywhere
/** /**
@@ -170,6 +170,7 @@ class ParcelListeners(val parcelProvider: ParcelProvider,
if (ppa.isNullOr { hasBlockVisitors }) event.isCancelled = true 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, * Prevents players from placing liquids, using flint and steel, changing redstone components,
* using inputs (unless allowed by the plot), * using inputs (unless allowed by the plot),
@@ -191,49 +192,33 @@ class ParcelListeners(val parcelProvider: ParcelProvider,
when (event.action) { when (event.action) {
Action.RIGHT_CLICK_BLOCK -> run { Action.RIGHT_CLICK_BLOCK -> run {
when (clickedBlock.type) { val type = clickedBlock.type
REPEATER, val interactable = parcel.effectiveInteractableConfig.isInteractable(type) || parcel.isPresentAnd { canBuild(user) }
COMPARATOR -> run { if (!interactable) {
if (!parcel.canBuildN(user)) { val interactableClassName = Interactables[type]!!.name
event.isCancelled = true; return@l user.sendParcelMessage(nopermit = true, message = "You cannot interact with $interactableClassName in this parcel")
} event.isCancelled = true
} return@l
LEVER, }
STONE_BUTTON,
ANVIL,
TRAPPED_CHEST,
OAK_BUTTON, BIRCH_BUTTON, SPRUCE_BUTTON, JUNGLE_BUTTON, ACACIA_BUTTON, DARK_OAK_BUTTON,
OAK_FENCE_GATE, BIRCH_FENCE_GATE, SPRUCE_FENCE_GATE, JUNGLE_FENCE_GATE, ACACIA_FENCE_GATE, DARK_OAK_FENCE_GATE,
OAK_DOOR, BIRCH_DOOR, SPRUCE_DOOR, JUNGLE_DOOR, ACACIA_DOOR, DARK_OAK_DOOR,
OAK_TRAPDOOR, BIRCH_TRAPDOOR, SPRUCE_TRAPDOOR, JUNGLE_TRAPDOOR, ACACIA_TRAPDOOR, DARK_OAK_TRAPDOOR
-> run {
if (!user.hasBuildAnywhere && !parcel.isNullOr { canBuild(user) || allowInteractInputs }) {
user.sendParcelMessage(nopermit = true, message = "You cannot use inputs in this parcel")
event.isCancelled = true; return@l
}
}
WHITE_BED, ORANGE_BED, MAGENTA_BED, LIGHT_BLUE_BED, YELLOW_BED, LIME_BED, PINK_BED, GRAY_BED, LIGHT_GRAY_BED, CYAN_BED, PURPLE_BED, BLUE_BED, BROWN_BED, GREEN_BED, RED_BED, BLACK_BED if (bedTypes.contains(type)) {
-> run { val bed = clickedBlock.blockData as Bed
if (world.options.disableExplosions) { val head = if (bed == Bed.Part.FOOT) clickedBlock.getRelative(bed.facing) else clickedBlock
val bed = clickedBlock.blockData as Bed when (head.biome) {
val head = if (bed == Bed.Part.FOOT) clickedBlock.getRelative(bed.facing) else clickedBlock Biome.NETHER, Biome.THE_END -> {
when (head.biome) { if (world.options.disableExplosions || parcel.isNullOr { !canBuild(user) }) {
Biome.NETHER, Biome.THE_END -> run { user.sendParcelMessage(nopermit = true, message = "You cannot use this bed because it would explode")
user.sendParcelMessage(nopermit = true, message = "You cannot use this bed because it would explode") event.isCancelled = true; return@l
event.isCancelled = true; return@l
}
} }
} }
} }
} }
onPlayerInteractEvent_RightClick(event, world, parcel) onPlayerInteractEvent_RightClick(event, world, parcel)
} }
Action.RIGHT_CLICK_AIR -> onPlayerInteractEvent_RightClick(event, world, parcel) Action.RIGHT_CLICK_AIR -> onPlayerInteractEvent_RightClick(event, world, parcel)
Action.PHYSICAL -> if (!user.hasBuildAnywhere && !parcel.isPresentAnd { canBuild(user) || 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") user.sendParcelMessage(nopermit = true, message = "You cannot use inputs in this parcel")
event.isCancelled = true; return@l event.isCancelled = true; return@l
} }
@@ -322,7 +307,7 @@ class ParcelListeners(val parcelProvider: ParcelProvider,
@field:ListenerMarker(priority = NORMAL) @field:ListenerMarker(priority = NORMAL)
val onPlayerDropItemEvent = RegistratorListener<PlayerDropItemEvent> l@{ event -> val onPlayerDropItemEvent = RegistratorListener<PlayerDropItemEvent> l@{ event ->
val (wo, ppa) = getWoAndPPa(event.itemDrop.location.block) ?: return@l 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 val user = event.whoClicked as? Player ?: return@l
if ((event.inventory ?: return@l).holder === user) return@l // inventory null: hotbar if ((event.inventory ?: return@l).holder === user) return@l // inventory null: hotbar
val (wo, ppa) = getWoAndPPa(event.inventory.location.block) ?: return@l 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 event.isCancelled = true
} }
} }
@@ -385,10 +370,10 @@ class ParcelListeners(val parcelProvider: ParcelProvider,
val cancel: Boolean = when (event.newState.type) { 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) FROSTED_ICE -> player != null && !ppa.canBuild(player)
// prevent snow generation from weather // prevent snow generation from weather
SNOW -> !hasEntity && wo.options.preventWeatherBlockChanges SNOW -> !hasEntity && wo.options.preventWeatherBlockChanges
else -> false else -> false

View File

@@ -58,10 +58,12 @@ interface Backing {
fun setLocalPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus) fun setLocalPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus)
fun setParcelOptionsInteractBitmask(parcel: ParcelId, bitmask: IntArray?)
/*
fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean)
fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean) fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean)
*/
fun transmitAllGlobalAddedData(channel: SendChannel<AddedDataPair<PlayerProfile>>) fun transmitAllGlobalAddedData(channel: SendChannel<AddedDataPair<PlayerProfile>>)

View File

@@ -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)
}
}
}

View File

@@ -50,9 +50,7 @@ interface Storage {
fun setParcelPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus): Job fun setParcelPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus): Job
fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean): Job fun setParcelOptionsInteractBitmask(parcel: ParcelId, bitmask: IntArray): Job
fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean): Job
fun transmitAllGlobalAddedData(): ReceiveChannel<AddedDataPair<PlayerProfile>> fun transmitAllGlobalAddedData(): ReceiveChannel<AddedDataPair<PlayerProfile>>
@@ -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 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 setParcelOptionsInteractBitmask(parcel: ParcelId, bitmask: IntArray) = b.launchJob { b.setParcelOptionsInteractBitmask(parcel, bitmask) }
override fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean) = b.launchJob { b.setParcelAllowsInteractInputs(parcel, value) }
override fun transmitAllGlobalAddedData(): ReceiveChannel<AddedDataPair<PlayerProfile>> = b.openChannel { b.transmitAllGlobalAddedData(it) } override fun transmitAllGlobalAddedData(): ReceiveChannel<AddedDataPair<PlayerProfile>> = b.openChannel { b.transmitAllGlobalAddedData(it) }

View File

@@ -5,12 +5,9 @@ package io.dico.parcels2.storage.exposed
import com.zaxxer.hikari.HikariDataSource import com.zaxxer.hikari.HikariDataSource
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.PlayerProfile.Star.name import io.dico.parcels2.PlayerProfile.Star.name
import io.dico.parcels2.storage.AddedDataPair import io.dico.parcels2.storage.*
import io.dico.parcels2.storage.Backing import io.dico.parcels2.util.ext.clampMax
import io.dico.parcels2.storage.DataPair
import io.dico.parcels2.util.ext.synchronized 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.*
import kotlinx.coroutines.channels.ArrayChannel import kotlinx.coroutines.channels.ArrayChannel
import kotlinx.coroutines.channels.LinkedListChannel import kotlinx.coroutines.channels.LinkedListChannel
@@ -196,8 +193,9 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
AddedLocalT.setPlayerStatus(parcel, profile, status) AddedLocalT.setPlayerStatus(parcel, profile, status)
} }
setParcelAllowsInteractInputs(parcel, data.allowInteractInputs) val bitmaskArray = (data.interactableConfig as? BitmaskInteractableConfiguration ?: return).bitmaskArray
setParcelAllowsInteractInventory(parcel, data.allowInteractInventory) val isAllZero = bitmaskArray.fold(false) { cur, elem -> cur || elem != 0 }
setParcelOptionsInteractBitmask(parcel, if (isAllZero) null else bitmaskArray)
} }
override fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) { override fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) {
@@ -227,19 +225,19 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
AddedLocalT.setPlayerStatus(parcel, player.toRealProfile(), status) AddedLocalT.setPlayerStatus(parcel, player.toRealProfile(), status)
} }
override fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) { override fun setParcelOptionsInteractBitmask(parcel: ParcelId, bitmask: IntArray?) {
val id = ParcelsT.getOrInitId(parcel) if (bitmask == null) {
ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { val id = ParcelsT.getId(parcel) ?: return
it[parcel_id] = id ParcelOptionsT.deleteWhere { ParcelOptionsT.parcel_id eq id }
it[interact_inventory] = value return
} }
}
override fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean) { if (bitmask.size != 1) throw IllegalArgumentException()
val array = bitmask.toByteArray()
val id = ParcelsT.getOrInitId(parcel) val id = ParcelsT.getOrInitId(parcel)
ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
it[parcel_id] = id it[parcel_id] = id
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] val id = row[ParcelsT.id]
ParcelOptionsT.select { ParcelOptionsT.parcel_id eq id }.firstOrNull()?.let { optrow -> ParcelOptionsT.select { ParcelOptionsT.parcel_id eq id }.firstOrNull()?.let { optrow ->
allowInteractInputs = optrow[ParcelOptionsT.interact_inputs] val source = optrow[ParcelOptionsT.interact_bitmask].toIntArray()
allowInteractInventory = optrow[ParcelOptionsT.interact_inventory] val target = (interactableConfig as? BitmaskInteractableConfiguration ?: return@let).bitmaskArray
System.arraycopy(source, 0, target, 0, source.size.clampMax(target.size))
} }
addedMap = AddedLocalT.readAddedData(id) addedMap = AddedLocalT.readAddedData(id)

View File

@@ -5,8 +5,8 @@ package io.dico.parcels2.storage.exposed
import io.dico.parcels2.ParcelId import io.dico.parcels2.ParcelId
import io.dico.parcels2.ParcelWorldId import io.dico.parcels2.ParcelWorldId
import io.dico.parcels2.PlayerProfile import io.dico.parcels2.PlayerProfile
import io.dico.parcels2.util.toByteArray import io.dico.parcels2.storage.toByteArray
import io.dico.parcels2.util.toUUID import io.dico.parcels2.storage.toUUID
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.UpdateBuilder import org.jetbrains.exposed.sql.statements.UpdateBuilder
import org.joda.time.DateTime import org.joda.time.DateTime

View File

@@ -3,6 +3,8 @@
package io.dico.parcels2.storage.exposed package io.dico.parcels2.storage.exposed
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.AddedStatus.ALLOWED
import io.dico.parcels2.AddedStatus.DEFAULT
import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.channels.SendChannel
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import java.util.UUID import java.util.UUID
@@ -12,8 +14,7 @@ object AddedGlobalT : AddedTable<PlayerProfile>("parcels_added_global", Profiles
object ParcelOptionsT : Table("parcel_options") { object ParcelOptionsT : Table("parcel_options") {
val parcel_id = integer("parcel_id").primaryKey().references(ParcelsT.id, ReferenceOption.CASCADE) val parcel_id = integer("parcel_id").primaryKey().references(ParcelsT.id, ReferenceOption.CASCADE)
val interact_inventory = bool("interact_inventory").default(true) val interact_bitmask = binary("interact_bitmask", 4).default(ByteArray(4) { 0 }) // all zero by default
val interact_inputs = bool("interact_inputs").default(true)
} }
typealias AddedStatusSendChannel<AttachT> = SendChannel<Pair<AttachT, MutableAddedDataMap>> typealias AddedStatusSendChannel<AttachT> = SendChannel<Pair<AttachT, MutableAddedDataMap>>
@@ -25,7 +26,7 @@ sealed class AddedTable<AttachT>(name: String, val idTable: IdTransactionsTable<
val index_pair = uniqueIndexR("index_pair", attach_id, profile_id) val index_pair = uniqueIndexR("index_pair", attach_id, profile_id)
fun setPlayerStatus(attachedOn: AttachT, player: PlayerProfile.Real, status: AddedStatus) { fun setPlayerStatus(attachedOn: AttachT, player: PlayerProfile.Real, status: AddedStatus) {
if (status.isDefault) { if (status == DEFAULT) {
val player_id = ProfilesT.getId(player) ?: return val player_id = ProfilesT.getId(player) ?: return
idTable.getId(attachedOn)?.let { holder -> idTable.getId(attachedOn)?.let { holder ->
deleteWhere { (attach_id eq holder) and (profile_id eq player_id) } deleteWhere { (attach_id eq holder) and (profile_id eq player_id) }
@@ -38,7 +39,7 @@ sealed class AddedTable<AttachT>(name: String, val idTable: IdTransactionsTable<
upsert(conflictIndex = index_pair) { upsert(conflictIndex = index_pair) {
it[attach_id] = holder it[attach_id] = holder
it[profile_id] = player_id it[profile_id] = player_id
it[allowed_flag] = status.isAllowed it[allowed_flag] = status == ALLOWED
} }
} }
@@ -97,6 +98,6 @@ sealed class AddedTable<AttachT>(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
} }

View File

@@ -9,7 +9,7 @@ import io.dico.parcels2.storage.Storage
import io.dico.parcels2.storage.exposed.abs import io.dico.parcels2.storage.exposed.abs
import io.dico.parcels2.storage.exposed.greater import io.dico.parcels2.storage.exposed.greater
import io.dico.parcels2.storage.migration.Migration import io.dico.parcels2.storage.migration.Migration
import io.dico.parcels2.util.toUUID import io.dico.parcels2.storage.toUUID
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.newFixedThreadPoolContext import kotlinx.coroutines.newFixedThreadPoolContext

View File

@@ -11,10 +11,4 @@ fun getPlayerName(uuid: UUID): String? {
return Bukkit.getOfflinePlayer(uuid)?.takeIf { it.isValid }?.name 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) }

View File

@@ -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") 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("OAK", name),
getMaterialPrefixed("BIRCH", name), getMaterialPrefixed("BIRCH", name),
getMaterialPrefixed("SPRUCE", name), getMaterialPrefixed("SPRUCE", name),
@@ -88,7 +88,7 @@ fun findWoodKindPrefixedMaterials(name: String) = arrayOf(
getMaterialPrefixed("DARK_OAK", name) getMaterialPrefixed("DARK_OAK", name)
) )
fun findColorPrefixedMaterials(name: String) = arrayOf( fun getMaterialsWithWoolColorPrefix(name: String) = arrayOf(
getMaterialPrefixed("WHITE", name), getMaterialPrefixed("WHITE", name),
getMaterialPrefixed("ORANGE", name), getMaterialPrefixed("ORANGE", name),
getMaterialPrefixed("MAGENTA", name), getMaterialPrefixed("MAGENTA", name),

View File

@@ -29,4 +29,7 @@ fun IntRange.clamp(min: Int, max: Int): IntRange {
return IntRange(first, max) return IntRange(first, max)
} }
return this return this
} }
// the name coerceAtMost is bad
fun Int.clampMax(max: Int) = coerceAtMost(max)

View File

@@ -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.~~ ~~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