summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDico200 <dico.karssiens@gmail.com>2018-07-27 16:55:25 +0100
committerDico200 <dico.karssiens@gmail.com>2018-07-27 16:55:25 +0100
commitcb3fb4771a824ec13e3da1647e3278f3ebb9a5bc (patch)
tree6ef9f19608fdb628938a9ed48a2d05ab740178e6
parent0de16eb1845e0573f9467e8e0b6b4b7f80534f4e (diff)
Make progress with listeners
-rw-r--r--.gitignore3
-rw-r--r--build.gradle.kts2
-rw-r--r--src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt28
-rw-r--r--src/main/kotlin/io/dico/parcels2/listener/ParcelEditListener.kt247
-rw-r--r--src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt43
-rw-r--r--src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt559
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/MiscExtensions.kt49
7 files changed, 668 insertions, 263 deletions
diff --git a/.gitignore b/.gitignore
index 1fe5721..601cc7d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,5 @@ out/
build/
/debug/
target/
-/gradle-output.txt \ No newline at end of file
+/gradle-output.txt
+/*.java \ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 4f06249..d358fcc 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -118,7 +118,7 @@ tasks {
"logback-core",
"logback-classic",
- "h2",
+ //"h2",
"HikariCP",
"kotlinx-coroutines-core",
"kotlinx-coroutines-core-common",
diff --git a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt
index a9691c5..ac389ca 100644
--- a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt
+++ b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt
@@ -4,7 +4,8 @@ import io.dico.dicore.Registrator
import io.dico.dicore.command.EOverridePolicy
import io.dico.dicore.command.ICommandDispatcher
import io.dico.parcels2.command.getParcelCommands
-import io.dico.parcels2.listener.ParcelEditListener
+import io.dico.parcels2.listener.ParcelEntityTracker
+import io.dico.parcels2.listener.ParcelListeners
import io.dico.parcels2.storage.Storage
import io.dico.parcels2.storage.yamlObjectMapper
import io.dico.parcels2.util.tryCreate
@@ -19,20 +20,20 @@ private inline val plogger get() = logger
const val debugging = true
class ParcelsPlugin : JavaPlugin() {
- lateinit var optionsFile: File
- lateinit var options: Options
- lateinit var worlds: Worlds
- lateinit var storage: Storage
+ lateinit var optionsFile: File; private set
+ lateinit var options: Options; private set
+ lateinit var worlds: Worlds; private set
+ lateinit var storage: Storage; private set
+
+ val registrator = Registrator(this)
+ lateinit var entityTracker: ParcelEntityTracker; private set
+ private var listeners: ParcelListeners? = null
private var cmdDispatcher: ICommandDispatcher? = null
override fun onEnable() {
if (!init()) {
Bukkit.getPluginManager().disablePlugin(this)
- return
}
-
- registerCommands()
- registerListeners()
}
override fun onDisable() {
@@ -61,6 +62,10 @@ class ParcelsPlugin : JavaPlugin() {
return false
}
+ entityTracker = ParcelEntityTracker(worlds)
+ registerListeners()
+ registerCommands()
+
return true
}
@@ -89,7 +94,10 @@ class ParcelsPlugin : JavaPlugin() {
}
private fun registerListeners() {
- Registrator(this).registerListeners(ParcelEditListener(worlds))
+ if (listeners != null) {
+ listeners = ParcelListeners(worlds, entityTracker)
+ registrator.registerListeners(listeners!!)
+ }
}
} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/listener/ParcelEditListener.kt b/src/main/kotlin/io/dico/parcels2/listener/ParcelEditListener.kt
deleted file mode 100644
index 788c9eb..0000000
--- a/src/main/kotlin/io/dico/parcels2/listener/ParcelEditListener.kt
+++ /dev/null
@@ -1,247 +0,0 @@
-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.ParcelWorld
-import io.dico.parcels2.Worlds
-import io.dico.parcels2.util.hasBanBypass
-import io.dico.parcels2.util.hasBuildAnywhere
-import io.dico.parcels2.util.sendParcelMessage
-import io.dico.parcels2.util.uuid
-import org.bukkit.Material.*
-import org.bukkit.block.Biome
-import org.bukkit.block.Block
-import org.bukkit.block.data.type.Bed
-import org.bukkit.entity.Player
-import org.bukkit.event.EventPriority.NORMAL
-import org.bukkit.event.block.*
-import org.bukkit.event.entity.EntityExplodeEvent
-import org.bukkit.event.entity.ExplosionPrimeEvent
-import org.bukkit.event.player.PlayerInteractEvent
-import org.bukkit.event.player.PlayerMoveEvent
-import org.bukkit.inventory.InventoryHolder
-
-@Suppress("NOTHING_TO_INLINE")
-class ParcelEditListener(val worlds: Worlds) {
- val entityTracker = ParcelEntityTracker()
-
- private inline fun <T> T?.isNullOr(condition: T.() -> Boolean): Boolean = this == null || condition()
- private inline fun <T> T?.isPresentAnd(condition: T.() -> Boolean): Boolean = this != null && condition()
- private inline fun Parcel?.canBuildN(user: Player) = isPresentAnd { canBuild(user) } || user.hasBuildAnywhere
-
- /**
- * Get the world and parcel that the block resides in
- * wo is the world, ppa is the parcel
- * ppa for possibly a parcel - it will be null if not in an existing parcel
- * returns null if not in a registered parcel world
- */
- private fun getWoAndPPa(block: Block): Pair<ParcelWorld, Parcel?>? {
- val world = worlds.getWorld(block.world) ?: return null
- return world to world.parcelAt(block)
- }
-
- /*
- * Prevents players from entering plots they are banned from
- */
- @ListenerMarker(priority = NORMAL)
- val onPlayerMove = RegistratorListener<PlayerMoveEvent> l@{ event ->
- val user = event.player
- if (user.hasBanBypass) return@l
- val parcel = worlds.getParcelAt(event.to) ?: return@l
- if (parcel.isBanned(user.uuid)) {
- worlds.getParcelAt(event.from)?.also {
- user.teleport(it.homeLocation)
- user.sendParcelMessage(nopermit = true, message = "You are banned from this parcel")
- } ?: run { event.to = event.from }
- }
- }
-
- /*
- * Prevents players from breaking blocks outside of their parcels
- * Prevents containers from dropping their contents when broken, if configured
- */
- @ListenerMarker(priority = NORMAL)
- val onBlockBreak = RegistratorListener<BlockBreakEvent> l@{ event ->
- val (wo, ppa) = getWoAndPPa(event.block) ?: return@l
- if (!event.player.hasBuildAnywhere && ppa.isNullOr { !canBuild(event.player) }) {
- event.isCancelled = true; return@l
- }
-
- if (!wo.options.dropEntityItems) {
- val state = event.block.state
- if (state is InventoryHolder) {
- state.inventory.clear()
- state.update()
- }
- }
- }
-
- /*
- * Prevents players from placing blocks outside of their parcels
- */
- @ListenerMarker(priority = NORMAL)
- val onBlockPlace = RegistratorListener<BlockBreakEvent> l@{ event ->
- val (wo, ppa) = getWoAndPPa(event.block) ?: return@l
- if (!event.player.hasBuildAnywhere && !ppa.isNullOr { !canBuild(event.player) }) {
- event.isCancelled = true
- }
- }
-
- /*
- * Control pistons
- */
- @ListenerMarker(priority = NORMAL)
- val onBlockPistonExtend = RegistratorListener<BlockPistonExtendEvent> l@{ event ->
- checkPistonMovement(event, event.blocks)
- }
-
- @ListenerMarker(priority = NORMAL)
- val onBlockPistonRetractEvent = RegistratorListener<BlockPistonRetractEvent> l@{ event ->
- checkPistonMovement(event, event.blocks)
- }
-
- // Doing some unnecessary optimizations here..
- //@formatter:off
- private inline fun Column(x: Int, z: Int): Long = x.toLong() or (z.toLong().shl(32))
-
- private inline val Long.columnX get() = and(0xFFFF_FFFFL).toInt()
- private inline val Long.columnZ get() = ushr(32).and(0xFFFF_FFFFL).toInt()
- private inline fun TLongCollection.forEachInline(block: (Long) -> Unit) = iterator().let { while (it.hasNext()) block(it.next()) }
- //@formatter:on
- private fun checkPistonMovement(event: BlockPistonEvent, blocks: List<Block>) {
- val world = worlds.getWorld(event.block.world) ?: return
- val direction = event.direction
- val columns = gnu.trove.set.hash.TLongHashSet(blocks.size * 2)
-
- blocks.forEach {
- columns.add(Column(it.x, it.z))
- it.getRelative(direction).let { columns.add(Column(it.x, it.z)) }
- }
-
- columns.forEachInline {
- val ppa = world.parcelAt(it.columnX, it.columnZ)
- if (ppa.isNullOr { hasBlockVisitors }) {
- event.isCancelled = true
- return
- }
- }
- }
-
- /*
- * Prevents explosions if enabled by the configs for that world
- */
- @ListenerMarker(priority = NORMAL)
- val onExplosionPrimeEvent = RegistratorListener<ExplosionPrimeEvent> l@{ event ->
- val (wo, ppa) = getWoAndPPa(event.entity.location.block) ?: return@l
- if (ppa?.hasBlockVisitors == true) {
- event.radius = 0F; event.isCancelled = true
- } else if (wo.options.disableExplosions) {
- event.radius = 0F
- }
- }
-
- /*
- * Prevents creepers and tnt minecarts from exploding if explosions are disabled
- */
- @ListenerMarker(priority = NORMAL)
- val onEntityExplodeEvent = RegistratorListener<EntityExplodeEvent> l@{ event ->
- entityTracker.untrack(event.entity)
- val world = worlds.getWorld(event.entity.world) ?: return@l
- if (world.options.disableExplosions || world.parcelAt(event.entity).isPresentAnd { hasBlockVisitors }) {
- event.isCancelled = true
- }
- }
-
- /*
- * Prevents creepers and tnt minecarts from exploding if explosions are disabled
- */
- @ListenerMarker(priority = NORMAL)
- val onBlockFromToEvent = RegistratorListener<BlockFromToEvent> l@{ event ->
- val (wo, ppa) = getWoAndPPa(event.toBlock) ?: return@l
- if (ppa.isNullOr { hasBlockVisitors }) event.isCancelled = true
- }
-
- /*
- * Prevents players from placing liquids, using flint and steel, changing redstone components,
- * using inputs (unless allowed by the plot),
- * and using items disabled in the configuration for that world.
- * Prevents player from using beds in HELL or SKY biomes if explosions are disabled.
- */
- @Suppress("NON_EXHAUSTIVE_WHEN")
- @ListenerMarker(priority = NORMAL)
- val onPlayerInteractEvent = RegistratorListener<PlayerInteractEvent> l@{ event ->
- val user = event.player
- val world = worlds.getWorld(user.world) ?: return@l
- val clickedBlock = event.clickedBlock
- val parcel = clickedBlock?.let { world.parcelAt(it) }
-
- if (!user.hasBuildAnywhere && parcel.isPresentAnd { isBanned(user.uuid) }) {
- user.sendParcelMessage(nopermit = true, message = "You cannot interact with parcels you're banned from")
- event.isCancelled = true; return@l
- }
-
- when (event.action) {
- Action.RIGHT_CLICK_BLOCK -> when (clickedBlock.type) {
- REPEATER,
- COMPARATOR -> run {
- if (!parcel.canBuildN(user)) {
- event.isCancelled = true; return@l
- }
- }
- LEVER,
- STONE_BUTTON,
- ANVIL,
- TRAPPED_CHEST,
- OAK_BUTTON, BIRCH_BUTTON, SPRUCE_BUTTON, JUNGLE_BUTTON, ACACIA_BUTTON, DARK_OAK_BUTTON,
- OAK_FENCE_GATE, BIRCH_FENCE_GATE, SPRUCE_FENCE_GATE, JUNGLE_FENCE_GATE, ACACIA_FENCE_GATE, DARK_OAK_FENCE_GATE,
- OAK_DOOR, BIRCH_DOOR, SPRUCE_DOOR, JUNGLE_DOOR, ACACIA_DOOR, DARK_OAK_DOOR,
- OAK_TRAPDOOR, BIRCH_TRAPDOOR, SPRUCE_TRAPDOOR, JUNGLE_TRAPDOOR, ACACIA_TRAPDOOR, DARK_OAK_TRAPDOOR
- -> run {
- if (!user.hasBuildAnywhere && !parcel.isNullOr { canBuild(user) || allowInteractInputs }) {
- user.sendParcelMessage(nopermit = true, message = "You cannot use inputs in this parcel")
- event.isCancelled = true; return@l
- }
- }
-
- WHITE_BED, ORANGE_BED, MAGENTA_BED, LIGHT_BLUE_BED, YELLOW_BED, LIME_BED, PINK_BED, GRAY_BED, LIGHT_GRAY_BED, CYAN_BED, PURPLE_BED, BLUE_BED, BROWN_BED, GREEN_BED, RED_BED, BLACK_BED
- -> run {
- if (world.options.disableExplosions) {
- val bed = clickedBlock.blockData as Bed
- val head = if (bed == Bed.Part.FOOT) clickedBlock.getRelative(bed.facing) else clickedBlock
- when (head.biome) {
- Biome.NETHER, Biome.THE_END -> run {
- user.sendParcelMessage(nopermit = true, message = "You cannot use this bed because it would explode")
- event.isCancelled = true; return@l
- }
- }
-
- }
-
- }
- }
-
- Action.RIGHT_CLICK_AIR -> if (event.hasItem()) {
- val item = event.item.type
- if (world.options.blockedItems.contains(item)) {
- user.sendParcelMessage(nopermit = true, message = "You cannot use this bed because it would explode")
- event.isCancelled = true; return@l
- }
-
- if (!parcel.canBuildN(user)) {
- when (item) {
- LAVA_BUCKET, WATER_BUCKET, BUCKET, FLINT_AND_STEEL -> event.isCancelled = true
- }
- }
- }
-
-
- Action.PHYSICAL -> if (!user.hasBuildAnywhere && !parcel.isPresentAnd { canBuild(user) || allowInteractInputs }) {
- event.isCancelled = true; return@l
- }
- }
- }
-
-
-} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt b/src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt
index fb94b8b..22a5486 100644
--- a/src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt
+++ b/src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt
@@ -1,14 +1,53 @@
package io.dico.parcels2.listener
import io.dico.parcels2.Parcel
+import io.dico.parcels2.Worlds
+import io.dico.parcels2.util.editLoop
+import io.dico.parcels2.util.isPresentAnd
import org.bukkit.entity.Entity
-class ParcelEntityTracker {
- val map = mutableMapOf<Entity, Parcel>()
+class ParcelEntityTracker(val worlds: Worlds) {
+ val map = mutableMapOf<Entity, Parcel?>()
fun untrack(entity: Entity) {
map.remove(entity)
}
+ fun track(entity: Entity, parcel: Parcel?) {
+ map[entity] = parcel
+ }
+
+ /*
+ * Tracks entities. If the entity is dead, they are removed from the collection.
+ * If the entity is found to have left the parcel it was created in, it will be removed from the world and from the list.
+ * If it is still in the parcel it was created in, and it is on the ground, it is removed from the list.
+ *
+ * Start after 5 seconds, run every 0.25 seconds
+ */
+ fun tick() {
+ map.editLoop { entity, parcel ->
+ if (entity.isDead || entity.isOnGround) {
+ remove(); return@editLoop
+ }
+ if (parcel.isPresentAnd { hasBlockVisitors }) {
+ remove()
+ }
+ val newParcel = worlds.getParcelAt(entity.location)
+ if (newParcel !== parcel && !newParcel.isPresentAnd { hasBlockVisitors }) {
+ remove()
+ entity.remove()
+ }
+ }
+ }
+
+ fun swapParcels(parcel1: Parcel, parcel2: Parcel) {
+ map.editLoop {
+ if (value === parcel1) {
+ value = parcel2
+ } else if (value === parcel2) {
+ value = parcel1
+ }
+ }
+ }
} \ 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
new file mode 100644
index 0000000..0ad7953
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt
@@ -0,0 +1,559 @@
+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.ParcelWorld
+import io.dico.parcels2.Worlds
+import io.dico.parcels2.util.*
+import org.bukkit.Material.*
+import org.bukkit.World
+import org.bukkit.block.Biome
+import org.bukkit.block.Block
+import org.bukkit.block.data.Directional
+import org.bukkit.block.data.type.Bed
+import org.bukkit.entity.*
+import org.bukkit.entity.minecart.ExplosiveMinecart
+import org.bukkit.event.EventPriority.NORMAL
+import org.bukkit.event.block.*
+import org.bukkit.event.entity.*
+import org.bukkit.event.hanging.HangingBreakByEntityEvent
+import org.bukkit.event.hanging.HangingBreakEvent
+import org.bukkit.event.hanging.HangingPlaceEvent
+import org.bukkit.event.inventory.InventoryInteractEvent
+import org.bukkit.event.player.*
+import org.bukkit.event.vehicle.VehicleMoveEvent
+import org.bukkit.event.weather.WeatherChangeEvent
+import org.bukkit.event.world.StructureGrowEvent
+import org.bukkit.event.world.WorldLoadEvent
+import org.bukkit.inventory.InventoryHolder
+
+@Suppress("NOTHING_TO_INLINE")
+class ParcelListeners(val worlds: Worlds, val entityTracker: ParcelEntityTracker) {
+ private inline fun Parcel?.canBuildN(user: Player) = isPresentAnd { canBuild(user) } || user.hasBuildAnywhere
+
+ /**
+ * Get the world and parcel that the block resides in
+ * wo is the world, ppa is the parcel
+ * ppa for possibly a parcel - it will be null if not in an existing parcel
+ * returns null if not in a registered parcel world
+ */
+ private fun getWoAndPPa(block: Block): Pair<ParcelWorld, Parcel?>? {
+ val world = worlds.getWorld(block.world) ?: return null
+ return world to world.parcelAt(block)
+ }
+
+ /*
+ * Prevents players from entering plots they are banned from
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onPlayerMoveEvent = RegistratorListener<PlayerMoveEvent> l@{ event ->
+ val user = event.player
+ if (user.hasBanBypass) return@l
+ val parcel = worlds.getParcelAt(event.to) ?: return@l
+ if (parcel.isBanned(user.uuid)) {
+ worlds.getParcelAt(event.from)?.also {
+ user.teleport(it.homeLocation)
+ user.sendParcelMessage(nopermit = true, message = "You are banned from this parcel")
+ } ?: run { event.to = event.from }
+ }
+ }
+
+ /*
+ * Prevents players from breaking blocks outside of their parcels
+ * Prevents containers from dropping their contents when broken, if configured
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onBlockBreakEvent = RegistratorListener<BlockBreakEvent> l@{ event ->
+ val (wo, ppa) = getWoAndPPa(event.block) ?: return@l
+ if (!event.player.hasBuildAnywhere && ppa.isNullOr { !canBuild(event.player) }) {
+ event.isCancelled = true; return@l
+ }
+
+ if (!wo.options.dropEntityItems) {
+ val state = event.block.state
+ if (state is InventoryHolder) {
+ state.inventory.clear()
+ state.update()
+ }
+ }
+ }
+
+ /*
+ * Prevents players from placing blocks outside of their parcels
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onBlockPlaceEvent = RegistratorListener<BlockBreakEvent> l@{ event ->
+ val (wo, ppa) = getWoAndPPa(event.block) ?: return@l
+ if (!event.player.hasBuildAnywhere && !ppa.isNullOr { !canBuild(event.player) }) {
+ event.isCancelled = true
+ }
+ }
+
+ /*
+ * Control pistons
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onBlockPistonExtendEvent = RegistratorListener<BlockPistonExtendEvent> l@{ event ->
+ checkPistonMovement(event, event.blocks)
+ }
+
+ @field:ListenerMarker(priority = NORMAL)
+ val onBlockPistonRetractEvent = RegistratorListener<BlockPistonRetractEvent> l@{ event ->
+ checkPistonMovement(event, event.blocks)
+ }
+
+ // Doing some unnecessary optimizations here..
+ //@formatter:off
+ private inline fun Column(x: Int, z: Int): Long = x.toLong() or (z.toLong().shl(32))
+
+ private inline val Long.columnX get() = and(0xFFFF_FFFFL).toInt()
+ private inline val Long.columnZ get() = ushr(32).and(0xFFFF_FFFFL).toInt()
+ private inline fun TLongCollection.forEachInline(block: (Long) -> Unit) = iterator().let { while (it.hasNext()) block(it.next()) }
+ //@formatter:on
+ private fun checkPistonMovement(event: BlockPistonEvent, blocks: List<Block>) {
+ val world = worlds.getWorld(event.block.world) ?: return
+ val direction = event.direction
+ val columns = gnu.trove.set.hash.TLongHashSet(blocks.size * 2)
+
+ blocks.forEach {
+ columns.add(Column(it.x, it.z))
+ it.getRelative(direction).let { columns.add(Column(it.x, it.z)) }
+ }
+
+ columns.forEachInline {
+ val ppa = world.parcelAt(it.columnX, it.columnZ)
+ if (ppa.isNullOr { hasBlockVisitors }) {
+ event.isCancelled = true
+ return
+ }
+ }
+ }
+
+ /*
+ * Prevents explosions if enabled by the configs for that world
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onExplosionPrimeEvent = RegistratorListener<ExplosionPrimeEvent> l@{ event ->
+ val (wo, ppa) = getWoAndPPa(event.entity.location.block) ?: return@l
+ if (ppa?.hasBlockVisitors == true) {
+ event.radius = 0F; event.isCancelled = true
+ } else if (wo.options.disableExplosions) {
+ event.radius = 0F
+ }
+ }
+
+ /*
+ * Prevents creepers and tnt minecarts from exploding if explosions are disabled
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onEntityExplodeEvent = RegistratorListener<EntityExplodeEvent> l@{ event ->
+ entityTracker.untrack(event.entity)
+ val world = worlds.getWorld(event.entity.world) ?: return@l
+ if (world.options.disableExplosions || world.parcelAt(event.entity).isPresentAnd { hasBlockVisitors }) {
+ event.isCancelled = true
+ }
+ }
+
+ /*
+ * Prevents creepers and tnt minecarts from exploding if explosions are disabled
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onBlockFromToEvent = RegistratorListener<BlockFromToEvent> l@{ event ->
+ val (wo, ppa) = getWoAndPPa(event.toBlock) ?: return@l
+ if (ppa.isNullOr { hasBlockVisitors }) event.isCancelled = true
+ }
+
+ /*
+ * Prevents players from placing liquids, using flint and steel, changing redstone components,
+ * using inputs (unless allowed by the plot),
+ * and using items disabled in the configuration for that world.
+ * Prevents player from using beds in HELL or SKY biomes if explosions are disabled.
+ */
+ @Suppress("NON_EXHAUSTIVE_WHEN")
+ @field:ListenerMarker(priority = NORMAL)
+ val onPlayerInteractEvent = RegistratorListener<PlayerInteractEvent> l@{ event ->
+ val user = event.player
+ val world = worlds.getWorld(user.world) ?: return@l
+ val clickedBlock = event.clickedBlock
+ val parcel = clickedBlock?.let { world.parcelAt(it) }
+
+ if (!user.hasBuildAnywhere && parcel.isPresentAnd { isBanned(user.uuid) }) {
+ user.sendParcelMessage(nopermit = true, message = "You cannot interact with parcels you're banned from")
+ event.isCancelled = true; return@l
+ }
+
+ when (event.action) {
+ Action.RIGHT_CLICK_BLOCK -> when (clickedBlock.type) {
+ REPEATER,
+ COMPARATOR -> run {
+ if (!parcel.canBuildN(user)) {
+ event.isCancelled = true; return@l
+ }
+ }
+ LEVER,
+ STONE_BUTTON,
+ ANVIL,
+ TRAPPED_CHEST,
+ OAK_BUTTON, BIRCH_BUTTON, SPRUCE_BUTTON, JUNGLE_BUTTON, ACACIA_BUTTON, DARK_OAK_BUTTON,
+ OAK_FENCE_GATE, BIRCH_FENCE_GATE, SPRUCE_FENCE_GATE, JUNGLE_FENCE_GATE, ACACIA_FENCE_GATE, DARK_OAK_FENCE_GATE,
+ OAK_DOOR, BIRCH_DOOR, SPRUCE_DOOR, JUNGLE_DOOR, ACACIA_DOOR, DARK_OAK_DOOR,
+ OAK_TRAPDOOR, BIRCH_TRAPDOOR, SPRUCE_TRAPDOOR, JUNGLE_TRAPDOOR, ACACIA_TRAPDOOR, DARK_OAK_TRAPDOOR
+ -> run {
+ if (!user.hasBuildAnywhere && !parcel.isNullOr { canBuild(user) || allowInteractInputs }) {
+ user.sendParcelMessage(nopermit = true, message = "You cannot use inputs in this parcel")
+ event.isCancelled = true; return@l
+ }
+ }
+
+ WHITE_BED, ORANGE_BED, MAGENTA_BED, LIGHT_BLUE_BED, YELLOW_BED, LIME_BED, PINK_BED, GRAY_BED, LIGHT_GRAY_BED, CYAN_BED, PURPLE_BED, BLUE_BED, BROWN_BED, GREEN_BED, RED_BED, BLACK_BED
+ -> run {
+ if (world.options.disableExplosions) {
+ val bed = clickedBlock.blockData as Bed
+ val head = if (bed == Bed.Part.FOOT) clickedBlock.getRelative(bed.facing) else clickedBlock
+ when (head.biome) {
+ Biome.NETHER, Biome.THE_END -> run {
+ user.sendParcelMessage(nopermit = true, message = "You cannot use this bed because it would explode")
+ event.isCancelled = true; return@l
+ }
+ }
+
+ }
+
+ }
+ }
+
+ Action.RIGHT_CLICK_AIR -> if (event.hasItem()) {
+ val item = event.item.type
+ if (world.options.blockedItems.contains(item)) {
+ user.sendParcelMessage(nopermit = true, message = "You cannot use this bed because it would explode")
+ event.isCancelled = true; return@l
+ }
+
+ if (!parcel.canBuildN(user)) {
+ when (item) {
+ LAVA_BUCKET, WATER_BUCKET, BUCKET, FLINT_AND_STEEL -> event.isCancelled = true
+ }
+ }
+ }
+
+
+ Action.PHYSICAL -> if (!user.hasBuildAnywhere && !parcel.isPresentAnd { canBuild(user) || allowInteractInputs }) {
+ event.isCancelled = true; return@l
+ }
+ }
+ }
+
+ /*
+ * Prevents players from breeding mobs, entering or opening boats/minecarts,
+ * rotating item frames, doing stuff with leashes, and putting stuff on armor stands.
+ */
+ @Suppress("NON_EXHAUSTIVE_WHEN")
+ @field:ListenerMarker(priority = NORMAL)
+ val onPlayerInteractEntityEvent = RegistratorListener<PlayerInteractEntityEvent> l@{ event ->
+ val (wo, ppa) = getWoAndPPa(event.rightClicked.location.block) ?: return@l
+ if (ppa.canBuildN(event.player)) return@l
+ when (event.rightClicked.type) {
+ EntityType.BOAT,
+ EntityType.MINECART,
+ EntityType.MINECART_CHEST,
+ EntityType.MINECART_COMMAND,
+ EntityType.MINECART_FURNACE,
+ EntityType.MINECART_HOPPER,
+ EntityType.MINECART_MOB_SPAWNER,
+ EntityType.MINECART_TNT,
+
+ EntityType.ARMOR_STAND,
+ EntityType.PAINTING,
+ EntityType.ITEM_FRAME,
+ EntityType.LEASH_HITCH,
+
+ EntityType.CHICKEN,
+ EntityType.COW,
+ EntityType.HORSE,
+ EntityType.SHEEP,
+ EntityType.VILLAGER,
+ EntityType.WOLF -> event.isCancelled = true
+ }
+ }
+
+ /*
+ * Prevents endermen from griefing.
+ * Prevents sand blocks from exiting the parcel in which they became an entity.
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onEntityChangeBlockEvent = RegistratorListener<EntityChangeBlockEvent> l@{ event ->
+ val (wo, ppa) = getWoAndPPa(event.block) ?: return@l
+ if (event.entity.type == EntityType.ENDERMAN || ppa.isNullOr { hasBlockVisitors }) {
+ event.isCancelled = true; return@l
+ }
+
+ if (event.entity.type == EntityType.FALLING_BLOCK) {
+ // a sand block started falling. Track it and delete it if it gets out of this parcel.
+ entityTracker.track(event.entity, ppa)
+ }
+ }
+
+ /*
+ * Prevents portals from being created if set so in the configs for that world
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onEntityCreatePortalEvent = RegistratorListener<EntityCreatePortalEvent> l@{ event ->
+ val world = worlds.getWorld(event.entity.world) ?: return@l
+ if (world.options.blockPortalCreation) event.isCancelled = true
+ }
+
+ /*
+ * Prevents players from dropping items
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onPlayerDropItemEvent = RegistratorListener<PlayerDropItemEvent> l@{ event ->
+ val (wo, ppa) = getWoAndPPa(event.itemDrop.location.block) ?: return@l
+ if (!ppa.canBuildN(event.player) && !ppa.isPresentAnd { allowInteractInventory }) event.isCancelled = true
+ }
+
+ /*
+ * Prevents players from picking up items
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onEntityPickupItemEvent = RegistratorListener<EntityPickupItemEvent> l@{ event ->
+ val user = event.entity as? Player ?: return@l
+ val (wo, ppa) = getWoAndPPa(event.item.location.block) ?: return@l
+ if (!ppa.canBuildN(user)) event.isCancelled = true
+ }
+
+ /*
+ * Prevents players from editing inventories
+ */
+ @field:ListenerMarker(priority = NORMAL, events = ["inventory.InventoryClickEvent", "inventory.InventoryDragEvent"])
+ val onInventoryClickEvent = RegistratorListener<InventoryInteractEvent> l@{ event ->
+ 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 }) {
+ event.isCancelled = true
+ }
+ }
+
+ /*
+ * Cancels weather changes and sets the weather to sunny if requested by the config for that world.
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onWeatherChangeEvent = RegistratorListener<WeatherChangeEvent> l@{ event ->
+ val world = worlds.getWorld(event.world) ?: return@l
+ if (world.options.noWeather && event.toWeatherState()) {
+ event.isCancelled = true
+ }
+ }
+
+ private fun resetWeather(world: World) {
+ world.setStorm(false)
+ world.isThundering = false
+ world.weatherDuration = Int.MAX_VALUE
+ }
+
+ /*
+ * Sets time to day and doDayLightCycle gamerule if requested by the config for that world
+ * Sets the weather to sunny if requested by the config for that world.
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onWorldLoadEvent = RegistratorListener<WorldLoadEvent> l@{ event ->
+ enforceWorldSettingsIfApplicable(event.world)
+ }
+
+ fun enforceWorldSettingsIfApplicable(w: World) {
+ val world = worlds.getWorld(w) ?: return
+ if (world.options.dayTime) {
+ w.setGameRuleValue("doDaylightCycle", "false")
+ w.time = 6000
+ }
+
+ if (world.options.noWeather) {
+ resetWeather(w)
+ }
+
+ w.setGameRuleValue("doTileDrops", world.options.doTileDrops.toString())
+ }
+
+ /*
+ * Prevents mobs (living entities) from spawning if that is disabled for that world in the config.
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onEntitySpawnEvent = RegistratorListener<EntitySpawnEvent> l@{ event ->
+ val world = worlds.getWorld(event.entity.world) ?: return@l
+ if (event.entity is Creature && world.options.blockMobSpawning) {
+ event.isCancelled = true
+ } else if (world.parcelAt(event.entity).isPresentAnd { hasBlockVisitors }) {
+ event.isCancelled = true
+ }
+ }
+
+ /*
+ * Prevents minecarts/boats from moving outside a plot
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onVehicleMoveEvent = RegistratorListener<VehicleMoveEvent> l@{ event ->
+ val (wo, ppa) = getWoAndPPa(event.to.block) ?: return@l
+ if (ppa == null) {
+ event.vehicle.eject()
+ event.vehicle.passengers.forEach {
+ if (it.type == EntityType.PLAYER) {
+ (it as Player).sendParcelMessage(except = true, message = "Your ride ends here")
+ } else it.remove()
+ }
+ } else if (ppa.hasBlockVisitors) {
+ event.to.subtract(event.to).add(event.from)
+ }
+ }
+
+ /*
+ * Prevents players from removing items from item frames
+ * Prevents TNT Minecarts and creepers from destroying entities (This event is called BEFORE EntityExplodeEvent GG)
+ * Actually doesn't prevent this because the entities are destroyed anyway, even though the code works?
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onEntityDamageByEntityEvent = RegistratorListener<EntityDamageByEntityEvent> l@{ event ->
+ val world = worlds.getWorld(event.entity.world) ?: return@l
+ if (world.options.disableExplosions && event.damager is ExplosiveMinecart || event.damager is Creeper) {
+ event.isCancelled = true; return@l
+ }
+
+ val user = event.damager as? Player
+ ?: (event.damager as? Projectile)?.let { it.shooter as? Player }
+ ?: return@l
+
+ if (!world.parcelAt(event.entity).canBuildN(user)) {
+ event.isCancelled = true
+ }
+ }
+
+ @field:ListenerMarker(priority = NORMAL)
+ val onHangingBreakEvent = RegistratorListener<HangingBreakEvent> l@{ event ->
+ val world = worlds.getWorld(event.entity.world) ?: return@l
+ if (event.cause == HangingBreakEvent.RemoveCause.EXPLOSION && world.options.disableExplosions) {
+ event.isCancelled = true; return@l
+ }
+
+ if (world.parcelAt(event.entity).isPresentAnd { hasBlockVisitors }) {
+ event.isCancelled = true
+ }
+ }
+
+ /*
+ * Prevents players from deleting paintings and item frames
+ * This appears to take care of shooting with a bow, throwing snowballs or throwing ender pearls.
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onHangingBreakByEntityEvent = RegistratorListener<HangingBreakByEntityEvent> l@{ event ->
+ val world = worlds.getWorld(event.entity.world) ?: return@l
+ val user = event.remover as? Player ?: return@l
+ if (!world.parcelAt(event.entity).canBuildN(user)) {
+ event.isCancelled = true
+ }
+ }
+
+ /*
+ * Prevents players from placing paintings and item frames
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onHangingPlaceEvent = RegistratorListener<HangingPlaceEvent> l@{ event ->
+ val world = worlds.getWorld(event.entity.world) ?: return@l
+ val block = event.block.getRelative(event.blockFace)
+ if (!world.parcelAt(block).canBuildN(event.player)) {
+ event.isCancelled = true
+ }
+ }
+
+ /*
+ * Prevents stuff from growing outside of plots
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onStructureGrowEvent = RegistratorListener<StructureGrowEvent> l@{ event ->
+ val (wo, ppa) = getWoAndPPa(event.location.block) ?: return@l
+ if (ppa == null) {
+ event.isCancelled = true; return@l
+ }
+
+ if (!event.player.hasBuildAnywhere && !ppa.canBuild(event.player)) {
+ event.isCancelled = true; return@l
+ }
+
+ event.blocks.removeIf { wo.parcelAt(it.block) !== ppa }
+ }
+
+ /*
+ * Prevents dispensers/droppers from dispensing out of parcels
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onBlockDispenseEvent = RegistratorListener<BlockDispenseEvent> l@{ event ->
+ val block = event.block
+ if (!block.type.let { it == DISPENSER || it == DROPPER }) return@l
+ val world = worlds.getWorld(block.world) ?: return@l
+ val data = block.blockData as Directional
+ val targetBlock = block.getRelative(data.facing)
+ if (world.parcelAt(targetBlock) == null) {
+ event.isCancelled = true
+ }
+ }
+
+ /*
+ * Track spawned items, making sure they don't leave the parcel.
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onItemSpawnEvent = RegistratorListener<ItemSpawnEvent> l@{ event ->
+ val (wo, ppa) = getWoAndPPa(event.location.block) ?: return@l
+ if (ppa == null) event.isCancelled = true
+ else entityTracker.track(event.entity, ppa)
+ }
+
+ /*
+ * Prevents endermen and endermite from teleporting outside their parcel
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onEntityTeleportEvent = RegistratorListener<EntityTeleportEvent> l@{ event ->
+ val (wo, ppa) = getWoAndPPa(event.from.block) ?: return@l
+ if (ppa !== wo.parcelAt(event.to)) {
+ event.isCancelled = true
+ }
+ }
+
+ /*
+ * Prevents projectiles from flying out of parcels
+ * Prevents players from firing projectiles if they cannot build
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onProjectileLaunchEvent = RegistratorListener<ProjectileLaunchEvent> l@{ event ->
+ val (wo, ppa) = getWoAndPPa(event.entity.location.block) ?: return@l
+ if (ppa == null || (event.entity.shooter as? Player)?.let { !ppa.canBuildN(it) } == true) {
+ event.isCancelled = true
+ } else {
+ entityTracker.track(event.entity, ppa)
+ }
+ }
+
+ /*
+ * Prevents entities from dropping items upon death, if configured that way
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onEntityDeathEvent = RegistratorListener<EntityDeathEvent> l@{ event ->
+ entityTracker.untrack(event.entity)
+ val world = worlds.getWorld(event.entity.world) ?: return@l
+ if (!world.options.dropEntityItems) {
+ event.drops.clear()
+ event.droppedExp = 0
+ }
+ }
+
+ /*
+ * Assigns players their default game mode upon entering the world
+ */
+ @field:ListenerMarker(priority = NORMAL)
+ val onPlayerChangedWorldEvent = RegistratorListener<PlayerChangedWorldEvent> l@{ event ->
+ val world = worlds.getWorld(event.player.world) ?: return@l
+ if (world.options.gameMode != null && !event.player.hasGamemodeBypass) {
+ event.player.gameMode = world.options.gameMode
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/util/MiscExtensions.kt b/src/main/kotlin/io/dico/parcels2/util/MiscExtensions.kt
index 952595b..7a2504d 100644
--- a/src/main/kotlin/io/dico/parcels2/util/MiscExtensions.kt
+++ b/src/main/kotlin/io/dico/parcels2/util/MiscExtensions.kt
@@ -12,6 +12,51 @@ fun File.tryCreate(): Boolean {
return true
}
-inline fun <R> Any.synchronized(block: () -> R): R {
- return synchronized(this, block)
+inline fun <R> Any.synchronized(block: () -> R): R = synchronized(this, block)
+
+inline fun <T> T?.isNullOr(condition: T.() -> Boolean): Boolean = this == null || condition()
+inline fun <T> T?.isPresentAnd(condition: T.() -> Boolean): Boolean = this != null && condition()
+
+inline fun <T, U> MutableMap<T, U>.editLoop(block: EditLoopScope<T, U>.(T, U) -> Unit) {
+ return EditLoopScope(this).doEditLoop(block)
+}
+
+inline fun <T, U> MutableMap<T, U>.editLoop(block: EditLoopScope<T, U>.() -> Unit) {
+ return EditLoopScope(this).doEditLoop(block)
+}
+
+class EditLoopScope<T, U>(val _map: MutableMap<T, U>) {
+ private var iterator: MutableIterator<MutableMap.MutableEntry<T, U>>? = null
+ lateinit var _entry: MutableMap.MutableEntry<T, U>
+
+ inline val key get() = _entry.key
+ inline var value
+ get() = _entry.value
+ set(target) = run { _entry.setValue(target) }
+
+ inline fun doEditLoop(block: EditLoopScope<T, U>.() -> Unit) {
+ val it = _initIterator()
+ while (it.hasNext()) {
+ _entry = it.next()
+ block()
+ }
+ }
+
+ inline fun doEditLoop(block: EditLoopScope<T, U>.(T, U) -> Unit) {
+ val it = _initIterator()
+ while (it.hasNext()) {
+ val entry = it.next().also { _entry = it }
+ block(entry.key, entry.value)
+ }
+ }
+
+ fun remove() {
+ iterator!!.remove()
+ }
+
+ fun _initIterator(): MutableIterator<MutableMap.MutableEntry<T, U>> {
+ iterator?.let { throw IllegalStateException() }
+ return _map.entries.iterator().also { iterator = it }
+ }
+
}