From a475226ffcf17a7327b78e5c1e6ba6ac0dfd10c7 Mon Sep 17 00:00:00 2001 From: Dico Karssiens Date: Sun, 6 Jan 2019 12:02:34 +0000 Subject: Perform some fixes --- .../io/dico/dicore/command/RootCommandAddress.java | 6 +- .../io/dico/parcels2/command/CommandsGeneral.kt | 283 ++--- .../io/dico/parcels2/command/ParcelTarget.kt | 3 +- .../parcels2/defaultimpl/DefaultParcelContainer.kt | 144 +-- .../dico/parcels2/defaultimpl/ParcelWorldImpl.kt | 150 +-- .../io/dico/parcels2/listener/ParcelListeners.kt | 1338 ++++++++++---------- .../io/dico/parcels2/listener/WorldEditListener.kt | 155 ++- .../kotlin/io/dico/parcels2/util/PluginAware.kt | 7 +- src/main/kotlin/io/dico/parcels2/util/parallel.kt | 9 - todo.md | 20 +- 10 files changed, 1063 insertions(+), 1052 deletions(-) delete mode 100644 src/main/kotlin/io/dico/parcels2/util/parallel.kt diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/RootCommandAddress.java b/dicore3/command/src/main/java/io/dico/dicore/command/RootCommandAddress.java index 4df8c49..b6d3b86 100644 --- a/dicore3/command/src/main/java/io/dico/dicore/command/RootCommandAddress.java +++ b/dicore3/command/src/main/java/io/dico/dicore/command/RootCommandAddress.java @@ -238,14 +238,14 @@ public class RootCommandAddress extends ModifiableCommandAddress implements ICom try { ICommandAddress target = getCommandTarget(context, buffer); - List out; - if (target.hasCommand()) { + List out = Collections.emptyList(); + /*if (target.hasCommand()) { context.setCommand(target.getCommand()); target.getCommand().initializeAndFilterContext(context); out = target.getCommand().tabComplete(sender, context, location); } else { out = Collections.emptyList(); - } + }*/ int cursor = buffer.getCursor(); String input; diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt index 61e8d9e..9929a4c 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt @@ -1,142 +1,143 @@ -package io.dico.parcels2.command - -import io.dico.dicore.command.ExecutionContext -import io.dico.dicore.command.Validate -import io.dico.dicore.command.annotation.Cmd -import io.dico.dicore.command.annotation.Desc -import io.dico.dicore.command.annotation.Flag -import io.dico.dicore.command.annotation.RequireParameters -import io.dico.parcels2.ParcelsPlugin -import io.dico.parcels2.PlayerProfile -import io.dico.parcels2.Privilege -import io.dico.parcels2.command.ParcelTarget.TargetKind -import io.dico.parcels2.util.ext.hasParcelHomeOthers -import io.dico.parcels2.util.ext.hasPermAdminManage -import io.dico.parcels2.util.ext.uuid -import org.bukkit.block.Biome -import org.bukkit.entity.Player - -class CommandsGeneral(plugin: ParcelsPlugin, parent: SpecialCommandAddress) : AbstractParcelCommands(plugin) { - - @Cmd("auto") - @Desc( - "Finds the unclaimed parcel nearest to origin,", - "and gives it to you", - shortVersion = "sets you up with a fresh, unclaimed parcel" - ) - suspend fun WorldScope.cmdAuto(player: Player): Any? { - checkConnected("be claimed") - checkParcelLimit(player, world) - - val parcel = world.nextEmptyParcel() - ?: err("This world is full, please ask an admin to upsize it") - parcel.owner = PlayerProfile(uuid = player.uuid) - player.teleport(parcel.homeLocation) - return "Enjoy your new parcel!" - } - - @Cmd("info", aliases = ["i"]) - @Desc( - "Displays general information", - "about the parcel you're on", - shortVersion = "displays information about this parcel" - ) - fun ParcelScope.cmdInfo(player: Player) = parcel.infoString - - init { - parent.addSpeciallyTreatedKeys("home", "h") - } - - @Cmd("home", aliases = ["h"]) - @Desc( - "Teleports you to your parcels,", - "unless another player was specified.", - "You can specify an index number if you have", - "more than one parcel", - shortVersion = "teleports you to parcels" - ) - @RequireParameters(0) - suspend fun cmdHome( - player: Player, - @TargetKind(TargetKind.OWNER_REAL) target: ParcelTarget - ): Any? { - return cmdGoto(player, target) - } - - @Cmd("tp", aliases = ["teleport"]) - suspend fun cmdTp( - player: Player, - @TargetKind(TargetKind.ID) target: ParcelTarget - ): Any? { - return cmdGoto(player, target) - } - - @Cmd("goto") - suspend fun cmdGoto( - player: Player, - @TargetKind(TargetKind.ANY) target: ParcelTarget - ): Any? { - if (target is ParcelTarget.ByOwner) { - target.resolveOwner(plugin.storage) - if (!target.owner.matches(player) && !player.hasParcelHomeOthers) { - err("You do not have permission to teleport to other people's parcels") - } - } - - val match = target.getParcelSuspend(plugin.storage) - ?: err("The specified parcel could not be matched") - player.teleport(match.homeLocation) - return null - } - - @Cmd("goto_fake") - suspend fun cmdGotoFake( - player: Player, - @TargetKind(TargetKind.OWNER_FAKE) target: ParcelTarget - ): Any? { - return cmdGoto(player, target) - } - - @Cmd("claim") - @Desc( - "If this parcel is unowned, makes you the owner", - shortVersion = "claims this parcel" - ) - suspend fun ParcelScope.cmdClaim(player: Player): Any? { - checkConnected("be claimed") - parcel.owner.takeIf { !player.hasPermAdminManage }?.let { - err(if (it.matches(player)) "You already own this parcel" else "This parcel is not available") - } - - checkParcelLimit(player, world) - parcel.owner = PlayerProfile(player) - return "Enjoy your new parcel!" - } - - @Cmd("unclaim") - @Desc("Unclaims this parcel") - @RequireParcelPrivilege(Privilege.OWNER) - fun ParcelScope.cmdUnclaim(player: Player): Any? { - checkConnected("be unclaimed") - parcel.dispose() - return "Your parcel has been disposed" - } - - @Cmd("clear") - @RequireParcelPrivilege(Privilege.OWNER) - fun ParcelScope.cmdClear(context: ExecutionContext, @Flag sure: Boolean): Any? { - Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel") - if (!sure) return areYouSureMessage(context) - world.blockManager.clearParcel(parcel.id)?.reportProgressUpdates(context, "Clear") - return null - } - - @Cmd("setbiome") - @RequireParcelPrivilege(Privilege.OWNER) - fun ParcelScope.cmdSetbiome(context: ExecutionContext, biome: Biome): Any? { - Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel") - world.blockManager.setBiome(parcel.id, biome)?.reportProgressUpdates(context, "Biome change") - return null - } - +package io.dico.parcels2.command + +import io.dico.dicore.command.ExecutionContext +import io.dico.dicore.command.Validate +import io.dico.dicore.command.annotation.Cmd +import io.dico.dicore.command.annotation.Desc +import io.dico.dicore.command.annotation.Flag +import io.dico.dicore.command.annotation.RequireParameters +import io.dico.parcels2.ParcelsPlugin +import io.dico.parcels2.PlayerProfile +import io.dico.parcels2.Privilege +import io.dico.parcels2.command.ParcelTarget.TargetKind +import io.dico.parcels2.util.ext.hasParcelHomeOthers +import io.dico.parcels2.util.ext.hasPermAdminManage +import io.dico.parcels2.util.ext.uuid +import org.bukkit.block.Biome +import org.bukkit.entity.Player + +class CommandsGeneral(plugin: ParcelsPlugin, parent: SpecialCommandAddress) : AbstractParcelCommands(plugin) { + + @Cmd("auto") + @Desc( + "Finds the unclaimed parcel nearest to origin,", + "and gives it to you", + shortVersion = "sets you up with a fresh, unclaimed parcel" + ) + suspend fun WorldScope.cmdAuto(player: Player): Any? { + checkConnected("be claimed") + checkParcelLimit(player, world) + + val parcel = world.nextEmptyParcel() + ?: err("This world is full, please ask an admin to upsize it") + parcel.owner = PlayerProfile(uuid = player.uuid) + player.teleport(parcel.homeLocation) + return "Enjoy your new parcel!" + } + + @Cmd("info", aliases = ["i"]) + @Desc( + "Displays general information", + "about the parcel you're on", + shortVersion = "displays information about this parcel" + ) + fun ParcelScope.cmdInfo(player: Player) = parcel.infoString + + init { + parent.addSpeciallyTreatedKeys("home", "h") + } + + @Cmd("home", aliases = ["h"]) + @Desc( + "Teleports you to your parcels,", + "unless another player was specified.", + "You can specify an index number if you have", + "more than one parcel", + shortVersion = "teleports you to parcels" + ) + @RequireParameters(0) + suspend fun cmdHome( + player: Player, + @TargetKind(TargetKind.OWNER_REAL) target: ParcelTarget + ): Any? { + return cmdGoto(player, target) + } + + @Cmd("tp", aliases = ["teleport"]) + suspend fun cmdTp( + player: Player, + @TargetKind(TargetKind.ID) target: ParcelTarget + ): Any? { + return cmdGoto(player, target) + } + + @Cmd("goto") + suspend fun cmdGoto( + player: Player, + @TargetKind(TargetKind.ANY) target: ParcelTarget + ): Any? { + if (target is ParcelTarget.ByOwner) { + target.resolveOwner(plugin.storage) + if (!target.owner.matches(player) && !player.hasParcelHomeOthers) { + err("You do not have permission to teleport to other people's parcels") + } + } + + val match = target.getParcelSuspend(plugin.storage) + ?: err("The specified parcel could not be matched") + player.teleport(match.homeLocation) + return null + } + + @Cmd("goto_fake") + suspend fun cmdGotoFake( + player: Player, + @TargetKind(TargetKind.OWNER_FAKE) target: ParcelTarget + ): Any? { + return cmdGoto(player, target) + } + + @Cmd("claim") + @Desc( + "If this parcel is unowned, makes you the owner", + shortVersion = "claims this parcel" + ) + suspend fun ParcelScope.cmdClaim(player: Player): Any? { + checkConnected("be claimed") + parcel.owner.takeIf { !player.hasPermAdminManage }?.let { + err(if (it.matches(player)) "You already own this parcel" else "This parcel is not available") + } + + checkParcelLimit(player, world) + parcel.owner = PlayerProfile(player) + return "Enjoy your new parcel!" + } + + /* + @Cmd("unclaim") + @Desc("Unclaims this parcel") + @RequireParcelPrivilege(Privilege.OWNER) + fun ParcelScope.cmdUnclaim(player: Player): Any? { + checkConnected("be unclaimed") + parcel.dispose() + return "Your parcel has been disposed" + }*/ + + @Cmd("clear") + @RequireParcelPrivilege(Privilege.OWNER) + fun ParcelScope.cmdClear(context: ExecutionContext, @Flag sure: Boolean): Any? { + Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel") + if (!sure) return areYouSureMessage(context) + world.blockManager.clearParcel(parcel.id)?.reportProgressUpdates(context, "Clear") + return null + } + + @Cmd("setbiome") + @RequireParcelPrivilege(Privilege.OWNER) + fun ParcelScope.cmdSetbiome(context: ExecutionContext, biome: Biome): Any? { + Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel") + world.blockManager.setBiome(parcel.id, biome)?.reportProgressUpdates(context, "Biome change") + return null + } + } \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt index 934a993..9ba3c25 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt @@ -106,7 +106,8 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef parcelProvider.getWorld(player.world) ?: invalidInput(parameter, "You cannot omit the world if you're not in a parcel world") } else { - parcelProvider.getWorld(worldString) ?: invalidInput(parameter, "$worldString is not a parcel world") + parcelProvider.getWorld(worldString) + ?: invalidInput(parameter, "$worldString is not a parcel world") } val kind = parameter.paramInfo ?: DEFAULT_KIND diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt index b49cad4..f3cd8d7 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt @@ -1,73 +1,73 @@ -package io.dico.parcels2.defaultimpl - -import io.dico.parcels2.Parcel -import io.dico.parcels2.ParcelContainer -import io.dico.parcels2.ParcelId -import io.dico.parcels2.ParcelWorld - -class DefaultParcelContainer(val world: ParcelWorld) : ParcelContainer { - private var parcels: Array> - - init { - parcels = initArray(world.options.axisLimit, world) - } - - fun resizeIfSizeChanged() { - if (parcels.size != world.options.axisLimit * 2 + 1) { - resize(world.options.axisLimit) - } - } - - fun resize(axisLimit: Int) { - parcels = initArray(axisLimit, world, this) - } - - fun initArray(axisLimit: Int, world: ParcelWorld, cur: DefaultParcelContainer? = null): Array> { - val arraySize = 2 * axisLimit + 1 - return Array(arraySize) { - val x = it - axisLimit - Array(arraySize) { - val z = it - axisLimit - cur?.getParcelById(x, z) ?: ParcelImpl(world, x, z) - } - } - } - - override fun getParcelById(x: Int, z: Int): Parcel? { - return parcels.getOrNull(x + world.options.axisLimit)?.getOrNull(z + world.options.axisLimit) - } - - override fun getParcelById(id: ParcelId): Parcel? { - if (!world.id.equals(id.worldId)) throw IllegalArgumentException() - return when (id) { - is Parcel -> id - else -> getParcelById(id.x, id.z) - } - } - - override fun nextEmptyParcel(): Parcel? { - return walkInCircle().find { it.owner == null } - } - - private fun walkInCircle(): Iterable = Iterable { - iterator { - val center = world.options.axisLimit - yield(parcels[center][center]) - for (radius in 0..center) { - var x = center - radius; - var z = center - radius - repeat(radius * 2) { yield(parcels[x++][z]) } - repeat(radius * 2) { yield(parcels[x][z++]) } - repeat(radius * 2) { yield(parcels[x--][z]) } - repeat(radius * 2) { yield(parcels[x][z--]) } - } - } - } - - fun getAllParcels(): Iterator = iterator { - for (array in parcels) { - yieldAll(array.iterator()) - } - } - +package io.dico.parcels2.defaultimpl + +import io.dico.parcels2.Parcel +import io.dico.parcels2.ParcelContainer +import io.dico.parcels2.ParcelId +import io.dico.parcels2.ParcelWorld + +class DefaultParcelContainer(val world: ParcelWorld) : ParcelContainer { + private var parcels: Array> + + init { + parcels = initArray(world.options.axisLimit, world) + } + + fun resizeIfSizeChanged() { + if (parcels.size != world.options.axisLimit * 2 + 1) { + resize(world.options.axisLimit) + } + } + + fun resize(axisLimit: Int) { + parcels = initArray(axisLimit, world, this) + } + + fun initArray(axisLimit: Int, world: ParcelWorld, cur: DefaultParcelContainer? = null): Array> { + val arraySize = 2 * axisLimit + 1 + return Array(arraySize) { + val x = it - axisLimit + Array(arraySize) { + val z = it - axisLimit + cur?.getParcelById(x, z) ?: ParcelImpl(world, x, z) + } + } + } + + override fun getParcelById(x: Int, z: Int): Parcel? { + return parcels.getOrNull(x + world.options.axisLimit)?.getOrNull(z + world.options.axisLimit) + } + + override fun getParcelById(id: ParcelId): Parcel? { + if (!world.id.equals(id.worldId)) throw IllegalArgumentException() + return when (id) { + is Parcel -> id + else -> getParcelById(id.x, id.z) + } + } + + override suspend fun nextEmptyParcel(): Parcel? { + return walkInCircle().find { it.owner == null } + } + + private fun walkInCircle(): Iterable = Iterable { + iterator { + val center = world.options.axisLimit + yield(parcels[center][center]) + for (radius in 0..center) { + var x = center - radius; + var z = center - radius + repeat(radius * 2) { yield(parcels[x++][z]) } + repeat(radius * 2) { yield(parcels[x][z++]) } + repeat(radius * 2) { yield(parcels[x--][z]) } + repeat(radius * 2) { yield(parcels[x][z--]) } + } + } + } + + fun getAllParcels(): Iterator = iterator { + for (array in parcels) { + yieldAll(array.iterator()) + } + } + } \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt index 6ce4f26..cb322d4 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt @@ -1,75 +1,75 @@ -@file:Suppress("CanBePrimaryConstructorProperty", "UsePropertyAccessSyntax") - -package io.dico.parcels2.defaultimpl - -import io.dico.parcels2.* -import io.dico.parcels2.options.RuntimeWorldOptions -import io.dico.parcels2.storage.Storage -import kotlinx.coroutines.CoroutineScope -import org.bukkit.GameRule -import org.bukkit.World -import org.joda.time.DateTime -import java.util.UUID - -class ParcelWorldImpl( - val plugin: ParcelsPlugin, - override val world: World, - override val generator: ParcelGenerator, - override var options: RuntimeWorldOptions, - containerFactory: ParcelContainerFactory -) : ParcelWorld, ParcelWorldId, ParcelContainer, ParcelLocator { - override val id: ParcelWorldId get() = this - override val uid: UUID? get() = world.uid - - override val storage get() = plugin.storage - override val globalPrivileges get() = plugin.globalPrivileges - - init { - if (generator.world != world) { - throw IllegalArgumentException() - } - } - - override val name: String = world.name!! - override val container: ParcelContainer = containerFactory(this) - override val locator: ParcelLocator - override val blockManager: ParcelBlockManager - - init { - val (locator, blockManager) = generator.makeParcelLocatorAndBlockManager(plugin.parcelProvider, container, plugin, plugin.jobDispatcher) - this.locator = locator - this.blockManager = blockManager - enforceOptions() - } - - fun enforceOptions() { - if (options.dayTime) { - world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false) - world.setTime(6000) - } - - if (options.noWeather) { - world.setStorm(false) - world.setThundering(false) - world.weatherDuration = Int.MAX_VALUE - } - - world.setGameRule(GameRule.DO_TILE_DROPS, options.doTileDrops) - } - - // Accessed by ParcelProviderImpl - override var creationTime: DateTime? = null - - - override fun getParcelAt(x: Int, z: Int): Parcel? = locator.getParcelAt(x, z) - - override fun getParcelIdAt(x: Int, z: Int): ParcelId? = locator.getParcelIdAt(x, z) - - override fun getParcelById(x: Int, z: Int): Parcel? = container.getParcelById(x, z) - - override fun getParcelById(id: ParcelId): Parcel? = container.getParcelById(id) - - override fun nextEmptyParcel(): Parcel? = container.nextEmptyParcel() - - override fun toString() = parcelWorldIdToString() -} +@file:Suppress("CanBePrimaryConstructorProperty", "UsePropertyAccessSyntax") + +package io.dico.parcels2.defaultimpl + +import io.dico.parcels2.* +import io.dico.parcels2.options.RuntimeWorldOptions +import io.dico.parcels2.storage.Storage +import kotlinx.coroutines.CoroutineScope +import org.bukkit.GameRule +import org.bukkit.World +import org.joda.time.DateTime +import java.util.UUID + +class ParcelWorldImpl( + val plugin: ParcelsPlugin, + override val world: World, + override val generator: ParcelGenerator, + override var options: RuntimeWorldOptions, + containerFactory: ParcelContainerFactory +) : ParcelWorld, ParcelWorldId, ParcelContainer, ParcelLocator { + override val id: ParcelWorldId get() = this + override val uid: UUID? get() = world.uid + + override val storage get() = plugin.storage + override val globalPrivileges get() = plugin.globalPrivileges + + init { + if (generator.world != world) { + throw IllegalArgumentException() + } + } + + override val name: String = world.name!! + override val container: ParcelContainer = containerFactory(this) + override val locator: ParcelLocator + override val blockManager: ParcelBlockManager + + init { + val (locator, blockManager) = generator.makeParcelLocatorAndBlockManager(plugin.parcelProvider, container, plugin, plugin.jobDispatcher) + this.locator = locator + this.blockManager = blockManager + enforceOptions() + } + + fun enforceOptions() { + if (options.dayTime) { + world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false) + world.setTime(6000) + } + + if (options.noWeather) { + world.setStorm(false) + world.setThundering(false) + world.weatherDuration = Int.MAX_VALUE + } + + world.setGameRule(GameRule.DO_TILE_DROPS, options.doTileDrops) + } + + // Accessed by ParcelProviderImpl + override var creationTime: DateTime? = null + + + override fun getParcelAt(x: Int, z: Int): Parcel? = locator.getParcelAt(x, z) + + override fun getParcelIdAt(x: Int, z: Int): ParcelId? = locator.getParcelIdAt(x, z) + + override fun getParcelById(x: Int, z: Int): Parcel? = container.getParcelById(x, z) + + override fun getParcelById(id: ParcelId): Parcel? = container.getParcelById(id) + + override suspend fun nextEmptyParcel(): Parcel? = container.nextEmptyParcel() + + override fun toString() = parcelWorldIdToString() +} diff --git a/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt b/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt index 8bef7d1..e87dd68 100644 --- a/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt +++ b/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt @@ -1,661 +1,679 @@ -package io.dico.parcels2.listener - -import gnu.trove.TLongCollection -import gnu.trove.set.hash.TLongHashSet -import io.dico.dicore.Formatting -import io.dico.dicore.ListenerMarker -import io.dico.dicore.RegistratorListener -import io.dico.parcels2.* -import io.dico.parcels2.storage.Storage -import io.dico.parcels2.util.ext.* -import io.dico.parcels2.util.math.* -import org.bukkit.Location -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 -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.ChunkLoadEvent -import org.bukkit.event.world.StructureGrowEvent -import org.bukkit.inventory.InventoryHolder -import java.util.EnumSet - -class ParcelListeners( - val parcelProvider: ParcelProvider, - val entityTracker: ParcelEntityTracker, - val storage: Storage -) { - private fun canBuildOnArea(user: Player, area: Parcel?) = - if (area == null) user.hasPermBuildAnywhere else area.canBuild(user) - - private fun canInteract(user: Player, area: Parcel?, interactClass: String) = - canBuildOnArea(user, area) || (area != null && area.interactableConfig(interactClass)) - - /** - * Get the world and parcel that the block resides in - * the parcel is nullable, and often named area because that means path. - * returns null if not in a registered parcel world - should always return in that case to not affect other worlds. - */ - private fun getWorldAndArea(block: Block): Pair? { - val world = parcelProvider.getWorld(block.world) ?: return null - return world to world.getParcelAt(block) - } - - - /* - * Prevents players from entering plots they are banned from - */ - @field:ListenerMarker(priority = NORMAL) - val onPlayerMoveEvent = RegistratorListener l@{ event -> - val user = event.player - if (user.hasPermBanBypass) return@l - val toLoc = event.to - val parcel = parcelProvider.getParcelAt(toLoc) ?: return@l - - if (!parcel.canEnterFast(user)) { - val region = parcel.world.blockManager.getRegion(parcel.id) - val dimension = region.getFirstUncontainedDimensionOf(Vec3i(event.from)) - - if (dimension == null) { - user.teleport(parcel.homeLocation) - user.sendParcelMessage(nopermit = true, message = "You are banned from this parcel") - - } else { - val speed = getPlayerSpeed(user) - val from = Vec3d(event.from) - val to = Vec3d(toLoc).with(dimension, from[dimension]) - - var newTo = to - dimension.otherDimensions.forEach { - val delta = to[it] - from[it] - newTo = newTo.add(it, delta * 100 * if (it == Dimension.Y) 0.5 else speed) - } - - event.to = Location( - toLoc.world, - newTo.x, newTo.y.clampMin(0.0).clampMax(255.0), newTo.z, - toLoc.yaw, toLoc.pitch - ) - } - } - } - - /* - * 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 l@{ event -> - val (world, area) = getWorldAndArea(event.block) ?: return@l - if (!canBuildOnArea(event.player, area)) { - event.isCancelled = true; return@l - } - - if (!world.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 l@{ event -> - val (_, area) = getWorldAndArea(event.block) ?: return@l - if (!canBuildOnArea(event.player, area)) { - event.isCancelled = true - } - - area?.updateOwnerSign() - } - - /* - * Control pistons - */ - @field:ListenerMarker(priority = NORMAL) - val onBlockPistonExtendEvent = RegistratorListener l@{ event -> - checkPistonMovement(event, event.blocks) - } - - @field:ListenerMarker(priority = NORMAL) - val onBlockPistonRetractEvent = RegistratorListener 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.troveForEach(block: (Long) -> Unit) = iterator().let { while (it.hasNext()) block(it.next()) } - //@formatter:on - private fun checkPistonMovement(event: BlockPistonEvent, blocks: List) { - val world = parcelProvider.getWorld(event.block.world) ?: return - val direction = event.direction - val columns = 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.troveForEach { - val area = world.getParcelAt(it.columnX, it.columnZ) - if (area == null || area.hasBlockVisitors) { - event.isCancelled = true - return - } - } - } - - /* - * Prevents explosions if enabled by the configs for that world - */ - @field:ListenerMarker(priority = NORMAL) - val onExplosionPrimeEvent = RegistratorListener l@{ event -> - val (world, area) = getWorldAndArea(event.entity.location.block) ?: return@l - if (area != null && area.hasBlockVisitors) { - event.radius = 0F; event.isCancelled = true - } else if (world.options.disableExplosions) { - event.radius = 0F - } - } - - /* - * Prevents creepers and tnt minecarts from exploding if explosions are disabled - */ - @field:ListenerMarker(priority = NORMAL) - val onEntityExplodeEvent = RegistratorListener l@{ event -> - entityTracker.untrack(event.entity) - val world = parcelProvider.getWorld(event.entity.world) ?: return@l - if (world.options.disableExplosions || world.getParcelAt(event.entity).let { it != null && it.hasBlockVisitors }) { - event.isCancelled = true - } - } - - /* - * Prevents liquids from flowing out of plots - */ - @field:ListenerMarker(priority = NORMAL) - val onBlockFromToEvent = RegistratorListener l@{ event -> - val (_, area) = getWorldAndArea(event.toBlock) ?: return@l - if (area == null || area.hasBlockVisitors) event.isCancelled = true - } - - private val bedTypes = EnumSet.copyOf(getMaterialsWithWoolColorPrefix("BED").toList()) - /* - * 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 l@{ event -> - val user = event.player - val world = parcelProvider.getWorld(user.world) ?: return@l - val clickedBlock = event.clickedBlock - val parcel = clickedBlock?.let { world.getParcelAt(it) } - - if (!user.hasPermBuildAnywhere && parcel != null && !parcel.canEnter(user)) { - 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 -> run { - val type = clickedBlock.type - - val interactableClass = Interactables[type] - if (interactableClass != null && !parcel.effectiveInteractableConfig.isInteractable(type) && (parcel == null || !parcel.canBuild(user))) { - user.sendParcelMessage(nopermit = true, message = "You cannot interact with ${interactableClass.name} here") - event.isCancelled = true - return@l - } - - if (bedTypes.contains(type)) { - val bed = clickedBlock.blockData as Bed - val head = if (bed.part == Bed.Part.FOOT) clickedBlock.getRelative(bed.facing) else clickedBlock - when (head.biome) { - Biome.NETHER, Biome.THE_END -> { - if (world.options.disableExplosions) { - user.sendParcelMessage(nopermit = true, message = "You cannot use this bed because it would explode") - event.isCancelled = true; return@l - } - } - } - - if (!canBuildOnArea(user, parcel)) { - user.sendParcelMessage(nopermit = true, message = "You may not sleep here") - event.isCancelled = true; return@l - } - } - - onPlayerRightClick(event, world, parcel) - - if (!event.isCancelled && parcel == null) { - world.blockManager.getParcelForInfoBlockInteraction(Vec3i(clickedBlock), type, event.blockFace) - ?.apply { user.sendMessage(Formatting.GREEN + infoString) } - } - } - - Action.RIGHT_CLICK_AIR -> onPlayerRightClick(event, world, parcel) - Action.PHYSICAL -> if (!canBuildOnArea(user, parcel) && !(parcel != null && parcel.interactableConfig("pressure_plates"))) { - user.sendParcelMessage(nopermit = true, message = "You cannot use inputs in this parcel") - event.isCancelled = true; return@l - } - } - } - - // private val blockPlaceInteractItems = EnumSet.of(LAVA_BUCKET, WATER_BUCKET, BUCKET, FLINT_AND_STEEL) - - @Suppress("NON_EXHAUSTIVE_WHEN") - private fun onPlayerRightClick(event: PlayerInteractEvent, world: ParcelWorld, parcel: Parcel?) { - if (event.hasItem()) { - val item = event.item.type - if (world.options.blockedItems.contains(item)) { - event.player.sendParcelMessage(nopermit = true, message = "You cannot use this item because it is disabled in this world") - event.isCancelled = true; return - } - - when (item) { - LAVA_BUCKET, WATER_BUCKET, BUCKET, FLINT_AND_STEEL -> { - val block = event.clickedBlock.getRelative(event.blockFace) - val otherParcel = world.getParcelAt(block) - if (!canBuildOnArea(event.player, otherParcel)) { - event.isCancelled = true - } - } - } - } - } - - /* - * 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 l@{ event -> - val (_, area) = getWorldAndArea(event.rightClicked.location.block) ?: return@l - if (canBuildOnArea(event.player, area)) 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 l@{ event -> - val (_, area) = getWorldAndArea(event.block) ?: return@l - if (event.entity.type == EntityType.ENDERMAN || area == null || area.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, area) - } - } - - /* - * Prevents portals from being created if set so in the configs for that world - */ - @field:ListenerMarker(priority = NORMAL) - val onEntityCreatePortalEvent = RegistratorListener l@{ event -> - val world = parcelProvider.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 l@{ event -> - val (_, area) = getWorldAndArea(event.itemDrop.location.block) ?: return@l - if (!canInteract(event.player, area, "containers")) event.isCancelled = true - } - - /* - * Prevents players from picking up items - */ - @field:ListenerMarker(priority = NORMAL) - val onEntityPickupItemEvent = RegistratorListener l@{ event -> - val user = event.entity as? Player ?: return@l - val (_, area) = getWorldAndArea(event.item.location.block) ?: return@l - if (!canInteract(user, area, "containers")) event.isCancelled = true - } - - /* - * Prevents players from editing inventories - */ - @field:ListenerMarker(priority = NORMAL, events = ["inventory.InventoryClickEvent", "inventory.InventoryDragEvent"]) - val onInventoryClickEvent = RegistratorListener l@{ event -> - val user = event.whoClicked as? Player ?: return@l - if ((event.inventory ?: return@l).holder === user) return@l // inventory null: hotbar - val (_, area) = getWorldAndArea(event.inventory.location.block) ?: return@l - if (!canInteract(user, area, "containers")) { - 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 l@{ event -> - val world = parcelProvider.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 - } - -// TODO: BlockFormEvent, BlockSpreadEvent, BlockFadeEvent, Fireworks - - /* - * Prevents natural blocks forming - */ - @ListenerMarker(priority = NORMAL) - val onBlockFormEvent = RegistratorListener l@{ event -> - val block = event.block - val (world, area) = getWorldAndArea(block) ?: return@l - - // prevent any generation whatsoever on paths - if (area == null) { - event.isCancelled = true; return@l - } - - val hasEntity = event is EntityBlockFormEvent - val player = (event as? EntityBlockFormEvent)?.entity as? Player - - val cancel: Boolean = when (event.newState.type) { - - // prevent ice generation from Frost Walkers enchantment - FROSTED_ICE -> player != null && !area.canBuild(player) - - // prevent snow generation from weather - SNOW -> !hasEntity && world.options.preventWeatherBlockChanges - - else -> false - } - - if (cancel) { - event.isCancelled = true - } - } - - /* - * Prevents mobs (living entities) from spawning if that is disabled for that world in the config. - */ - @field:ListenerMarker(priority = NORMAL) - val onEntitySpawnEvent = RegistratorListener l@{ event -> - val world = parcelProvider.getWorld(event.entity.world) ?: return@l - if (event.entity is Creature && world.options.blockMobSpawning) { - event.isCancelled = true - } else if (world.getParcelAt(event.entity).let { it != null && it.hasBlockVisitors }) { - event.isCancelled = true - } - } - - /* - * Prevents minecarts/boats from moving outside a plot - */ - @field:ListenerMarker(priority = NORMAL) - val onVehicleMoveEvent = RegistratorListener l@{ event -> - val (_, area) = getWorldAndArea(event.to.block) ?: return@l - if (area == null) { - event.vehicle.passengers.forEach { - if (it.type == EntityType.PLAYER) { - (it as Player).sendParcelMessage(except = true, message = "Your ride ends here") - } else it.remove() - } - event.vehicle.eject() - event.vehicle.remove() - } else if (area.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 l@{ event -> - val world = parcelProvider.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 (!canBuildOnArea(user, world.getParcelAt(event.entity))) { - event.isCancelled = true - } - } - - @field:ListenerMarker(priority = NORMAL) - val onHangingBreakEvent = RegistratorListener l@{ event -> - val world = parcelProvider.getWorld(event.entity.world) ?: return@l - if (event.cause == HangingBreakEvent.RemoveCause.EXPLOSION && world.options.disableExplosions) { - event.isCancelled = true; return@l - } - - if (world.getParcelAt(event.entity).let { it != null && it.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 l@{ event -> - val world = parcelProvider.getWorld(event.entity.world) ?: return@l - val user = event.remover as? Player ?: return@l - if (!canBuildOnArea(user, world.getParcelAt(event.entity))) { - event.isCancelled = true - } - } - - /* - * Prevents players from placing paintings and item frames - */ - @field:ListenerMarker(priority = NORMAL) - val onHangingPlaceEvent = RegistratorListener l@{ event -> - val world = parcelProvider.getWorld(event.entity.world) ?: return@l - val block = event.block.getRelative(event.blockFace) - if (!canBuildOnArea(event.player, world.getParcelAt(block))) { - event.isCancelled = true - } - } - - /* - * Prevents stuff from growing outside of plots - */ - @field:ListenerMarker(priority = NORMAL) - val onStructureGrowEvent = RegistratorListener l@{ event -> - val (world, area) = getWorldAndArea(event.location.block) ?: return@l - if (area == null) { - event.isCancelled = true; return@l - } - - if (!event.player.hasPermBuildAnywhere && !area.canBuild(event.player)) { - event.isCancelled = true; return@l - } - - event.blocks.removeIf { world.getParcelAt(it.block) !== area } - } - - /* - * Prevents dispensers/droppers from dispensing out of parcels - */ - @field:ListenerMarker(priority = NORMAL) - val onBlockDispenseEvent = RegistratorListener l@{ event -> - val block = event.block - if (!block.type.let { it == DISPENSER || it == DROPPER }) return@l - val world = parcelProvider.getWorld(block.world) ?: return@l - val data = block.blockData as Directional - val targetBlock = block.getRelative(data.facing) - if (world.getParcelAt(targetBlock) == null) { - event.isCancelled = true - } - } - - /* - * Track spawned items, making sure they don't leave the parcel. - */ - @field:ListenerMarker(priority = NORMAL) - val onItemSpawnEvent = RegistratorListener l@{ event -> - val (_, area) = getWorldAndArea(event.location.block) ?: return@l - if (area == null) event.isCancelled = true - else entityTracker.track(event.entity, area) - } - - /* - * Prevents endermen and endermite from teleporting outside their parcel - */ - @field:ListenerMarker(priority = NORMAL) - val onEntityTeleportEvent = RegistratorListener l@{ event -> - val (world, area) = getWorldAndArea(event.from.block) ?: return@l - if (area !== world.getParcelAt(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 l@{ event -> - val (_, area) = getWorldAndArea(event.entity.location.block) ?: return@l - if (area == null || (event.entity.shooter as? Player)?.let { !canBuildOnArea(it, area) } == true) { - event.isCancelled = true - } else { - entityTracker.track(event.entity, area) - } - } - - /* - * Prevents entities from dropping items upon death, if configured that way - */ - @field:ListenerMarker(priority = NORMAL) - val onEntityDeathEvent = RegistratorListener l@{ event -> - entityTracker.untrack(event.entity) - val world = parcelProvider.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 l@{ event -> - val world = parcelProvider.getWorld(event.player.world) ?: return@l - if (world.options.gameMode != null && !event.player.hasPermGamemodeBypass) { - event.player.gameMode = world.options.gameMode - } - } - - /** - * Updates owner signs of parcels that get loaded if it is marked outdated - */ - @ListenerMarker(priority = EventPriority.NORMAL) - val onChunkLoadEvent = RegistratorListener l@{ event -> - val world = parcelProvider.getWorld(event.chunk.world) ?: return@l - val parcels = world.blockManager.getParcelsWithOwnerBlockIn(event.chunk) - if (parcels.isEmpty()) return@l - - parcels.forEach { id -> - val parcel = world.getParcelById(id)?.takeIf { it.isOwnerSignOutdated } ?: return@forEach - world.blockManager.updateParcelInfo(parcel.id, parcel.owner) - parcel.isOwnerSignOutdated = false - } - - } - - @ListenerMarker - val onPlayerJoinEvent = RegistratorListener l@{ event -> - storage.updatePlayerName(event.player.uuid, event.player.name) - } - - /** - * Attempts to prevent redstone contraptions from breaking while they are being swapped - * Might remove if it causes lag - */ - @ListenerMarker - val onBlockRedstoneEvent = RegistratorListener l@{ event -> - val (_, area) = getWorldAndArea(event.block) ?: return@l - if (area == null || area.hasBlockVisitors) { - event.newCurrent = event.oldCurrent - } - } - - - private fun getPlayerSpeed(player: Player): Double = - if (player.isFlying) { - player.flySpeed * if (player.isSprinting) 21.6 else 10.92 - } else { - player.walkSpeed * when { - player.isSprinting -> 5.612 - player.isSneaking -> 1.31 - else -> 4.317 - } / 1.5 //? - } / 20.0 - +package io.dico.parcels2.listener + +import gnu.trove.TLongCollection +import gnu.trove.set.hash.TLongHashSet +import io.dico.dicore.Formatting +import io.dico.dicore.ListenerMarker +import io.dico.dicore.RegistratorListener +import io.dico.parcels2.* +import io.dico.parcels2.storage.Storage +import io.dico.parcels2.util.ext.* +import io.dico.parcels2.util.math.* +import org.bukkit.Location +import org.bukkit.Material +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 +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.ChunkLoadEvent +import org.bukkit.event.world.StructureGrowEvent +import org.bukkit.inventory.InventoryHolder +import java.util.EnumSet + +class ParcelListeners( + val parcelProvider: ParcelProvider, + val entityTracker: ParcelEntityTracker, + val storage: Storage +) { + private fun canBuildOnArea(user: Player, area: Parcel?) = + if (area == null) user.hasPermBuildAnywhere else area.canBuild(user) + + private fun canInteract(user: Player, area: Parcel?, interactClass: String) = + canBuildOnArea(user, area) || (area != null && area.interactableConfig(interactClass)) + + /** + * Get the world and parcel that the block resides in + * the parcel is nullable, and often named area because that means path. + * returns null if not in a registered parcel world - should always return in that case to not affect other worlds. + */ + private fun getWorldAndArea(block: Block): Pair? { + val world = parcelProvider.getWorld(block.world) ?: return null + return world to world.getParcelAt(block) + } + + + /* + * Prevents players from entering plots they are banned from + */ + @field:ListenerMarker(priority = NORMAL) + val onPlayerMoveEvent = RegistratorListener l@{ event -> + val user = event.player + if (user.hasPermBanBypass) return@l + val toLoc = event.to + val parcel = parcelProvider.getParcelAt(toLoc) ?: return@l + + if (!parcel.canEnterFast(user)) { + val region = parcel.world.blockManager.getRegion(parcel.id) + val dimension = region.getFirstUncontainedDimensionOf(Vec3i(event.from)) + + if (dimension == null) { + user.teleport(parcel.homeLocation) + user.sendParcelMessage(nopermit = true, message = "You are banned from this parcel") + + } else { + val speed = getPlayerSpeed(user) + val from = Vec3d(event.from) + val to = Vec3d(toLoc).with(dimension, from[dimension]) + + var newTo = to + dimension.otherDimensions.forEach { + val delta = to[it] - from[it] + newTo = newTo.add(it, delta * 100 * if (it == Dimension.Y) 0.5 else speed) + } + + event.to = Location( + toLoc.world, + newTo.x, newTo.y.clampMin(0.0).clampMax(255.0), newTo.z, + toLoc.yaw, toLoc.pitch + ) + } + } + } + + /* + * 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 l@{ event -> + val (world, area) = getWorldAndArea(event.block) ?: return@l + if (!canBuildOnArea(event.player, area)) { + event.isCancelled = true; return@l + } + + if (!world.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 l@{ event -> + val (_, area) = getWorldAndArea(event.block) ?: return@l + if (!canBuildOnArea(event.player, area)) { + event.isCancelled = true + } + + area?.updateOwnerSign() + } + + /* + * Control pistons + */ + @field:ListenerMarker(priority = NORMAL) + val onBlockPistonExtendEvent = RegistratorListener l@{ event -> + checkPistonMovement(event, event.blocks) + } + + @field:ListenerMarker(priority = NORMAL) + val onBlockPistonRetractEvent = RegistratorListener 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.troveForEach(block: (Long) -> Unit) = iterator().let { while (it.hasNext()) block(it.next()) } + //@formatter:on + private fun checkPistonMovement(event: BlockPistonEvent, blocks: List) { + val world = parcelProvider.getWorld(event.block.world) ?: return + val direction = event.direction + val columns = 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.troveForEach { + val area = world.getParcelAt(it.columnX, it.columnZ) + if (area == null || area.hasBlockVisitors) { + event.isCancelled = true + return + } + } + } + + /* + * Prevents explosions if enabled by the configs for that world + */ + @field:ListenerMarker(priority = NORMAL) + val onExplosionPrimeEvent = RegistratorListener l@{ event -> + val (world, area) = getWorldAndArea(event.entity.location.block) ?: return@l + if (area != null && area.hasBlockVisitors) { + event.radius = 0F; event.isCancelled = true + } else if (world.options.disableExplosions) { + event.radius = 0F + } + } + + /* + * Prevents creepers and tnt minecarts from exploding if explosions are disabled + */ + @field:ListenerMarker(priority = NORMAL) + val onEntityExplodeEvent = RegistratorListener l@{ event -> + entityTracker.untrack(event.entity) + val world = parcelProvider.getWorld(event.entity.world) ?: return@l + if (world.options.disableExplosions || world.getParcelAt(event.entity).let { it != null && it.hasBlockVisitors }) { + event.isCancelled = true + } + } + + /* + * Prevents liquids from flowing out of plots + */ + @field:ListenerMarker(priority = NORMAL) + val onBlockFromToEvent = RegistratorListener l@{ event -> + val (_, area) = getWorldAndArea(event.toBlock) ?: return@l + if (area == null || area.hasBlockVisitors) event.isCancelled = true + } + + private val bedTypes = EnumSet.copyOf(getMaterialsWithWoolColorPrefix("BED").toList()) + /* + * 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, ignoreCancelled = false) + val onPlayerInteractEvent = RegistratorListener l@{ event -> + val user = event.player + val world = parcelProvider.getWorld(user.world) ?: return@l + val clickedBlock = event.clickedBlock + val parcel = clickedBlock?.let { world.getParcelAt(it) } + + if (!user.hasPermBuildAnywhere && parcel != null && !parcel.canEnter(user)) { + 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 -> run { + if (event.isCancelled) return@l + val type = clickedBlock.type + + val interactableClass = Interactables[type] + if (interactableClass != null && !parcel.effectiveInteractableConfig.isInteractable(type) && (parcel == null || !parcel.canBuild(user))) { + user.sendParcelMessage(nopermit = true, message = "You cannot interact with ${interactableClass.name} here") + event.isCancelled = true + return@l + } + + if (bedTypes.contains(type)) { + val bed = clickedBlock.blockData as Bed + val head = if (bed.part == Bed.Part.FOOT) clickedBlock.getRelative(bed.facing) else clickedBlock + when (head.biome) { + Biome.NETHER, Biome.THE_END -> { + if (world.options.disableExplosions) { + user.sendParcelMessage(nopermit = true, message = "You cannot use this bed because it would explode") + event.isCancelled = true; return@l + } + } + } + + if (!canBuildOnArea(user, parcel)) { + user.sendParcelMessage(nopermit = true, message = "You may not sleep here") + event.isCancelled = true; return@l + } + } + + onPlayerRightClick(event, world, parcel) + + if (!event.isCancelled && parcel == null) { + world.blockManager.getParcelForInfoBlockInteraction(Vec3i(clickedBlock), type, event.blockFace) + ?.apply { user.sendMessage(Formatting.GREEN + infoString) } + } + } + + Action.RIGHT_CLICK_AIR -> onPlayerRightClick(event, world, parcel) + Action.PHYSICAL -> if (!event.isCancelled && !canBuildOnArea(user, parcel)) { + if (clickedBlock.type == Material.TURTLE_EGG) { + event.isCancelled = true; return@l + } + + if (!(parcel != null && parcel.interactableConfig("pressure_plates"))) { + user.sendParcelMessage(nopermit = true, message = "You cannot use inputs in this parcel") + event.isCancelled = true; return@l + } + } + } + } + + // private val blockPlaceInteractItems = EnumSet.of(LAVA_BUCKET, WATER_BUCKET, BUCKET, FLINT_AND_STEEL) + + @Suppress("NON_EXHAUSTIVE_WHEN") + private fun onPlayerRightClick(event: PlayerInteractEvent, world: ParcelWorld, parcel: Parcel?) { + if (event.hasItem()) { + val item = event.item.type + if (world.options.blockedItems.contains(item)) { + event.player.sendParcelMessage(nopermit = true, message = "You cannot use this item because it is disabled in this world") + event.isCancelled = true; return + } + + when (item) { + LAVA_BUCKET, WATER_BUCKET, BUCKET, FLINT_AND_STEEL -> { + val block = event.clickedBlock.getRelative(event.blockFace) + val otherParcel = world.getParcelAt(block) + if (!canBuildOnArea(event.player, otherParcel)) { + event.isCancelled = true + } + } + } + } + } + + /* + * 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 l@{ event -> + val (_, area) = getWorldAndArea(event.rightClicked.location.block) ?: return@l + if (canBuildOnArea(event.player, area)) 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 l@{ event -> + val (_, area) = getWorldAndArea(event.block) ?: return@l + if (event.entity.type == EntityType.ENDERMAN || area == null || area.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, area) + } + } + + /* + * Prevents portals from being created if set so in the configs for that world + */ + @field:ListenerMarker(priority = NORMAL) + val onEntityCreatePortalEvent = RegistratorListener l@{ event -> + val world = parcelProvider.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 l@{ event -> + val (_, area) = getWorldAndArea(event.itemDrop.location.block) ?: return@l + if (!canInteract(event.player, area, "containers")) event.isCancelled = true + } + + /* + * Prevents players from picking up items + */ + @field:ListenerMarker(priority = NORMAL) + val onEntityPickupItemEvent = RegistratorListener l@{ event -> + val user = event.entity as? Player ?: return@l + val (_, area) = getWorldAndArea(event.item.location.block) ?: return@l + if (!canInteract(user, area, "containers")) event.isCancelled = true + } + + /* + * Prevents players from editing inventories + */ + @field:ListenerMarker(priority = NORMAL, events = ["inventory.InventoryClickEvent", "inventory.InventoryDragEvent"]) + val onInventoryClickEvent = RegistratorListener l@{ event -> + val user = event.whoClicked as? Player ?: return@l + if ((event.inventory ?: return@l).holder === user) return@l // inventory null: hotbar + val (_, area) = getWorldAndArea(event.inventory.location.block) ?: return@l + if (!canInteract(user, area, "containers")) { + 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 l@{ event -> + val world = parcelProvider.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 + } + +// TODO: BlockFormEvent, BlockSpreadEvent, BlockFadeEvent, Fireworks + + /* + * Prevents natural blocks forming + */ + @ListenerMarker(priority = NORMAL) + val onBlockFormEvent = RegistratorListener l@{ event -> + val block = event.block + val (world, area) = getWorldAndArea(block) ?: return@l + + // prevent any generation whatsoever on paths + if (area == null) { + event.isCancelled = true; return@l + } + + val hasEntity = event is EntityBlockFormEvent + val player = (event as? EntityBlockFormEvent)?.entity as? Player + + val cancel: Boolean = when (event.newState.type) { + + // prevent ice generation from Frost Walkers enchantment + FROSTED_ICE -> player != null && !area.canBuild(player) + + // prevent snow generation from weather + SNOW -> !hasEntity && world.options.preventWeatherBlockChanges + + else -> false + } + + if (cancel) { + event.isCancelled = true + } + } + + /* + * Prevents mobs (living entities) from spawning if that is disabled for that world in the config. + */ + @field:ListenerMarker(priority = NORMAL) + val onEntitySpawnEvent = RegistratorListener l@{ event -> + val world = parcelProvider.getWorld(event.entity.world) ?: return@l + if (event.entity is Mob && world.options.blockMobSpawning) { + event.isCancelled = true + } else if (world.getParcelAt(event.entity).let { it != null && it.hasBlockVisitors }) { + event.isCancelled = true + } + } + + + + /* + * Prevents minecarts/boats from moving outside a plot + */ + @field:ListenerMarker(priority = NORMAL) + val onVehicleMoveEvent = RegistratorListener l@{ event -> + val (_, area) = getWorldAndArea(event.to.block) ?: return@l + if (area == null) { + event.vehicle.passengers.forEach { + if (it.type == EntityType.PLAYER) { + (it as Player).sendParcelMessage(except = true, message = "Your ride ends here") + } else it.remove() + } + event.vehicle.eject() + event.vehicle.remove() + } else if (area.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 l@{ event -> + val world = parcelProvider.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 (!canBuildOnArea(user, world.getParcelAt(event.entity))) { + event.isCancelled = true + } + } + + @field:ListenerMarker(priority = NORMAL) + val onHangingBreakEvent = RegistratorListener l@{ event -> + val world = parcelProvider.getWorld(event.entity.world) ?: return@l + if (event.cause == HangingBreakEvent.RemoveCause.EXPLOSION && world.options.disableExplosions) { + event.isCancelled = true; return@l + } + + if (world.getParcelAt(event.entity).let { it != null && it.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 l@{ event -> + val world = parcelProvider.getWorld(event.entity.world) ?: return@l + val user = event.remover as? Player ?: return@l + if (!canBuildOnArea(user, world.getParcelAt(event.entity))) { + event.isCancelled = true + } + } + + /* + * Prevents players from placing paintings and item frames + */ + @field:ListenerMarker(priority = NORMAL) + val onHangingPlaceEvent = RegistratorListener l@{ event -> + val world = parcelProvider.getWorld(event.entity.world) ?: return@l + val block = event.block.getRelative(event.blockFace) + if (!canBuildOnArea(event.player, world.getParcelAt(block))) { + event.isCancelled = true + } + } + + /* + * Prevents stuff from growing outside of plots + */ + @field:ListenerMarker(priority = NORMAL) + val onStructureGrowEvent = RegistratorListener l@{ event -> + val (world, area) = getWorldAndArea(event.location.block) ?: return@l + if (area == null) { + event.isCancelled = true; return@l + } + + if (!event.player.hasPermBuildAnywhere && !area.canBuild(event.player)) { + event.isCancelled = true; return@l + } + + event.blocks.removeIf { world.getParcelAt(it.block) !== area } + } + + @field:ListenerMarker(priority = NORMAL) + val onBlockGrowEvent = RegistratorListener l@{ event -> + val (world, area) = getWorldAndArea(event.block) ?: return@l + if (area == null) { + event.isCancelled = true + } + } + + /* + * Prevents dispensers/droppers from dispensing out of parcels + */ + @field:ListenerMarker(priority = NORMAL) + val onBlockDispenseEvent = RegistratorListener l@{ event -> + val block = event.block + if (!block.type.let { it == DISPENSER || it == DROPPER }) return@l + val world = parcelProvider.getWorld(block.world) ?: return@l + val data = block.blockData as Directional + val targetBlock = block.getRelative(data.facing) + if (world.getParcelAt(targetBlock) == null) { + event.isCancelled = true + } + } + + /* + * Track spawned items, making sure they don't leave the parcel. + */ + @field:ListenerMarker(priority = NORMAL) + val onItemSpawnEvent = RegistratorListener l@{ event -> + val (_, area) = getWorldAndArea(event.location.block) ?: return@l + if (area == null) event.isCancelled = true + else entityTracker.track(event.entity, area) + } + + /* + * Prevents endermen and endermite from teleporting outside their parcel + */ + @field:ListenerMarker(priority = NORMAL) + val onEntityTeleportEvent = RegistratorListener l@{ event -> + val (world, area) = getWorldAndArea(event.from.block) ?: return@l + if (area !== world.getParcelAt(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 l@{ event -> + val (_, area) = getWorldAndArea(event.entity.location.block) ?: return@l + if (area == null || (event.entity.shooter as? Player)?.let { !canBuildOnArea(it, area) } == true) { + event.isCancelled = true + } else { + entityTracker.track(event.entity, area) + } + } + + /* + * Prevents entities from dropping items upon death, if configured that way + */ + @field:ListenerMarker(priority = NORMAL) + val onEntityDeathEvent = RegistratorListener l@{ event -> + entityTracker.untrack(event.entity) + val world = parcelProvider.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 l@{ event -> + val world = parcelProvider.getWorld(event.player.world) ?: return@l + if (world.options.gameMode != null && !event.player.hasPermGamemodeBypass) { + event.player.gameMode = world.options.gameMode + } + } + + /** + * Updates owner signs of parcels that get loaded if it is marked outdated + */ + @ListenerMarker(priority = EventPriority.NORMAL) + val onChunkLoadEvent = RegistratorListener l@{ event -> + val world = parcelProvider.getWorld(event.chunk.world) ?: return@l + val parcels = world.blockManager.getParcelsWithOwnerBlockIn(event.chunk) + if (parcels.isEmpty()) return@l + + parcels.forEach { id -> + val parcel = world.getParcelById(id)?.takeIf { it.isOwnerSignOutdated } ?: return@forEach + world.blockManager.updateParcelInfo(parcel.id, parcel.owner) + parcel.isOwnerSignOutdated = false + } + + } + + @ListenerMarker + val onPlayerJoinEvent = RegistratorListener l@{ event -> + storage.updatePlayerName(event.player.uuid, event.player.name) + } + + /** + * Attempts to prevent redstone contraptions from breaking while they are being swapped + * Might remove if it causes lag + */ + @ListenerMarker + val onBlockRedstoneEvent = RegistratorListener l@{ event -> + val (_, area) = getWorldAndArea(event.block) ?: return@l + if (area == null || area.hasBlockVisitors) { + event.newCurrent = event.oldCurrent + } + } + + + private fun getPlayerSpeed(player: Player): Double = + if (player.isFlying) { + player.flySpeed * if (player.isSprinting) 21.6 else 10.92 + } else { + player.walkSpeed * when { + player.isSprinting -> 5.612 + player.isSneaking -> 1.31 + else -> 4.317 + } / 1.5 //? + } / 20.0 + } \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/listener/WorldEditListener.kt b/src/main/kotlin/io/dico/parcels2/listener/WorldEditListener.kt index fc31305..5bab29a 100644 --- a/src/main/kotlin/io/dico/parcels2/listener/WorldEditListener.kt +++ b/src/main/kotlin/io/dico/parcels2/listener/WorldEditListener.kt @@ -1,79 +1,78 @@ -package io.dico.parcels2.listener - -import com.sk89q.worldedit.EditSession.Stage.BEFORE_REORDER -import com.sk89q.worldedit.Vector -import com.sk89q.worldedit.Vector2D -import com.sk89q.worldedit.WorldEdit -import com.sk89q.worldedit.bukkit.WorldEditPlugin -import com.sk89q.worldedit.event.extent.EditSessionEvent -import com.sk89q.worldedit.extent.AbstractDelegateExtent -import com.sk89q.worldedit.extent.Extent -import com.sk89q.worldedit.util.eventbus.EventHandler.Priority.VERY_EARLY -import com.sk89q.worldedit.util.eventbus.Subscribe -import com.sk89q.worldedit.world.biome.BaseBiome -import com.sk89q.worldedit.world.block.BlockStateHolder -import io.dico.parcels2.ParcelWorld -import io.dico.parcels2.ParcelsPlugin -import io.dico.parcels2.canBuildFast -import io.dico.parcels2.util.ext.hasPermBuildAnywhere -import io.dico.parcels2.util.ext.sendParcelMessage -import org.bukkit.entity.Player -import org.bukkit.plugin.Plugin - -class WorldEditListener(val parcels: ParcelsPlugin, val worldEdit: WorldEdit) { - - @Subscribe(priority = VERY_EARLY) - fun onEditSession(event: EditSessionEvent) { - val worldName = event.world?.name ?: return - val world = parcels.parcelProvider.getWorld(worldName) ?: return - if (event.stage == BEFORE_REORDER) return - - val actor = event.actor - if (actor == null || !actor.isPlayer) return - - val player = parcels.server.getPlayer(actor.uniqueId) - if (player.hasPermBuildAnywhere) return - - event.extent = ParcelsExtent(event.extent, world, player) - } - - private class ParcelsExtent(extent: Extent, - val world: ParcelWorld, - val player: Player) : AbstractDelegateExtent(extent) { - private var messageSent = false - - private fun canBuild(x: Int, z: Int): Boolean { - world.getParcelAt(x, z)?.let { parcel -> - if (parcel.canBuildFast(player)) { - return true - } - } - - if (!messageSent) { - messageSent = true - player.sendParcelMessage(except = true, message = "You can't use WorldEdit there") - } - - return false - } - - override fun setBlock(location: Vector, block: BlockStateHolder<*>): Boolean { - return canBuild(location.blockX, location.blockZ) && super.setBlock(location, block) - } - - override fun setBiome(coord: Vector2D, biome: BaseBiome): Boolean { - return canBuild(coord.blockX, coord.blockZ) && super.setBiome(coord, biome) - } - - } - - companion object { - fun register(parcels: ParcelsPlugin, worldEditPlugin: Plugin) { - if (worldEditPlugin !is WorldEditPlugin) return - val worldEdit = worldEditPlugin.worldEdit - val listener = WorldEditListener(parcels, worldEdit) - worldEdit.eventBus.register(listener) - } - } - +package io.dico.parcels2.listener + +import com.sk89q.worldedit.EditSession.Stage.BEFORE_REORDER +import com.sk89q.worldedit.WorldEdit +import com.sk89q.worldedit.bukkit.WorldEditPlugin +import com.sk89q.worldedit.event.extent.EditSessionEvent +import com.sk89q.worldedit.extent.AbstractDelegateExtent +import com.sk89q.worldedit.extent.Extent +import com.sk89q.worldedit.math.BlockVector2 +import com.sk89q.worldedit.math.BlockVector3 +import com.sk89q.worldedit.util.eventbus.EventHandler.Priority.VERY_EARLY +import com.sk89q.worldedit.util.eventbus.Subscribe +import com.sk89q.worldedit.world.biome.BaseBiome +import com.sk89q.worldedit.world.block.BlockStateHolder +import io.dico.parcels2.ParcelWorld +import io.dico.parcels2.ParcelsPlugin +import io.dico.parcels2.canBuildFast +import io.dico.parcels2.util.ext.hasPermBuildAnywhere +import io.dico.parcels2.util.ext.sendParcelMessage +import org.bukkit.entity.Player +import org.bukkit.plugin.Plugin + +class WorldEditListener(val parcels: ParcelsPlugin, val worldEdit: WorldEdit) { + + @Subscribe(priority = VERY_EARLY) + fun onEditSession(event: EditSessionEvent) { + val worldName = event.world?.name ?: return + val world = parcels.parcelProvider.getWorld(worldName) ?: return + if (event.stage == BEFORE_REORDER) return + + val actor = event.actor + if (actor == null || !actor.isPlayer) return + + val player = parcels.server.getPlayer(actor.uniqueId) + if (player.hasPermBuildAnywhere) return + + event.extent = ParcelsExtent(event.extent, world, player) + } + + private class ParcelsExtent(extent: Extent, + val world: ParcelWorld, + val player: Player) : AbstractDelegateExtent(extent) { + private var messageSent = false + + private fun canBuild(x: Int, z: Int): Boolean { + world.getParcelAt(x, z)?.let { parcel -> + if (parcel.canBuildFast(player)) { + return true + } + } + + if (!messageSent) { + messageSent = true + player.sendParcelMessage(except = true, message = "You can't use WorldEdit there") + } + + return false + } + + override fun setBiome(coord: BlockVector2, biome: BaseBiome): Boolean { + return canBuild(coord.blockX, coord.blockZ) && super.setBiome(coord, biome) + } + + override fun > setBlock(location: BlockVector3, block: T): Boolean { + return canBuild(location.blockX, location.blockZ) && super.setBlock(location, block) + } + } + + companion object { + fun register(parcels: ParcelsPlugin, worldEditPlugin: Plugin) { + if (worldEditPlugin !is WorldEditPlugin) return + val worldEdit = worldEditPlugin.worldEdit + val listener = WorldEditListener(parcels, worldEdit) + worldEdit.eventBus.register(listener) + } + } + } \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/util/PluginAware.kt b/src/main/kotlin/io/dico/parcels2/util/PluginAware.kt index de75519..b55f991 100644 --- a/src/main/kotlin/io/dico/parcels2/util/PluginAware.kt +++ b/src/main/kotlin/io/dico/parcels2/util/PluginAware.kt @@ -1,3 +1,5 @@ +@file:Suppress("RedundantLambdaArrow") + package io.dico.parcels2.util import org.bukkit.plugin.Plugin @@ -8,11 +10,10 @@ interface PluginAware { } inline fun PluginAware.schedule(delay: Int = 0, crossinline task: () -> Unit): BukkitTask { - return plugin.server.scheduler.runTaskLater(plugin, { task() }, delay.toLong()) + return plugin.server.scheduler.runTaskLater(plugin, { -> task() }, delay.toLong()) } inline fun PluginAware.scheduleRepeating(interval: Int, delay: Int = 0, crossinline task: () -> Unit): BukkitTask { - return plugin.server.scheduler.runTaskTimer(plugin, { task() }, delay.toLong(), interval.toLong()) + return plugin.server.scheduler.runTaskTimer(plugin, { -> task() }, delay.toLong(), interval.toLong()) } - diff --git a/src/main/kotlin/io/dico/parcels2/util/parallel.kt b/src/main/kotlin/io/dico/parcels2/util/parallel.kt deleted file mode 100644 index a4edc3c..0000000 --- a/src/main/kotlin/io/dico/parcels2/util/parallel.kt +++ /dev/null @@ -1,9 +0,0 @@ -package io.dico.parcels2.util - -fun doParallel() { - - val array = IntArray(1000) - IntRange(0, 1000).chunked() - - -} \ No newline at end of file diff --git a/todo.md b/todo.md index 74a1dca..3de0b4b 100644 --- a/todo.md +++ b/todo.md @@ -88,16 +88,16 @@ After testing on Redstoner - ~~Clear (and swap) entities on /p clear etc~~ -Fix command lag -Chorus fruit can grow outside plots -Vines can grow outside plots -Ghasts, bats, phantoms and magma cubes can be spawned with eggs -ParcelTarget doesn't report a world that wasn't found correctly -Jumping on turtle eggs is considered as interacting with pressure plates +~~Fix command lag~~ +Chorus fruit can grow outside plots -- not detectable? +~~Vines can grow outside plots~~ +~~Ghasts, bats, phantoms and magma cubes can be spawned with eggs~~ +ParcelTarget doesn't report a world that wasn't found correctly -- ?? +~~Jumping on turtle eggs is considered as interacting with pressure plates~~ Setbiome internal error when progress reporting is attached -Unclaim doesn't clear the plot. It probably should. -Players can shoot boats and minecarts. -You can use disabled items by rightclicking air. -Tab complete isn't working correctly. +~~Unclaim doesn't clear the plot. It probably should.~~ removed +Players can shoot boats and minecarts. -- ?? +~~You can use disabled items by rightclicking air.~~ +Tab complete isn't working correctly. -- disabled much of it now ~~Bed use in nether and end might not have to be blocked.~~ -- cgit v1.2.3