diff options
author | Dico200 <dico.karssiens@gmail.com> | 2018-07-27 16:55:25 +0100 |
---|---|---|
committer | Dico200 <dico.karssiens@gmail.com> | 2018-07-27 16:55:25 +0100 |
commit | cb3fb4771a824ec13e3da1647e3278f3ebb9a5bc (patch) | |
tree | 6ef9f19608fdb628938a9ed48a2d05ab740178e6 | |
parent | 0de16eb1845e0573f9467e8e0b6b4b7f80534f4e (diff) |
Make progress with listeners
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | build.gradle.kts | 2 | ||||
-rw-r--r-- | src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt | 28 | ||||
-rw-r--r-- | src/main/kotlin/io/dico/parcels2/listener/ParcelEditListener.kt | 247 | ||||
-rw-r--r-- | src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt | 43 | ||||
-rw-r--r-- | src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt | 559 | ||||
-rw-r--r-- | src/main/kotlin/io/dico/parcels2/util/MiscExtensions.kt | 49 |
7 files changed, 668 insertions, 263 deletions
@@ -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 } + } + } |