Archived
0

Make progress with listeners

This commit is contained in:
Dico200
2018-07-27 16:55:25 +01:00
parent 0de16eb184
commit cb3fb4771a
7 changed files with 668 additions and 263 deletions

3
.gitignore vendored
View File

@@ -5,4 +5,5 @@ out/
build/ build/
/debug/ /debug/
target/ target/
/gradle-output.txt /gradle-output.txt
/*.java

View File

@@ -118,7 +118,7 @@ tasks {
"logback-core", "logback-core",
"logback-classic", "logback-classic",
"h2", //"h2",
"HikariCP", "HikariCP",
"kotlinx-coroutines-core", "kotlinx-coroutines-core",
"kotlinx-coroutines-core-common", "kotlinx-coroutines-core-common",

View File

@@ -4,7 +4,8 @@ import io.dico.dicore.Registrator
import io.dico.dicore.command.EOverridePolicy import io.dico.dicore.command.EOverridePolicy
import io.dico.dicore.command.ICommandDispatcher import io.dico.dicore.command.ICommandDispatcher
import io.dico.parcels2.command.getParcelCommands 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.Storage
import io.dico.parcels2.storage.yamlObjectMapper import io.dico.parcels2.storage.yamlObjectMapper
import io.dico.parcels2.util.tryCreate import io.dico.parcels2.util.tryCreate
@@ -19,20 +20,20 @@ private inline val plogger get() = logger
const val debugging = true const val debugging = true
class ParcelsPlugin : JavaPlugin() { class ParcelsPlugin : JavaPlugin() {
lateinit var optionsFile: File lateinit var optionsFile: File; private set
lateinit var options: Options lateinit var options: Options; private set
lateinit var worlds: Worlds lateinit var worlds: Worlds; private set
lateinit var storage: Storage 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 private var cmdDispatcher: ICommandDispatcher? = null
override fun onEnable() { override fun onEnable() {
if (!init()) { if (!init()) {
Bukkit.getPluginManager().disablePlugin(this) Bukkit.getPluginManager().disablePlugin(this)
return
} }
registerCommands()
registerListeners()
} }
override fun onDisable() { override fun onDisable() {
@@ -61,6 +62,10 @@ class ParcelsPlugin : JavaPlugin() {
return false return false
} }
entityTracker = ParcelEntityTracker(worlds)
registerListeners()
registerCommands()
return true return true
} }
@@ -89,7 +94,10 @@ class ParcelsPlugin : JavaPlugin() {
} }
private fun registerListeners() { private fun registerListeners() {
Registrator(this).registerListeners(ParcelEditListener(worlds)) if (listeners != null) {
listeners = ParcelListeners(worlds, entityTracker)
registrator.registerListeners(listeners!!)
}
} }
} }

View File

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

View File

@@ -1,14 +1,53 @@
package io.dico.parcels2.listener package io.dico.parcels2.listener
import io.dico.parcels2.Parcel 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 import org.bukkit.entity.Entity
class ParcelEntityTracker { class ParcelEntityTracker(val worlds: Worlds) {
val map = mutableMapOf<Entity, Parcel>() val map = mutableMapOf<Entity, Parcel?>()
fun untrack(entity: Entity) { fun untrack(entity: Entity) {
map.remove(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
}
}
}
} }

View File

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

View File

@@ -12,6 +12,51 @@ fun File.tryCreate(): Boolean {
return true return true
} }
inline fun <R> Any.synchronized(block: () -> R): R { inline fun <R> Any.synchronized(block: () -> R): R = synchronized(this, block)
return 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 }
}
} }