diff --git a/src/main/kotlin/com/mineinabyss/deeperworld/listeners/MovementListener.kt b/src/main/kotlin/com/mineinabyss/deeperworld/listeners/MovementListener.kt index 480aa8a..79260d6 100644 --- a/src/main/kotlin/com/mineinabyss/deeperworld/listeners/MovementListener.kt +++ b/src/main/kotlin/com/mineinabyss/deeperworld/listeners/MovementListener.kt @@ -18,10 +18,8 @@ object MovementListener : Listener { @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) fun PlayerMoveEvent.move() { - if (!hasExplicitlyChangedPosition()) return - if (player.hasPermission(Permissions.ADMIN_PERMISSION) && player.canMoveSections) { - MovementHandler.handleMovement(player, from, to) - } + if (!hasExplicitlyChangedBlock() || !player.hasPermission(Permissions.ADMIN_PERMISSION) || !player.canMoveSections) return + MovementHandler.handleMovement(player, from, to) } @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) diff --git a/src/main/kotlin/com/mineinabyss/deeperworld/listeners/PlayerListener.kt b/src/main/kotlin/com/mineinabyss/deeperworld/listeners/PlayerListener.kt index 48619cb..a5d3235 100644 --- a/src/main/kotlin/com/mineinabyss/deeperworld/listeners/PlayerListener.kt +++ b/src/main/kotlin/com/mineinabyss/deeperworld/listeners/PlayerListener.kt @@ -19,15 +19,10 @@ object PlayerListener : Listener { fun PlayerTeleportEvent.onPlayerTeleport() { if (player.gameMode == CREATIVE || !player.canMoveSections) return if (cause != ENDER_PEARL && cause != CHORUS_FRUIT) return + if (to.section != null && to.section == player.location.section && !to.inSectionTransition) return - if ( - to.section == null || - to.section != player.location.section || - to.inSectionTransition - ) { - player.error("Teleportation is disabled between Layers and Sections.") - isCancelled = true - } + player.error("Teleportation is disabled between Layers and Sections.") + isCancelled = true } @EventHandler diff --git a/src/main/kotlin/com/mineinabyss/deeperworld/movement/MovementHandler.kt b/src/main/kotlin/com/mineinabyss/deeperworld/movement/MovementHandler.kt index 5282897..11ad813 100644 --- a/src/main/kotlin/com/mineinabyss/deeperworld/movement/MovementHandler.kt +++ b/src/main/kotlin/com/mineinabyss/deeperworld/movement/MovementHandler.kt @@ -13,28 +13,23 @@ import org.bukkit.GameMode import org.bukkit.Location import org.bukkit.attribute.Attribute import org.bukkit.entity.Player +import java.util.* import kotlin.time.Duration.Companion.seconds import kotlin.time.toJavaDuration object MovementHandler { private val sectionCheckers = listOf(ConfigSectionChecker) + val teleportCooldown = mutableSetOf() 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() - } + if (this.isValidTeleport()) it.toEvent(player).call { this@with.handleTeleport() } + else this.handleTeleport() } - } ?: return - } else { - player.applyOutOfBoundsDamage() - } + } + } else player.applyOutOfBoundsDamage() } //TODO abstract this away. Should instead do out of bounds action if out of bounds. @@ -66,11 +61,14 @@ object MovementHandler { player: Player, sectionTransition: SectionTransition ): TeleportHandler { - if (sectionTransition.teleportUnnecessary) return object : TeleportHandler { + if (sectionTransition.teleportUnnecessary || player.uniqueId in teleportCooldown) return object : TeleportHandler { override fun handleTeleport() {} override fun isValidTeleport() = true } + + teleportCooldown += player.uniqueId + if (player.gameMode != GameMode.SPECTATOR && sectionTransition.to.block.isSolid) { return if (sectionTransition.kind == TransitionKind.ASCEND) { UndoMovementInvalidTeleportHandler( diff --git a/src/main/kotlin/com/mineinabyss/deeperworld/movement/SectionTeleportPacketAdapter.kt b/src/main/kotlin/com/mineinabyss/deeperworld/movement/SectionTeleportPacketAdapter.kt deleted file mode 100644 index 8fe4146..0000000 --- a/src/main/kotlin/com/mineinabyss/deeperworld/movement/SectionTeleportPacketAdapter.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.mineinabyss.deeperworld.movement - -import com.github.shynixn.mccoroutine.bukkit.launch -import com.mineinabyss.deeperworld.datastructures.VehicleTree -import com.mineinabyss.deeperworld.deeperWorld -import com.mineinabyss.idofront.nms.PacketListener -import com.mineinabyss.idofront.nms.aliases.toNMS -import com.mineinabyss.idofront.time.ticks -import kotlinx.coroutines.delay -import net.kyori.adventure.key.Key -import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket -import net.minecraft.network.protocol.game.ClientboundPlayerLookAtPacket -import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket -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 -) { - - private val PACKET_KEY = Key.key("deeperworld", "section_teleport_handler_${player.name}") - - fun addPacketListener() { - PacketListener.interceptClientbound(deeperWorld.plugin, PACKET_KEY.value()) { packet, player: Player? -> - if (this.player.uniqueId != player?.uniqueId) return@interceptClientbound packet - if (packet !is ClientboundPlayerLookAtPacket && packet !is ClientboundMoveEntityPacket) return@interceptClientbound packet - - PacketListener.unregisterListener(PACKET_KEY) - - deeperWorld.plugin.launch { - delay(1.ticks) - - oldLeashedEntities.asSequence().forEach { - if (it == player) return@forEach - - it.teleport(player) - it.setLeashHolder(player) - } - - if (vehicleTree != null) { - vehicleTree.root.values().asSequence().forEach { - if (it != player) 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 - - delay(deeperWorld.config.remountPacketDelay) - - // Resends a mount packet to clients to prevent potential visual glitches where the client thinks it's dismounted. - player.vehicle?.toNMS()?.let { vehicle -> - player.toNMS().connection.send(ClientboundSetPassengersPacket(vehicle)) - } - } - } - - return@interceptClientbound packet - } - } -} diff --git a/src/main/kotlin/com/mineinabyss/deeperworld/movement/TransitionTeleportHandler.kt b/src/main/kotlin/com/mineinabyss/deeperworld/movement/TransitionTeleportHandler.kt index 48b0b76..82ae886 100644 --- a/src/main/kotlin/com/mineinabyss/deeperworld/movement/TransitionTeleportHandler.kt +++ b/src/main/kotlin/com/mineinabyss/deeperworld/movement/TransitionTeleportHandler.kt @@ -1,86 +1,41 @@ package com.mineinabyss.deeperworld.movement import com.github.shynixn.mccoroutine.bukkit.launch -import com.mineinabyss.deeperworld.datastructures.VehicleTree import com.mineinabyss.deeperworld.deeperWorld -import com.mineinabyss.deeperworld.extensions.getPassengersRecursive -import com.mineinabyss.deeperworld.extensions.getRootVehicle import com.mineinabyss.idofront.time.ticks +import io.papermc.paper.entity.TeleportFlag import kotlinx.coroutines.delay +import kotlinx.coroutines.future.asDeferred import org.bukkit.Location import org.bukkit.entity.LivingEntity import org.bukkit.entity.Player +import org.bukkit.event.player.PlayerTeleportEvent -class TransitionTeleportHandler(val player: Player, val from: Location, val to: Location) : - TeleportHandler { +class TransitionTeleportHandler(val player: Player, val from: Location, val to: Location) : TeleportHandler { override fun handleTeleport() { val oldLeashedEntities = player.getLeashedEntities() + val spectators = player.world.players.filter { it.spectatorTarget?.uniqueId == player.uniqueId } + val teleportFlags: Array = listOf(TeleportFlag.Relative.YAW, TeleportFlag.Relative.PITCH, TeleportFlag.Relative.X, TeleportFlag.Relative.Y, TeleportFlag.Relative.Z, TeleportFlag.EntityState.RETAIN_PASSENGERS, TeleportFlag.EntityState.RETAIN_VEHICLE).toTypedArray() // 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) - } + for (it in oldLeashedEntities) it.setLeashHolder(null) + for (it in spectators) it.spectatorTarget = null + + deeperWorld.plugin.launch { + player.teleportAsync(to, PlayerTeleportEvent.TeleportCause.PLUGIN, *teleportFlags).asDeferred().await() + oldLeashedEntities.forEach { leashEntity -> + leashEntity.teleportAsync(player.location, PlayerTeleportEvent.TeleportCause.PLUGIN, *teleportFlags).asDeferred().await() + leashEntity.setLeashHolder(player) } - - // Delay the teleportation by 1 tick after passenger removal to avoid occasional - // "Removing ticking entity!" exceptions. - deeperWorld.plugin.launch { - delay(1.ticks) - - player.teleportWithSpectatorsAsync(to) { - SectionTeleportPacketAdapter( - player, - oldLeashedEntities, - oldFallDistance, - oldVelocity, - vehicleTree - ).addPacketListener() - } - + spectators.forEach { spectator -> + spectator.teleportAsync(player.location, PlayerTeleportEvent.TeleportCause.PLUGIN, *teleportFlags).asDeferred().await() + spectator.spectatorTarget = player } - } else { - val oldFallDistance = player.fallDistance - val oldVelocity = player.velocity - player.teleportWithSpectatorsAsync(to) { - player.fallDistance = oldFallDistance - player.velocity = oldVelocity - - if (oldLeashedEntities.isNotEmpty()) { - SectionTeleportPacketAdapter( - player, - oldLeashedEntities, - oldFallDistance, - oldVelocity - ).addPacketListener() - } - } + delay(10.ticks) + MovementHandler.teleportCooldown -= player.uniqueId } } @@ -91,26 +46,6 @@ class TransitionTeleportHandler(val player: Player, val from: Location, val to: private fun Player.getLeashedEntities(): List { // Max leashed entity range is 10 blocks, therefore these parameter values return location.getNearbyEntitiesByType(LivingEntity::class.java, 20.0) - .filter { it.isLeashed && it.leashHolder == this } - } - - private fun Player.teleportWithSpectatorsAsync(loc: Location, thenRun: (Boolean) -> Unit) { - val nearbySpectators = - location.getNearbyEntitiesByType(Player::class.java, 5.0) - .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) - } + .filter { it.isLeashed && it.leashHolder.uniqueId == this.uniqueId } } - }