@@ -7,6 +7,8 @@ import net.kyori.adventure.text.Component
77import cc.modlabs.kpaper.main.PluginInstance
88import org.bukkit.Bukkit
99import org.bukkit.Location
10+ import org.bukkit.Material
11+ import org.bukkit.World
1012import org.bukkit.entity.Entity
1113import org.bukkit.entity.LivingEntity
1214import org.bukkit.entity.Mannequin
@@ -902,25 +904,55 @@ class NPCImpl(
902904 return
903905 }
904906
905- // Mannequins are display entities and don't respond to velocity properly
906- // Use teleportation for movement instead
907+ // Use hybrid approach: teleport for horizontal movement, simulate gravity for vertical
907908 val normalizedDirection = direction.normalize()
908909 val moveDistance = speed.coerceAtMost(distance) // Don't overshoot the target
909- val newPosition = currentLoc.clone().add(normalizedDirection.multiply(moveDistance))
910+
911+ // Calculate horizontal movement (X, Z only)
912+ val horizontalDirection = Vector (normalizedDirection.x, 0.0 , normalizedDirection.z).normalize()
913+ val horizontalMove = horizontalDirection.multiply(moveDistance)
914+ val newPosition = currentLoc.clone().add(horizontalMove)
910915
911916 // Check if NPC needs to jump
912917 val needsJump = usePathfinding && Pathfinder .needsJump(entity, target)
918+
919+ // Check for ground below the new horizontal position
920+ val world = currentLoc.world
921+ val groundY = findGroundLevel(world, newPosition.blockX, newPosition.blockZ, currentLoc.blockY.toInt())
922+
923+ // Handle vertical positioning
913924 if (needsJump) {
914925 logDebug(" [NPC] moveTowards: Needs to jump, adding jump height" )
915- // Add jump height to the new position
916- newPosition.y + = 0.42 // Standard jump height
926+ // Jump: move up from current position
927+ newPosition.y = currentLoc.y + 0.42
917928 } else {
918- // Normal movement - handle small steps up
919929 val heightDiff = target.y - currentLoc.y
920930 if (heightDiff > 0.1 && heightDiff <= 0.5 ) {
921931 logDebug(" [NPC] moveTowards: Small step up (heightDiff=$heightDiff ), adding step height" )
922- // Small step up, add slight upward movement
923- newPosition.y + = 0.2
932+ // Small step up
933+ newPosition.y = currentLoc.y + 0.2
934+ } else if (groundY != null ) {
935+ // There's ground below - check if we should be on it or falling to it
936+ val distanceToGround = currentLoc.y - groundY
937+ if (distanceToGround > 0.1 ) {
938+ // We're above ground - simulate gravity (fall down)
939+ // Gravity: 0.08 blocks per tick (1.6 blocks per second)
940+ val fallDistance = 0.08 .coerceAtMost(distanceToGround - 0.1 )
941+ newPosition.y = currentLoc.y - fallDistance
942+ logDebug(" [NPC] moveTowards: Falling - distanceToGround=$distanceToGround , fallDistance=$fallDistance , newY=${newPosition.y} " )
943+ } else if (distanceToGround < - 0.1 ) {
944+ // We're below ground - place on ground
945+ newPosition.y = groundY
946+ logDebug(" [NPC] moveTowards: Below ground, placed on ground at Y=$groundY " )
947+ } else {
948+ // We're on ground - stay there
949+ newPosition.y = currentLoc.y
950+ logDebug(" [NPC] moveTowards: On ground, maintaining Y=${newPosition.y} " )
951+ }
952+ } else {
953+ // No ground found - keep current Y (might be in air, let it fall naturally if gravity is enabled)
954+ newPosition.y = currentLoc.y
955+ logDebug(" [NPC] moveTowards: No ground found, keeping current Y=${newPosition.y} " )
924956 }
925957 }
926958
@@ -932,10 +964,26 @@ class NPCImpl(
932964 newPosition.yaw = yaw
933965 newPosition.pitch = pitch.coerceIn(- 90f , 90f )
934966
935- // Teleport to new position (Mannequins need teleportation for movement)
967+ // Teleport to new position
936968 entity.teleport(newPosition)
937969 logDebug(" [NPC] moveTowards: Teleported to ${newPosition.blockX} ,${newPosition.blockY} ,${newPosition.blockZ} , yaw=$yaw , pitch=$pitch " )
938970 }
971+
972+ /* *
973+ * Finds the ground level (top solid block) at the given X, Z coordinates.
974+ * Returns null if no solid ground is found within reasonable range.
975+ */
976+ private fun findGroundLevel (world : World , x : Int , z : Int , startY : Int ): Double? {
977+ // Search from startY + 2 down to startY - 10
978+ for (y in (startY + 2 ).downTo(startY - 10 )) {
979+ val block = world.getBlockAt(x, y, z)
980+ if (block.type.isSolid && block.type != Material .BARRIER ) {
981+ // Found solid ground, return the top of this block
982+ return (y + 1 ).toDouble()
983+ }
984+ }
985+ return null
986+ }
939987
940988 /* *
941989 * Enable or disable pathfinding for this NPC.
0 commit comments