mirror of
https://gitlab.com/2009scape/2009scape.git
synced 2026-08-28 05:45:10 -06:00
NPC wander fix
This commit is contained in:
parent
fad4726760
commit
4328d591f5
2 changed files with 152 additions and 14 deletions
|
|
@ -21,8 +21,11 @@ import content.global.skill.slayer.Tasks;
|
|||
import content.global.skill.summoning.familiar.Familiar;
|
||||
import core.game.world.map.Direction;
|
||||
import core.game.world.map.Location;
|
||||
import core.game.world.map.Point;
|
||||
import core.game.world.map.RegionManager;
|
||||
import core.game.world.map.build.DynamicRegion;
|
||||
import core.game.world.map.path.ClipMaskSupplier;
|
||||
import core.game.world.map.path.Path;
|
||||
import core.game.world.map.path.Pathfinder;
|
||||
import core.game.world.update.flag.context.Animation;
|
||||
import core.game.world.update.flag.context.Graphics;
|
||||
|
|
@ -466,25 +469,22 @@ public class NPC extends Entity {
|
|||
return;
|
||||
if (!getLocks().isInteractionLocked()) {
|
||||
if (!getLocks().isMovementLocked()) {
|
||||
int effectiveWalkRadius = getWalkRadius();
|
||||
if (
|
||||
!pathBoundMovement
|
||||
&& walkRadius > 0
|
||||
&& walkRadius <= 20
|
||||
&& !getLocation().withinDistance(getProperties().getSpawnLocation(), (int)(walkRadius * 1.5))
|
||||
&& effectiveWalkRadius > 0
|
||||
&& effectiveWalkRadius <= 20
|
||||
&& !getLocation().withinDistance(getProperties().getSpawnLocation(), getSpawnReturnDistance(effectiveWalkRadius))
|
||||
&& !getAttribute("no-spawn-return", false)
|
||||
)
|
||||
{
|
||||
MovementPulse current = getAttribute("return-to-spawn-pulse");
|
||||
if (current != null && current.isRunning()) return;
|
||||
|
||||
if(!isNeverWalks()){
|
||||
if(walkRadius == 0)
|
||||
walkRadius = 3;
|
||||
}
|
||||
if (aggressiveHandler != null) {
|
||||
aggressiveHandler.setPauseTicks(walkRadius + 1);
|
||||
aggressiveHandler.setPauseTicks(effectiveWalkRadius + 1);
|
||||
}
|
||||
nextWalk = GameWorld.getTicks() + walkRadius + 1;
|
||||
nextWalk = GameWorld.getTicks() + effectiveWalkRadius + 1;
|
||||
getLocks().lockMovement(100);
|
||||
getImpactHandler().setDisabledTicks(100);
|
||||
setAttribute("return-to-spawn", true);
|
||||
|
|
@ -530,15 +530,84 @@ public class NPC extends Entity {
|
|||
setNextWalk();
|
||||
Location l = getMovementDestination();
|
||||
if (canMove(l)) {
|
||||
if((Boolean) definition.getHandlers().getOrDefault("water_npc",false)){
|
||||
Pathfinder.findWater(this,l,true,Pathfinder.DUMB).walk(this);
|
||||
} else {
|
||||
Pathfinder.find(this, l, true, Pathfinder.DUMB).walk(this);
|
||||
}
|
||||
Path path = pathBoundMovement ? findMovementPath(l) : findRandomMovementPath(l);
|
||||
path.walk(this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Path findMovementPath(Location destination) {
|
||||
if (isWaterNPC()) {
|
||||
return Pathfinder.findWater(this, destination, true, Pathfinder.DUMB);
|
||||
}
|
||||
return Pathfinder.find(this, destination, true, Pathfinder.DUMB);
|
||||
}
|
||||
|
||||
private Path findRandomMovementPath(Location destination) {
|
||||
if (isWaterNPC() || size() != 1) {
|
||||
Path path = findMovementPath(destination);
|
||||
return isRandomMovementPathWithinBounds(path) ? path : new Path();
|
||||
}
|
||||
return findDirectRandomMovementPath(destination);
|
||||
}
|
||||
|
||||
private Path findDirectRandomMovementPath(Location destination) {
|
||||
Path path = new Path();
|
||||
path.setSuccesful(true);
|
||||
Location current = getLocation();
|
||||
int maxSteps = Math.min(14, Math.max(1, getSpawnReturnDistance(Math.max(1, getWalkRadius()))));
|
||||
for (int steps = 0; !current.equals(destination) && steps < maxSteps; steps++) {
|
||||
Direction direction = Direction.getDirection(current, destination);
|
||||
if (direction == null || !canTakeRandomMovementStep(current, direction)) {
|
||||
path.setSuccesful(false);
|
||||
path.setMoveNear(!path.getPoints().isEmpty());
|
||||
break;
|
||||
}
|
||||
Location next = current.transform(direction);
|
||||
if (!isWithinRandomMovementBounds(next)) {
|
||||
path.setSuccesful(false);
|
||||
path.setMoveNear(!path.getPoints().isEmpty());
|
||||
break;
|
||||
}
|
||||
path.getPoints().add(new Point(next.getX(), next.getY(), direction, direction.getStepX(), direction.getStepY()));
|
||||
current = next;
|
||||
}
|
||||
if (!current.equals(destination) && !path.getPoints().isEmpty()) {
|
||||
path.setMoveNear(true);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
private boolean canTakeRandomMovementStep(Location current, Direction direction) {
|
||||
ClipMaskSupplier clipMaskSupplier = behavior != null ? behavior.getClippingSupplier(this) : null;
|
||||
if (clipMaskSupplier == null) {
|
||||
clipMaskSupplier = RegionManager::getClippingFlag;
|
||||
}
|
||||
return direction.canMoveFrom(current.getZ(), current.getX(), current.getY(), clipMaskSupplier);
|
||||
}
|
||||
|
||||
private boolean isRandomMovementPathWithinBounds(Path path) {
|
||||
for (Point point : path.getPoints()) {
|
||||
if (!isWithinRandomMovementBounds(Location.create(point.getX(), point.getY(), getLocation().getZ()))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isWithinRandomMovementBounds(Location location) {
|
||||
int walkRadius = getWalkRadius();
|
||||
return walkRadius <= 0 || location.withinDistance(getProperties().getSpawnLocation(), getSpawnReturnDistance(walkRadius));
|
||||
}
|
||||
|
||||
private int getSpawnReturnDistance(int walkRadius) {
|
||||
return (int) (walkRadius * 1.5);
|
||||
}
|
||||
|
||||
private boolean isWaterNPC() {
|
||||
return (Boolean) definition.getHandlers().getOrDefault("water_npc", false);
|
||||
}
|
||||
|
||||
public int getNextWalk() {
|
||||
return nextWalk;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -584,6 +584,56 @@ class PathfinderTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test fun npcReturnToSpawnShouldUseOverriddenWalkRadius() {
|
||||
TestUtils.getMockPlayer("overriddenRadiusReturn").use {
|
||||
val spawn = ServerConstants.HOME_LOCATION!!
|
||||
val npc = object : NPC(1, spawn.transform(5, 0, 0)) {
|
||||
override fun getWalkRadius(): Int {
|
||||
return 3
|
||||
}
|
||||
}
|
||||
npc.isWalks = true
|
||||
npc.isNeverWalks = false
|
||||
npc.init()
|
||||
npc.properties.spawnLocation = spawn
|
||||
try {
|
||||
npc.handleTickActions()
|
||||
|
||||
Assertions.assertEquals(true, npc.getAttribute("return-to-spawn", false))
|
||||
} finally {
|
||||
npc.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun randomWalkingNpcShouldNotRouteAroundBlockedLocalDestination() {
|
||||
val origin = Location.create(3200, 3600, 0)
|
||||
val blocked = origin.transform(1, 0, 0)
|
||||
val destination = origin.transform(2, 0, 0)
|
||||
val npc = FixedDestinationNPC(origin, destination, 3)
|
||||
npc.isWalks = true
|
||||
npc.isNeverWalks = false
|
||||
npc.init()
|
||||
npc.properties.spawnLocation = origin
|
||||
RegionManager.addClippingFlag(blocked.z, blocked.x, blocked.y, false, movementBlockFlag)
|
||||
try {
|
||||
npc.resetWalk()
|
||||
repeat(20) {
|
||||
npc.handleTickActions()
|
||||
}
|
||||
|
||||
Assertions.assertFalse(
|
||||
npc.walkingQueue.hasPath(),
|
||||
"Random-walking NPCs should not take an RSMOD detour around a clipped boundary tile."
|
||||
)
|
||||
npc.walkingQueue.update()
|
||||
Assertions.assertEquals(origin, npc.location)
|
||||
} finally {
|
||||
RegionManager.removeClippingFlag(blocked.z, blocked.x, blocked.y, false, movementBlockFlag)
|
||||
npc.clear()
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun npcShouldReliablyReturnToSpawnEvenIfRegionUnloaded() {
|
||||
//spawn a player into the area just to make sure it ticks...
|
||||
TestUtils.getMockPlayer("areaunloadtest").use { p ->
|
||||
|
|
@ -602,4 +652,23 @@ class PathfinderTests {
|
|||
Assertions.assertEquals(true, npc.location.getDistance(ServerConstants.HOME_LOCATION!!) <= 5)
|
||||
}
|
||||
}
|
||||
|
||||
private class FixedDestinationNPC(
|
||||
location: Location,
|
||||
private val destination: Location,
|
||||
private val radius: Int
|
||||
) : NPC(1, location) {
|
||||
override fun getMovementDestination(): Location {
|
||||
return destination
|
||||
}
|
||||
|
||||
override fun getWalkRadius(): Int {
|
||||
return radius
|
||||
}
|
||||
}
|
||||
|
||||
private val movementBlockFlag = Pathfinder.PREVENT_NORTH or
|
||||
Pathfinder.PREVENT_EAST or
|
||||
Pathfinder.PREVENT_SOUTH or
|
||||
Pathfinder.PREVENT_WEST
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue