Skip to content

Commit e943faf

Browse files
committed
feat: add npc physics
1 parent e624f95 commit e943faf

1 file changed

Lines changed: 57 additions & 9 deletions

File tree

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

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import net.kyori.adventure.text.Component
77
import cc.modlabs.kpaper.main.PluginInstance
88
import org.bukkit.Bukkit
99
import org.bukkit.Location
10+
import org.bukkit.Material
11+
import org.bukkit.World
1012
import org.bukkit.entity.Entity
1113
import org.bukkit.entity.LivingEntity
1214
import 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

Comments
 (0)