diff options
author | Dico Karssiens <dico.karssiens@gmail.com> | 2018-07-27 01:10:46 +0100 |
---|---|---|
committer | Dico Karssiens <dico.karssiens@gmail.com> | 2018-07-27 01:10:46 +0100 |
commit | 963b65714c1abf5a8edb748fc99e4692db7e5bf5 (patch) | |
tree | 5f774f90d6d0f7794ea66b7a989de198346d9131 | |
parent | ea7c27a7fd7c127920eb5e2aa8f2b2b7c921c607 (diff) |
Start adding listeners as per RedstonerServer/Parcels
9 files changed, 396 insertions, 37 deletions
diff --git a/dicore3/core/src/main/java/io/dico/dicore/ListenerMarker.java b/dicore3/core/src/main/java/io/dico/dicore/ListenerMarker.java new file mode 100644 index 0000000..e3c0e90 --- /dev/null +++ b/dicore3/core/src/main/java/io/dico/dicore/ListenerMarker.java @@ -0,0 +1,19 @@ +package io.dico.dicore; + +import org.bukkit.event.EventPriority; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ListenerMarker { + + String[] events() default {}; + + EventPriority priority() default EventPriority.HIGHEST; + + boolean ignoreCancelled() default true; +} diff --git a/dicore3/core/src/main/java/io/dico/dicore/Registrator.java b/dicore3/core/src/main/java/io/dico/dicore/Registrator.java index 9977418..588fba8 100644 --- a/dicore3/core/src/main/java/io/dico/dicore/Registrator.java +++ b/dicore3/core/src/main/java/io/dico/dicore/Registrator.java @@ -11,10 +11,6 @@ import org.bukkit.plugin.EventExecutor; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.RegisteredListener; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import java.lang.reflect.*; import java.util.*; import java.util.function.Consumer; @@ -434,22 +430,6 @@ public final class Registrator { // # Public types // ############################################ - public interface IEventListener<T extends Event> extends Consumer<T> { - @Override - void accept(T event); - } - - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - public @interface ListenerInfo { - - String[] events() default {}; - - EventPriority priority() default EventPriority.HIGHEST; - - boolean ignoreCancelled() default true; - } - public static class Registration extends RegisteredListener { private final EventExecutor executor; @@ -658,17 +638,17 @@ public final class Registrator { fieldLoop: for (Field f : fields) { if (isStatic != Modifier.isStatic(f.getModifiers()) - || !f.isAnnotationPresent(ListenerInfo.class)) { + || !f.isAnnotationPresent(ListenerMarker.class)) { continue; } - if (!IEventListener.class.isAssignableFrom(f.getType())) { - handleListenerFieldError(new ListenerFieldError(f, "Field type cannot be assigned to IEventListener: " + f.getGenericType().getTypeName())); + if (!RegistratorListener.class.isAssignableFrom(f.getType())) { + handleListenerFieldError(new ListenerFieldError(f, "Field type cannot be assigned to RegistratorListener: " + f.getGenericType().getTypeName())); continue; } Type eventType = null; - if (f.getType() == IEventListener.class) { + if (f.getType() == RegistratorListener.class) { Type[] typeArgs; if (!(f.getGenericType() instanceof ParameterizedType) @@ -681,7 +661,7 @@ public final class Registrator { eventType = typeArgs[0]; } else { - // field type is subtype of IEventListener. + // field type is subtype of RegistratorListener. // TODO: link type arguments from field declaration (f.getGenericType()) to matching TypeVariables Type[] interfaces = f.getType().getGenericInterfaces(); for (Type itf : interfaces) { @@ -702,7 +682,7 @@ public final class Registrator { continue; } - if (itfClass == IEventListener.class) { + if (itfClass == RegistratorListener.class) { if (arguments == null || arguments.length != 1) { // Log a warning or throw an exception handleListenerFieldError(new ListenerFieldError(f, "")); @@ -749,7 +729,7 @@ public final class Registrator { Class<? extends Event> baseEventClass = (Class<? extends Event>) eventType; - ListenerInfo anno = f.getAnnotation(ListenerInfo.class); + ListenerMarker anno = f.getAnnotation(ListenerMarker.class); String[] eventClassNames = anno.events(); if (eventClassNames.length > 0) { @@ -844,9 +824,9 @@ public final class Registrator { private static final class ListenerFieldInfo { final Class<? extends Event> eventClass; final Consumer<? super Event> lambda; - final ListenerInfo anno; + final ListenerMarker anno; - ListenerFieldInfo(Class<? extends Event> eventClass, Consumer<? super Event> lambda, ListenerInfo anno) { + ListenerFieldInfo(Class<? extends Event> eventClass, Consumer<? super Event> lambda, ListenerMarker anno) { this.eventClass = eventClass; this.lambda = lambda; this.anno = anno; diff --git a/dicore3/core/src/main/java/io/dico/dicore/RegistratorListener.java b/dicore3/core/src/main/java/io/dico/dicore/RegistratorListener.java new file mode 100644 index 0000000..99fe072 --- /dev/null +++ b/dicore3/core/src/main/java/io/dico/dicore/RegistratorListener.java @@ -0,0 +1,10 @@ +package io.dico.dicore; + +import org.bukkit.event.Event; + +import java.util.function.Consumer; + +public interface RegistratorListener<T extends Event> extends Consumer<T> { + @Override + void accept(T event); +} diff --git a/src/main/kotlin/io/dico/parcels2/Parcel.kt b/src/main/kotlin/io/dico/parcels2/Parcel.kt index cd05fb3..4df60b1 100644 --- a/src/main/kotlin/io/dico/parcels2/Parcel.kt +++ b/src/main/kotlin/io/dico/parcels2/Parcel.kt @@ -90,6 +90,8 @@ class Parcel(val world: ParcelWorld, val pos: Vec2i) : ParcelData { world.storage.setParcelAllowsInteractInventory(this, value) data.allowInteractInventory = value } + + var hasBlockVisitors: Boolean = false; private set } class ParcelDataHolder : ParcelData { diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelCommands.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelCommands.kt index bae138a..ac09e4a 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelCommands.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelCommands.kt @@ -36,12 +36,9 @@ class ParcelCommands(val plugin: ParcelsPlugin) : ICommandReceiver.Factory { "and gives it to you", shortVersion = "sets you up with a fresh, unclaimed parcel") suspend fun WorldScope.cmdAuto(player: Player): Any? { - logger.info("cmdAuto thread before await: ${Thread.currentThread().name}") val numOwnedParcels = plugin.storage.getNumParcels(ParcelOwner(uuid = player.uuid)).await() - logger.info("cmdAuto thread before await: ${Thread.currentThread().name}") val limit = player.parcelLimit - if (numOwnedParcels >= limit) { error("You have enough plots for now") } @@ -67,15 +64,12 @@ class ParcelCommands(val plugin: ParcelsPlugin) : ICommandReceiver.Factory { shortVersion = "teleports you to parcels") @RequireParameters(0) suspend fun cmdHome(player: Player, - @NamedParcelDefault(FIRST_OWNED) target: NamedParcelTarget): Any? - { + @NamedParcelDefault(FIRST_OWNED) target: NamedParcelTarget): Any? { if (player !== target.player && !player.hasParcelHomeOthers) { error("You do not have permission to teleport to other people's parcels") } - logger.info("cmdHome thread before await: ${Thread.currentThread().name}") val ownedParcelsResult = plugin.storage.getOwnedParcels(ParcelOwner(uuid = target.player.uuid)).await() - logger.info("cmdHome thread after await: ${Thread.currentThread().name}") val uuid = target.player.uuid val ownedParcels = ownedParcelsResult diff --git a/src/main/kotlin/io/dico/parcels2/listener/ParcelEditListener.kt b/src/main/kotlin/io/dico/parcels2/listener/ParcelEditListener.kt new file mode 100644 index 0000000..357819b --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/listener/ParcelEditListener.kt @@ -0,0 +1,248 @@ +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 + 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 new file mode 100644 index 0000000..fb94b8b --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt @@ -0,0 +1,14 @@ +package io.dico.parcels2.listener + +import io.dico.parcels2.Parcel +import org.bukkit.entity.Entity + +class ParcelEntityTracker { + val map = mutableMapOf<Entity, Parcel>() + + fun untrack(entity: Entity) { + map.remove(entity) + } + + +}
\ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/util/MaterialExtensions.kt b/src/main/kotlin/io/dico/parcels2/util/MaterialExtensions.kt new file mode 100644 index 0000000..5cfcb47 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/util/MaterialExtensions.kt @@ -0,0 +1,92 @@ +package io.dico.parcels2.util + +import org.bukkit.Material +import org.bukkit.Material.* + +/* +colors: +WHITE_$, +ORANGE_$, +MAGENTA_$, +LIGHT_BLUE_$, +YELLOW_$, +LIME_$, +PINK_$, +GRAY_$, +LIGHT_GRAY_$, +CYAN_$, +PURPLE_$, +BLUE_$, +BROWN_$, +GREEN_$, +RED_$, +BLACK_$, + +wood: +OAK_$, +BIRCH_$, +SPRUCE_$, +JUNGLE_$, +ACACIA_$, +DARK_OAK_$, + */ + +val Material.isBed get() = when(this) { + 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 -> true + else -> false +} + +val Material.isWoodDoor get() = when(this) { + OAK_DOOR, + BIRCH_DOOR, + SPRUCE_DOOR, + JUNGLE_DOOR, + ACACIA_DOOR, + DARK_OAK_DOOR -> true + else -> false +} + +val Material.isWoodTrapdoor get() = when(this) { + OAK_TRAPDOOR, + BIRCH_TRAPDOOR, + SPRUCE_TRAPDOOR, + JUNGLE_TRAPDOOR, + ACACIA_TRAPDOOR, + DARK_OAK_TRAPDOOR -> true + else -> false +} + +val Material.isWoodFenceGate get() = when(this) { + OAK_FENCE_GATE, + BIRCH_FENCE_GATE, + SPRUCE_FENCE_GATE, + JUNGLE_FENCE_GATE, + ACACIA_FENCE_GATE, + DARK_OAK_FENCE_GATE -> true + else -> false +} + +val Material.isWoodButton get() = when(this) { + OAK_BUTTON, + BIRCH_BUTTON, + SPRUCE_BUTTON, + JUNGLE_BUTTON, + ACACIA_BUTTON, + DARK_OAK_BUTTON -> true + else -> false +} diff --git a/src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt b/src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt index 4953e8a..9054e3f 100644 --- a/src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt +++ b/src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt @@ -9,8 +9,8 @@ import org.bukkit.plugin.java.JavaPlugin inline val OfflinePlayer.uuid get() = uniqueId inline val Player.hasBanBypass get() = hasPermission("parcels.admin.bypass.ban") -inline val Player.hasBuildAnywhere get() = hasPermission("parcels.admin.bypass.build") inline val Player.hasGamemodeBypass get() = hasPermission("parcels.admin.bypass.gamemode") +inline val Player.hasBuildAnywhere get() = hasPermission("parcels.admin.bypass.build") inline val Player.hasAdminManage get() = hasPermission("parcels.admin.manage") inline val Player.hasParcelHomeOthers get() = hasPermission("parcels.command.home.others") inline val Player.hasRandomSpecific get() = hasPermission("parcels.command.random.specific") |