diff --git a/module/bukkit/bukkit-navigation/build.gradle.kts b/module/bukkit/bukkit-navigation/build.gradle.kts index 1ac0a1e0b..ad06ce05a 100644 --- a/module/bukkit/bukkit-navigation/build.gradle.kts +++ b/module/bukkit/bukkit-navigation/build.gradle.kts @@ -1,5 +1,7 @@ dependencies { compileOnly(project(":common")) + compileOnly(project(":platform:platform-bukkit")) + compileOnly(project(":platform:platform-bukkit-impl")) compileOnly(project(":module:bukkit-nms")) // 服务端 compileOnly("ink.ptms.core:v12101:12101-minimize:mapped") @@ -9,4 +11,4 @@ dependencies { compileOnly("ink.ptms.core:v10900:10900") compileOnly("ink.ptms:nms-all:1.0.0") compileOnly("ink.ptms.core:v260100:260100-minimize") -} \ No newline at end of file +} diff --git a/module/bukkit/bukkit-navigation/src/main/kotlin/taboolib/module/navigation/NodeEntity.kt b/module/bukkit/bukkit-navigation/src/main/kotlin/taboolib/module/navigation/NodeEntity.kt index fb993b87c..64a022736 100644 --- a/module/bukkit/bukkit-navigation/src/main/kotlin/taboolib/module/navigation/NodeEntity.kt +++ b/module/bukkit/bukkit-navigation/src/main/kotlin/taboolib/module/navigation/NodeEntity.kt @@ -5,6 +5,7 @@ import org.bukkit.Location import org.bukkit.World import org.bukkit.block.BlockFace import org.bukkit.util.Vector +import taboolib.platform.util.callRegion import java.util.* /** @@ -69,7 +70,9 @@ open class NodeEntity( } fun getWalkTargetValue(pos: Vector): Double { - return this.getWalkTargetValue(pos, location.world!!) + return location.callRegion { + this.getWalkTargetValue(pos, location.world!!) + } } /** @@ -94,14 +97,18 @@ open class NodeEntity( } open fun isInWater(): Boolean { - return location.block.isLiquid + return location.callRegion { + location.block.isLiquid + } } open fun isOnGround(): Boolean { - return location.block.getRelative(BlockFace.DOWN).type.isSolid + return location.callRegion { + location.block.getRelative(BlockFace.DOWN).type.isSolid + } } open fun getAirSupply(): Int { return 3 } -} \ No newline at end of file +} diff --git a/module/bukkit/bukkit-navigation/src/main/kotlin/taboolib/module/navigation/NodeReader.kt b/module/bukkit/bukkit-navigation/src/main/kotlin/taboolib/module/navigation/NodeReader.kt index 9fc7d7043..22b44774a 100644 --- a/module/bukkit/bukkit-navigation/src/main/kotlin/taboolib/module/navigation/NodeReader.kt +++ b/module/bukkit/bukkit-navigation/src/main/kotlin/taboolib/module/navigation/NodeReader.kt @@ -3,6 +3,7 @@ package taboolib.module.navigation import org.bukkit.util.NumberConversions import org.bukkit.util.Vector import taboolib.module.navigation.Fluid.Companion.getFluid +import taboolib.platform.util.callRegion import kotlin.math.abs /** @@ -50,6 +51,12 @@ open class NodeReader(val entity: NodeEntity) { * 获取起点 */ fun getStart(): Node { + return entity.location.callRegion { + getStartAtRegion() + } + } + + private fun getStartAtRegion(): Node { val position = Vector(0, 0, 0) var y = entity.location.blockY var block = world.getBlockAt(position.set(entity.location.blockX, y, entity.location.blockZ)) @@ -109,9 +116,11 @@ open class NodeReader(val entity: NodeEntity) { * 指最顶层碰撞箱 maxY > 0 的方块 */ fun getLandHeight(position: Vector): Double { - val block = position.toBlock(world) - val blockHeight = NMS.instance.getBlockHeight(block) - return if (blockHeight == 0.0) 0.0 else blockHeight + block.y + return position.toLocation(world).callRegion { + val block = position.toBlock(world) + val blockHeight = NMS.instance.getBlockHeight(block) + if (blockHeight == 0.0) 0.0 else blockHeight + block.y + } } /** @@ -126,6 +135,12 @@ open class NodeReader(val entity: NodeEntity) { * @param z z */ open fun getLandNode(x: Int, y: Int, z: Int): Node? { + return Vector(x, y, z).toLocation(world).callRegion { + getLandNodeAtRegion(x, y, z) + } + } + + private fun getLandNodeAtRegion(x: Int, y: Int, z: Int): Node? { var h = y var node: Node? = null // 获取方块类型 @@ -215,6 +230,12 @@ open class NodeReader(val entity: NodeEntity) { * 临近合法 */ open fun isNeighborValid(neighbor: Node?, node: Node): Boolean { + return node.asBlockPos().toLocation(world).callRegion { + isNeighborValidAtRegion(neighbor, node) + } + } + + private fun isNeighborValidAtRegion(neighbor: Node?, node: Node): Boolean { if (neighbor != null && !neighbor.isClosed && (neighbor.costMalus >= 0.0f || node.costMalus < 0.0f)) { val blockHeight = NMS.instance.getBlockHeight(node.asBlockPos().down().toBlock(world)) + node.y - 1 val neighborHeight = NMS.instance.getBlockHeight(neighbor.asBlockPos().down().toBlock(world)) + neighbor.y - 1 @@ -247,6 +268,12 @@ open class NodeReader(val entity: NodeEntity) { } open fun getNeighbors(nodes: Array, node: Node): Int { + return node.asBlockPos().toLocation(world).callRegion { + getNeighborsAtRegion(nodes, node) + } + } + + private fun getNeighborsAtRegion(nodes: Array, node: Node): Int { var neighbors = 0 // 北 val north = getLandNode(node.x, node.y, node.z - 1) @@ -290,4 +317,4 @@ open class NodeReader(val entity: NodeEntity) { } return neighbors } -} \ No newline at end of file +} diff --git a/module/bukkit/bukkit-navigation/src/main/kotlin/taboolib/module/navigation/PathFinder.kt b/module/bukkit/bukkit-navigation/src/main/kotlin/taboolib/module/navigation/PathFinder.kt index b87bb1fad..b7b193fa3 100644 --- a/module/bukkit/bukkit-navigation/src/main/kotlin/taboolib/module/navigation/PathFinder.kt +++ b/module/bukkit/bukkit-navigation/src/main/kotlin/taboolib/module/navigation/PathFinder.kt @@ -3,6 +3,7 @@ package taboolib.module.navigation import com.google.common.collect.Lists import org.bukkit.Location import org.bukkit.util.Vector +import taboolib.platform.util.callRegion /** * Navigation @@ -25,6 +26,12 @@ class PathFinder(val nodeReader: NodeReader, val heuristicWeight: Float = 1.5f) } fun findPath(position: Set, distance: Float, distanceManhattan: Int = 1, deep: Float = 1f): Path? { + return nodeReader.entity.location.callRegion { + findPathAtRegion(position, distance, distanceManhattan, deep) + } + } + + private fun findPathAtRegion(position: Set, distance: Float, distanceManhattan: Int, deep: Float): Path? { openSet.clear() val start = nodeReader.getStart() val map = position.map { @@ -127,4 +134,4 @@ class PathFinder(val nodeReader: NodeReader, val heuristicWeight: Float = 1.5f) } return Path(list, position, flag) } -} \ No newline at end of file +} diff --git a/module/bukkit/bukkit-navigation/src/main/kotlin/taboolib/module/navigation/PathSmoothing.kt b/module/bukkit/bukkit-navigation/src/main/kotlin/taboolib/module/navigation/PathSmoothing.kt index 47a731923..1eddd2602 100644 --- a/module/bukkit/bukkit-navigation/src/main/kotlin/taboolib/module/navigation/PathSmoothing.kt +++ b/module/bukkit/bukkit-navigation/src/main/kotlin/taboolib/module/navigation/PathSmoothing.kt @@ -1,7 +1,9 @@ package taboolib.module.navigation +import org.bukkit.Location import org.bukkit.World import org.bukkit.util.Vector +import taboolib.platform.util.callRegion import kotlin.math.ceil import kotlin.math.floor import kotlin.math.sqrt @@ -25,6 +27,12 @@ object PathSmoothing { * 返回平滑后的世界坐标点列表(方块中心) */ fun smooth(path: Path, entity: NodeEntity): List { + return entity.location.callRegion { + smoothAtRegion(path, entity) + } + } + + private fun smoothAtRegion(path: Path, entity: NodeEntity): List { val nodes = path.nodes if (nodes.size <= 2) { return nodes.map { nodeCenter(it) } @@ -38,7 +46,7 @@ object PathSmoothing { for (probe in (current + 2)..last) { // y 不同时不跨越高度差 if (nodes[probe].y != nodes[current].y) break - if (hasLineOfSight(nodeCenter(nodes[current]), nodeCenter(nodes[probe]), entity, world)) { + if (hasLineOfSightAtRegion(nodeCenter(nodes[current]), nodeCenter(nodes[probe]), entity, world)) { farthest = probe } } @@ -52,6 +60,12 @@ object PathSmoothing { * 检查两点之间是否存在无障碍直线路径 */ fun hasLineOfSight(from: Vector, to: Vector, entity: NodeEntity, world: World): Boolean { + return Location(world, from.x, from.y, from.z).callRegion { + hasLineOfSightAtRegion(from, to, entity, world) + } + } + + 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) @@ -61,7 +75,7 @@ object PathSmoothing { val t = i.toDouble() / steps val x = from.x + dx * t val z = from.z + dz * t - if (!isStandable(x, from.y, z, entity, world)) return false + if (!isStandableAtRegion(x, from.y, z, entity, world)) return false } return true } @@ -72,6 +86,12 @@ object PathSmoothing { * - 实体碰撞箱范围内无不可通行方块 */ fun isStandable(x: Double, y: Double, z: Double, entity: NodeEntity, world: World): Boolean { + return Location(world, x, y, z).callRegion { + isStandableAtRegion(x, y, z, entity, world) + } + } + + private fun isStandableAtRegion(x: Double, y: Double, z: Double, entity: NodeEntity, world: World): Boolean { val halfWidth = entity.width / 2.0 val halfDepth = entity.depth / 2.0 val minBx = floor(x - halfWidth).toInt() diff --git a/module/bukkit/bukkit-navigation/src/main/kotlin/taboolib/module/navigation/RandomPositionGenerator.kt b/module/bukkit/bukkit-navigation/src/main/kotlin/taboolib/module/navigation/RandomPositionGenerator.kt index 29513ad5a..cf0b8a58e 100644 --- a/module/bukkit/bukkit-navigation/src/main/kotlin/taboolib/module/navigation/RandomPositionGenerator.kt +++ b/module/bukkit/bukkit-navigation/src/main/kotlin/taboolib/module/navigation/RandomPositionGenerator.kt @@ -1,6 +1,8 @@ package taboolib.module.navigation import org.bukkit.util.Vector +import taboolib.platform.Folia +import taboolib.platform.util.callRegion import java.util.* import kotlin.math.* @@ -95,8 +97,15 @@ object RandomPositionGenerator { * @param requireWalkable 依赖可行走的方块 */ fun generate(nodeEntity: NodeEntity, rX: Int, yY: Int, start: Vector?, onWater: Boolean, aboveLand: Boolean, requireWalkable: Boolean): Vector? { + return nodeEntity.location.callRegion { + generateAtRegion(nodeEntity, rX, yY, start, onWater, aboveLand, requireWalkable) + } + } + + private fun generateAtRegion(nodeEntity: NodeEntity, rX: Int, yY: Int, start: Vector?, onWater: Boolean, aboveLand: Boolean, requireWalkable: Boolean): Vector? { val random: Random = nodeEntity.random val navigation = PathTypeFactory(nodeEntity) + val world = nodeEntity.location.world!! val hasRestriction: Boolean = if (nodeEntity.hasRestriction) { nodeEntity.restrictCenter.closerThan(nodeEntity.location.toCommonVector(), (nodeEntity.restrictRadius + rX.toFloat()).toDouble() + 1.0) } else { @@ -138,11 +147,20 @@ object RandomPositionGenerator { } if (aboveLand) { result = moveUp(result, 0, 256) { - nodeEntity.location.world!!.getBlockAt(it.toLocation(nodeEntity.location.world!!)).type.isSolid + if (Folia.isFolia) { + world.getBlockAtIfLoaded(it)?.type?.isSolid == true + } else { + world.getBlockAt(it.toLocation(world)).type.isSolid + } } } - if (onWater || nodeEntity.location.world!!.getBlockAt(result.toLocation(nodeEntity.location.world!!)).type.isWater()) { - val type = navigation.getTypeAsWalkable(nodeEntity.location.world!!, result) + val blockType = if (Folia.isFolia) { + world.getBlockAtIfLoaded(result)?.type + } else { + world.getBlockAt(result.toLocation(world)).type + } + if (onWater || blockType?.isWater() == true) { + val type = navigation.getTypeAsWalkable(world, result) if (nodeEntity.getPathfindingMalus(type) == 0.0f) { val walk = nodeEntity.getWalkTargetValue(result) if (walk > sort) { @@ -201,4 +219,4 @@ object RandomPositionGenerator { Vector(rX, rY, rZ) } } -} \ No newline at end of file +} diff --git a/module/bukkit/bukkit-navigation/src/main/kotlin/taboolib/module/navigation/Utils.kt b/module/bukkit/bukkit-navigation/src/main/kotlin/taboolib/module/navigation/Utils.kt index e45a907b9..5ef74bd76 100644 --- a/module/bukkit/bukkit-navigation/src/main/kotlin/taboolib/module/navigation/Utils.kt +++ b/module/bukkit/bukkit-navigation/src/main/kotlin/taboolib/module/navigation/Utils.kt @@ -7,6 +7,7 @@ import org.bukkit.block.Block import org.bukkit.util.NumberConversions import org.bukkit.util.Vector import taboolib.module.nms.MinecraftVersion +import taboolib.platform.util.callRegion fun createPathfinder(nodeEntity: NodeEntity): PathFinder { return PathFinder(NodeReader(nodeEntity)) @@ -17,10 +18,15 @@ fun World.getBlockAt(position: Vector): Block { } fun World.getBlockAtIfLoaded(position: Vector): Block? { - return if (ChunkAccess.instance.isChunkLoaded(this, position.blockX shr 4, position.blockZ shr 4)) { - getBlockAt(position.blockX, position.blockY, position.blockZ) - } else { - null + val x = position.blockX + val y = position.blockY + val z = position.blockZ + return callRegion(x, y, z) { + if (ChunkAccess.instance.isChunkLoaded(this, x shr 4, z shr 4)) { + getBlockAt(x, y, z) + } else { + null + } } } @@ -135,4 +141,4 @@ fun Block.isBottomSlab(): Boolean { } else { NMS.instance.isBottomSlab(this) } -} \ No newline at end of file +} diff --git a/platform/platform-bukkit-impl/src/main/kotlin/taboolib/platform/util/FoliaExecutor.kt b/platform/platform-bukkit-impl/src/main/kotlin/taboolib/platform/util/FoliaExecutor.kt index fdd7b1932..5f04a690d 100644 --- a/platform/platform-bukkit-impl/src/main/kotlin/taboolib/platform/util/FoliaExecutor.kt +++ b/platform/platform-bukkit-impl/src/main/kotlin/taboolib/platform/util/FoliaExecutor.kt @@ -1,22 +1,40 @@ package taboolib.platform.util import io.papermc.paper.threadedregions.scheduler.ScheduledTask +import org.bukkit.Bukkit import org.bukkit.Chunk import org.bukkit.Location import org.bukkit.World import org.bukkit.block.Block import org.bukkit.entity.Entity +import org.tabooproject.reflex.Reflex.Companion.invokeMethod import taboolib.common.platform.function.submit as submitPlatform import taboolib.common.platform.service.PlatformExecutor import taboolib.platform.BukkitExecutor import taboolib.platform.BukkitPlugin import taboolib.platform.Folia import taboolib.platform.FoliaExecutor +import java.util.concurrent.CompletableFuture +import java.util.concurrent.ExecutionException // ============================================ // Location 扩展函数 // ============================================ +/** + * 在指定位置所属的 Folia 区域线程中执行回调并返回结果。 + */ +fun Location.callRegion(executor: () -> T): T { + if (isOwnedByCurrentRegion()) { + return callDirect(executor) + } + val future = CompletableFuture() + FoliaExecutor.REGION_SCHEDULER.run(BukkitPlugin.getInstance(), this) { + future.completeWith(executor) + } + return future.awaitResult() +} + /** * 在指定位置执行一个任务(支持 Folia 区域调度) * @@ -40,7 +58,7 @@ fun Location.runTask(executor: Runnable, useScheduler: Boolean = true): Platform executor.run() } - return BukkitExecutor.BukkitPlatformTask { scheduledTask?.cancel() } + return BukkitExecutor.BukkitPlatformTask { scheduledTask.cancel() } } /** @@ -119,6 +137,25 @@ fun Location.submit( // Entity 扩展函数 // ============================================ +/** + * 在实体所属的 Folia 实体线程中执行回调并返回结果。 + */ +fun Entity.callRegion(executor: () -> T): T { + if (isOwnedByCurrentRegion()) { + return callDirect(executor) + } + val future = CompletableFuture() + val scheduledTask = FoliaExecutor.getEntityScheduler(this).run(BukkitPlugin.getInstance(), { + future.completeWith(executor) + }, { + future.completeExceptionally(IllegalStateException("Entity scheduler retired.")) + }) + if (scheduledTask == null && !future.isDone) { + future.completeExceptionally(IllegalStateException("Entity scheduler rejected task.")) + } + return future.awaitResult() +} + /** * 在实体所在位置执行一个任务(Folia 安全) * @@ -225,6 +262,13 @@ fun Entity.submit( // Block 扩展函数 // ============================================ +/** + * 在方块所属的 Folia 区域线程中执行回调并返回结果。 + */ +fun Block.callRegion(executor: () -> T): T { + return location.callRegion(executor) +} + /** * 在方块所在位置执行一个任务(Folia 安全) * @@ -262,6 +306,13 @@ fun Block.submit( // Chunk 扩展函数 // ============================================ +/** + * 在区块中心所属的 Folia 区域线程中执行回调并返回结果。 + */ +fun Chunk.callRegion(executor: () -> T): T { + return Location(world, (x shl 4) + 8.0, 64.0, (z shl 4) + 8.0).callRegion(executor) +} + /** * 在区块中心位置执行一个任务(Folia 安全) * @@ -301,6 +352,20 @@ fun Chunk.submit( // World 扩展函数(坐标) // ============================================ +/** + * 在指定世界坐标所属的 Folia 区域线程中执行回调并返回结果。 + */ +fun World.callRegion(x: Double, z: Double, executor: () -> T): T { + return Location(this, x, 64.0, z).callRegion(executor) +} + +/** + * 在指定世界方块坐标所属的 Folia 区域线程中执行回调并返回结果。 + */ +fun World.callRegion(x: Int, y: Int, z: Int, executor: () -> T): T { + return Location(this, x.toDouble(), y.toDouble(), z.toDouble()).callRegion(executor) +} + /** * 在指定世界坐标执行一个任务(Folia 安全) * @@ -340,4 +405,52 @@ fun World.submit( ): PlatformExecutor.PlatformTask { val location = Location(this, x, 64.0, z) return location.submit(now, async, delay, period, useScheduler, executor) -} \ No newline at end of file +} + +private fun callDirect(executor: () -> T): T { + return executor() +} + +private fun Location.isOwnedByCurrentRegion(): Boolean { + if (!Folia.isFolia) { + return true + } + return kotlin.runCatching { + Bukkit::class.java.invokeMethod("isOwnedByCurrentRegion", this, isStatic = true, remap = false) ?: true + }.getOrDefault(true) +} + +private fun Entity.isOwnedByCurrentRegion(): Boolean { + if (!Folia.isFolia) { + return true + } + return kotlin.runCatching { + Bukkit::class.java.invokeMethod("isOwnedByCurrentRegion", this, isStatic = true, remap = false) ?: true + }.getOrDefault(true) +} + +private fun CompletableFuture.completeWith(executor: () -> T) { + try { + complete(executor()) + } catch (throwable: Throwable) { + completeExceptionally(throwable) + } +} + +private fun CompletableFuture.awaitResult(): T { + try { + return get() + } catch (exception: InterruptedException) { + Thread.currentThread().interrupt() + throw RuntimeException(exception) + } catch (exception: ExecutionException) { + val cause = exception.cause + if (cause is RuntimeException) { + throw cause + } + if (cause is Error) { + throw cause + } + throw RuntimeException(cause) + } +}