Skip to content

Commit

Permalink
DW Refactoring (#84)
Browse files Browse the repository at this point in the history
Broke up the massive methods in the MovementListener.kt and allow for better abstraction.
  • Loading branch information
Derongan authored Aug 24, 2021
1 parent e51dd1f commit 57e82a9
Show file tree
Hide file tree
Showing 12 changed files with 433 additions and 288 deletions.
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -25,30 +22,4 @@ internal fun Entity.getPassengersRecursive(): List<Entity> {

return@let passengerList
}
}

internal fun Player.getLeashedEntities(): List<LivingEntity> {
// Max leashed entity range is 10 blocks, therefore these parameter values
return getNearbyEntities(20.0, 20.0, 20.0)
.filterIsInstance<LivingEntity>()
.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<Player>()
.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)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,49 +1,25 @@
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<Block>()

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

Expand All @@ -52,7 +28,7 @@ object MovementListener : Listener {
val players = vehicle.getPassengersRecursive().filterIsInstance<Player>()

players.firstOrNull { it.hasPermission(Permissions.CHANGE_SECTION_PERMISSION) && it.canMoveSections }?.let {
onPlayerMoveInternal(it, from, to)
MovementHandler.handleMovement(it, from, to)
}
}

Expand All @@ -61,237 +37,7 @@ object MovementListener : Listener {
if (entity.getPassengersRecursive().isEmpty()) return
entity.getPassengersRecursive().filterIsInstance<Player>()
.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<Player>().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<LivingEntity>,
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)
})
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Loading

0 comments on commit 57e82a9

Please sign in to comment.