From 57e82a94a080a63bad9672e060f916cda9a944f0 Mon Sep 17 00:00:00 2001 From: David Friedman Date: Mon, 23 Aug 2021 19:06:41 -0700 Subject: [PATCH] DW Refactoring (#84) Broke up the massive methods in the MovementListener.kt and allow for better abstraction. --- .../extensions/DeeperWorldExtensions.kt | 31 +-- .../deeperworld/listeners/MovementListener.kt | 262 +----------------- .../BedrockBlockingInvalidTeleportHandler.kt | 38 +++ .../movement/InvalidTeleportHandler.kt | 19 ++ .../deeperworld/movement/MovementHandler.kt | 80 ++++++ .../movement/SectionTeleportPacketAdapter.kt | 80 ++++++ .../deeperworld/movement/TeleportHandler.kt | 6 + .../movement/TransitionTeleportHandler.kt | 120 ++++++++ .../UndoMovementInvalidTeleportHandler.kt | 11 + .../transition/ConfigSectionChecker.kt | 30 ++ .../movement/transition/SectionChecker.kt | 15 + .../movement/transition/SectionTransition.kt | 29 ++ 12 files changed, 433 insertions(+), 288 deletions(-) create mode 100644 src/main/java/com/derongan/minecraft/deeperworld/movement/BedrockBlockingInvalidTeleportHandler.kt create mode 100644 src/main/java/com/derongan/minecraft/deeperworld/movement/InvalidTeleportHandler.kt create mode 100644 src/main/java/com/derongan/minecraft/deeperworld/movement/MovementHandler.kt create mode 100644 src/main/java/com/derongan/minecraft/deeperworld/movement/SectionTeleportPacketAdapter.kt create mode 100644 src/main/java/com/derongan/minecraft/deeperworld/movement/TeleportHandler.kt create mode 100644 src/main/java/com/derongan/minecraft/deeperworld/movement/TransitionTeleportHandler.kt create mode 100644 src/main/java/com/derongan/minecraft/deeperworld/movement/UndoMovementInvalidTeleportHandler.kt create mode 100644 src/main/java/com/derongan/minecraft/deeperworld/movement/transition/ConfigSectionChecker.kt create mode 100644 src/main/java/com/derongan/minecraft/deeperworld/movement/transition/SectionChecker.kt create mode 100644 src/main/java/com/derongan/minecraft/deeperworld/movement/transition/SectionTransition.kt diff --git a/src/main/java/com/derongan/minecraft/deeperworld/extensions/DeeperWorldExtensions.kt b/src/main/java/com/derongan/minecraft/deeperworld/extensions/DeeperWorldExtensions.kt index e7b9773..9a1cedd 100644 --- a/src/main/java/com/derongan/minecraft/deeperworld/extensions/DeeperWorldExtensions.kt +++ b/src/main/java/com/derongan/minecraft/deeperworld/extensions/DeeperWorldExtensions.kt @@ -1,9 +1,6 @@ package com.derongan.minecraft.deeperworld.extensions -import org.bukkit.Location import org.bukkit.entity.Entity -import org.bukkit.entity.LivingEntity -import org.bukkit.entity.Player internal fun Entity.getRootVehicle(): Entity? { var currentVehicle = vehicle @@ -25,30 +22,4 @@ internal fun Entity.getPassengersRecursive(): List { return@let passengerList } -} - -internal fun Player.getLeashedEntities(): List { - // Max leashed entity range is 10 blocks, therefore these parameter values - return getNearbyEntities(20.0, 20.0, 20.0) - .filterIsInstance() - .filter { it.isLeashed && it.leashHolder == this } -} - -internal fun Player.teleportWithSpectatorsAsync(loc: Location, thenRun: (Boolean) -> Unit) { - val nearbySpectators = getNearbyEntities(5.0, 5.0, 5.0) - .filterIsInstance() - .filter { it.spectatorTarget == this } - - nearbySpectators.forEach { - it.spectatorTarget = null - } - - teleportAsync(loc).thenAccept { success -> - if (!success) return@thenAccept - nearbySpectators.forEach { - it.teleport(loc) - it.spectatorTarget = this - } - thenRun(success) - } -} +} \ No newline at end of file diff --git a/src/main/java/com/derongan/minecraft/deeperworld/listeners/MovementListener.kt b/src/main/java/com/derongan/minecraft/deeperworld/listeners/MovementListener.kt index c70338d..26ac0ab 100644 --- a/src/main/java/com/derongan/minecraft/deeperworld/listeners/MovementListener.kt +++ b/src/main/java/com/derongan/minecraft/deeperworld/listeners/MovementListener.kt @@ -1,41 +1,17 @@ package com.derongan.minecraft.deeperworld.listeners -import com.comphenix.protocol.PacketType -import com.comphenix.protocol.events.PacketAdapter -import com.comphenix.protocol.events.PacketContainer -import com.comphenix.protocol.events.PacketEvent import com.derongan.minecraft.deeperworld.Permissions -import com.derongan.minecraft.deeperworld.config.DeeperConfig -import com.derongan.minecraft.deeperworld.datastructures.VehicleTree -import com.derongan.minecraft.deeperworld.deeperWorld -import com.derongan.minecraft.deeperworld.event.PlayerAscendEvent -import com.derongan.minecraft.deeperworld.event.PlayerDescendEvent -import com.derongan.minecraft.deeperworld.extensions.getLeashedEntities import com.derongan.minecraft.deeperworld.extensions.getPassengersRecursive -import com.derongan.minecraft.deeperworld.extensions.getRootVehicle -import com.derongan.minecraft.deeperworld.extensions.teleportWithSpectatorsAsync -import com.derongan.minecraft.deeperworld.protocolManager -import com.derongan.minecraft.deeperworld.services.WorldManager +import com.derongan.minecraft.deeperworld.movement.MovementHandler import com.derongan.minecraft.deeperworld.services.canMoveSections -import com.derongan.minecraft.deeperworld.world.section.* -import com.mineinabyss.idofront.events.call -import com.mineinabyss.idofront.location.up -import com.mineinabyss.idofront.messaging.color -import com.okkero.skedule.schedule import io.papermc.paper.event.entity.EntityMoveEvent -import org.bukkit.GameMode -import org.bukkit.Location -import org.bukkit.Material -import org.bukkit.attribute.Attribute import org.bukkit.block.Block -import org.bukkit.entity.LivingEntity import org.bukkit.entity.Player import org.bukkit.event.EventHandler import org.bukkit.event.EventPriority import org.bukkit.event.Listener import org.bukkit.event.player.PlayerMoveEvent import org.bukkit.event.vehicle.VehicleMoveEvent -import org.bukkit.util.Vector object MovementListener : Listener { val temporaryBedrock = mutableListOf() @@ -43,7 +19,7 @@ object MovementListener : Listener { @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) fun PlayerMoveEvent.move() { if (player.hasPermission(Permissions.CHANGE_SECTION_PERMISSION) && player.canMoveSections) { - onPlayerMoveInternal(player, from, to) + MovementHandler.handleMovement(player, from, to) } } @@ -52,7 +28,7 @@ object MovementListener : Listener { val players = vehicle.getPassengersRecursive().filterIsInstance() players.firstOrNull { it.hasPermission(Permissions.CHANGE_SECTION_PERMISSION) && it.canMoveSections }?.let { - onPlayerMoveInternal(it, from, to) + MovementHandler.handleMovement(it, from, to) } } @@ -61,237 +37,7 @@ object MovementListener : Listener { if (entity.getPassengersRecursive().isEmpty()) return entity.getPassengersRecursive().filterIsInstance() .filter { rider -> rider.hasPermission(Permissions.CHANGE_SECTION_PERMISSION) && rider.canMoveSections } - .forEach { onPlayerMoveInternal(it, from, to) } - } - - private fun onPlayerMoveInternal(player: Player, from: Location, to: Location) { - val current = WorldManager.getSectionFor(player.location) ?: let { - //damage players outside of sections - if (DeeperConfig.data.damageOutsideSections > 0.0 - && player.location.world !in DeeperConfig.data.damageExcludedWorlds - && (player.gameMode == GameMode.SURVIVAL || player.gameMode == GameMode.ADVENTURE) - && player.location.world in (DeeperConfig.data.worlds) - ) { - player.damage(0.01) //give a damage effect - player.health = (player.health - DeeperConfig.data.damageOutsideSections / 10) - .coerceIn(0.0, player.getAttribute(Attribute.GENERIC_MAX_HEALTH)?.value) //ignores armor - player.sendTitle( - "&cYou are not in a managed section".color(), - "&7You will take damage upon moving!".color(), - 0, 20, 10 - ) - } - return - } - - if(!player.location.inSectionOverlap) return - - val changeY = to.y - from.y - if (changeY == 0.0) return - - val inSpectator = player.gameMode == GameMode.SPECTATOR - - fun tpIfAbleTo( - key: SectionKey, - tpFun: (Player, Location, Section, Section) -> Unit - ) { - val toSection = key.section ?: return - val correspondingPos = to.getCorrespondingLocation(current, toSection) ?: return - - if (!to.inSectionTransition) return - if (!toSection.region.contains(correspondingPos.blockX, correspondingPos.blockZ) - || !inSpectator && correspondingPos.block.type.isSolid - ) { - if (current.isOnTopOf(toSection)) { - from.block.type = Material.BEDROCK - val spawnedBedrock = from.block - temporaryBedrock.add(spawnedBedrock) - - // Keep bedrock spawned if there are players within a 1.5 radius (regular jump height). - // If no players are in this radius, destroy the bedrock. - deeperWorld.schedule { - this.repeating(1) - while (spawnedBedrock.location.up(1).getNearbyPlayers(1.5).isNotEmpty()) { - yield() - } - spawnedBedrock.type = Material.AIR - temporaryBedrock.remove(spawnedBedrock) - } - - val oldFallDistance = player.fallDistance - val oldVelocity = player.velocity - - player.teleport(from.up(1)) - - player.fallDistance = oldFallDistance - player.velocity = oldVelocity - } else { - player.teleport(from) - } - player.sendMessage("&cThere is no where for you to teleport".color()) - } else - tpFun(player, to, current, toSection) - } - - when { - changeY > 0.0 -> tpIfAbleTo( - current.aboveKey, - MovementListener::ascend, - ) - changeY < 0.0 -> tpIfAbleTo( - current.belowKey, - MovementListener::descend, - ) - } - } - - private fun descend(player: Player, to: Location, oldSection: Section, newSection: Section) { - PlayerDescendEvent(player, oldSection, newSection).call { - teleportBetweenSections(player, to, oldSection, newSection) - } - } - - private fun ascend(player: Player, to: Location, oldSection: Section, newSection: Section) { - PlayerAscendEvent(player, oldSection, newSection).call { - teleportBetweenSections(player, to, oldSection, newSection) - } - } - - private fun teleportBetweenSections(player: Player, to: Location, oldSection: Section, newSection: Section) { - val newLoc = to.getCorrespondingLocation(oldSection, newSection) ?: return - - val oldLeashedEntities = player.getLeashedEntities() - - // Unleash all the leashed entities before teleporting them, to prevent leads from dropping. - // The leashes are restored after teleportation. - oldLeashedEntities.forEach { - it.setLeashHolder(null) - } - - val rootVehicle = player.getRootVehicle() - if (rootVehicle != null) { - newLoc.yaw = player.location.yaw - newLoc.pitch = player.location.pitch - - // Prevent teleportation of other players in the vehicle-passenger structure. - rootVehicle.getPassengersRecursive().filterIsInstance().forEach { - if (it != player) { - it.vehicle?.removePassenger(it) - } - } - - val oldFallDistance = rootVehicle.fallDistance - val oldVelocity = rootVehicle.velocity - - val vehicleTree = VehicleTree(rootVehicle) - - // Dismount every passenger in the vehicleTree in order to teleport them separately with a delay. - // This avoids the bug of entities not rendering if they are teleported within 1 tick of the player. - vehicleTree.root.applyAll { - it.value.passengers.forEach { passenger -> - it.value.removePassenger(passenger) - } - } - - // Delay the teleportation by 1 tick after passenger removal to avoid occasional - // "Removing ticking entity!" exceptions. - deeperWorld.schedule { - waitFor(1) - - player.teleportWithSpectatorsAsync(newLoc) { - protocolManager.addPacketListener( - SectionTeleportPacketAdapter( - player, - oldLeashedEntities, - oldFallDistance, - oldVelocity, - vehicleTree - ) - ) - } - - } - } else { - val oldFallDistance = player.fallDistance - val oldVelocity = player.velocity - - player.teleportWithSpectatorsAsync(newLoc) { - player.fallDistance = oldFallDistance - player.velocity = oldVelocity - - if (oldLeashedEntities.isNotEmpty()) { - protocolManager.addPacketListener( - SectionTeleportPacketAdapter( - player, - oldLeashedEntities, - oldFallDistance, - oldVelocity - ) - ) - } - } - } + .forEach { MovementHandler.handleMovement(it, from, to) } } } -/** - * This PacketAdapter serves to teleport entities with the player at the correct time - * (after the client sends a POSITION or POSITION_LOOK packet). This circumvents the client side rendering bug - * when entities are teleported with the player in the same tick. - * - * TODO: Remove listener if a player disconnects before sending a POSITION or POSITION_LOOK packet - */ -class SectionTeleportPacketAdapter( - private val player: Player, - private val oldLeashedEntities: List, - private val oldFallDistance: Float, - private val oldVelocity: Vector, - private val vehicleTree: VehicleTree? = null -) : PacketAdapter(deeperWorld, PacketType.Play.Client.POSITION, PacketType.Play.Client.POSITION_LOOK) { - override fun onPacketReceiving(event: PacketEvent) { - if (event.player != player) return - - protocolManager.removePacketListener(this) - - deeperWorld.schedule { - waitFor(1) - - oldLeashedEntities.toSet().forEach { - if (it == player) return@forEach - - it.teleport(player) - it.setLeashHolder(player) - } - - if (vehicleTree != null) { - vehicleTree.root.values().toSet().forEach { - if (it == player) return@forEach - - it.teleport(player) - } - - vehicleTree.root.applyAll { vehicleNode -> - vehicleNode.children.forEach { - vehicleNode.value.addPassenger(it.value) - } - } - - vehicleTree.root.value.fallDistance = oldFallDistance - vehicleTree.root.value.velocity = oldVelocity - - waitFor(DeeperConfig.data.remountPacketDelay.inTicks) - - player.vehicle?.let { vehicle -> - val playerVehicleID = vehicle.entityId - val passengerIDs = vehicle.passengers.map { it.entityId }.toIntArray() - - // Resends a mount packet to clients to prevent potential visual glitches where the client thinks it's dismounted. - protocolManager.sendServerPacket(player, PacketContainer(PacketType.Play.Server.MOUNT).apply { - integers.write(0, playerVehicleID) - integerArrays.write(0, passengerIDs) - }) - } - } - } - } -} diff --git a/src/main/java/com/derongan/minecraft/deeperworld/movement/BedrockBlockingInvalidTeleportHandler.kt b/src/main/java/com/derongan/minecraft/deeperworld/movement/BedrockBlockingInvalidTeleportHandler.kt new file mode 100644 index 0000000..7793711 --- /dev/null +++ b/src/main/java/com/derongan/minecraft/deeperworld/movement/BedrockBlockingInvalidTeleportHandler.kt @@ -0,0 +1,38 @@ +package com.derongan.minecraft.deeperworld.movement + +import com.derongan.minecraft.deeperworld.deeperWorld +import com.derongan.minecraft.deeperworld.listeners.MovementListener +import com.mineinabyss.idofront.location.up +import com.okkero.skedule.schedule +import org.bukkit.Location +import org.bukkit.Material +import org.bukkit.entity.Player + +class BedrockBlockingInvalidTeleportHandler(player: Player, from: Location, to: Location) : + InvalidTeleportHandler(player, from, to) { + override fun handleInvalidTeleport() { + from.block.type = Material.BEDROCK + + val spawnedBedrock = from.block + MovementListener.temporaryBedrock.add(spawnedBedrock) + + // Keep bedrock spawned if there are players within a 1.5 radius (regular jump height). + // If no players are in this radius, destroy the bedrock. + deeperWorld.schedule { + this.repeating(1) + while (spawnedBedrock.location.up(1).getNearbyPlayers(1.5).isNotEmpty()) { + yield() + } + spawnedBedrock.type = Material.AIR + MovementListener.temporaryBedrock.remove(spawnedBedrock) + } + + val oldFallDistance = player.fallDistance + val oldVelocity = player.velocity + + player.teleport(from.up(1)) + + player.fallDistance = oldFallDistance + player.velocity = oldVelocity + } +} \ No newline at end of file diff --git a/src/main/java/com/derongan/minecraft/deeperworld/movement/InvalidTeleportHandler.kt b/src/main/java/com/derongan/minecraft/deeperworld/movement/InvalidTeleportHandler.kt new file mode 100644 index 0000000..715c597 --- /dev/null +++ b/src/main/java/com/derongan/minecraft/deeperworld/movement/InvalidTeleportHandler.kt @@ -0,0 +1,19 @@ +package com.derongan.minecraft.deeperworld.movement + +import com.mineinabyss.idofront.messaging.color +import org.bukkit.Location +import org.bukkit.entity.Player + +abstract class InvalidTeleportHandler(val player: Player, val from: Location, val to: Location) : + TeleportHandler { + final override fun handleTeleport() { + handleInvalidTeleport() + player.sendMessage("&cThere is no where for you to teleport".color()) + } + + override fun isValidTeleport(): Boolean { + return false + } + + abstract fun handleInvalidTeleport(); +} \ No newline at end of file diff --git a/src/main/java/com/derongan/minecraft/deeperworld/movement/MovementHandler.kt b/src/main/java/com/derongan/minecraft/deeperworld/movement/MovementHandler.kt new file mode 100644 index 0000000..e49177a --- /dev/null +++ b/src/main/java/com/derongan/minecraft/deeperworld/movement/MovementHandler.kt @@ -0,0 +1,80 @@ +package com.derongan.minecraft.deeperworld.movement + +import com.derongan.minecraft.deeperworld.config.DeeperConfig +import com.derongan.minecraft.deeperworld.movement.transition.ConfigSectionChecker +import com.derongan.minecraft.deeperworld.movement.transition.SectionTransition +import com.derongan.minecraft.deeperworld.movement.transition.TransitionKind +import com.derongan.minecraft.deeperworld.movement.transition.toEvent +import com.mineinabyss.idofront.events.call +import com.mineinabyss.idofront.messaging.color +import org.bukkit.GameMode +import org.bukkit.Location +import org.bukkit.attribute.Attribute +import org.bukkit.entity.Player + +object MovementHandler { + private val sectionCheckers = listOf(ConfigSectionChecker) + + fun handleMovement(player: Player, from: Location, to: Location) { + + if (sectionCheckers.any { it.inSection(player) }) + sectionCheckers.firstNotNullOfOrNull { it.checkForTransition(player, from, to) }?.let { + with(getTeleportHandler(player, it)) { + if (this.isValidTeleport()) { + it.toEvent(player).call { + this@with.handleTeleport() + } + } else { + this.handleTeleport() + } + } + } else { + applyOutOfBoundsDamage(player) + } + } + + //TODO abstract this away. Should instead do out of bounds action if out of bounds. + private fun applyOutOfBoundsDamage(player: Player) { + if (DeeperConfig.data.damageOutsideSections > 0.0 + && player.location.world !in DeeperConfig.data.damageExcludedWorlds + && (player.gameMode == GameMode.SURVIVAL || player.gameMode == GameMode.ADVENTURE) + && player.location.world in (DeeperConfig.data.worlds) + ) { + player.damage(0.01) //give a damage effect + player.health = (player.health - DeeperConfig.data.damageOutsideSections / 10) + .coerceIn( + 0.0, + player.getAttribute(Attribute.GENERIC_MAX_HEALTH)?.value + ) //ignores armor + player.sendTitle( + "&cYou are not in a managed section".color(), + "&7You will take damage upon moving!".color(), + 0, 20, 10 + ) + } + } + + + private fun getTeleportHandler( + player: Player, + sectionTransition: SectionTransition + ): TeleportHandler { + if (player.gameMode != GameMode.SPECTATOR && sectionTransition.to.block.isSolid) { + return if (sectionTransition.kind == TransitionKind.ASCEND) { + UndoMovementInvalidTeleportHandler( + player, + sectionTransition.from, + sectionTransition.to + ) + } else { + BedrockBlockingInvalidTeleportHandler( + player, + sectionTransition.from, + sectionTransition.to + ) + } + } + + return TransitionTeleportHandler(player, sectionTransition.from, sectionTransition.to); + } +} \ No newline at end of file diff --git a/src/main/java/com/derongan/minecraft/deeperworld/movement/SectionTeleportPacketAdapter.kt b/src/main/java/com/derongan/minecraft/deeperworld/movement/SectionTeleportPacketAdapter.kt new file mode 100644 index 0000000..3e555a1 --- /dev/null +++ b/src/main/java/com/derongan/minecraft/deeperworld/movement/SectionTeleportPacketAdapter.kt @@ -0,0 +1,80 @@ +package com.derongan.minecraft.deeperworld.movement + +import com.comphenix.protocol.PacketType +import com.comphenix.protocol.events.PacketAdapter +import com.comphenix.protocol.events.PacketContainer +import com.comphenix.protocol.events.PacketEvent +import com.derongan.minecraft.deeperworld.config.DeeperConfig +import com.derongan.minecraft.deeperworld.datastructures.VehicleTree +import com.derongan.minecraft.deeperworld.deeperWorld +import com.derongan.minecraft.deeperworld.protocolManager +import com.okkero.skedule.schedule +import org.bukkit.entity.LivingEntity +import org.bukkit.entity.Player +import org.bukkit.util.Vector + +/** + * This PacketAdapter serves to teleport entities with the player at the correct time + * (after the client sends a POSITION or POSITION_LOOK packet). This circumvents the client side rendering bug + * when entities are teleported with the player in the same tick. + * + * TODO: Remove listener if a player disconnects before sending a POSITION or POSITION_LOOK packet + */ +class SectionTeleportPacketAdapter( + private val player: Player, + private val oldLeashedEntities: List, + private val oldFallDistance: Float, + private val oldVelocity: Vector, + private val vehicleTree: VehicleTree? = null +) : PacketAdapter( + deeperWorld, + PacketType.Play.Client.POSITION, + PacketType.Play.Client.POSITION_LOOK +) { + override fun onPacketReceiving(event: PacketEvent) { + if (event.player != player) return + + protocolManager.removePacketListener(this) + + deeperWorld.schedule { + waitFor(1) + + oldLeashedEntities.toSet().forEach { + if (it == player) return@forEach + + it.teleport(player) + it.setLeashHolder(player) + } + + if (vehicleTree != null) { + vehicleTree.root.values().toSet().forEach { + if (it == player) return@forEach + + it.teleport(player) + } + + vehicleTree.root.applyAll { vehicleNode -> + vehicleNode.children.forEach { + vehicleNode.value.addPassenger(it.value) + } + } + + vehicleTree.root.value.fallDistance = oldFallDistance + vehicleTree.root.value.velocity = oldVelocity + + waitFor(DeeperConfig.data.remountPacketDelay.inTicks) + + player.vehicle?.let { vehicle -> + val playerVehicleID = vehicle.entityId + val passengerIDs = vehicle.passengers.map { it.entityId }.toIntArray() + + // Resends a mount packet to clients to prevent potential visual glitches where the client thinks it's dismounted. + protocolManager.sendServerPacket(player, PacketContainer(PacketType.Play.Server.MOUNT).apply { + integers.write(0, playerVehicleID) + integerArrays.write(0, passengerIDs) + }) + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/derongan/minecraft/deeperworld/movement/TeleportHandler.kt b/src/main/java/com/derongan/minecraft/deeperworld/movement/TeleportHandler.kt new file mode 100644 index 0000000..da4a471 --- /dev/null +++ b/src/main/java/com/derongan/minecraft/deeperworld/movement/TeleportHandler.kt @@ -0,0 +1,6 @@ +package com.derongan.minecraft.deeperworld.movement + +interface TeleportHandler { + fun handleTeleport() + fun isValidTeleport() : Boolean +} \ No newline at end of file diff --git a/src/main/java/com/derongan/minecraft/deeperworld/movement/TransitionTeleportHandler.kt b/src/main/java/com/derongan/minecraft/deeperworld/movement/TransitionTeleportHandler.kt new file mode 100644 index 0000000..2ae39d1 --- /dev/null +++ b/src/main/java/com/derongan/minecraft/deeperworld/movement/TransitionTeleportHandler.kt @@ -0,0 +1,120 @@ +package com.derongan.minecraft.deeperworld.movement + +import com.derongan.minecraft.deeperworld.datastructures.VehicleTree +import com.derongan.minecraft.deeperworld.deeperWorld +import com.derongan.minecraft.deeperworld.extensions.getPassengersRecursive +import com.derongan.minecraft.deeperworld.extensions.getRootVehicle +import com.derongan.minecraft.deeperworld.protocolManager +import com.okkero.skedule.schedule +import org.bukkit.Location +import org.bukkit.entity.LivingEntity +import org.bukkit.entity.Player + +class TransitionTeleportHandler(val player: Player, val from: Location, val to: Location) : + TeleportHandler { + + override fun handleTeleport() { + val oldLeashedEntities = player.getLeashedEntities() + + // Unleash all the leashed entities before teleporting them, to prevent leads from dropping. + // The leashes are restored after teleportation. + oldLeashedEntities.forEach { + it.setLeashHolder(null) + } + + val rootVehicle = player.getRootVehicle() + if (rootVehicle != null) { + to.yaw = player.location.yaw + to.pitch = player.location.pitch + + // Prevent teleportation of other players in the vehicle-passenger structure. + rootVehicle.getPassengersRecursive().filterIsInstance().forEach { + if (it != player) { + it.vehicle?.removePassenger(it) + } + } + + val oldFallDistance = rootVehicle.fallDistance + val oldVelocity = rootVehicle.velocity + + val vehicleTree = VehicleTree(rootVehicle) + + // Dismount every passenger in the vehicleTree in order to teleport them separately with a delay. + // This avoids the bug of entities not rendering if they are teleported within 1 tick of the player. + vehicleTree.root.applyAll { + it.value.passengers.forEach { passenger -> + it.value.removePassenger(passenger) + } + } + + // Delay the teleportation by 1 tick after passenger removal to avoid occasional + // "Removing ticking entity!" exceptions. + deeperWorld.schedule { + waitFor(1) + + player.teleportWithSpectatorsAsync(to) { + protocolManager.addPacketListener( + SectionTeleportPacketAdapter( + player, + oldLeashedEntities, + oldFallDistance, + oldVelocity, + vehicleTree + ) + ) + } + + } + } else { + val oldFallDistance = player.fallDistance + val oldVelocity = player.velocity + + player.teleportWithSpectatorsAsync(to) { + player.fallDistance = oldFallDistance + player.velocity = oldVelocity + + if (oldLeashedEntities.isNotEmpty()) { + protocolManager.addPacketListener( + SectionTeleportPacketAdapter( + player, + oldLeashedEntities, + oldFallDistance, + oldVelocity + ) + ) + } + } + } + } + + override fun isValidTeleport(): Boolean { + return true + } + + private fun Player.getLeashedEntities(): List { + // Max leashed entity range is 10 blocks, therefore these parameter values + return getNearbyEntities(20.0, 20.0, 20.0) + .filterIsInstance() + .filter { it.isLeashed && it.leashHolder == this } + } + + private fun Player.teleportWithSpectatorsAsync(loc: Location, thenRun: (Boolean) -> Unit) { + val nearbySpectators = getNearbyEntities(5.0, 5.0, 5.0) + .filterIsInstance() + .filter { it.spectatorTarget == this } + + nearbySpectators.forEach { + it.spectatorTarget = null + } + + teleportAsync(loc).thenAccept { success -> + if (!success) return@thenAccept + nearbySpectators.forEach { + it.teleport(loc) + it.spectatorTarget = this + } + thenRun(success) + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/derongan/minecraft/deeperworld/movement/UndoMovementInvalidTeleportHandler.kt b/src/main/java/com/derongan/minecraft/deeperworld/movement/UndoMovementInvalidTeleportHandler.kt new file mode 100644 index 0000000..673db0b --- /dev/null +++ b/src/main/java/com/derongan/minecraft/deeperworld/movement/UndoMovementInvalidTeleportHandler.kt @@ -0,0 +1,11 @@ +package com.derongan.minecraft.deeperworld.movement + +import org.bukkit.Location +import org.bukkit.entity.Player + +class UndoMovementInvalidTeleportHandler(player: Player, from: Location, to: Location) : + InvalidTeleportHandler(player, from, to) { + override fun handleInvalidTeleport() { + player.teleport(from) + } +} \ No newline at end of file diff --git a/src/main/java/com/derongan/minecraft/deeperworld/movement/transition/ConfigSectionChecker.kt b/src/main/java/com/derongan/minecraft/deeperworld/movement/transition/ConfigSectionChecker.kt new file mode 100644 index 0000000..80b50c5 --- /dev/null +++ b/src/main/java/com/derongan/minecraft/deeperworld/movement/transition/ConfigSectionChecker.kt @@ -0,0 +1,30 @@ +package com.derongan.minecraft.deeperworld.movement.transition + +import com.derongan.minecraft.deeperworld.world.section.correspondingLocation +import com.derongan.minecraft.deeperworld.world.section.inSectionTransition +import com.derongan.minecraft.deeperworld.world.section.section +import org.bukkit.Location +import org.bukkit.entity.Player + +object ConfigSectionChecker : SectionChecker { + override fun inSection(player: Player): Boolean { + return player.location.section != null + } + + override fun checkForTransition( + player: Player, + from: Location, + to: Location + ): SectionTransition? { + return to.takeIf { to.inSectionTransition }?.correspondingLocation?.let { + SectionTransition( + from, + it, + from.section!!, // If inSectionTransition, must be non-null + it.section!!, + if (to.y < from.y) TransitionKind.DESCEND else TransitionKind.ASCEND + ) + } + + } +} \ No newline at end of file diff --git a/src/main/java/com/derongan/minecraft/deeperworld/movement/transition/SectionChecker.kt b/src/main/java/com/derongan/minecraft/deeperworld/movement/transition/SectionChecker.kt new file mode 100644 index 0000000..75c6b5b --- /dev/null +++ b/src/main/java/com/derongan/minecraft/deeperworld/movement/transition/SectionChecker.kt @@ -0,0 +1,15 @@ +package com.derongan.minecraft.deeperworld.movement.transition + +import org.bukkit.Location +import org.bukkit.entity.Player + +interface SectionChecker { + + fun inSection(player: Player) : Boolean + + fun checkForTransition( + player: Player, + from: Location, + to: Location + ): SectionTransition? +} \ No newline at end of file diff --git a/src/main/java/com/derongan/minecraft/deeperworld/movement/transition/SectionTransition.kt b/src/main/java/com/derongan/minecraft/deeperworld/movement/transition/SectionTransition.kt new file mode 100644 index 0000000..e06a1a6 --- /dev/null +++ b/src/main/java/com/derongan/minecraft/deeperworld/movement/transition/SectionTransition.kt @@ -0,0 +1,29 @@ +package com.derongan.minecraft.deeperworld.movement.transition + +import com.derongan.minecraft.deeperworld.event.PlayerAscendEvent +import com.derongan.minecraft.deeperworld.event.PlayerChangeSectionEvent +import com.derongan.minecraft.deeperworld.event.PlayerDescendEvent +import com.derongan.minecraft.deeperworld.world.section.Section +import org.bukkit.Location +import org.bukkit.entity.Player + +data class SectionTransition( + val from: Location, + val to: Location, + val fromSection: Section, + val toSection: Section, + val kind: TransitionKind +) + +enum class TransitionKind { + ASCEND, + DESCEND +} + +internal fun SectionTransition.toEvent(player: Player): PlayerChangeSectionEvent { + return if (this.kind == TransitionKind.ASCEND) { + PlayerAscendEvent(player, fromSection, toSection) + } else { + PlayerDescendEvent(player, fromSection, toSection) + } +} \ No newline at end of file