Skip to content

Commit ebf6cec

Browse files
committed
fix: npc teleport
1 parent e943faf commit ebf6cec

1 file changed

Lines changed: 57 additions & 2 deletions

File tree

src/main/kotlin/cc/modlabs/kpaper/npc/NPCImpl.kt

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -956,6 +956,40 @@ class NPCImpl(
956956
}
957957
}
958958

959+
// Validate that NPC can stand at the new position (both feet and head must be air)
960+
if (world != null && !canStandAt(world, newPosition.blockX, newPosition.blockY, newPosition.blockZ)) {
961+
logDebug("[NPC] moveTowards: Cannot stand at ${newPosition.blockX},${newPosition.blockY},${newPosition.blockZ} - collision detected, aborting movement")
962+
// Try to find a valid Y position nearby
963+
// Search downward first (most common case - ceiling collision)
964+
var foundValidY: Int? = null
965+
for (offset in 0..3) {
966+
val testY = newPosition.blockY - offset
967+
if (testY >= 0 && canStandAt(world, newPosition.blockX, testY, newPosition.blockZ)) {
968+
foundValidY = testY
969+
break
970+
}
971+
}
972+
// If not found downward, try upward (floor collision)
973+
if (foundValidY == null) {
974+
for (offset in 1..3) {
975+
val testY = newPosition.blockY + offset
976+
if (canStandAt(world, newPosition.blockX, testY, newPosition.blockZ)) {
977+
foundValidY = testY
978+
break
979+
}
980+
}
981+
}
982+
983+
if (foundValidY != null) {
984+
newPosition.y = foundValidY.toDouble() + 0.5 // Center in block
985+
logDebug("[NPC] moveTowards: Adjusted Y to ${newPosition.y} to avoid collision")
986+
} else {
987+
// No valid position found, abort movement for this tick
988+
logDebug("[NPC] moveTowards: No valid position found, aborting movement")
989+
return
990+
}
991+
}
992+
959993
// Make entity look at target
960994
val lookDirection = target.toVector().subtract(newPosition.toVector())
961995
val yaw = Math.toDegrees(-atan2(lookDirection.x, lookDirection.z)).toFloat()
@@ -972,19 +1006,40 @@ class NPCImpl(
9721006
/**
9731007
* Finds the ground level (top solid block) at the given X, Z coordinates.
9741008
* Returns null if no solid ground is found within reasonable range.
1009+
* Only returns a ground level if there's sufficient air space above for the NPC to stand.
9751010
*/
9761011
private fun findGroundLevel(world: World, x: Int, z: Int, startY: Int): Double? {
9771012
// Search from startY + 2 down to startY - 10
9781013
for (y in (startY + 2).downTo(startY - 10)) {
9791014
val block = world.getBlockAt(x, y, z)
9801015
if (block.type.isSolid && block.type != Material.BARRIER) {
981-
// Found solid ground, return the top of this block
982-
return (y + 1).toDouble()
1016+
// Found solid ground, check if there's air space above for NPC to stand
1017+
// Check air at y + 1 (feet) and y + 2 (head) for a 2-block tall NPC
1018+
val above = world.getBlockAt(x, y + 1, z)
1019+
val above2 = world.getBlockAt(x, y + 2, z)
1020+
if (above.type.isAir && above2.type.isAir) {
1021+
// Sufficient air space, return the top of this block
1022+
return (y + 1).toDouble()
1023+
}
9831024
}
9841025
}
9851026
return null
9861027
}
9871028

1029+
/**
1030+
* Checks if the NPC can stand at the given position (both feet and head blocks must be air).
1031+
* @param world The world to check in
1032+
* @param x The X coordinate
1033+
* @param y The Y coordinate (feet level)
1034+
* @param z The Z coordinate
1035+
* @return true if both the feet and head positions are air blocks
1036+
*/
1037+
private fun canStandAt(world: World, x: Int, y: Int, z: Int): Boolean {
1038+
val blockAtFeet = world.getBlockAt(x, y, z)
1039+
val blockAtHead = world.getBlockAt(x, y + 1, z)
1040+
return blockAtFeet.type.isAir && blockAtHead.type.isAir
1041+
}
1042+
9881043
/**
9891044
* Enable or disable pathfinding for this NPC.
9901045
* When enabled, NPCs will use A* pathfinding to navigate around obstacles and jump when needed.

0 commit comments

Comments
 (0)