Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package taboolib.module.navigation

import org.bukkit.block.Block
import org.bukkit.block.data.Waterlogged
import taboolib.module.nms.MinecraftVersion

/**
* Navigation
Expand All @@ -26,7 +28,13 @@ enum class Fluid {
"WATER" -> WATER
"STATIONARY_WATER" -> WATER
"FLOWING_WATER" -> FLOWING_WATER
else -> EMPTY
else -> {
if (MinecraftVersion.isHigherOrEqual(MinecraftVersion.V1_13)) {
(blockData as? Waterlogged)?.takeIf { it.isWaterlogged }?.let { WATER } ?: EMPTY
} else {
EMPTY
}
}
}

fun String.getFluid() = when (this) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import org.bukkit.Location
import org.bukkit.World
import org.bukkit.block.BlockFace
import org.bukkit.util.Vector
import taboolib.module.navigation.Fluid.Companion.getFluid
import taboolib.platform.util.callRegion
import java.util.*

Expand Down Expand Up @@ -70,8 +71,9 @@ open class NodeEntity(
}

fun getWalkTargetValue(pos: Vector): Double {
return location.callRegion {
this.getWalkTargetValue(pos, location.world!!)
val world = location.world!!
return pos.toLocation(world).callRegion {
this.getWalkTargetValue(pos, world)
}
}

Expand All @@ -98,7 +100,7 @@ open class NodeEntity(

open fun isInWater(): Boolean {
return location.callRegion {
location.block.isLiquid
location.block.getFluid().isWater()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ open class NodeReader(val entity: NodeEntity) {
}

fun getNode(x: Int, y: Int, z: Int): Node {
return nodes.computeIfAbsent(Node.createHash(x, y, z)) { Node(x, y, z) }
return getOrCreateNavigationNode(nodes, x, y, z)
}

fun getCachedBlockType(x: Int, y: Int, z: Int): PathType {
Expand All @@ -58,37 +58,41 @@ open class NodeReader(val entity: NodeEntity) {

private fun getStartAtRegion(): Node {
val position = Vector(0, 0, 0)
var y = entity.location.blockY
val minHeight = world.navigationMinHeight()
val maxHeight = world.maxHeight
var y = entity.location.blockY.coerceIn(minHeight, maxHeight - 1)
var block = world.getBlockAt(position.set(entity.location.blockX, y, entity.location.blockZ))
var blockposition: Vector
if (!entity.canStandOnFluid(block.getFluid())) {
if (entity.canFloat && entity.isInWater()) {
while (true) {
if (!block.isLiquid) {
--y
break
}
while (block.getFluid().isWater() && y < maxHeight - 1) {
++y
block = world.getBlockAt(position.set(entity.location.blockX, y, entity.location.blockZ))
}
if (!block.getFluid().isWater()) {
--y
}
} else if (entity.isOnGround()) {
y = NumberConversions.floor(entity.location.y + 0.5)
} else {
blockposition = entity.location.toVector()
while (!blockposition.toBlock(block.world).type.isSolid && blockposition.y > 0) {
blockposition = entity.location.toVector().apply {
setY(blockY.coerceIn(minHeight, maxHeight - 1).toDouble())
}
var ground = blockposition.toBlock(block.world)
while (!ground.type.isSolid && blockposition.blockY > minHeight) {
blockposition = blockposition.down()
ground = blockposition.toBlock(block.world)
}
y = blockposition.up().blockY
y = if (ground.type.isSolid) blockposition.up().blockY.coerceAtMost(maxHeight - 1) else minHeight
}
} else {
while (true) {
if (!entity.canStandOnFluid(block.getFluid())) {
--y
break
}
while (entity.canStandOnFluid(block.getFluid()) && y < maxHeight - 1) {
++y
block = world.getBlockAt(position.set(entity.location.blockX, y, entity.location.blockZ))
}
if (!entity.canStandOnFluid(block.getFluid())) {
--y
}
}
blockposition = entity.location.toVector()
val blockPathType = getCachedBlockType(blockposition.blockX, y, blockposition.blockZ)
Expand Down Expand Up @@ -164,7 +168,7 @@ open class NodeReader(val entity: NodeEntity) {
if (getCachedBlockType(x, h - 1, z) != PathType.WATER) {
return node
}
while (h > 0) {
while (h > world.navigationMinHeight()) {
--h
pathTypes = getCachedBlockType(x, h, z)
if (pathTypes != PathType.WATER) {
Expand All @@ -181,7 +185,7 @@ open class NodeReader(val entity: NodeEntity) {
var air = h
while (pathTypes == PathType.OPEN) {
--air
if (air < 0) {
if (air < world.navigationMinHeight()) {
val node1 = getNode(x, air, z)
node1.type = PathType.BLOCKED
node1.costMalus = -1.0f
Expand Down Expand Up @@ -318,3 +322,20 @@ open class NodeReader(val entity: NodeEntity) {
return neighbors
}
}

@JvmSynthetic
internal fun getOrCreateNavigationNode(nodes: MutableMap<Int, Node>, x: Int, y: Int, z: Int): Node {
val initialKey = Node.createHash(x, y, z)
var key = initialKey
while (true) {
val existing = nodes[key]
if (existing == null) {
return Node(x, y, z).also { nodes[key] = it }
}
if (existing.x == x && existing.y == y && existing.z == z) {
return existing
}
key = key * 31 + 1
check(key != initialKey) { "Unable to resolve navigation node hash collision" }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import org.bukkit.Location
import org.bukkit.World
import org.bukkit.util.Vector
import taboolib.platform.util.callRegion
import kotlin.math.abs
import kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.sqrt
import kotlin.math.max
import kotlin.math.min

/**
* 路径平滑后处理(String Pulling / 拉绳法)
Expand All @@ -19,14 +21,14 @@ import kotlin.math.sqrt
*/
object PathSmoothing {

/** 视线检测采样步长(格) */
private const val SAMPLE_STEP = 0.5

/**
* 对 A* 路径进行平滑处理
* 返回平滑后的世界坐标点列表(方块中心)
*/
fun smooth(path: Path, entity: NodeEntity): List<Vector> {
if (path.nodes.isEmpty()) {
return emptyList()
}
return entity.location.callRegion {
smoothAtRegion(path, entity)
}
Expand Down Expand Up @@ -68,18 +70,37 @@ object PathSmoothing {
private fun hasLineOfSightAtRegion(from: Vector, to: Vector, entity: NodeEntity, world: World): Boolean {
val dx = to.x - from.x
val dz = to.z - from.z
val dist = sqrt(dx * dx + dz * dz)
if (dist < 1e-6) return true
val steps = ceil(dist / SAMPLE_STEP).toInt()
for (i in 0..steps) {
val t = i.toDouble() / steps
val x = from.x + dx * t
val z = from.z + dz * t
if (!isStandableAtRegion(x, from.y, z, entity, world)) return false
if (abs(dx) < 1.0E-6 && abs(dz) < 1.0E-6) return true
val boundaries = sortedSetOf(0.0, 1.0)
addSweepBoundaries(from.x - entity.width / 2.0, dx, boundaries)
addSweepBoundaries(from.x + entity.width / 2.0, dx, boundaries)
addSweepBoundaries(from.z - entity.depth / 2.0, dz, boundaries)
addSweepBoundaries(from.z + entity.depth / 2.0, dz, boundaries)
val samples = boundaries.toList()
for (index in samples.indices) {
val t = samples[index]
if (!isStandableAtRegion(from.x + dx * t, from.y, from.z + dz * t, entity, world)) return false
if (index + 1 < samples.size) {
val midpoint = (t + samples[index + 1]) / 2.0
if (!isStandableAtRegion(from.x + dx * midpoint, from.y, from.z + dz * midpoint, entity, world)) return false
}
}
return true
}

private fun addSweepBoundaries(start: Double, delta: Double, boundaries: MutableSet<Double>) {
if (abs(delta) < 1.0E-6) return
val end = start + delta
val first = floor(min(start, end)).toInt()
val last = ceil(max(start, end)).toInt()
for (boundary in first..last) {
val t = (boundary - start) / delta
if (t > 0.0 && t < 1.0) {
boundaries += t
}
}
}

/**
* 检查某个世界坐标位置是否可供实体站立
* - 脚下有支撑(非空气)
Expand All @@ -95,26 +116,48 @@ object PathSmoothing {
val halfWidth = entity.width / 2.0
val halfDepth = entity.depth / 2.0
val minBx = floor(x - halfWidth).toInt()
val maxBx = floor(x + halfWidth).toInt()
val maxBx = ceil(x + halfWidth).toInt() - 1
val minBz = floor(z - halfDepth).toInt()
val maxBz = floor(z + halfDepth).toInt()
val maxBz = ceil(z + halfDepth).toInt() - 1
val by = floor(y).toInt()
val heightBlocks = ceil(entity.height).toInt()
if (!isWithinNavigationHeight(by, world.navigationMinHeight(), world.maxHeight)
|| !isWithinNavigationHeight(by + heightBlocks - 1, world.navigationMinHeight(), world.maxHeight)) {
return false
}
val typeFactory = PathTypeFactory(entity)
for (bx in minBx..maxBx) {
for (bz in minBz..maxBz) {
// 脚下方块必须有支撑
val below = world.getBlockAtIfLoaded(Vector(bx, by - 1, bz)) ?: return false
if (below.type.isAirLegacy()) return false
// 实体身体占据的空间必须可通行
val supportY = below.y + NMS.instance.getBlockHeight(below)
if (abs(supportY - y) > 1.0E-3) {
return false
}
val feetType = typeFactory.getTypeAsWalkable(world, Vector(bx, by, bz))
if (!isSafeSmoothingFeetType(feetType, entity.getPathfindingMalus(feetType))) {
return false
}
for (oy in 0 until heightBlocks) {
val block = world.getBlockAtIfLoaded(Vector(bx, by + oy, bz)) ?: return false
if (block.type.isSolid) return false
val bodyType = typeFactory.evaluateType(PathTypeFactory.getRawType(world, Vector(bx, by + oy, bz)))
if (!isSafeSmoothingBodyType(entity.getPathfindingMalus(bodyType))) {
return false
}
}
}
}
return true
}

@JvmSynthetic
internal fun isSafeSmoothingFeetType(pathType: PathType, malus: Float): Boolean {
return pathType != PathType.OPEN && malus == 0.0f
}

@JvmSynthetic
internal fun isSafeSmoothingBodyType(malus: Float): Boolean {
return malus == 0.0f
}

private fun nodeCenter(node: Node): Vector {
return Vector(node.x + 0.5, node.y.toDouble(), node.z + 0.5)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ object RandomPositionGenerator {
}
}
var result = Vector((x + nodeEntity.x).toInt(), (y + nodeEntity.y).toInt(), (z + nodeEntity.z).toInt())
if (result.y < 0) {
if (!isWithinNavigationHeight(result.blockY, world.navigationMinHeight(), world.maxHeight)) {
return@repeat
}
if (hasRestriction && !nodeEntity.isWithinRestriction(result)) {
Expand All @@ -146,7 +146,7 @@ object RandomPositionGenerator {
return@repeat
}
if (aboveLand) {
result = moveUp(result, 0, 256) {
result = moveUp(result, 0, world.maxHeight) {
if (Folia.isFolia) {
world.getBlockAtIfLoaded(it)?.type?.isSolid == true
} else {
Expand All @@ -159,7 +159,7 @@ object RandomPositionGenerator {
} else {
world.getBlockAt(result.toLocation(world)).type
}
if (onWater || blockType?.isWater() == true) {
if (acceptsNavigationSurface(onWater, blockType?.isWater() == true)) {
val type = navigation.getTypeAsWalkable(world, result)
if (nodeEntity.getPathfindingMalus(type) == 0.0f) {
val walk = nodeEntity.getWalkTargetValue(result)
Expand Down Expand Up @@ -200,6 +200,11 @@ object RandomPositionGenerator {
}
}

@JvmSynthetic
internal fun acceptsNavigationSurface(allowWater: Boolean, isWater: Boolean): Boolean {
return allowWater || !isWater
}

private fun randomDelta(random: Random, restrictX: Int, restrictY: Int, vector: Vector?): Vector? {
return if (vector != null) {
val size = atan2(vector.z, vector.x) - PI_OF_TWO
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ fun World.getBlockAtIfLoaded(position: Vector): Block? {
val x = position.blockX
val y = position.blockY
val z = position.blockZ
if (!isWithinNavigationHeight(y, navigationMinHeight(), maxHeight)) {
return null
}
return callRegion(x, y, z) {
if (ChunkAccess.instance.isChunkLoaded(this, x shr 4, z shr 4)) {
getBlockAt(x, y, z)
Expand All @@ -30,6 +33,16 @@ fun World.getBlockAtIfLoaded(position: Vector): Block? {
}
}

@JvmSynthetic
internal fun World.navigationMinHeight(): Int {
return if (MinecraftVersion.isHigherOrEqual(MinecraftVersion.V1_17)) minHeight else 0
}

@JvmSynthetic
internal fun isWithinNavigationHeight(y: Int, minHeight: Int, maxHeight: Int): Boolean {
return y >= minHeight && y < maxHeight
}

fun Vector.toBlock(world: World) = toLocation(world).block

fun Vector.down() = Vector(x, y - 1, z)
Expand Down Expand Up @@ -115,7 +128,7 @@ fun Material.isAirLegacy(): Boolean {
}

fun Material.isWater(): Boolean {
return name.contains("WATER")
return name == "WATER" || name == "STATIONARY_WATER" || name == "FLOWING_WATER"
}

fun Block.isTrapdoorOpen(): Boolean {
Expand Down
Loading
Loading