mirror of
https://gitlab.com/2009scape/2009scape.git
synced 2026-08-28 05:45:10 -06:00
Old dumb pathfinding, some reformatting, various fixes
This commit is contained in:
parent
4328d591f5
commit
cd2f57f1b9
11 changed files with 1821 additions and 1254 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -29,9 +29,7 @@ object CombatMovementIntents {
|
|||
|
||||
private data class Intent(val attacker: Entity, val target: Entity)
|
||||
private data class MovementDestination(
|
||||
val node: Node,
|
||||
val pathfinder: Pathfinder,
|
||||
val allowPartialPath: Boolean
|
||||
val node: Node, val pathfinder: Pathfinder, val allowPartialPath: Boolean
|
||||
) {
|
||||
val location: Location
|
||||
get() = node.location
|
||||
|
|
@ -48,12 +46,9 @@ object CombatMovementIntents {
|
|||
) {
|
||||
fun summary(): String {
|
||||
val slowest = slowestIntent?.let {
|
||||
", slowestIntent=${it.elapsedMicros}us attacker=${it.attacker} target=${it.target} " +
|
||||
"candidates=${it.candidateCount} directPathHits=${it.directPathHits} " +
|
||||
"rsmodRouteCalls=${it.rsmodRouteCalls} losCalls=${it.losCalls}"
|
||||
", slowestIntent=${it.elapsedMicros}us attacker=${it.attacker} target=${it.target} " + "candidates=${it.candidateCount} directPathHits=${it.directPathHits} " + "rsmodRouteCalls=${it.rsmodRouteCalls} losCalls=${it.losCalls}"
|
||||
} ?: ""
|
||||
return "combatMovementStats intents=$intents candidates=$candidateCount " +
|
||||
"directPathHits=$directPathHits rsmodRouteCalls=$rsmodRouteCalls losCalls=$losCalls$slowest"
|
||||
return "combatMovementStats intents=$intents candidates=$candidateCount " + "directPathHits=$directPathHits rsmodRouteCalls=$rsmodRouteCalls losCalls=$losCalls$slowest"
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
@ -189,10 +184,7 @@ object CombatMovementIntents {
|
|||
return
|
||||
}
|
||||
|
||||
val pending = intents.values.sortedWith(
|
||||
compareBy<Intent> { it.attacker.index }
|
||||
.thenBy { it.target.index }
|
||||
)
|
||||
val pending = intents.values.sortedWith(compareBy<Intent> { it.attacker.index }.thenBy { it.target.index })
|
||||
intents.clear()
|
||||
|
||||
val reservedTiles = LinkedHashSet<Location>()
|
||||
|
|
@ -277,11 +269,7 @@ object CombatMovementIntents {
|
|||
val targetLocation = targetLocationFor(attacker, target)
|
||||
val projectedTargetLocation = projectedLocations[target]
|
||||
if (projectedTargetLocation != null && canAttackFrom(
|
||||
attacker,
|
||||
target,
|
||||
attacker.location,
|
||||
projectedTargetLocation,
|
||||
trace
|
||||
attacker, target, attacker.location, projectedTargetLocation, trace
|
||||
)
|
||||
) {
|
||||
attacker.walkingQueue.reset()
|
||||
|
|
@ -321,11 +309,7 @@ object CombatMovementIntents {
|
|||
val standingOnCandidate = candidates.any { it.location == attacker.location }
|
||||
val projectedAttackerLocation = CombatMovementPlanner.predictedMovementLocation(attacker) ?: attacker.location
|
||||
if (projectedAttackerLocation != attacker.location && canAttackFrom(
|
||||
attacker,
|
||||
target,
|
||||
projectedAttackerLocation,
|
||||
targetLocation,
|
||||
trace
|
||||
attacker, target, projectedAttackerLocation, targetLocation, trace
|
||||
)
|
||||
) {
|
||||
attacker.face(target)
|
||||
|
|
@ -334,13 +318,15 @@ object CombatMovementIntents {
|
|||
return
|
||||
}
|
||||
|
||||
val queueStationaryContinuation = attacker.properties.combatPulse.style == CombatStyle.MELEE &&
|
||||
!CombatMovementPlanner.hasMovementStepThisTick(target)
|
||||
val queueStationaryContinuation =
|
||||
attacker.properties.combatPulse.style == CombatStyle.MELEE && !CombatMovementPlanner.hasMovementStepThisTick(
|
||||
target
|
||||
)
|
||||
var blockedByReservation = false
|
||||
for (candidate in candidates) {
|
||||
val candidatePath = pathTo(attacker, candidate, trace, queueStationaryContinuation) ?: continue
|
||||
val attackPath = truncateAtFirstAttackOpportunity(attacker, target, targetLocation, candidatePath, trace)
|
||||
?: continue
|
||||
val attackPath =
|
||||
truncateAtFirstAttackOpportunity(attacker, target, targetLocation, candidatePath, trace) ?: continue
|
||||
if (hasReservedOccupiedTile(reservedTiles, attacker, attackPath.projectedLocation)) {
|
||||
blockedByReservation = true
|
||||
continue
|
||||
|
|
@ -393,11 +379,7 @@ object CombatMovementIntents {
|
|||
}
|
||||
return when (attacker.properties.combatPulse.style) {
|
||||
CombatStyle.RANGE, CombatStyle.MAGIC -> canAttackFromRange(
|
||||
attacker,
|
||||
target,
|
||||
attackerLocation,
|
||||
targetLocation,
|
||||
trace
|
||||
attacker, target, attackerLocation, targetLocation, trace
|
||||
)
|
||||
|
||||
else -> canAttackFromMelee(attacker, target, attackerLocation, targetLocation, trace)
|
||||
|
|
@ -405,11 +387,7 @@ object CombatMovementIntents {
|
|||
}
|
||||
|
||||
private fun movementDestinationsFor(
|
||||
attacker: Entity,
|
||||
target: Entity,
|
||||
targetLocation: Location,
|
||||
pathfinder: Pathfinder,
|
||||
trace: IntentTrace
|
||||
attacker: Entity, target: Entity, targetLocation: Location, pathfinder: Pathfinder, trace: IntentTrace
|
||||
): List<MovementDestination> {
|
||||
if (attacker is NPC && pathfinder === Pathfinder.DUMB) {
|
||||
val destinations = ArrayList<MovementDestination>(4)
|
||||
|
|
@ -418,7 +396,7 @@ object CombatMovementIntents {
|
|||
destinations.add(MovementDestination(location, pathfinder, allowPartialPath = true))
|
||||
}
|
||||
} else {
|
||||
addDumbNpcAttackDestinations(attacker, target, pathfinder, destinations)
|
||||
destinations.add(MovementDestination(targetLocation, pathfinder, allowPartialPath = true))
|
||||
}
|
||||
trace.candidateCount += destinations.size
|
||||
return destinations
|
||||
|
|
@ -433,8 +411,9 @@ object CombatMovementIntents {
|
|||
for (location in attackTiles) {
|
||||
destinations.add(MovementDestination(location, pathfinder, allowPartialPath = false))
|
||||
}
|
||||
val targetFallback = if (!occupiedTilesOverlap(attacker, target) &&
|
||||
shouldAllowPartialTargetPath(attacker, target, targetLocation)
|
||||
val targetFallback = if (!occupiedTilesOverlap(attacker, target) && shouldAllowPartialTargetPath(
|
||||
attacker, target, targetLocation
|
||||
)
|
||||
) {
|
||||
MovementDestination(target, pathfinder, allowPartialPath = true)
|
||||
} else {
|
||||
|
|
@ -456,10 +435,7 @@ object CombatMovementIntents {
|
|||
}
|
||||
|
||||
private fun attackTilesFor(
|
||||
attacker: Entity,
|
||||
target: Entity,
|
||||
targetLocation: Location,
|
||||
trace: IntentTrace
|
||||
attacker: Entity, target: Entity, targetLocation: Location, trace: IntentTrace
|
||||
): List<Location> {
|
||||
if (attacker.properties.combatPulse.style != CombatStyle.MELEE) {
|
||||
return CombatMovementPlanner.candidateAttackTiles(attacker, target, targetLocation)
|
||||
|
|
@ -468,12 +444,11 @@ object CombatMovementIntents {
|
|||
val walkable = borderTiles.filter { RegionManager.isTeleportPermitted(it) }
|
||||
val candidates = walkable.ifEmpty { borderTiles }
|
||||
val attackable = candidates.filter { canAttackFrom(attacker, target, it, targetLocation, trace) }
|
||||
return attackable.ifEmpty { candidates }.sortedWith(
|
||||
compareBy<Location> { distanceSquared(it, attacker.location) }
|
||||
.thenBy { distanceSquaredToClosestOccupiedTile(target, targetLocation, it) }
|
||||
.thenBy { it.x }
|
||||
.thenBy { it.y }
|
||||
)
|
||||
return attackable.ifEmpty { candidates }.sortedWith(compareBy<Location> {
|
||||
distanceSquared(
|
||||
it, attacker.location
|
||||
)
|
||||
}.thenBy { distanceSquaredToClosestOccupiedTile(target, targetLocation, it) }.thenBy { it.x }.thenBy { it.y })
|
||||
}
|
||||
|
||||
private fun shouldAllowPartialTargetPath(attacker: Player, target: Entity, targetLocation: Location): Boolean {
|
||||
|
|
@ -488,11 +463,7 @@ object CombatMovementIntents {
|
|||
}
|
||||
|
||||
private fun playerAttackRangeDestinations(
|
||||
attacker: Player,
|
||||
target: Entity,
|
||||
targetLocation: Location,
|
||||
pathfinder: Pathfinder,
|
||||
trace: IntentTrace
|
||||
attacker: Player, target: Entity, targetLocation: Location, pathfinder: Pathfinder, trace: IntentTrace
|
||||
): List<MovementDestination> {
|
||||
val range = playerAttackRange(attacker)
|
||||
if (range <= CombatReach.meleeDistance(attacker)) {
|
||||
|
|
@ -524,21 +495,14 @@ object CombatMovementIntents {
|
|||
distance += 2
|
||||
}
|
||||
val rangeWeapon = RangeWeapon.get(attacker.equipment.getNew(EquipmentContainer.SLOT_WEAPON).id)
|
||||
if (rangeWeapon != null &&
|
||||
(rangeWeapon.weaponType == Weapon.WeaponType.DOUBLE_SHOT ||
|
||||
rangeWeapon.weaponType == Weapon.WeaponType.DEGRADING)
|
||||
) {
|
||||
if (rangeWeapon != null && (rangeWeapon.weaponType == Weapon.WeaponType.DOUBLE_SHOT || rangeWeapon.weaponType == Weapon.WeaponType.DEGRADING)) {
|
||||
distance = 10
|
||||
}
|
||||
return distance
|
||||
}
|
||||
|
||||
private fun attackRangeTiles(
|
||||
attacker: Player,
|
||||
target: Entity,
|
||||
targetLocation: Location,
|
||||
range: Int,
|
||||
trace: IntentTrace
|
||||
attacker: Player, target: Entity, targetLocation: Location, range: Int, trace: IntentTrace
|
||||
): List<Location> {
|
||||
val tiles = ArrayList<Location>()
|
||||
val minX = targetLocation.x - range
|
||||
|
|
@ -556,22 +520,16 @@ object CombatMovementIntents {
|
|||
}
|
||||
}
|
||||
}
|
||||
tiles.sortWith(
|
||||
compareBy<Location> { distanceSquared(it, attacker.location) }
|
||||
.thenBy { distanceSquaredToClosestOccupiedTile(target, targetLocation, it) }
|
||||
.thenBy { it.x }
|
||||
.thenBy { it.y }
|
||||
)
|
||||
tiles.sortWith(compareBy<Location> {
|
||||
distanceSquared(
|
||||
it, attacker.location
|
||||
)
|
||||
}.thenBy { distanceSquaredToClosestOccupiedTile(target, targetLocation, it) }.thenBy { it.x }.thenBy { it.y })
|
||||
val attackTiles = ArrayList<Location>(minOf(MAX_RANGED_APPROACH_CANDIDATES, tiles.size))
|
||||
RsmodPathfinder.loadLineOfSightWindow(targetLocation)
|
||||
for (tile in tiles) {
|
||||
if (!hasProjectileLineOfSight(
|
||||
tile,
|
||||
attacker.size(),
|
||||
target,
|
||||
targetLocation,
|
||||
loadWindow = false,
|
||||
trace = trace
|
||||
tile, attacker.size(), target, targetLocation, loadWindow = false, trace = trace
|
||||
)
|
||||
) {
|
||||
continue
|
||||
|
|
@ -595,8 +553,9 @@ object CombatMovementIntents {
|
|||
if (distance == 1 && !isAdjacentToTarget(attacker, attackerLocation, target, targetLocation)) {
|
||||
return false
|
||||
}
|
||||
if (distance > 1 &&
|
||||
distanceSquaredToClosestOccupiedTile(target, targetLocation, attackerLocation) > distance * distance
|
||||
if (distance > 1 && distanceSquaredToClosestOccupiedTile(
|
||||
target, targetLocation, attackerLocation
|
||||
) > distance * distance
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
|
@ -621,20 +580,18 @@ object CombatMovementIntents {
|
|||
playerAttackRange(attacker)
|
||||
} else {
|
||||
CombatReach.combatDistance(
|
||||
attacker,
|
||||
target,
|
||||
if (attacker.properties.combatPulse.style == CombatStyle.MAGIC) 10 else 7
|
||||
attacker, target, if (attacker.properties.combatPulse.style == CombatStyle.MAGIC) 10 else 7
|
||||
)
|
||||
}
|
||||
return distanceSquaredToClosestOccupiedTile(target, targetLocation, attackerLocation) <= range * range &&
|
||||
hasProjectileLineOfSight(attackerLocation, attacker.size(), target, targetLocation, trace = trace)
|
||||
return distanceSquaredToClosestOccupiedTile(
|
||||
target, targetLocation, attackerLocation
|
||||
) <= range * range && hasProjectileLineOfSight(
|
||||
attackerLocation, attacker.size(), target, targetLocation, trace = trace
|
||||
)
|
||||
}
|
||||
|
||||
private fun isAdjacentToTarget(
|
||||
attacker: Entity,
|
||||
attackerLocation: Location,
|
||||
target: Entity,
|
||||
targetLocation: Location
|
||||
attacker: Entity, attackerLocation: Location, target: Entity, targetLocation: Location
|
||||
): Boolean {
|
||||
for (i in 0 until attacker.size()) {
|
||||
if (Pathfinder.isStandingIn(
|
||||
|
|
@ -708,19 +665,11 @@ object CombatMovementIntents {
|
|||
val maxRaySteps = if (checkClose) 1 else Int.MAX_VALUE
|
||||
if (!loadWindow) {
|
||||
return RsmodPathfinder.hasLineOfSightBetweenLoaded(
|
||||
attackerLocation,
|
||||
attackerSize,
|
||||
targetLocation,
|
||||
target.size(),
|
||||
maxRaySteps = maxRaySteps
|
||||
attackerLocation, attackerSize, targetLocation, target.size(), maxRaySteps = maxRaySteps
|
||||
)
|
||||
}
|
||||
return RsmodPathfinder.hasLineOfSightBetween(
|
||||
attackerLocation,
|
||||
attackerSize,
|
||||
targetLocation,
|
||||
target.size(),
|
||||
maxRaySteps = maxRaySteps
|
||||
attackerLocation, attackerSize, targetLocation, target.size(), maxRaySteps = maxRaySteps
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -746,11 +695,7 @@ object CombatMovementIntents {
|
|||
}
|
||||
|
||||
private fun pathToTargetFootprint(
|
||||
attacker: Entity,
|
||||
target: Entity,
|
||||
targetLocation: Location,
|
||||
pathfinder: Pathfinder,
|
||||
trace: IntentTrace
|
||||
attacker: Entity, target: Entity, targetLocation: Location, pathfinder: Pathfinder, trace: IntentTrace
|
||||
): CandidatePath? {
|
||||
val targetMoving = CombatMovementPlanner.hasMovementStepThisTick(target)
|
||||
val movingTargetPath =
|
||||
|
|
@ -798,20 +743,14 @@ object CombatMovementIntents {
|
|||
return null
|
||||
}
|
||||
if (attacker is Player && !path.isMoveNear && isExcessiveCombatDetourToTarget(
|
||||
attacker,
|
||||
target,
|
||||
targetLocation,
|
||||
path
|
||||
attacker, target, targetLocation, path
|
||||
)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
val steps = immediateMovementSteps(attacker, path)
|
||||
if (attacker is Player && path.isMoveNear && !partialPathMovesCloserToTarget(
|
||||
attacker,
|
||||
target,
|
||||
targetLocation,
|
||||
steps
|
||||
attacker, target, targetLocation, steps
|
||||
)
|
||||
) {
|
||||
return null
|
||||
|
|
@ -819,9 +758,7 @@ object CombatMovementIntents {
|
|||
if (attacker is Player && steps.any {
|
||||
!RegionManager.isTeleportPermitted(
|
||||
Location.create(
|
||||
it.x,
|
||||
it.y,
|
||||
attacker.location.z
|
||||
it.x, it.y, attacker.location.z
|
||||
)
|
||||
)
|
||||
}) {
|
||||
|
|
@ -832,9 +769,7 @@ object CombatMovementIntents {
|
|||
}
|
||||
|
||||
private fun directPathTowardMovingTarget(
|
||||
attacker: Entity,
|
||||
target: Entity,
|
||||
targetLocation: Location
|
||||
attacker: Entity, target: Entity, targetLocation: Location
|
||||
): CandidatePath? {
|
||||
if (!CombatMovementPlanner.hasMovementStepThisTick(target)) {
|
||||
return null
|
||||
|
|
@ -904,10 +839,7 @@ object CombatMovementIntents {
|
|||
}
|
||||
|
||||
private fun meleeDestination(
|
||||
attacker: Entity,
|
||||
target: Entity,
|
||||
targetLocation: Location,
|
||||
direction: Direction
|
||||
attacker: Entity, target: Entity, targetLocation: Location, direction: Direction
|
||||
): Location {
|
||||
val minAlignedX = targetLocation.x - attacker.size() + 1
|
||||
val maxAlignedX = targetLocation.x + target.size() - 1
|
||||
|
|
@ -941,83 +873,14 @@ object CombatMovementIntents {
|
|||
}
|
||||
}
|
||||
|
||||
private fun addDumbNpcAttackDestinations(
|
||||
attacker: NPC,
|
||||
target: Entity,
|
||||
pathfinder: Pathfinder,
|
||||
destinations: MutableList<MovementDestination>
|
||||
) {
|
||||
val directions = ArrayList<Direction>(3)
|
||||
addDirection(directions, Direction.getLogicalDirection(target.centerLocation, attacker.centerLocation))
|
||||
|
||||
if (attacker.centerLocation.x < target.centerLocation.x) {
|
||||
addDirection(directions, Direction.WEST)
|
||||
} else if (attacker.centerLocation.x > target.centerLocation.x) {
|
||||
addDirection(directions, Direction.EAST)
|
||||
}
|
||||
if (attacker.centerLocation.y < target.centerLocation.y) {
|
||||
addDirection(directions, Direction.SOUTH)
|
||||
} else if (attacker.centerLocation.y > target.centerLocation.y) {
|
||||
addDirection(directions, Direction.NORTH)
|
||||
}
|
||||
|
||||
for (direction in directions) {
|
||||
destinations.add(
|
||||
MovementDestination(
|
||||
dumbNpcAttackDestination(attacker, target, direction),
|
||||
pathfinder,
|
||||
allowPartialPath = true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addDirection(directions: MutableList<Direction>, direction: Direction) {
|
||||
if (!directions.contains(direction)) {
|
||||
directions.add(direction)
|
||||
}
|
||||
}
|
||||
|
||||
private fun dumbNpcAttackDestination(attacker: NPC, target: Entity, direction: Direction): Location {
|
||||
val targetLocation = target.location
|
||||
val minAlignedX = targetLocation.x - attacker.size() + 1
|
||||
val maxAlignedX = targetLocation.x + target.size() - 1
|
||||
val minAlignedY = targetLocation.y - attacker.size() + 1
|
||||
val maxAlignedY = targetLocation.y + target.size() - 1
|
||||
|
||||
return when (direction) {
|
||||
Direction.WEST -> Location.create(
|
||||
targetLocation.x - attacker.size(),
|
||||
attacker.location.y.coerceIn(minAlignedY, maxAlignedY),
|
||||
targetLocation.z
|
||||
)
|
||||
|
||||
Direction.EAST -> Location.create(
|
||||
targetLocation.x + target.size(),
|
||||
attacker.location.y.coerceIn(minAlignedY, maxAlignedY),
|
||||
targetLocation.z
|
||||
)
|
||||
|
||||
Direction.SOUTH -> Location.create(
|
||||
attacker.location.x.coerceIn(minAlignedX, maxAlignedX),
|
||||
targetLocation.y - attacker.size(),
|
||||
targetLocation.z
|
||||
)
|
||||
|
||||
else -> Location.create(
|
||||
attacker.location.x.coerceIn(minAlignedX, maxAlignedX),
|
||||
targetLocation.y + target.size(),
|
||||
targetLocation.z
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun truncateAtFirstAttackOpportunity(
|
||||
attacker: Entity,
|
||||
target: Entity,
|
||||
targetLocation: Location,
|
||||
path: CandidatePath,
|
||||
trace: IntentTrace
|
||||
attacker: Entity, target: Entity, targetLocation: Location, path: CandidatePath, trace: IntentTrace
|
||||
): CandidatePath? {
|
||||
val steps = ArrayList<Point>(path.steps.size)
|
||||
for (step in path.steps) {
|
||||
|
|
@ -1034,30 +897,20 @@ object CombatMovementIntents {
|
|||
}
|
||||
|
||||
private fun pathTo(
|
||||
attacker: Entity,
|
||||
destination: MovementDestination,
|
||||
trace: IntentTrace,
|
||||
queueContinuation: Boolean = false
|
||||
attacker: Entity, destination: MovementDestination, trace: IntentTrace, queueContinuation: Boolean = false
|
||||
): CandidatePath? {
|
||||
if (attacker.location == destination.location) {
|
||||
return null
|
||||
}
|
||||
val stepLimit = movementStepLimit(attacker, queueContinuation && !destination.allowPartialPath)
|
||||
if (attacker is NPC && destination.pathfinder === Pathfinder.DUMB) {
|
||||
val directPath = directPartialPathTo(attacker, destination, stepLimit)
|
||||
if (directPath != null) {
|
||||
trace.directPathHits++
|
||||
return directPath
|
||||
}
|
||||
return null
|
||||
}
|
||||
val directPath = directPathTo(attacker, destination, stepLimit)
|
||||
if (directPath != null) {
|
||||
trace.directPathHits++
|
||||
return directPath
|
||||
}
|
||||
if (destination.pathfinder === Pathfinder.SMART &&
|
||||
!RsmodPathfinder.canAttempt(attacker.location, destination.location)
|
||||
if (destination.pathfinder === Pathfinder.SMART && !RsmodPathfinder.canAttempt(
|
||||
attacker.location, destination.location
|
||||
)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
|
@ -1068,18 +921,14 @@ object CombatMovementIntents {
|
|||
return null
|
||||
}
|
||||
if (attacker is Player && !destination.allowPartialPath && isExcessiveCombatDetour(
|
||||
attacker,
|
||||
destination,
|
||||
path
|
||||
attacker, destination, path
|
||||
)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
val steps = immediateMovementSteps(attacker, path, stepLimit)
|
||||
if (attacker is Player && destination.allowPartialPath && !partialPathMovesCloser(
|
||||
attacker,
|
||||
destination,
|
||||
steps
|
||||
attacker, destination, steps
|
||||
)
|
||||
) {
|
||||
return null
|
||||
|
|
@ -1087,9 +936,7 @@ object CombatMovementIntents {
|
|||
if (attacker is Player && steps.any {
|
||||
!RegionManager.isTeleportPermitted(
|
||||
Location.create(
|
||||
it.x,
|
||||
it.y,
|
||||
attacker.location.z
|
||||
it.x, it.y, attacker.location.z
|
||||
)
|
||||
)
|
||||
}) {
|
||||
|
|
@ -1100,9 +947,7 @@ object CombatMovementIntents {
|
|||
}
|
||||
|
||||
private fun directPathTo(
|
||||
attacker: Entity,
|
||||
destination: MovementDestination,
|
||||
maxSteps: Int = movementStepsFor(attacker)
|
||||
attacker: Entity, destination: MovementDestination, maxSteps: Int = movementStepsFor(attacker)
|
||||
): CandidatePath? {
|
||||
if (attacker.size() != 1 || destination.node !is Location || attacker.location.z != destination.location.z) {
|
||||
return null
|
||||
|
|
@ -1111,9 +956,7 @@ object CombatMovementIntents {
|
|||
}
|
||||
|
||||
private fun directPathTo(
|
||||
attacker: Entity,
|
||||
destination: Location,
|
||||
maxSteps: Int = movementStepsFor(attacker)
|
||||
attacker: Entity, destination: Location, maxSteps: Int = movementStepsFor(attacker)
|
||||
): CandidatePath? {
|
||||
if (attacker.size() != 1 || attacker.location.z != destination.z) {
|
||||
return null
|
||||
|
|
@ -1145,41 +988,8 @@ object CombatMovementIntents {
|
|||
return CandidatePath(steps, projected)
|
||||
}
|
||||
|
||||
private fun directPartialPathTo(
|
||||
attacker: Entity,
|
||||
destination: MovementDestination,
|
||||
maxSteps: Int
|
||||
): CandidatePath? {
|
||||
if (attacker.size() != 1 || destination.node !is Location || attacker.location.z != destination.location.z) {
|
||||
return null
|
||||
}
|
||||
val steps = ArrayList<Point>(maxSteps)
|
||||
var current = attacker.location
|
||||
var distance = 0
|
||||
while (current != destination.location && steps.size < maxSteps) {
|
||||
if (++distance > MAX_DIRECT_COMBAT_PATH_DISTANCE) {
|
||||
break
|
||||
}
|
||||
val direction = Direction.getDirection(current, destination.location) ?: break
|
||||
if (!direction.canMoveFrom(current.z, current.x, current.y, RegionManager::getClippingFlag)) {
|
||||
break
|
||||
}
|
||||
val next = current.transform(direction)
|
||||
if (!RegionManager.isTeleportPermitted(next)) {
|
||||
break
|
||||
}
|
||||
steps.add(Point(next.x, next.y, direction, direction.stepX, direction.stepY))
|
||||
current = next
|
||||
}
|
||||
val projected = steps.lastOrNull()?.let { Location.create(it.x, it.y, attacker.location.z) } ?: return null
|
||||
return CandidatePath(steps, projected)
|
||||
}
|
||||
|
||||
private fun isExcessiveCombatDetourToTarget(
|
||||
attacker: Player,
|
||||
target: Entity,
|
||||
targetLocation: Location,
|
||||
path: Path
|
||||
attacker: Player, target: Entity, targetLocation: Location, path: Path
|
||||
): Boolean {
|
||||
val pathLength = (path.points.size - 1).coerceAtLeast(0)
|
||||
val directDistance =
|
||||
|
|
@ -1188,14 +998,12 @@ object CombatMovementIntents {
|
|||
}
|
||||
|
||||
private fun partialPathMovesCloserToTarget(
|
||||
attacker: Player,
|
||||
target: Entity,
|
||||
targetLocation: Location,
|
||||
steps: List<Point>
|
||||
attacker: Player, target: Entity, targetLocation: Location, steps: List<Point>
|
||||
): Boolean {
|
||||
val projected = steps.lastOrNull()?.let { Location.create(it.x, it.y, attacker.location.z) } ?: return false
|
||||
return distanceSquaredToClosestOccupiedTile(target, targetLocation, projected) <
|
||||
distanceSquaredToClosestOccupiedTile(target, targetLocation, attacker.location)
|
||||
return distanceSquaredToClosestOccupiedTile(
|
||||
target, targetLocation, projected
|
||||
) < distanceSquaredToClosestOccupiedTile(target, targetLocation, attacker.location)
|
||||
}
|
||||
|
||||
private fun isExcessiveCombatDetour(attacker: Player, destination: MovementDestination, path: Path): Boolean {
|
||||
|
|
@ -1205,9 +1013,7 @@ object CombatMovementIntents {
|
|||
}
|
||||
|
||||
private fun partialPathMovesCloser(
|
||||
attacker: Player,
|
||||
destination: MovementDestination,
|
||||
steps: List<Point>
|
||||
attacker: Player, destination: MovementDestination, steps: List<Point>
|
||||
): Boolean {
|
||||
val projected = steps.lastOrNull()?.let { Location.create(it.x, it.y, attacker.location.z) } ?: return false
|
||||
return projected.getDistance(destination.location) < attacker.location.getDistance(destination.location)
|
||||
|
|
@ -1222,9 +1028,7 @@ object CombatMovementIntents {
|
|||
}
|
||||
|
||||
private fun immediateMovementSteps(
|
||||
attacker: Entity,
|
||||
path: Path,
|
||||
maxSteps: Int = movementStepsFor(attacker)
|
||||
attacker: Entity, path: Path, maxSteps: Int = movementStepsFor(attacker)
|
||||
): List<Point> {
|
||||
val steps = ArrayList<Point>(maxSteps)
|
||||
for (point in path.points) {
|
||||
|
|
@ -1271,9 +1075,7 @@ object CombatMovementIntents {
|
|||
}
|
||||
|
||||
private fun shouldStopUnreachableCombat(
|
||||
attacker: Entity,
|
||||
target: Entity,
|
||||
exhaustedLocalApproach: Boolean = false
|
||||
attacker: Entity, target: Entity, exhaustedLocalApproach: Boolean = false
|
||||
): Boolean {
|
||||
return attacker is Player && (exhaustedLocalApproach || !CombatMovementPlanner.hasMovementStepThisTick(target))
|
||||
}
|
||||
|
|
@ -1318,15 +1120,9 @@ object CombatMovementIntents {
|
|||
}
|
||||
|
||||
private fun occupiedTilesOverlap(
|
||||
first: Entity,
|
||||
firstLocation: Location,
|
||||
second: Entity,
|
||||
secondLocation: Location
|
||||
first: Entity, firstLocation: Location, second: Entity, secondLocation: Location
|
||||
): Boolean {
|
||||
return firstLocation.x < secondLocation.x + second.size() &&
|
||||
firstLocation.x + first.size() > secondLocation.x &&
|
||||
firstLocation.y < secondLocation.y + second.size() &&
|
||||
firstLocation.y + first.size() > secondLocation.y
|
||||
return firstLocation.x < secondLocation.x + second.size() && firstLocation.x + first.size() > secondLocation.x && firstLocation.y < secondLocation.y + second.size() && firstLocation.y + first.size() > secondLocation.y
|
||||
}
|
||||
|
||||
private fun pathfinderFor(attacker: Entity): Pathfinder {
|
||||
|
|
|
|||
|
|
@ -14,16 +14,14 @@ import core.game.world.map.path.Pathfinder
|
|||
*/
|
||||
object CombatMovementPlanner {
|
||||
data class Plan(
|
||||
val targetLocation: Location,
|
||||
val attackTile: Location?
|
||||
val targetLocation: Location, val attackTile: Location?
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
fun plan(attacker: Entity, target: Entity): Plan {
|
||||
val targetLocation = predictedTargetLocation(target)
|
||||
return Plan(
|
||||
targetLocation = targetLocation,
|
||||
attackTile = chooseTargetBorderTile(attacker, target, targetLocation)
|
||||
targetLocation = targetLocation, attackTile = chooseTargetBorderTile(attacker, target, targetLocation)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -125,14 +123,16 @@ object CombatMovementPlanner {
|
|||
val candidates = borderTiles(target, targetLocation, attacker.size())
|
||||
val walkable = candidates.filter { RegionManager.isTeleportPermitted(it) }
|
||||
val attackable = walkable.filter { canInteractFrom(attacker, it, target, targetLocation) }
|
||||
return (attackable.ifEmpty { walkable.ifEmpty { candidates } }).sortedWith(
|
||||
compareBy<Location> { it.getDistance(attacker.location) }
|
||||
.thenBy { it.x }
|
||||
.thenBy { it.y }
|
||||
)
|
||||
return (attackable.ifEmpty { walkable.ifEmpty { candidates } }).sortedWith(compareBy<Location> {
|
||||
it.getDistance(
|
||||
attacker.location
|
||||
)
|
||||
}.thenBy { it.x }.thenBy { it.y })
|
||||
}
|
||||
|
||||
private fun canInteractFrom(attacker: Entity, location: Location, target: Entity, targetLocation: Location): Boolean {
|
||||
private fun canInteractFrom(
|
||||
attacker: Entity, location: Location, target: Entity, targetLocation: Location
|
||||
): Boolean {
|
||||
if (attacker.size() == 1 && target.size() == 1) {
|
||||
val direction = Direction.getDirection(location, targetLocation) ?: return false
|
||||
return direction.canMoveFrom(location.z, location.x, location.y, RegionManager::getClippingFlag)
|
||||
|
|
|
|||
|
|
@ -8,14 +8,7 @@ import core.game.world.map.Direction
|
|||
import core.game.world.map.Location
|
||||
import core.game.world.map.RegionManager.getClippingFlag
|
||||
import core.game.world.map.path.Pathfinder
|
||||
import core.game.world.map.path.Pathfinder.PREVENT_EAST
|
||||
import core.game.world.map.path.Pathfinder.PREVENT_NORTH
|
||||
import core.game.world.map.path.Pathfinder.PREVENT_NORTHEAST
|
||||
import core.game.world.map.path.Pathfinder.PREVENT_NORTHWEST
|
||||
import core.game.world.map.path.Pathfinder.PREVENT_SOUTH
|
||||
import core.game.world.map.path.Pathfinder.PREVENT_SOUTHEAST
|
||||
import core.game.world.map.path.Pathfinder.PREVENT_SOUTHWEST
|
||||
import core.game.world.map.path.Pathfinder.PREVENT_WEST
|
||||
import core.game.world.map.path.Pathfinder.*
|
||||
|
||||
/**
|
||||
* Shared combat reach calculations.
|
||||
|
|
@ -69,14 +62,13 @@ object CombatReach {
|
|||
return true
|
||||
}
|
||||
}
|
||||
return victim.getSwingHandler(false).type == CombatStyle.MELEE &&
|
||||
e.withinDistance(victim.location, 1) &&
|
||||
victim.properties.combatPulse.getVictim() === entity &&
|
||||
entity.index < victim.index
|
||||
return victim.getSwingHandler(false).type == CombatStyle.MELEE && e.withinDistance(
|
||||
victim.location,
|
||||
1
|
||||
) && victim.properties.combatPulse.getVictim() === entity && entity.index < victim.index
|
||||
}
|
||||
return entity.centerLocation.withinDistance(
|
||||
victim.centerLocation,
|
||||
distance + (size shr 1) + (victim.size() shr 1)
|
||||
victim.centerLocation, distance + (size shr 1) + (victim.size() shr 1)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -111,8 +103,7 @@ object CombatReach {
|
|||
fun canStepTowards(entity: Entity, victim: Entity): InteractionType {
|
||||
val closestVictimTile = victim.getClosestOccupiedTile(entity.location)
|
||||
val closestEntityTile = entity.getClosestOccupiedTile(closestVictimTile)
|
||||
val dir = closestEntityTile.deriveDirection(closestVictimTile)
|
||||
?: return InteractionType.STILL_INTERACT
|
||||
val dir = closestEntityTile.deriveDirection(closestVictimTile) ?: return InteractionType.STILL_INTERACT
|
||||
var next = closestEntityTile
|
||||
|
||||
// A fixed-direction walk can pass beside an oblique target without converging, so
|
||||
|
|
@ -151,30 +142,30 @@ object CombatReach {
|
|||
Direction.WEST -> if (getClippingFlag(next) and PREVENT_WEST != 0) return InteractionType.NO_INTERACT
|
||||
|
||||
Direction.NORTH_EAST -> {
|
||||
if (getClippingFlag(components[0]) and PREVENT_EAST != 0 ||
|
||||
getClippingFlag(components[1]) and PREVENT_NORTH != 0 ||
|
||||
getClippingFlag(next) and PREVENT_NORTHEAST != 0
|
||||
if (getClippingFlag(components[0]) and PREVENT_EAST != 0 || getClippingFlag(components[1]) and PREVENT_NORTH != 0 || getClippingFlag(
|
||||
next
|
||||
) and PREVENT_NORTHEAST != 0
|
||||
) return InteractionType.NO_INTERACT
|
||||
}
|
||||
|
||||
Direction.NORTH_WEST -> {
|
||||
if (getClippingFlag(components[0]) and PREVENT_WEST != 0 ||
|
||||
getClippingFlag(components[1]) and PREVENT_NORTH != 0 ||
|
||||
getClippingFlag(next) and PREVENT_NORTHWEST != 0
|
||||
if (getClippingFlag(components[0]) and PREVENT_WEST != 0 || getClippingFlag(components[1]) and PREVENT_NORTH != 0 || getClippingFlag(
|
||||
next
|
||||
) and PREVENT_NORTHWEST != 0
|
||||
) return InteractionType.NO_INTERACT
|
||||
}
|
||||
|
||||
Direction.SOUTH_EAST -> {
|
||||
if (getClippingFlag(components[0]) and PREVENT_EAST != 0 ||
|
||||
getClippingFlag(components[1]) and PREVENT_SOUTH != 0 ||
|
||||
getClippingFlag(next) and PREVENT_SOUTHEAST != 0
|
||||
if (getClippingFlag(components[0]) and PREVENT_EAST != 0 || getClippingFlag(components[1]) and PREVENT_SOUTH != 0 || getClippingFlag(
|
||||
next
|
||||
) and PREVENT_SOUTHEAST != 0
|
||||
) return InteractionType.NO_INTERACT
|
||||
}
|
||||
|
||||
Direction.SOUTH_WEST -> {
|
||||
if (getClippingFlag(components[0]) and PREVENT_WEST != 0 ||
|
||||
getClippingFlag(components[1]) and PREVENT_SOUTH != 0 ||
|
||||
getClippingFlag(next) and PREVENT_SOUTHWEST != 0
|
||||
if (getClippingFlag(components[0]) and PREVENT_WEST != 0 || getClippingFlag(components[1]) and PREVENT_SOUTH != 0 || getClippingFlag(
|
||||
next
|
||||
) and PREVENT_SOUTHWEST != 0
|
||||
) return InteractionType.NO_INTERACT
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -556,20 +556,25 @@ public class NPC extends Entity {
|
|||
path.setSuccesful(true);
|
||||
Location current = getLocation();
|
||||
int maxSteps = Math.min(14, Math.max(1, getSpawnReturnDistance(Math.max(1, getWalkRadius()))));
|
||||
boolean usedSidestep = false;
|
||||
for (int steps = 0; !current.equals(destination) && steps < maxSteps; steps++) {
|
||||
Direction direction = Direction.getDirection(current, destination);
|
||||
if (direction == null || !canTakeRandomMovementStep(current, direction)) {
|
||||
Direction stepDirection = chooseRandomMovementStep(current, direction, usedSidestep);
|
||||
if (stepDirection == null) {
|
||||
path.setSuccesful(false);
|
||||
path.setMoveNear(!path.getPoints().isEmpty());
|
||||
break;
|
||||
}
|
||||
Location next = current.transform(direction);
|
||||
Location next = current.transform(stepDirection);
|
||||
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()));
|
||||
if (stepDirection != direction) {
|
||||
usedSidestep = true;
|
||||
}
|
||||
path.getPoints().add(new Point(next.getX(), next.getY(), stepDirection, stepDirection.getStepX(), stepDirection.getStepY()));
|
||||
current = next;
|
||||
}
|
||||
if (!current.equals(destination) && !path.getPoints().isEmpty()) {
|
||||
|
|
@ -578,6 +583,47 @@ public class NPC extends Entity {
|
|||
return path;
|
||||
}
|
||||
|
||||
private Direction chooseRandomMovementStep(Location current, Direction direction, boolean usedSidestep) {
|
||||
if (direction == null) {
|
||||
return null;
|
||||
}
|
||||
Location next = current.transform(direction);
|
||||
if (isWithinRandomMovementBounds(next) && canTakeRandomMovementStep(current, direction)) {
|
||||
return direction;
|
||||
}
|
||||
if (usedSidestep) {
|
||||
return null;
|
||||
}
|
||||
for (Direction sidestep : sidestepDirections(direction)) {
|
||||
next = current.transform(sidestep);
|
||||
if (isWithinRandomMovementBounds(next) && canTakeRandomMovementStep(current, sidestep)) {
|
||||
return sidestep;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Direction[] sidestepDirections(Direction direction) {
|
||||
switch (direction) {
|
||||
case NORTH:
|
||||
case SOUTH:
|
||||
return new Direction[] { Direction.EAST, Direction.WEST };
|
||||
case EAST:
|
||||
case WEST:
|
||||
return new Direction[] { Direction.NORTH, Direction.SOUTH };
|
||||
case NORTH_EAST:
|
||||
return new Direction[] { Direction.EAST, Direction.NORTH };
|
||||
case SOUTH_EAST:
|
||||
return new Direction[] { Direction.EAST, Direction.SOUTH };
|
||||
case SOUTH_WEST:
|
||||
return new Direction[] { Direction.WEST, Direction.SOUTH };
|
||||
case NORTH_WEST:
|
||||
return new Direction[] { Direction.WEST, Direction.NORTH };
|
||||
default:
|
||||
return new Direction[0];
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canTakeRandomMovementStep(Location current, Direction direction) {
|
||||
ClipMaskSupplier clipMaskSupplier = behavior != null ? behavior.getClippingSupplier(this) : null;
|
||||
if (clipMaskSupplier == null) {
|
||||
|
|
|
|||
478
Server/src/main/core/game/world/map/path/DumbPathfinder.java
Normal file
478
Server/src/main/core/game/world/map/path/DumbPathfinder.java
Normal file
|
|
@ -0,0 +1,478 @@
|
|||
package core.game.world.map.path;
|
||||
|
||||
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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Pathfinder for simple local movement. It walks directly toward the
|
||||
* destination and only tries the horizontal/vertical alternatives of a blocked
|
||||
* diagonal step; it does not search around obstacles.
|
||||
*/
|
||||
public final class DumbPathfinder extends Pathfinder {
|
||||
|
||||
private boolean found;
|
||||
private int z;
|
||||
private int x;
|
||||
private int y;
|
||||
|
||||
@Override
|
||||
public Path find(Location start,
|
||||
int size,
|
||||
Location end,
|
||||
int sizeX,
|
||||
int sizeY,
|
||||
int rotation,
|
||||
int type,
|
||||
int walkingFlag,
|
||||
boolean near,
|
||||
ClipMaskSupplier clipMaskSupplier) {
|
||||
ClipMaskSupplier supplier = clipMaskSupplier != null ? clipMaskSupplier : RegionManager::getClippingFlag;
|
||||
Path path = new Path();
|
||||
z = start.getZ();
|
||||
x = start.getX();
|
||||
y = start.getY();
|
||||
List<Point> points = new ArrayList<>(20);
|
||||
path.setSuccesful(true);
|
||||
while (x != end.getX() || y != end.getY()) {
|
||||
Direction[] directions = getDirection(x, y, end);
|
||||
if (type >= 0) {
|
||||
if ((type < 5 || type == 9) && canDoorInteract(x, y, size, end.getX(), end.getY(), type, rotation, z, supplier)) {
|
||||
break;
|
||||
}
|
||||
if (type < 10 && canDecorationInteract(x, y, size, end.getX(), end.getY(), rotation, type, z, supplier)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sizeX != 0 && sizeY != 0) {
|
||||
if (canInteract(x, y, size, end.getX(), end.getY(), sizeX, sizeY, walkingFlag, z, supplier)) {
|
||||
break;
|
||||
}
|
||||
if (directions.length > 1) {
|
||||
Direction dir = directions[0];
|
||||
if (x + dir.getStepX() == end.getX() && y + dir.getStepY() == end.getY()) {
|
||||
directions[0] = directions[directions.length - 1];
|
||||
directions[directions.length - 1] = dir;
|
||||
}
|
||||
}
|
||||
}
|
||||
found = true;
|
||||
if (size < 2) {
|
||||
checkSingleTraversal(points, supplier, directions);
|
||||
} else if (size == 2) {
|
||||
checkDoubleTraversal(points, supplier, directions);
|
||||
} else {
|
||||
checkVariableTraversal(points, directions, size, supplier);
|
||||
}
|
||||
if (!found) {
|
||||
path.setMoveNear(x != start.getX() || y != start.getY());
|
||||
path.setSuccesful(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
path.getPoints().addAll(points);
|
||||
return path;
|
||||
}
|
||||
|
||||
private void checkSingleTraversal(List<Point> points, ClipMaskSupplier clipMaskSupplier, Direction... directions) {
|
||||
for (Direction dir : directions) {
|
||||
found = true;
|
||||
switch (dir) {
|
||||
case NORTH:
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x, y + 1) & PREVENT_NORTH) != 0) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
points.add(new Point(x, y + 1, dir));
|
||||
y++;
|
||||
break;
|
||||
case NORTH_EAST:
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x + 1, y) & PREVENT_EAST) != 0 || (clipMaskSupplier.getClippingFlag(z,
|
||||
x,
|
||||
y + 1) & PREVENT_NORTH) != 0 || (clipMaskSupplier.getClippingFlag(
|
||||
z,
|
||||
x + 1,
|
||||
y + 1) & PREVENT_NORTHEAST) != 0) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
points.add(new Point(x + 1, y + 1, dir));
|
||||
x++;
|
||||
y++;
|
||||
break;
|
||||
case EAST:
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x + 1, y) & PREVENT_EAST) != 0) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
points.add(new Point(x + 1, y, dir));
|
||||
x++;
|
||||
break;
|
||||
case SOUTH_EAST:
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x + 1, y) & PREVENT_EAST) != 0 || (clipMaskSupplier.getClippingFlag(z,
|
||||
x,
|
||||
y - 1) & PREVENT_SOUTH) != 0 || (clipMaskSupplier.getClippingFlag(
|
||||
z,
|
||||
x + 1,
|
||||
y - 1) & PREVENT_SOUTHEAST) != 0) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
points.add(new Point(x + 1, y - 1, dir));
|
||||
x++;
|
||||
y--;
|
||||
break;
|
||||
case SOUTH:
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x, y - 1) & PREVENT_SOUTH) != 0) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
points.add(new Point(x, y - 1, dir));
|
||||
y--;
|
||||
break;
|
||||
case SOUTH_WEST:
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x - 1, y) & PREVENT_WEST) != 0 || (clipMaskSupplier.getClippingFlag(z,
|
||||
x,
|
||||
y - 1) & PREVENT_SOUTH) != 0 || (clipMaskSupplier.getClippingFlag(
|
||||
z,
|
||||
x - 1,
|
||||
y - 1) & PREVENT_SOUTHWEST) != 0) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
points.add(new Point(x - 1, y - 1, dir));
|
||||
x--;
|
||||
y--;
|
||||
break;
|
||||
case WEST:
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x - 1, y) & PREVENT_WEST) != 0) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
points.add(new Point(x - 1, y, dir));
|
||||
x--;
|
||||
break;
|
||||
case NORTH_WEST:
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x - 1, y) & PREVENT_WEST) != 0 || (clipMaskSupplier.getClippingFlag(z,
|
||||
x,
|
||||
y + 1) & PREVENT_NORTH) != 0 || (clipMaskSupplier.getClippingFlag(
|
||||
z,
|
||||
x - 1,
|
||||
y + 1) & PREVENT_NORTHWEST) != 0) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
points.add(new Point(x - 1, y + 1, dir));
|
||||
x--;
|
||||
y++;
|
||||
break;
|
||||
}
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkDoubleTraversal(List<Point> points, ClipMaskSupplier clipMaskSupplier, Direction... directions) {
|
||||
for (Direction dir : directions) {
|
||||
found = true;
|
||||
switch (dir) {
|
||||
case NORTH:
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x, y + 2) & 0x12c0138) != 0 || (clipMaskSupplier.getClippingFlag(z,
|
||||
x + 1,
|
||||
y + 2) & 0x12c01e0) != 0) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
points.add(new Point(x, y + 1, dir));
|
||||
y++;
|
||||
break;
|
||||
case NORTH_EAST:
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x + 1, y + 2) & 0x12c0138) != 0 || (clipMaskSupplier.getClippingFlag(z,
|
||||
x + 2,
|
||||
y + 2) & 0x12c01e0) != 0 || (clipMaskSupplier.getClippingFlag(
|
||||
z,
|
||||
x + 2,
|
||||
y + 1) & 0x12c0183) != 0) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
points.add(new Point(x + 1, y + 1, dir));
|
||||
x++;
|
||||
y++;
|
||||
break;
|
||||
case EAST:
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x + 2, y) & 0x12c0183) != 0 || (clipMaskSupplier.getClippingFlag(z,
|
||||
x + 2,
|
||||
y + 1) & 0x12c01e0) != 0) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
points.add(new Point(x + 1, y, dir));
|
||||
x++;
|
||||
break;
|
||||
case SOUTH_EAST:
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x + 1, y - 1) & 0x12c010e) != 0 || (clipMaskSupplier.getClippingFlag(z,
|
||||
x + 2,
|
||||
y) & 0x12c01e0) != 0 || (clipMaskSupplier.getClippingFlag(
|
||||
z,
|
||||
x + 2,
|
||||
y - 1) & 0x12c0183) != 0) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
points.add(new Point(x + 1, y - 1, dir));
|
||||
x++;
|
||||
y--;
|
||||
break;
|
||||
case SOUTH:
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x, y - 1) & 0x12c010e) != 0 || (clipMaskSupplier.getClippingFlag(z,
|
||||
x + 1,
|
||||
y - 1) & 0x12c0183) != 0) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
points.add(new Point(x, y - 1, dir));
|
||||
y--;
|
||||
break;
|
||||
case SOUTH_WEST:
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x - 1, y - 1) & 0x12c010e) != 0 || (clipMaskSupplier.getClippingFlag(z,
|
||||
x - 1,
|
||||
y) & 0x12c0138) != 0 || (clipMaskSupplier.getClippingFlag(
|
||||
z,
|
||||
x,
|
||||
y - 1) & 0x12c0183) != 0) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
points.add(new Point(x - 1, y - 1, dir));
|
||||
x--;
|
||||
y--;
|
||||
break;
|
||||
case WEST:
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x - 1, y) & 0x12c010e) != 0 || (clipMaskSupplier.getClippingFlag(z,
|
||||
x - 1,
|
||||
y + 1) & 0x12c0138) != 0) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
points.add(new Point(x - 1, y, dir));
|
||||
x--;
|
||||
break;
|
||||
case NORTH_WEST:
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x - 1, y + 1) & 0x12c010e) != 0 || (clipMaskSupplier.getClippingFlag(z,
|
||||
x - 1,
|
||||
y + 2) & 0x12c0138) != 0 || (clipMaskSupplier.getClippingFlag(
|
||||
z,
|
||||
x,
|
||||
y + 2) & 0x12c01e0) != 0) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
points.add(new Point(x - 1, y + 1, dir));
|
||||
x--;
|
||||
y++;
|
||||
break;
|
||||
}
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkVariableTraversal(List<Point> points, Direction[] directions, int size, ClipMaskSupplier clipMaskSupplier) {
|
||||
for (Direction dir : directions) {
|
||||
found = true;
|
||||
roar:
|
||||
switch (dir) {
|
||||
case NORTH:
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x, y + size) & 0x12c0138) != 0 || (clipMaskSupplier.getClippingFlag(z,
|
||||
x + (size - 1),
|
||||
y + size) & 0x12c01e0) != 0) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
for (int i = 1; i < size - 1; i++) {
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x + i, y + size) & 0x12c01f8) != 0) {
|
||||
found = false;
|
||||
break roar;
|
||||
}
|
||||
}
|
||||
points.add(new Point(x, y + 1, dir));
|
||||
y++;
|
||||
break;
|
||||
case NORTH_EAST:
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x + 1, y + size) & 0x12c0138) != 0 || (clipMaskSupplier.getClippingFlag(z,
|
||||
x + size,
|
||||
y + size) & 0x12c01e0) != 0 || (clipMaskSupplier.getClippingFlag(
|
||||
z,
|
||||
x + size,
|
||||
y + 1) & 0x12c0183) != 0) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
for (int i = 1; i < size - 1; i++) {
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x + (i + 1), y + size) & 0x12c01f8) != 0 || (clipMaskSupplier.getClippingFlag(z,
|
||||
x + size,
|
||||
y + (i + 1)) & 0x12c01e3) != 0) {
|
||||
found = false;
|
||||
break roar;
|
||||
}
|
||||
}
|
||||
points.add(new Point(x + 1, y + 1, dir));
|
||||
x++;
|
||||
y++;
|
||||
break;
|
||||
case EAST:
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x + size, y) & 0x12c0183) != 0 || (clipMaskSupplier.getClippingFlag(z,
|
||||
x + size,
|
||||
y + (size - 1)) & 0x12c01e0) != 0) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
for (int i = 1; i < size - 1; i++) {
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x + size, y + i) & 0x12c01e3) != 0) {
|
||||
found = false;
|
||||
break roar;
|
||||
}
|
||||
}
|
||||
points.add(new Point(x + 1, y, dir));
|
||||
x++;
|
||||
break;
|
||||
case SOUTH_EAST:
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x + 1, y - 1) & 0x12c010e) != 0 || (clipMaskSupplier.getClippingFlag(z,
|
||||
x + size,
|
||||
y + (size - 2)) & 0x12c01e0) != 0 || (clipMaskSupplier.getClippingFlag(
|
||||
z,
|
||||
x + size,
|
||||
y - 1) & 0x12c0183) != 0) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
for (int i = 1; i < size - 1; i++) {
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x + size, y + (i - 1)) & 0x12c01e3) != 0 || (clipMaskSupplier.getClippingFlag(z,
|
||||
x + (i + 1),
|
||||
y - 1) & 0x12c018f) != 0) {
|
||||
found = false;
|
||||
break roar;
|
||||
}
|
||||
}
|
||||
points.add(new Point(x + 1, y - 1, dir));
|
||||
x++;
|
||||
y--;
|
||||
break;
|
||||
case SOUTH:
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x, y - 1) & 0x12c010e) != 0 || (clipMaskSupplier.getClippingFlag(z,
|
||||
x + (size - 1),
|
||||
y - 1) & 0x12c0183) != 0) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
for (int i = 1; i < size - 1; i++) {
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x + i, y - 1) & 0x12c018f) != 0) {
|
||||
found = false;
|
||||
break roar;
|
||||
}
|
||||
}
|
||||
points.add(new Point(x, y - 1, dir));
|
||||
y--;
|
||||
break;
|
||||
case SOUTH_WEST:
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x - 1, y + (size - 2)) & 0x12c0138) != 0 || (clipMaskSupplier.getClippingFlag(z,
|
||||
x - 1,
|
||||
y - 1) & 0x12c010e) != 0 || (clipMaskSupplier.getClippingFlag(
|
||||
z,
|
||||
x + (size - 2),
|
||||
y - 1) & 0x12c0183) != 0) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
for (int i = 1; i < size - 1; i++) {
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x - 1, y + (i - 1)) & 0x12c013e) != 0 || (clipMaskSupplier.getClippingFlag(z,
|
||||
x + (i - 1),
|
||||
y - 1) & 0x12c018f) != 0) {
|
||||
found = false;
|
||||
break roar;
|
||||
}
|
||||
}
|
||||
points.add(new Point(x - 1, y - 1, dir));
|
||||
x--;
|
||||
y--;
|
||||
break;
|
||||
case WEST:
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x - 1, y) & 0x12c010e) != 0 || (clipMaskSupplier.getClippingFlag(z,
|
||||
x - 1,
|
||||
y + (size - 1)) & 0x12c0138) != 0) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
for (int i = 1; i < size - 1; i++) {
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x - 1, y + i) & 0x12c013e) != 0) {
|
||||
found = false;
|
||||
break roar;
|
||||
}
|
||||
}
|
||||
points.add(new Point(x - 1, y, dir));
|
||||
x--;
|
||||
break;
|
||||
case NORTH_WEST:
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x - 1, y + 1) & 0x12c010e) != 0 || (clipMaskSupplier.getClippingFlag(z,
|
||||
x - 1,
|
||||
y + size) & 0x12c0138) != 0 || (clipMaskSupplier.getClippingFlag(
|
||||
z,
|
||||
x,
|
||||
y + size) & 0x12c01e0) != 0) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
for (int i = 1; i < size - 1; i++) {
|
||||
if ((clipMaskSupplier.getClippingFlag(z, x - 1, y + (i + 1)) & 0x12c013e) != 0 || (clipMaskSupplier.getClippingFlag(z,
|
||||
x + (i - 1),
|
||||
y + size) & 0x12c01f8) != 0) {
|
||||
found = false;
|
||||
break roar;
|
||||
}
|
||||
}
|
||||
points.add(new Point(x - 1, y + 1, dir));
|
||||
x--;
|
||||
y++;
|
||||
break;
|
||||
}
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Direction[] getDirection(int startX, int startY, Location end) {
|
||||
int endX = end.getX();
|
||||
int endY = end.getY();
|
||||
if (startX == endX) {
|
||||
if (startY > endY) {
|
||||
return new Direction[]{Direction.SOUTH};
|
||||
} else if (startY < endY) {
|
||||
return new Direction[]{Direction.NORTH};
|
||||
}
|
||||
} else if (startY == endY) {
|
||||
if (startX > endX) {
|
||||
return new Direction[]{Direction.WEST};
|
||||
}
|
||||
return new Direction[]{Direction.EAST};
|
||||
} else {
|
||||
if (startX < endX && startY < endY) {
|
||||
return new Direction[]{Direction.NORTH_EAST, Direction.EAST, Direction.NORTH};
|
||||
} else if (startX < endX && startY > endY) {
|
||||
return new Direction[]{Direction.SOUTH_EAST, Direction.EAST, Direction.SOUTH};
|
||||
} else if (startX > endX && startY < endY) {
|
||||
return new Direction[]{Direction.NORTH_WEST, Direction.WEST, Direction.NORTH};
|
||||
} else if (startX > endX && startY > endY) {
|
||||
return new Direction[]{Direction.SOUTH_WEST, Direction.WEST, Direction.SOUTH};
|
||||
}
|
||||
}
|
||||
return new Direction[0];
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import core.game.world.map.Location;
|
|||
import core.game.world.map.RegionManager;
|
||||
|
||||
public abstract class Pathfinder {
|
||||
|
||||
public static final int PREVENT_NORTH = 0x12c0120;
|
||||
public static final int PREVENT_EAST = 0x12c0180;
|
||||
public static final int PREVENT_NORTHEAST = 0x12c01e0;
|
||||
|
|
@ -17,56 +18,67 @@ public abstract class Pathfinder {
|
|||
public static final int PREVENT_WEST = 0x12c0108;
|
||||
public static final int PREVENT_SOUTHWEST = 0x12c010e;
|
||||
public static final int PREVENT_NORTHWEST = 0x12c0138;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The smart path finder.
|
||||
*/
|
||||
public static final Pathfinder SMART = new RsmodPathfinder();
|
||||
|
||||
|
||||
/**
|
||||
* The dumb path finder.
|
||||
*/
|
||||
public static final Pathfinder DUMB = new RsmodPathfinder();
|
||||
|
||||
public static final Pathfinder DUMB = new DumbPathfinder();
|
||||
|
||||
/**
|
||||
* The projectile path finder.
|
||||
*/
|
||||
public static final Pathfinder PROJECTILE = new RsmodProjectilePathfinder();
|
||||
|
||||
|
||||
/**
|
||||
* Finds a path from the location to the end location.
|
||||
* @param location The start location.
|
||||
* @param size The mover size.
|
||||
* @param end The end location.
|
||||
* @param sizeX The x-size of the destination node.
|
||||
* @param sizeY The y-size of the destination node.
|
||||
* @param rotation The object rotation.
|
||||
* @param type The object type.
|
||||
*
|
||||
* @param location The start location.
|
||||
* @param size The mover size.
|
||||
* @param end The end location.
|
||||
* @param sizeX The x-size of the destination node.
|
||||
* @param sizeY The y-size of the destination node.
|
||||
* @param rotation The object rotation.
|
||||
* @param type The object type.
|
||||
* @param walkingFlag The object walking flag.
|
||||
* @param near If we should find the nearest location if a path can't be
|
||||
* found.
|
||||
* @param near If we should find the nearest location if a path can't be
|
||||
* found.
|
||||
* @return The path.
|
||||
*/
|
||||
public abstract Path find(Location location, int size, Location end, int sizeX, int sizeY, int rotation, int type, int walkingFlag, boolean near, ClipMaskSupplier clipMaskSupplier);
|
||||
|
||||
public abstract Path find(Location location,
|
||||
int size,
|
||||
Location end,
|
||||
int sizeX,
|
||||
int sizeY,
|
||||
int rotation,
|
||||
int type,
|
||||
int walkingFlag,
|
||||
boolean near,
|
||||
ClipMaskSupplier clipMaskSupplier);
|
||||
|
||||
/**
|
||||
* Finds a path from the start location to the end location.
|
||||
* @param mover The moving entity.
|
||||
*
|
||||
* @param mover The moving entity.
|
||||
* @param destination The destination node.
|
||||
* @return The path.
|
||||
*/
|
||||
public static Path find(Entity mover, Node destination) {
|
||||
return find(mover, destination, true, SMART);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds a path from the start location to the end location.
|
||||
* @param mover The moving entity.
|
||||
*
|
||||
* @param mover The moving entity.
|
||||
* @param destination The destination node.
|
||||
* @param near If we should move near the end location, if we can't reach
|
||||
* it.
|
||||
* @param finder The pathfinder to use.
|
||||
* @param near If we should move near the end location, if we can't reach
|
||||
* it.
|
||||
* @param finder The pathfinder to use.
|
||||
* @return The path.
|
||||
*/
|
||||
public static Path find(Entity mover, Node destination, boolean near, Pathfinder finder) {
|
||||
|
|
@ -77,46 +89,49 @@ public abstract class Pathfinder {
|
|||
}
|
||||
return find(mover.getLocation(), mover.size(), destination, near, finder, cms);
|
||||
}
|
||||
|
||||
public static Path findWater(Entity mover, Node destination, boolean near, Pathfinder finder){
|
||||
return find(mover.getLocation(),mover.size(),destination,near,finder, RegionManager::getWaterClipFlag);
|
||||
|
||||
public static Path findWater(Entity mover, Node destination, boolean near, Pathfinder finder) {
|
||||
return find(mover.getLocation(), mover.size(), destination, near, finder, RegionManager::getWaterClipFlag);
|
||||
}
|
||||
|
||||
|
||||
public static Path find(Entity mover, Node destination, boolean near, Pathfinder finder, ClipMaskSupplier clipMaskSupplier) {
|
||||
return find(mover.getLocation(), mover.size(), destination, near, finder, clipMaskSupplier);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds a path from the start location to the end location.
|
||||
*
|
||||
* @param destination The destination node.
|
||||
* @return The path.
|
||||
*/
|
||||
public static Path find(Location start, Node destination) {
|
||||
return find(start, destination, true, SMART);
|
||||
}
|
||||
|
||||
|
||||
public static Path find(Location start, Node destination, int moverSize) {
|
||||
return find(start, moverSize, destination, true, SMART, null);
|
||||
}
|
||||
|
||||
return find(start, moverSize, destination, true, SMART, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a path from the start location to the end location.
|
||||
*
|
||||
* @param destination The destination node.
|
||||
* @param near If we should move near the end location, if we can't reach
|
||||
* it.
|
||||
* @param finder The pathfinder to use.
|
||||
* @param near If we should move near the end location, if we can't reach
|
||||
* it.
|
||||
* @param finder The pathfinder to use.
|
||||
* @return The path.
|
||||
*/
|
||||
public static Path find(Location start, Node destination, boolean near, Pathfinder finder) {
|
||||
return find(start, 1, destination, near, finder, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds a path from the start location to the end location.
|
||||
*
|
||||
* @param destination The destination node.
|
||||
* @param near If we should move near the end location, if we can't reach
|
||||
* it.
|
||||
* @param finder The pathfinder to use.
|
||||
* @param near If we should move near the end location, if we can't reach
|
||||
* it.
|
||||
* @param finder The pathfinder to use.
|
||||
* @return The path.
|
||||
*/
|
||||
public static Path find(Location start, int moverSize, Node destination, boolean near, Pathfinder finder, ClipMaskSupplier clipMaskSupplier) {
|
||||
|
|
@ -125,7 +140,16 @@ public abstract class Pathfinder {
|
|||
int type = object.getType();
|
||||
int rotation = object.getRotation();
|
||||
if (type == 10 || type == 11 || type == 22) {
|
||||
return finder.find(start, moverSize, object.getLocation(), object.getDefinition().sizeX, object.getDefinition().sizeY, rotation, type, object.getDefinition().getWalkingFlag(), near, clipMaskSupplier);
|
||||
return finder.find(start,
|
||||
moverSize,
|
||||
object.getLocation(),
|
||||
object.getDefinition().sizeX,
|
||||
object.getDefinition().sizeY,
|
||||
rotation,
|
||||
type,
|
||||
object.getDefinition().getWalkingFlag(),
|
||||
near,
|
||||
clipMaskSupplier);
|
||||
}
|
||||
return finder.find(start, moverSize, object.getLocation(), 0, 0, rotation, type, 0, near, clipMaskSupplier);
|
||||
}
|
||||
|
|
@ -137,7 +161,7 @@ public abstract class Pathfinder {
|
|||
}
|
||||
return finder.find(start, moverSize, destination.getLocation(), size, size, 0, -1, 0, near, clipMaskSupplier);
|
||||
}
|
||||
|
||||
|
||||
private static Scenery getRouteScenery(Scenery object) {
|
||||
Scenery wrapper = object.getWrapper();
|
||||
if (wrapper == object) {
|
||||
|
|
@ -148,51 +172,70 @@ public abstract class Pathfinder {
|
|||
}
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
|
||||
private static int getFootprintArea(Scenery object) {
|
||||
return object.getDefinition().sizeX * object.getDefinition().sizeY;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if interaction with decoration is possible.
|
||||
* @param curX The current x-coordinate in viewport.
|
||||
* @param curY The current y-coordinate in viewport.
|
||||
* @param size The mover size.
|
||||
* @param destX The destination x-coordinate in viewport.
|
||||
* @param destY The destination y-coordinate in viewport.
|
||||
* @param type The object type.
|
||||
*
|
||||
* @param curX The current x-coordinate in viewport.
|
||||
* @param curY The current y-coordinate in viewport.
|
||||
* @param size The mover size.
|
||||
* @param destX The destination x-coordinate in viewport.
|
||||
* @param destY The destination y-coordinate in viewport.
|
||||
* @param type The object type.
|
||||
* @param rotation The object rotation.
|
||||
* @return {@code True} if so.
|
||||
*/
|
||||
public static boolean canDecorationInteract(int curX, int curY, int size, int destX, int destY, int rotation, int type, int z, ClipMaskSupplier clipMaskSupplier) {
|
||||
public static boolean canDecorationInteract(int curX,
|
||||
int curY,
|
||||
int size,
|
||||
int destX,
|
||||
int destY,
|
||||
int rotation,
|
||||
int type,
|
||||
int z,
|
||||
ClipMaskSupplier clipMaskSupplier) {
|
||||
return RsmodPathfinder.canReach(curX, curY, size, destX, destY, 1, 1, rotation, type, 0, z, clipMaskSupplier);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if interaction with a door is possible.
|
||||
* @param curX The current x-coordinate in viewport.
|
||||
* @param curY The current y-coordinate in viewport.
|
||||
* @param size The mover size.
|
||||
* @param destX The destination x-coordinate in viewport.
|
||||
* @param destY The destination y-coordinate in viewport.
|
||||
* @param type The object type.
|
||||
*
|
||||
* @param curX The current x-coordinate in viewport.
|
||||
* @param curY The current y-coordinate in viewport.
|
||||
* @param size The mover size.
|
||||
* @param destX The destination x-coordinate in viewport.
|
||||
* @param destY The destination y-coordinate in viewport.
|
||||
* @param type The object type.
|
||||
* @param rotation The object rotation.
|
||||
* @return {@code True} if so.
|
||||
*/
|
||||
public static boolean canDoorInteract(int curX, int curY, int size, int destX, int destY, int type, int rotation, int z, ClipMaskSupplier clipMaskSupplier) {
|
||||
public static boolean canDoorInteract(int curX,
|
||||
int curY,
|
||||
int size,
|
||||
int destX,
|
||||
int destY,
|
||||
int type,
|
||||
int rotation,
|
||||
int z,
|
||||
ClipMaskSupplier clipMaskSupplier) {
|
||||
return RsmodPathfinder.canReach(curX, curY, size, destX, destY, 1, 1, rotation, type, 0, z, clipMaskSupplier);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the mover is standing on the destination.
|
||||
* @param x The current x-location (in viewport).
|
||||
* @param y The current y-location (in viewport).
|
||||
*
|
||||
* @param x The current x-location (in viewport).
|
||||
* @param y The current y-location (in viewport).
|
||||
* @param moverSizeX The mover x size.
|
||||
* @param moverSizeY The mover y size.
|
||||
* @param destX The destination x-location in viewport.
|
||||
* @param destY The destination y-location in viewport.
|
||||
* @param sizeX The destination node x-size.
|
||||
* @param sizeY The destination node y-size.
|
||||
* @param destX The destination x-location in viewport.
|
||||
* @param destY The destination y-location in viewport.
|
||||
* @param sizeX The destination node x-size.
|
||||
* @param sizeY The destination node y-size.
|
||||
* @return {@code True} if so.
|
||||
*/
|
||||
public static boolean isStandingIn(int x, int y, int moverSizeX, int moverSizeY, int destX, int destY, int sizeX, int sizeY) {
|
||||
|
|
@ -204,32 +247,52 @@ public abstract class Pathfinder {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if interaction is possible from the current location.
|
||||
* @param x The current x-location (in viewport).
|
||||
* @param y The current y-location (in viewport).
|
||||
*
|
||||
* @param x The current x-location (in viewport).
|
||||
* @param y The current y-location (in viewport).
|
||||
* @param moverSize The mover size.
|
||||
* @param destX The destination x-location in viewport.
|
||||
* @param destY The destination y-location in viewport.
|
||||
* @param sizeX The destination node x-size.
|
||||
* @param sizeY The destination node y-size.
|
||||
* @param walkFlag The walking flag.
|
||||
* @param destX The destination x-location in viewport.
|
||||
* @param destY The destination y-location in viewport.
|
||||
* @param sizeX The destination node x-size.
|
||||
* @param sizeY The destination node y-size.
|
||||
* @param walkFlag The walking flag.
|
||||
* @return {@code True} if so.
|
||||
*/
|
||||
public static boolean canInteract(int x, int y, int moverSize, int destX, int destY, int sizeX, int sizeY, int walkFlag, int z, ClipMaskSupplier clipMaskSupplier) {
|
||||
public static boolean canInteract(int x,
|
||||
int y,
|
||||
int moverSize,
|
||||
int destX,
|
||||
int destY,
|
||||
int sizeX,
|
||||
int sizeY,
|
||||
int walkFlag,
|
||||
int z,
|
||||
ClipMaskSupplier clipMaskSupplier) {
|
||||
return RsmodPathfinder.canReach(x, y, moverSize, destX, destY, sizeX, sizeY, 0, -1, walkFlag, z, clipMaskSupplier);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if interaction is possible from the current location.
|
||||
*
|
||||
* @param destX The destination x-location in viewport.
|
||||
* @param destY The destination y-location in viewport.
|
||||
* @param sizeX The destination node x-size.
|
||||
* @param sizeY The destination node y-size.
|
||||
* @return {@code True} if so.
|
||||
*/
|
||||
public static boolean canInteractSized(int curX, int curY, int moverSizeX, int moverSizeY, int destX, int destY, int sizeX, int sizeY, int walkingFlag, int z) {
|
||||
public static boolean canInteractSized(int curX,
|
||||
int curY,
|
||||
int moverSizeX,
|
||||
int moverSizeY,
|
||||
int destX,
|
||||
int destY,
|
||||
int sizeX,
|
||||
int sizeY,
|
||||
int walkingFlag,
|
||||
int z) {
|
||||
return RsmodPathfinder.canReach(curX, curY, moverSizeX, destX, destY, sizeX, sizeY, 0, -1, walkingFlag, z, null);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,9 +125,7 @@ class RsmodPathfinder(
|
|||
} else {
|
||||
CollisionFlagMap().also {
|
||||
loadCollisionWindow(
|
||||
flags = it,
|
||||
start = Location.create(srcX, srcY, z),
|
||||
supplier = clipMaskSupplier
|
||||
flags = it, start = Location.create(srcX, srcY, z), supplier = clipMaskSupplier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -149,22 +147,14 @@ class RsmodPathfinder(
|
|||
|
||||
@JvmStatic
|
||||
fun lineOfSight(
|
||||
start: Location,
|
||||
dest: Location,
|
||||
moverSize: Int,
|
||||
destWidth: Int,
|
||||
destHeight: Int
|
||||
start: Location, dest: Location, moverSize: Int, destWidth: Int, destHeight: Int
|
||||
): RayCast {
|
||||
RegionManager.loadClippingWindow(start, SEARCH_MAP_SIZE)
|
||||
return lineOfSightLoaded(start, dest, moverSize, destWidth, destHeight)
|
||||
}
|
||||
|
||||
private fun lineOfSightLoaded(
|
||||
start: Location,
|
||||
dest: Location,
|
||||
moverSize: Int,
|
||||
destWidth: Int,
|
||||
destHeight: Int
|
||||
start: Location, dest: Location, moverSize: Int, destWidth: Int, destHeight: Int
|
||||
): RayCast {
|
||||
return projectileLineFinder.get().lineOfSight(
|
||||
level = start.z,
|
||||
|
|
@ -278,9 +268,7 @@ class RsmodPathfinder(
|
|||
}
|
||||
|
||||
private fun loadCollisionWindow(
|
||||
flags: CollisionFlagMap,
|
||||
start: Location,
|
||||
supplier: ClipMaskSupplier
|
||||
flags: CollisionFlagMap, start: Location, supplier: ClipMaskSupplier
|
||||
) {
|
||||
val baseX = start.x - (SEARCH_MAP_SIZE / 2)
|
||||
val baseY = start.y - (SEARCH_MAP_SIZE / 2)
|
||||
|
|
|
|||
|
|
@ -19,11 +19,7 @@ class RsmodProjectilePathfinder : Pathfinder() {
|
|||
val source = requireNotNull(start)
|
||||
val destination = requireNotNull(end)
|
||||
val rayCast = RsmodPathfinder.lineOfSight(
|
||||
start = source,
|
||||
dest = destination,
|
||||
moverSize = size,
|
||||
destWidth = sizeX,
|
||||
destHeight = sizeY
|
||||
start = source, dest = destination, moverSize = size, destWidth = sizeX, destHeight = sizeY
|
||||
)
|
||||
val path = Path()
|
||||
if (!rayCast.success) {
|
||||
|
|
|
|||
|
|
@ -6,11 +6,7 @@ import core.api.EquipmentSlot
|
|||
import core.game.global.action.DoorActionHandler
|
||||
import core.game.node.Node
|
||||
import core.game.node.entity.Entity
|
||||
import core.game.node.entity.combat.BattleState
|
||||
import core.game.node.entity.combat.CombatReach
|
||||
import core.game.node.entity.combat.CombatSwingHandler
|
||||
import core.game.node.entity.combat.CombatMovementIntents
|
||||
import core.game.node.entity.combat.CombatMovementPlanner
|
||||
import core.game.node.entity.combat.*
|
||||
import core.game.node.entity.combat.equipment.WeaponInterface
|
||||
import core.game.node.entity.combat.spell.CombatSpell
|
||||
import core.game.node.entity.combat.spell.SpellType
|
||||
|
|
@ -24,16 +20,12 @@ import core.game.world.GameWorld
|
|||
import core.game.world.map.Location
|
||||
import core.game.world.map.RegionManager
|
||||
import core.game.world.map.path.Pathfinder
|
||||
import core.plugin.Plugin
|
||||
import core.net.packet.PacketProcessor
|
||||
import core.net.packet.`in`.Packet
|
||||
import org.rs09.consts.Items
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
import org.junit.jupiter.api.Assertions.assertNotEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import core.plugin.Plugin
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.rs09.consts.Items
|
||||
import org.rsmod.game.pathfinder.flag.CollisionFlag
|
||||
import kotlin.math.abs
|
||||
|
||||
|
|
@ -61,8 +53,7 @@ class CombatMovementTests {
|
|||
|
||||
assertTrue(attacker.properties.combatPulse.isAttacking)
|
||||
assertTrue(
|
||||
meleeReach(attacker, victim),
|
||||
"Attacker should stay in melee reach while the victim is running."
|
||||
meleeReach(attacker, victim), "Attacker should stay in melee reach while the victim is running."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -93,8 +84,7 @@ class CombatMovementTests {
|
|||
|
||||
assertTrue(
|
||||
meleeReach(attacker, victim),
|
||||
"Attacker should run with a target leaving melee range. " +
|
||||
"attacker=${attacker.location}, victim=${victim.location}"
|
||||
"Attacker should run with a target leaving melee range. " + "attacker=${attacker.location}, victim=${victim.location}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -119,13 +109,18 @@ class CombatMovementTests {
|
|||
GameWorld.Pulser.updateAll()
|
||||
CombatMovementIntents.resolve()
|
||||
|
||||
assertFalse(attacker.walkingQueue.isRunning, "Combat movement must not persist the transient running flag.")
|
||||
assertFalse(
|
||||
attacker.walkingQueue.isRunning, "Combat movement must not persist the transient running flag."
|
||||
)
|
||||
assertFalse(attacker.walkingQueue.isRunningBoth, "Combat movement must not make the attacker run.")
|
||||
|
||||
victim.walkingQueue.update()
|
||||
attacker.walkingQueue.update()
|
||||
|
||||
assertTrue(attacker.properties.combatPulse.isAttacking, "Combat should remain active while the target is moving.")
|
||||
assertTrue(
|
||||
attacker.properties.combatPulse.isAttacking,
|
||||
"Combat should remain active while the target is moving."
|
||||
)
|
||||
assertEquals(-1, attacker.walkingQueue.runDir, "Combat movement must not force run when run is off.")
|
||||
assertFalse(attacker.settings.isRunToggled, "Combat movement must not toggle run on.")
|
||||
assertEquals(100.0, attacker.settings.runEnergy, 0.0, "Walking combat chase must not drain run energy.")
|
||||
|
|
@ -158,7 +153,9 @@ class CombatMovementTests {
|
|||
)
|
||||
assertFalse(receivedMessage(attacker, "I can't reach that!"))
|
||||
assertFalse(attacker.walkingQueue.isRunningBoth, "Waiting for the next tick must not force running.")
|
||||
assertEquals(100.0, attacker.settings.runEnergy, 0.0, "Waiting for a moving target must not drain run energy.")
|
||||
assertEquals(
|
||||
100.0, attacker.settings.runEnergy, 0.0, "Waiting for a moving target must not drain run energy."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -188,8 +185,7 @@ class CombatMovementTests {
|
|||
|
||||
assertTrue(
|
||||
meleeReach(attacker, victim),
|
||||
"Attacker should mirror a live run-click on the tick it is queued. " +
|
||||
"attacker=${attacker.location}, victim=${victim.location}"
|
||||
"Attacker should mirror a live run-click on the tick it is queued. " + "attacker=${attacker.location}, victim=${victim.location}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -326,11 +322,7 @@ class CombatMovementTests {
|
|||
attacker.attack(victim)
|
||||
queueWalk(victim, predictedVictim)
|
||||
RegionManager.addClippingFlag(
|
||||
predictedVictim.z,
|
||||
predictedVictim.x,
|
||||
predictedVictim.y,
|
||||
false,
|
||||
Pathfinder.PREVENT_NORTH
|
||||
predictedVictim.z, predictedVictim.x, predictedVictim.y, false, Pathfinder.PREVENT_NORTH
|
||||
)
|
||||
RegionManager.addClippingFlag(
|
||||
predictedVictim.z,
|
||||
|
|
@ -359,9 +351,7 @@ class CombatMovementTests {
|
|||
assertNotEquals(
|
||||
origin,
|
||||
attacker.location,
|
||||
"Melee pressure should not stay on a clipped side of the moving target. " +
|
||||
CombatMovementIntents.lastResolveSummary() +
|
||||
" queue=${attacker.walkingQueue.queue.size}"
|
||||
"Melee pressure should not stay on a clipped side of the moving target. " + CombatMovementIntents.lastResolveSummary() + " queue=${attacker.walkingQueue.queue.size}"
|
||||
)
|
||||
assertTrue(
|
||||
attacker.properties.combatPulse.isAttacking,
|
||||
|
|
@ -369,16 +359,11 @@ class CombatMovementTests {
|
|||
)
|
||||
assertTrue(
|
||||
meleeReach(attacker, victim),
|
||||
"Attacker should end the tick on an attackable side of the moving target. " +
|
||||
"attacker=${attacker.location}, victim=${victim.location}"
|
||||
"Attacker should end the tick on an attackable side of the moving target. " + "attacker=${attacker.location}, victim=${victim.location}"
|
||||
)
|
||||
} finally {
|
||||
RegionManager.removeClippingFlag(
|
||||
predictedVictim.z,
|
||||
predictedVictim.x,
|
||||
predictedVictim.y,
|
||||
false,
|
||||
Pathfinder.PREVENT_NORTH
|
||||
predictedVictim.z, predictedVictim.x, predictedVictim.y, false, Pathfinder.PREVENT_NORTH
|
||||
)
|
||||
RegionManager.removeClippingFlag(
|
||||
predictedVictim.z,
|
||||
|
|
@ -398,8 +383,7 @@ class CombatMovementTests {
|
|||
TestUtils.getMockPlayer("combat_overlap_player_escape").use { player ->
|
||||
val origin = arenaOrigin()
|
||||
val blockedTiles = listOf(
|
||||
origin.transform(0, 1, 0),
|
||||
origin.transform(1, 0, 0)
|
||||
origin.transform(0, 1, 0), origin.transform(1, 0, 0)
|
||||
)
|
||||
place(player, origin)
|
||||
configureMelee(player)
|
||||
|
|
@ -420,8 +404,7 @@ class CombatMovementTests {
|
|||
|
||||
assertTrue(
|
||||
player.location == origin.transform(-1, 0, 0) || player.location == origin.transform(0, -1, 0),
|
||||
"An overlapped melee player should step to the open west/south side instead of stopping. " +
|
||||
"player=${player.location}, npc=${npc.location}"
|
||||
"An overlapped melee player should step to the open west/south side instead of stopping. " + "player=${player.location}, npc=${npc.location}"
|
||||
)
|
||||
assertTrue(CombatReach.canMelee(player, npc, CombatReach.meleeDistance(player)))
|
||||
assertTrue(player.properties.combatPulse.isAttacking)
|
||||
|
|
@ -439,8 +422,7 @@ class CombatMovementTests {
|
|||
TestUtils.getMockPlayer("combat_overlap_npc_target").use { player ->
|
||||
val origin = arenaOrigin()
|
||||
val blockedTiles = listOf(
|
||||
origin.transform(0, 1, 0),
|
||||
origin.transform(1, 0, 0)
|
||||
origin.transform(0, 1, 0), origin.transform(1, 0, 0)
|
||||
)
|
||||
place(player, origin)
|
||||
configureMelee(player)
|
||||
|
|
@ -457,8 +439,7 @@ class CombatMovementTests {
|
|||
|
||||
assertTrue(
|
||||
npc.location == origin.transform(-1, 0, 0) || npc.location == origin.transform(0, -1, 0),
|
||||
"An overlapped dumb melee NPC should try the open west/south side, not only north/east. " +
|
||||
"npc=${npc.location}, player=${player.location}"
|
||||
"An overlapped dumb melee NPC should try the open west/south side, not only north/east. " + "npc=${npc.location}, player=${player.location}"
|
||||
)
|
||||
assertTrue(CombatReach.canMelee(npc, player, CombatReach.meleeDistance(npc)))
|
||||
assertTrue(npc.properties.combatPulse.isAttacking)
|
||||
|
|
@ -475,8 +456,7 @@ class CombatMovementTests {
|
|||
TestUtils.getMockPlayer("combat_overlap_mutual_player").use { player ->
|
||||
val origin = arenaOrigin()
|
||||
val blockedTiles = listOf(
|
||||
origin.transform(0, 1, 0),
|
||||
origin.transform(1, 0, 0)
|
||||
origin.transform(0, 1, 0), origin.transform(1, 0, 0)
|
||||
)
|
||||
place(player, origin)
|
||||
configureMelee(player)
|
||||
|
|
@ -499,8 +479,7 @@ class CombatMovementTests {
|
|||
assertTrue(player.location != origin || npc.location != origin)
|
||||
assertTrue(
|
||||
player.location == origin || npc.location == origin,
|
||||
"When both overlapped actors are attacking, one side stepping out is enough. " +
|
||||
"player=${player.location}, npc=${npc.location}"
|
||||
"When both overlapped actors are attacking, one side stepping out is enough. " + "player=${player.location}, npc=${npc.location}"
|
||||
)
|
||||
assertTrue(CombatReach.canMelee(player, npc, CombatReach.meleeDistance(player)))
|
||||
assertTrue(CombatReach.canMelee(npc, player, CombatReach.meleeDistance(npc)))
|
||||
|
|
@ -563,9 +542,7 @@ class CombatMovementTests {
|
|||
assertTrue(player.properties.combatPulse.isAttacking)
|
||||
assertTrue(
|
||||
player.properties.combatPulse.getNextAttack() > -1,
|
||||
"Run-enabled melee chase should create an attack opportunity before the walking NPC stops. " +
|
||||
"player=${player.location}, npc=${npc.location}, " +
|
||||
CombatMovementIntents.lastResolveSummary()
|
||||
"Run-enabled melee chase should create an attack opportunity before the walking NPC stops. " + "player=${player.location}, npc=${npc.location}, " + CombatMovementIntents.lastResolveSummary()
|
||||
)
|
||||
} finally {
|
||||
npc.clear()
|
||||
|
|
@ -580,8 +557,7 @@ class CombatMovementTests {
|
|||
val origin = openHorizontalOrigin()
|
||||
val npcLocation = origin.transform(2, 0, 0)
|
||||
val blockedTiles = listOf(
|
||||
npcLocation.transform(0, 1, 0),
|
||||
npcLocation.transform(0, -1, 0)
|
||||
npcLocation.transform(0, 1, 0), npcLocation.transform(0, -1, 0)
|
||||
)
|
||||
place(player, origin)
|
||||
configureMelee(player)
|
||||
|
|
@ -594,11 +570,7 @@ class CombatMovementTests {
|
|||
configureMelee(npc)
|
||||
blockMovementTiles(blockedTiles)
|
||||
RegionManager.addClippingFlag(
|
||||
npcLocation.z,
|
||||
npcLocation.x,
|
||||
npcLocation.y,
|
||||
true,
|
||||
CollisionFlag.WALL_WEST_PROJECTILE_BLOCKER
|
||||
npcLocation.z, npcLocation.x, npcLocation.y, true, CollisionFlag.WALL_WEST_PROJECTILE_BLOCKER
|
||||
)
|
||||
|
||||
player.attack(npc)
|
||||
|
|
@ -609,17 +581,12 @@ class CombatMovementTests {
|
|||
|
||||
assertTrue(
|
||||
player.location.x < npcLocation.x,
|
||||
"Melee movement must stop before crossing the NPC footprint when the far-side tile is chosen. " +
|
||||
"player=${player.location}, npc=$npcLocation, ${CombatMovementIntents.lastResolveSummary()}"
|
||||
"Melee movement must stop before crossing the NPC footprint when the far-side tile is chosen. " + "player=${player.location}, npc=$npcLocation, ${CombatMovementIntents.lastResolveSummary()}"
|
||||
)
|
||||
assertNotEquals(npcLocation, player.location, "The player must not run onto the NPC footprint.")
|
||||
} finally {
|
||||
RegionManager.removeClippingFlag(
|
||||
npcLocation.z,
|
||||
npcLocation.x,
|
||||
npcLocation.y,
|
||||
true,
|
||||
CollisionFlag.WALL_WEST_PROJECTILE_BLOCKER
|
||||
npcLocation.z, npcLocation.x, npcLocation.y, true, CollisionFlag.WALL_WEST_PROJECTILE_BLOCKER
|
||||
)
|
||||
unblockMovementTiles(blockedTiles)
|
||||
npc.clear()
|
||||
|
|
@ -728,8 +695,7 @@ class CombatMovementTests {
|
|||
|
||||
assertEquals(seers, player.location)
|
||||
assertFalse(
|
||||
player.properties.combatPulse.isAttacking,
|
||||
"Teleporting should discard the previous combat target."
|
||||
player.properties.combatPulse.isAttacking, "Teleporting should discard the previous combat target."
|
||||
)
|
||||
} finally {
|
||||
npc.clear()
|
||||
|
|
@ -750,7 +716,9 @@ class CombatMovementTests {
|
|||
val duck = stationaryNpc(46, duckLocation)
|
||||
try {
|
||||
assertTrue(RegionManager.isTeleportPermitted(start), "Test start tile must be walkable.")
|
||||
assertFalse(RegionManager.isTeleportPermitted(duckLocation), "Lumbridge river duck spawn should be on water.")
|
||||
assertFalse(
|
||||
RegionManager.isTeleportPermitted(duckLocation), "Lumbridge river duck spawn should be on water."
|
||||
)
|
||||
|
||||
val startDistance = start.getDistance(duck.location)
|
||||
player.attack(duck)
|
||||
|
|
@ -799,7 +767,9 @@ class CombatMovementTests {
|
|||
val duck = stationaryNpc(46, duckLocation)
|
||||
try {
|
||||
assertTrue(RegionManager.isTeleportPermitted(start), "Test start tile must be walkable.")
|
||||
assertFalse(RegionManager.isTeleportPermitted(duckLocation), "Lumbridge river duck spawn should be on water.")
|
||||
assertFalse(
|
||||
RegionManager.isTeleportPermitted(duckLocation), "Lumbridge river duck spawn should be on water."
|
||||
)
|
||||
|
||||
player.attack(duck)
|
||||
TestUtils.advanceTicks(12, false)
|
||||
|
|
@ -807,8 +777,11 @@ class CombatMovementTests {
|
|||
assertTrue(player.properties.combatPulse.isAttacking)
|
||||
assertTrue(
|
||||
magicReach(player, duck),
|
||||
"Autocast should stop on a projectile-clear tile instead of dancing within blocked range. " +
|
||||
"player=${player.location}, duck=${duck.location}, distance=${player.location.getDistance(duck.location)}"
|
||||
"Autocast should stop on a projectile-clear tile instead of dancing within blocked range. " + "player=${player.location}, duck=${duck.location}, distance=${
|
||||
player.location.getDistance(
|
||||
duck.location
|
||||
)
|
||||
}"
|
||||
)
|
||||
assertFalse(receivedMessage(player, "I can't reach that!"))
|
||||
} finally {
|
||||
|
|
@ -830,16 +803,20 @@ class CombatMovementTests {
|
|||
val duck = stationaryNpc(46, duckLocation)
|
||||
try {
|
||||
assertTrue(RegionManager.isTeleportPermitted(start), "Test start tile must be walkable.")
|
||||
assertFalse(RegionManager.isTeleportPermitted(duckLocation), "Lumbridge river duck spawn should be on water.")
|
||||
assertFalse(
|
||||
RegionManager.isTeleportPermitted(duckLocation), "Lumbridge river duck spawn should be on water."
|
||||
)
|
||||
|
||||
player.attack(duck)
|
||||
TestUtils.advanceTicks(18, false)
|
||||
|
||||
assertFalse(
|
||||
player.properties.combatPulse.isAttacking && !magicReach(player, duck),
|
||||
"Autocast should not stay active while standing in projectile-blocked spell range. " +
|
||||
"player=${player.location}, duck=${duck.location}, distance=${player.location.getDistance(duck.location)}, " +
|
||||
"projectile=${CombatSwingHandler.isProjectileClipped(player, duck, false)}"
|
||||
"Autocast should not stay active while standing in projectile-blocked spell range. " + "player=${player.location}, duck=${duck.location}, distance=${
|
||||
player.location.getDistance(
|
||||
duck.location
|
||||
)
|
||||
}, " + "projectile=${CombatSwingHandler.isProjectileClipped(player, duck, false)}"
|
||||
)
|
||||
if (!player.properties.combatPulse.isAttacking) {
|
||||
assertTrue(receivedMessage(player, "I can't reach that!"))
|
||||
|
|
@ -871,7 +848,9 @@ class CombatMovementTests {
|
|||
val duck = stationaryNpc(46, duckLocation)
|
||||
try {
|
||||
assertTrue(RegionManager.isTeleportPermitted(start), "Test start tile must be walkable.")
|
||||
assertFalse(RegionManager.isTeleportPermitted(duckLocation), "Lumbridge river duck spawn should be on water.")
|
||||
assertFalse(
|
||||
RegionManager.isTeleportPermitted(duckLocation), "Lumbridge river duck spawn should be on water."
|
||||
)
|
||||
|
||||
val startDistance = start.getDistance(duck.location)
|
||||
player.attack(duck)
|
||||
|
|
@ -894,9 +873,9 @@ class CombatMovementTests {
|
|||
assertFalse(player.properties.combatPulse.isAttacking)
|
||||
assertTrue(
|
||||
receivedMessage(player, "I can't reach that!"),
|
||||
"Melee combat should report unreachable after pathing. " +
|
||||
"location=${player.location}, distance=${player.location.getDistance(duck.location)}, " +
|
||||
"isAttacking=${player.properties.combatPulse.isAttacking}"
|
||||
"Melee combat should report unreachable after pathing. " + "location=${player.location}, distance=${
|
||||
player.location.getDistance(duck.location)
|
||||
}, " + "isAttacking=${player.properties.combatPulse.isAttacking}"
|
||||
)
|
||||
assertTrue(
|
||||
player.location.getDistance(duck.location) <= startDistance,
|
||||
|
|
@ -1085,8 +1064,7 @@ class CombatMovementTests {
|
|||
TestUtils.getMockPlayer("combat_safespot_target").use { player ->
|
||||
val origin = arenaOrigin()
|
||||
val blockedTiles = listOf(
|
||||
origin.transform(1, 0, 0),
|
||||
origin.transform(0, -1, 0)
|
||||
origin.transform(1, 0, 0), origin.transform(0, -1, 0)
|
||||
)
|
||||
place(player, origin.transform(4, 0, 0))
|
||||
|
||||
|
|
@ -1151,13 +1129,88 @@ class CombatMovementTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dumbMeleeNpcShouldNotSidestepAroundImmediateSafespotBlocker() {
|
||||
TestUtils.getMockPlayer("combat_cardinal_north_safespot_target").use { player ->
|
||||
val origin = arenaOrigin()
|
||||
val blocker = origin.transform(0, 1, 0)
|
||||
val npcStart = origin
|
||||
place(player, origin.transform(0, 4, 0))
|
||||
|
||||
val npc = NPC.create(100, npcStart)
|
||||
npc.init()
|
||||
try {
|
||||
configureMelee(npc)
|
||||
blockMovementTiles(listOf(blocker))
|
||||
|
||||
npc.attack(player)
|
||||
CombatMovementIntents.clear()
|
||||
CombatMovementIntents.request(npc, player)
|
||||
CombatMovementIntents.resolve()
|
||||
npc.walkingQueue.update()
|
||||
|
||||
assertEquals(
|
||||
npcStart,
|
||||
npc.location,
|
||||
"A dumb NPC should not route around a blocked next tile on the direct path to the player."
|
||||
)
|
||||
assertTrue(npc.properties.combatPulse.isAttacking)
|
||||
} finally {
|
||||
unblockMovementTiles(listOf(blocker))
|
||||
npc.clear()
|
||||
CombatMovementIntents.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun varrockGuardShouldStepAroundBakeryStallAfterRangedTargetRunsNorthEast() {
|
||||
TestUtils.getMockPlayer("combat_varrock_bakery_target").use { player ->
|
||||
val spawn = Location.create(2651, 3307, 0)
|
||||
val originalTargetLocation = Location.create(2657, 3309, 0)
|
||||
val guardStart = Location.create(2656, 3309, 0)
|
||||
val targetDestination = Location.create(2658, 3311, 0)
|
||||
place(player, originalTargetLocation)
|
||||
configureRanged(player)
|
||||
enableRun(player)
|
||||
|
||||
val npc = NPC.create(32, guardStart)
|
||||
npc.init()
|
||||
npc.properties.spawnLocation = spawn
|
||||
try {
|
||||
configureMelee(npc)
|
||||
|
||||
npc.attack(player)
|
||||
queueRunPath(
|
||||
player, listOf(
|
||||
Location.create(2658, 3309, 0), Location.create(2658, 3310, 0), targetDestination
|
||||
)
|
||||
)
|
||||
TestUtils.advanceTicks(8, false)
|
||||
|
||||
assertNotEquals(
|
||||
guardStart,
|
||||
npc.location,
|
||||
"The Varrock guard should not stall west of the player when the bakery stall blocks north. " + "guard=${npc.location}, player=${player.location}, ${CombatMovementIntents.lastResolveSummary()}"
|
||||
)
|
||||
assertTrue(
|
||||
npc.location.getDistance(player.location) < originalTargetLocation.getDistance(targetDestination),
|
||||
"The guard should keep taking local dumb steps toward the running target without full smart routing. " + "guard=${npc.location}, player=${player.location}, ${CombatMovementIntents.lastResolveSummary()}"
|
||||
)
|
||||
assertTrue(npc.properties.combatPulse.isAttacking)
|
||||
} finally {
|
||||
npc.clear()
|
||||
CombatMovementIntents.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dumbMeleeNpcShouldNotRouteAroundWhenBothCornerSidesAreBlocked() {
|
||||
TestUtils.getMockPlayer("combat_blocked_diagonal_corner_target").use { player ->
|
||||
val origin = arenaOrigin()
|
||||
val blockedTiles = listOf(
|
||||
origin.transform(0, 1, 0),
|
||||
origin.transform(-1, 0, 0)
|
||||
origin.transform(0, 1, 0), origin.transform(-1, 0, 0)
|
||||
)
|
||||
val npcStart = origin.transform(-1, 1, 0)
|
||||
place(player, origin)
|
||||
|
|
@ -1195,10 +1248,7 @@ class CombatMovementTests {
|
|||
val obstacle = origin.transform(1, 0, 0)
|
||||
val playerEnd = origin.transform(2, 0, 0)
|
||||
val playerPath = listOf(
|
||||
origin.transform(0, 1, 0),
|
||||
origin.transform(1, 1, 0),
|
||||
origin.transform(2, 1, 0),
|
||||
playerEnd
|
||||
origin.transform(0, 1, 0), origin.transform(1, 1, 0), origin.transform(2, 1, 0), playerEnd
|
||||
)
|
||||
place(player, origin)
|
||||
enableRun(player)
|
||||
|
|
@ -1242,8 +1292,8 @@ class CombatMovementTests {
|
|||
val door = RegionManager.getObject(doorLocation.z, doorLocation.x, doorLocation.y, 36846)
|
||||
?: throw AssertionError("Expected Lumbridge house door 36846 at $doorLocation.")
|
||||
assertEquals(1, door.rotation, "Expected the Lumbridge house door to face east.")
|
||||
val doorConfig = DoorConfigLoader.forId(door.id)
|
||||
?: throw AssertionError("Expected door config for ${door.id}.")
|
||||
val doorConfig =
|
||||
DoorConfigLoader.forId(door.id) ?: throw AssertionError("Expected door config for ${door.id}.")
|
||||
val openedDoorLocation = door.location.transform(0, 1, 0)
|
||||
DoorActionHandler.open(door, null, doorConfig.replaceId, -1, true, -1, doorConfig.isFence)
|
||||
|
||||
|
|
@ -1265,10 +1315,7 @@ class CombatMovementTests {
|
|||
} finally {
|
||||
npc.clear()
|
||||
val openedDoor = RegionManager.getObject(
|
||||
openedDoorLocation.z,
|
||||
openedDoorLocation.x,
|
||||
openedDoorLocation.y,
|
||||
doorConfig.replaceId
|
||||
openedDoorLocation.z, openedDoorLocation.x, openedDoorLocation.y, doorConfig.replaceId
|
||||
)
|
||||
if (openedDoor != null) {
|
||||
SceneryBuilder.replace(openedDoor, door)
|
||||
|
|
@ -1284,8 +1331,7 @@ class CombatMovementTests {
|
|||
val start = Location.create(3252, 3227, 0)
|
||||
val npcLocation = Location.create(3253, 3226, 0)
|
||||
val expectedAttackTiles = setOf(
|
||||
Location.create(3252, 3226, 0),
|
||||
Location.create(3253, 3227, 0)
|
||||
Location.create(3252, 3226, 0), Location.create(3253, 3227, 0)
|
||||
)
|
||||
place(player, start)
|
||||
configureMelee(player)
|
||||
|
|
@ -1307,8 +1353,7 @@ class CombatMovementTests {
|
|||
|
||||
assertTrue(
|
||||
player.location in expectedAttackTiles,
|
||||
"Player should step to the nearest cardinal attack tile at the ruin corner, not route away. " +
|
||||
"player=${player.location}, npc=${npc.location}, ${CombatMovementIntents.lastResolveSummary()}"
|
||||
"Player should step to the nearest cardinal attack tile at the ruin corner, not route away. " + "player=${player.location}, npc=${npc.location}, ${CombatMovementIntents.lastResolveSummary()}"
|
||||
)
|
||||
assertTrue(meleeReach(player, npc))
|
||||
assertTrue(player.properties.combatPulse.isAttacking)
|
||||
|
|
@ -1362,8 +1407,7 @@ class CombatMovementTests {
|
|||
|
||||
private fun configureMelee(entity: Entity) {
|
||||
entity.properties.attackStyle = WeaponInterface.AttackStyle(
|
||||
WeaponInterface.STYLE_AGGRESSIVE,
|
||||
WeaponInterface.BONUS_CRUSH
|
||||
WeaponInterface.STYLE_AGGRESSIVE, WeaponInterface.BONUS_CRUSH
|
||||
)
|
||||
entity.properties.combatPulse.updateStyle()
|
||||
}
|
||||
|
|
@ -1372,8 +1416,7 @@ class CombatMovementTests {
|
|||
player.equipment.replace(Item(6724), EquipmentSlot.WEAPON.ordinal)
|
||||
player.equipment.replace(Item(Items.BRONZE_ARROW_882, 100), EquipmentSlot.AMMO.ordinal)
|
||||
player.properties.attackStyle = WeaponInterface.AttackStyle(
|
||||
WeaponInterface.STYLE_RANGE_ACCURATE,
|
||||
WeaponInterface.BONUS_RANGE
|
||||
WeaponInterface.STYLE_RANGE_ACCURATE, WeaponInterface.BONUS_RANGE
|
||||
)
|
||||
player.properties.combatPulse.updateStyle()
|
||||
}
|
||||
|
|
@ -1384,8 +1427,7 @@ class CombatMovementTests {
|
|||
player.skills.setLevel(Skills.MAGIC, 99)
|
||||
player.properties.autocastSpell = TestAutocastSpell
|
||||
player.properties.attackStyle = WeaponInterface.AttackStyle(
|
||||
WeaponInterface.STYLE_CAST,
|
||||
WeaponInterface.BONUS_MAGIC
|
||||
WeaponInterface.STYLE_CAST, WeaponInterface.BONUS_MAGIC
|
||||
)
|
||||
player.properties.combatPulse.updateStyle()
|
||||
}
|
||||
|
|
@ -1445,8 +1487,11 @@ class CombatMovementTests {
|
|||
}
|
||||
|
||||
private fun magicReach(attacker: Entity, victim: Entity): Boolean {
|
||||
return attacker.location.getDistance(victim.getClosestOccupiedTile(attacker.location)) <= 10.0 &&
|
||||
CombatSwingHandler.isProjectileClipped(attacker, victim, false)
|
||||
return attacker.location.getDistance(victim.getClosestOccupiedTile(attacker.location)) <= 10.0 && CombatSwingHandler.isProjectileClipped(
|
||||
attacker,
|
||||
victim,
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
private object TestAutocastSpell : CombatSpell() {
|
||||
|
|
@ -1488,8 +1533,6 @@ class CombatMovementTests {
|
|||
}
|
||||
}
|
||||
|
||||
private val movementBlockFlag = Pathfinder.PREVENT_NORTH or
|
||||
Pathfinder.PREVENT_EAST or
|
||||
Pathfinder.PREVENT_SOUTH or
|
||||
Pathfinder.PREVENT_WEST
|
||||
private val movementBlockFlag =
|
||||
Pathfinder.PREVENT_NORTH or Pathfinder.PREVENT_EAST or Pathfinder.PREVENT_SOUTH or Pathfinder.PREVENT_WEST
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package core
|
||||
|
||||
import TestUtils
|
||||
import content.global.handlers.scenery.BankBoothListener
|
||||
import content.global.handlers.npc.NPCTalkListener
|
||||
import content.global.handlers.scenery.BankBoothListener
|
||||
import content.global.skill.gather.GatheringSkillOptionListeners
|
||||
import content.global.skill.gather.woodcutting.WoodcuttingListener
|
||||
import content.region.misthalin.varrock.dialogue.GrandExchangeClerk
|
||||
|
|
@ -11,25 +11,22 @@ import core.api.log
|
|||
import core.cache.def.impl.NPCDefinition
|
||||
import core.game.dialogue.DialogueInterpreter
|
||||
import core.game.interaction.*
|
||||
import core.game.node.scenery.Scenery
|
||||
import core.game.world.map.Direction
|
||||
import core.game.world.map.Location
|
||||
import core.game.world.map.RegionManager
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import core.game.node.Node
|
||||
import core.game.node.entity.impl.PulseType
|
||||
import core.game.node.entity.npc.NPC
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.node.scenery.Scenery
|
||||
import core.game.world.GameWorld
|
||||
import core.game.world.map.Region
|
||||
import core.game.world.map.Direction
|
||||
import core.game.world.map.Location
|
||||
import core.game.world.map.RegionManager
|
||||
import core.game.world.map.path.ClipMaskSupplier
|
||||
import core.game.world.map.path.Pathfinder
|
||||
import core.net.packet.PacketProcessor
|
||||
import core.plugin.ClassScanner
|
||||
import core.game.world.map.path.RsmodPathfinder
|
||||
import core.plugin.Plugin
|
||||
import core.tools.Log
|
||||
import org.rs09.consts.NPCs
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.rs09.consts.Scenery as SceneryIds
|
||||
|
||||
class PathfinderTests {
|
||||
|
|
@ -40,31 +37,42 @@ class PathfinderTests {
|
|||
WoodcuttingListener().defineListeners()
|
||||
BankBoothListener().defineListeners()
|
||||
}
|
||||
|
||||
val NPC_TEST_LOC = ServerConstants.HOME_LOCATION!!.transform(2, 10, 0)
|
||||
}
|
||||
|
||||
@Test fun getOccupiedTilesShouldReturnCorrectSetOfTilesThatAnObjectOccupiesAtAllRotations() {
|
||||
@Test
|
||||
fun rsmodPathfinderShouldRejectDestinationsAtTheTruncationLimit() {
|
||||
val start = Location.create(3165, 3218, 0)
|
||||
|
||||
Assertions.assertTrue(RsmodPathfinder.canAttempt(start, Location.create(3203, 3186, 0)))
|
||||
Assertions.assertFalse(RsmodPathfinder.canAttempt(start, Location.create(3203, 3185, 0)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getOccupiedTilesShouldReturnCorrectSetOfTilesThatAnObjectOccupiesAtAllRotations() {
|
||||
//clay fireplace - 13609 - sizex: 1, sizey: 2
|
||||
val scenery = Scenery(13609, Location.create(50, 50, 0))
|
||||
|
||||
scenery.rotation = 0
|
||||
val occupiedAt0 = scenery.occupiedTiles.toTypedArray()
|
||||
Assertions.assertArrayEquals(arrayOf(Location.create(50, 50), Location.create(50,51)), occupiedAt0)
|
||||
Assertions.assertArrayEquals(arrayOf(Location.create(50, 50), Location.create(50, 51)), occupiedAt0)
|
||||
|
||||
scenery.rotation = 1
|
||||
val occupiedAt1 = scenery.occupiedTiles.toTypedArray()
|
||||
Assertions.assertArrayEquals(arrayOf(Location.create(50,50), Location.create(51,50)), occupiedAt1)
|
||||
Assertions.assertArrayEquals(arrayOf(Location.create(50, 50), Location.create(51, 50)), occupiedAt1)
|
||||
|
||||
scenery.rotation = 2
|
||||
val occupiedAt2 = scenery.occupiedTiles.toTypedArray()
|
||||
Assertions.assertArrayEquals(arrayOf(Location.create(50,50), Location.create(50,49)), occupiedAt2)
|
||||
Assertions.assertArrayEquals(arrayOf(Location.create(50, 50), Location.create(50, 49)), occupiedAt2)
|
||||
|
||||
scenery.rotation = 3
|
||||
val occupiedAt3 = scenery.occupiedTiles.toTypedArray()
|
||||
Assertions.assertArrayEquals(arrayOf(Location.create(50,50), Location.create(49,50)), occupiedAt3)
|
||||
Assertions.assertArrayEquals(arrayOf(Location.create(50, 50), Location.create(49, 50)), occupiedAt3)
|
||||
}
|
||||
|
||||
@Test fun smartPathfinderShouldRespectSuppliedClipMask() {
|
||||
@Test
|
||||
fun smartPathfinderShouldRespectSuppliedClipMask() {
|
||||
val start = Location.create(3200, 3200, 0)
|
||||
val dest = Location.create(3202, 3200, 0)
|
||||
val blockedDestination = ClipMaskSupplier { _, x, y ->
|
||||
|
|
@ -75,25 +83,42 @@ class PathfinderTests {
|
|||
|
||||
Assertions.assertTrue(path.isSuccessful)
|
||||
Assertions.assertEquals(
|
||||
Location.create(3201, 3200, 0),
|
||||
Location.create(path.points.last.x, path.points.last.y, 0)
|
||||
Location.create(3201, 3200, 0), Location.create(path.points.last.x, path.points.last.y, 0)
|
||||
)
|
||||
}
|
||||
|
||||
@Test fun dumbPathfinderShouldUseRsmodRouting() {
|
||||
@Test
|
||||
fun dumbPathfinderShouldNotRouteAroundBlockedCardinalStep() {
|
||||
val start = Location.create(3200, 3200, 0)
|
||||
val dest = Location.create(3202, 3200, 0)
|
||||
val blockedMiddle = ClipMaskSupplier { _, x, y ->
|
||||
if (x == 3201 && y == 3200) 0x100 else 0
|
||||
if (x == 3201 && y == 3200) movementBlockFlag else 0
|
||||
}
|
||||
|
||||
val path = Pathfinder.DUMB.find(start, 1, dest, 0, 0, 0, -1, 0, false, blockedMiddle)
|
||||
|
||||
Assertions.assertTrue(path.isSuccessful)
|
||||
Assertions.assertTrue(path.points.isNotEmpty())
|
||||
Assertions.assertFalse(path.isSuccessful)
|
||||
Assertions.assertTrue(path.points.isEmpty())
|
||||
}
|
||||
|
||||
@Test fun walkingQueueHasPathShouldIgnoreResetAnchor() {
|
||||
@Test
|
||||
fun dumbPathfinderShouldUseAxisFallbackForBlockedDiagonalStep() {
|
||||
val start = Location.create(3200, 3200, 0)
|
||||
val dest = Location.create(3202, 3202, 0)
|
||||
val blockedNorth = ClipMaskSupplier { _, x, y ->
|
||||
if (x == 3200 && y == 3201) movementBlockFlag else 0
|
||||
}
|
||||
|
||||
val path = Pathfinder.DUMB.find(start, 1, dest, 0, 0, 0, -1, 0, false, blockedNorth)
|
||||
|
||||
Assertions.assertTrue(path.isSuccessful)
|
||||
Assertions.assertTrue(path.points.isNotEmpty())
|
||||
Assertions.assertEquals(3201, path.points.first.x)
|
||||
Assertions.assertEquals(3200, path.points.first.y)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun walkingQueueHasPathShouldIgnoreResetAnchor() {
|
||||
TestUtils.getMockPlayer("walkingQueueAnchor").use { player ->
|
||||
val start = Location.create(3200, 3200, 0)
|
||||
player.location = start
|
||||
|
|
@ -107,7 +132,8 @@ class PathfinderTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test fun projectilePathfinderShouldUseRsmodLineOfSightFlags() {
|
||||
@Test
|
||||
fun projectilePathfinderShouldUseRsmodLineOfSightFlags() {
|
||||
val start = Location.create(3200, 3200, 0)
|
||||
val dest = Location.create(3202, 3200, 0)
|
||||
RegionManager.loadClippingWindow(start, 128)
|
||||
|
|
@ -125,7 +151,8 @@ class PathfinderTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test fun metadataSceneryInteractionShouldTriggerWhenAlreadyAtRsmodApproachTile() {
|
||||
@Test
|
||||
fun metadataSceneryInteractionShouldTriggerWhenAlreadyAtRsmodApproachTile() {
|
||||
TestUtils.getMockPlayer("bankBoothApproach").use { p ->
|
||||
val (booth, approach) = findReachableBankBoothFixture()
|
||||
p.location = approach
|
||||
|
|
@ -140,16 +167,14 @@ class PathfinderTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test fun metadataSceneryCollectShouldTriggerWhenAlreadyAtRsmodApproachTile() {
|
||||
@Test
|
||||
fun metadataSceneryCollectShouldTriggerWhenAlreadyAtRsmodApproachTile() {
|
||||
TestUtils.getMockPlayer("bankBoothCollectApproach").use { p ->
|
||||
val (booth, approach) = findReachableBankBoothFixture()
|
||||
p.location = approach
|
||||
var collected = false
|
||||
InteractionListeners.addMetadata(
|
||||
booth.id,
|
||||
IntType.SCENERY,
|
||||
arrayOf("collect"),
|
||||
InteractionListener.InteractionMetadata({ _, _, _ ->
|
||||
booth.id, IntType.SCENERY, arrayOf("collect"), InteractionListener.InteractionMetadata({ _, _, _ ->
|
||||
collected = true
|
||||
true
|
||||
}, 1, false)
|
||||
|
|
@ -166,10 +191,11 @@ class PathfinderTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test fun directObjectMovementPulseShouldTriggerWhenAlreadyAtRsmodApproachTile() {
|
||||
@Test
|
||||
fun directObjectMovementPulseShouldTriggerWhenAlreadyAtRsmodApproachTile() {
|
||||
TestUtils.getMockPlayer("objectPulseApproach").use { p ->
|
||||
val tree = RegionManager.getObject(0, 2720, 3475, 1307)
|
||||
?: throw AssertionError("Expected test tree object.")
|
||||
val tree =
|
||||
RegionManager.getObject(0, 2720, 3475, 1307) ?: throw AssertionError("Expected test tree object.")
|
||||
val approach = findReachableApproachTile(tree)
|
||||
var pulsed = false
|
||||
p.location = approach
|
||||
|
|
@ -186,7 +212,8 @@ class PathfinderTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test fun pathfinderShouldUseWrapperFootprintWhenSceneryChildIsSmallerThanWrapper() {
|
||||
@Test
|
||||
fun pathfinderShouldUseWrapperFootprintWhenSceneryChildIsSmallerThanWrapper() {
|
||||
TestUtils.getMockPlayer("taverleyPatchChildPath").use { p ->
|
||||
val wrapper = RegionManager.getObject(0, 2935, 3437, 8388)
|
||||
?: throw AssertionError("Expected Taverley tree patch wrapper.")
|
||||
|
|
@ -206,7 +233,8 @@ class PathfinderTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test fun entityMovementPulseShouldTriggerWhenDestinationOverrideIsAlreadyReached() {
|
||||
@Test
|
||||
fun entityMovementPulseShouldTriggerWhenDestinationOverrideIsAlreadyReached() {
|
||||
TestUtils.getMockPlayer("bankerOverrideApproach").use { p ->
|
||||
val npc = NPC.create(0, NPC_TEST_LOC)
|
||||
npc.isNeverWalks = true
|
||||
|
|
@ -226,7 +254,8 @@ class PathfinderTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test fun movingEntityMovementPulseShouldNotInteractFromDiagonalTile() {
|
||||
@Test
|
||||
fun movingEntityMovementPulseShouldNotInteractFromDiagonalTile() {
|
||||
TestUtils.getMockPlayer("movingNpcDiagonalApproach").use { p ->
|
||||
val origin = Location.create(3200, 3600, 0)
|
||||
val npc = NPC.create(0, origin.transform(1, 1, 0))
|
||||
|
|
@ -250,8 +279,7 @@ class PathfinderTests {
|
|||
|
||||
TestUtils.advanceTicks(1, false)
|
||||
Assertions.assertNull(
|
||||
pulseLocation,
|
||||
"A moving entity interaction must not trigger from a diagonal non-interaction tile."
|
||||
pulseLocation, "A moving entity interaction must not trigger from a diagonal non-interaction tile."
|
||||
)
|
||||
|
||||
repeat(8) {
|
||||
|
|
@ -276,8 +304,7 @@ class PathfinderTests {
|
|||
0,
|
||||
actualPulseLocation.z,
|
||||
null
|
||||
),
|
||||
"Entity interaction must fire only from a currently valid interaction tile."
|
||||
), "Entity interaction must fire only from a currently valid interaction tile."
|
||||
)
|
||||
} finally {
|
||||
npc.clear()
|
||||
|
|
@ -285,7 +312,67 @@ class PathfinderTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test fun interactionListenerShouldUseOptionHandlerDestinationWhenNoListenerDestinationOverride() {
|
||||
@Test
|
||||
fun runEnabledEntityMovementPulseShouldCatchWalkingNpcMovingDirectlyAway() {
|
||||
TestUtils.getMockPlayer("runNpcInteractionChaser").use { p ->
|
||||
val origin = openHorizontalInteractionOrigin()
|
||||
val npc = NPC.create(0, origin.transform(4, 0, 0))
|
||||
npc.init()
|
||||
p.location = origin
|
||||
p.settings.runEnergy = 100.0
|
||||
p.settings.setRunToggled(true)
|
||||
npc.walkingQueue.reset(false)
|
||||
npc.walkingQueue.addPath(origin.transform(12, 0, 0).x, origin.transform(12, 0, 0).y)
|
||||
|
||||
var pulseLocation: Location? = null
|
||||
var pulseTargetLocation: Location? = null
|
||||
try {
|
||||
GameWorld.Pulser.submit(object : MovementPulse(p, npc) {
|
||||
override fun pulse(): Boolean {
|
||||
pulseLocation = p.location
|
||||
pulseTargetLocation = npc.location
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
repeat(6) {
|
||||
if (pulseLocation == null) {
|
||||
TestUtils.advanceTicks(1, false)
|
||||
}
|
||||
}
|
||||
|
||||
val actualPulseLocation = pulseLocation ?: throw AssertionError(
|
||||
"Run-enabled player should catch the walking NPC before it stops. " + "player=${p.location}, npc=${npc.location}, queue=${p.walkingQueue.queue}"
|
||||
)
|
||||
val actualTargetLocation = pulseTargetLocation
|
||||
?: throw AssertionError("Expected target location to be captured when the pulse fired.")
|
||||
Assertions.assertNotEquals(
|
||||
origin.transform(12, 0, 0),
|
||||
actualTargetLocation,
|
||||
"The interaction should not wait until the NPC finishes walking away."
|
||||
)
|
||||
Assertions.assertTrue(
|
||||
Pathfinder.canInteract(
|
||||
actualPulseLocation.x,
|
||||
actualPulseLocation.y,
|
||||
p.size(),
|
||||
actualTargetLocation.x,
|
||||
actualTargetLocation.y,
|
||||
npc.size(),
|
||||
npc.size(),
|
||||
0,
|
||||
actualPulseLocation.z,
|
||||
null
|
||||
), "Entity interaction must fire from a currently valid interaction tile."
|
||||
)
|
||||
} finally {
|
||||
npc.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun interactionListenerShouldUseOptionHandlerDestinationWhenNoListenerDestinationOverride() {
|
||||
val npc = NPC.create(0, NPC_TEST_LOC)
|
||||
npc.isNeverWalks = true
|
||||
npc.init()
|
||||
|
|
@ -328,7 +415,8 @@ class PathfinderTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test fun genericTalkToShouldOpenGrandExchangeClerkDialogueFromCounterApproachTile() {
|
||||
@Test
|
||||
fun genericTalkToShouldOpenGrandExchangeClerkDialogueFromCounterApproachTile() {
|
||||
GrandExchangePlugin().newInstance(null)
|
||||
if (!DialogueInterpreter.contains(6528)) {
|
||||
GrandExchangeClerk().init()
|
||||
|
|
@ -361,9 +449,7 @@ class PathfinderTests {
|
|||
val base = ServerConstants.HOME_LOCATION!!.transform(8, 8, 0)
|
||||
for (rotation in 0..3) {
|
||||
val booth = Scenery(SceneryIds.BANK_BOOTH_2213, base, 10, rotation)
|
||||
runCatching { findReachableApproachTile(booth) }
|
||||
.getOrNull()
|
||||
?.let { return booth to it }
|
||||
runCatching { findReachableApproachTile(booth) }.getOrNull()?.let { return booth to it }
|
||||
}
|
||||
throw AssertionError("Could not find a reachable synthetic bank booth fixture.")
|
||||
}
|
||||
|
|
@ -392,7 +478,8 @@ class PathfinderTests {
|
|||
throw AssertionError("Could not find a reachable approach tile for $scenery.")
|
||||
}
|
||||
|
||||
@Test fun movementPulseShouldStopEarlyIfNextToATileOccupiedByTargetObject() {
|
||||
@Test
|
||||
fun movementPulseShouldStopEarlyIfNextToATileOccupiedByTargetObject() {
|
||||
val start = Location.create(2731, 3481)
|
||||
val dest = RegionManager.getObject(0, 2720, 3475, 1307)
|
||||
val p = TestUtils.getMockPlayer("treefindtest")
|
||||
|
|
@ -404,15 +491,17 @@ class PathfinderTests {
|
|||
Assertions.assertEquals(Location.create(2722, 3475, 0), p.location)
|
||||
}
|
||||
|
||||
@Test fun movementInteractionShouldTrigger() {
|
||||
@Test
|
||||
fun movementInteractionShouldTrigger() {
|
||||
val npc = NPC.create(0, NPC_TEST_LOC)
|
||||
npc.init()
|
||||
|
||||
var intListenerRan = false
|
||||
InteractionListeners.add(0, IntType.NPC.ordinal, arrayOf("testoptlistener"), method = {player: Player, node: Node ->
|
||||
intListenerRan = true
|
||||
return@add true
|
||||
})
|
||||
InteractionListeners.add(
|
||||
0, IntType.NPC.ordinal, arrayOf("testoptlistener"), method = { player: Player, node: Node ->
|
||||
intListenerRan = true
|
||||
return@add true
|
||||
})
|
||||
|
||||
var pluginRan = false
|
||||
val option = Option("testoption", 4)
|
||||
|
|
@ -422,6 +511,7 @@ class PathfinderTests {
|
|||
NPCDefinition.forId(0).handlers["option:testoption"] = this
|
||||
return this
|
||||
}
|
||||
|
||||
override fun handle(player: Player?, node: Node?, option: String?): Boolean {
|
||||
pluginRan = true
|
||||
return true
|
||||
|
|
@ -432,7 +522,7 @@ class PathfinderTests {
|
|||
npc.interaction.set(option2)
|
||||
option.handler = testHandler
|
||||
|
||||
TestUtils.getMockPlayer("interactionTest").use {p ->
|
||||
TestUtils.getMockPlayer("interactionTest").use { p ->
|
||||
p.location = ServerConstants.HOME_LOCATION
|
||||
TestUtils.simulateInteraction(p, npc, 0)
|
||||
TestUtils.advanceTicks(10, false)
|
||||
|
|
@ -444,8 +534,9 @@ class PathfinderTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test fun entityMovingToStationaryNPCShouldNotIdleIndefinitely() {
|
||||
TestUtils.getMockPlayer("idlenpcdest").use {p ->
|
||||
@Test
|
||||
fun entityMovingToStationaryNPCShouldNotIdleIndefinitely() {
|
||||
TestUtils.getMockPlayer("idlenpcdest").use { p ->
|
||||
val startLoc = ServerConstants.HOME_LOCATION
|
||||
p.location = startLoc
|
||||
val npc = NPC.create(0, NPC_TEST_LOC)
|
||||
|
|
@ -461,8 +552,9 @@ class PathfinderTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test fun entityTargetMovementPulseShouldNotStopOnSameTileAsEntity() {
|
||||
TestUtils.getMockPlayer("entitystoptest").use {p ->
|
||||
@Test
|
||||
fun entityTargetMovementPulseShouldNotStopOnSameTileAsEntity() {
|
||||
TestUtils.getMockPlayer("entitystoptest").use { p ->
|
||||
p.location = ServerConstants.HOME_LOCATION
|
||||
val npc = NPC.create(0, NPC_TEST_LOC)
|
||||
npc.isNeverWalks = true
|
||||
|
|
@ -478,7 +570,8 @@ class PathfinderTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test fun entityTargetMovementPulseWithExplicitParamsShouldNotStopOnSameTile() {
|
||||
@Test
|
||||
fun entityTargetMovementPulseWithExplicitParamsShouldNotStopOnSameTile() {
|
||||
TestUtils.getMockPlayer("entitystoptest2").use { p ->
|
||||
p.location = ServerConstants.HOME_LOCATION
|
||||
val npc = NPC.create(0, NPC_TEST_LOC)
|
||||
|
|
@ -495,7 +588,8 @@ class PathfinderTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test fun doubleMovementPulseToEntityShouldNotStopOnSameTile() {
|
||||
@Test
|
||||
fun doubleMovementPulseToEntityShouldNotStopOnSameTile() {
|
||||
TestUtils.getMockPlayer("entitystoptest3").use { p ->
|
||||
p.location = ServerConstants.HOME_LOCATION
|
||||
val npc = NPC.create(0, NPC_TEST_LOC)
|
||||
|
|
@ -518,12 +612,14 @@ class PathfinderTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test fun simulatedInteractionPacketWithMovementFromPluginShouldNotEndOnSameTile() {
|
||||
@Test
|
||||
fun simulatedInteractionPacketWithMovementFromPluginShouldNotEndOnSameTile() {
|
||||
val testHandler = object : OptionHandler() {
|
||||
override fun newInstance(arg: Any?): Plugin<Any> {
|
||||
NPCDefinition.forId(0).handlers["option:testoption"] = this
|
||||
return this
|
||||
}
|
||||
|
||||
override fun handle(player: Player?, node: Node?, option: String?): Boolean {
|
||||
log(this::class.java, Log.ERR, "Interaction triggered")
|
||||
return true
|
||||
|
|
@ -547,14 +643,16 @@ class PathfinderTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test fun simulatedInteractionPacketWithMovementFromListenerShouldNotEndOnSameTile() {
|
||||
@Test
|
||||
fun simulatedInteractionPacketWithMovementFromListenerShouldNotEndOnSameTile() {
|
||||
val npc = NPC.create(0, NPC_TEST_LOC)
|
||||
npc.isNeverWalks = true
|
||||
npc.init()
|
||||
|
||||
InteractionListeners.add(0, IntType.NPC.ordinal, arrayOf("testoptlistener2"), method = {player: Player, node: Node ->
|
||||
return@add true
|
||||
})
|
||||
InteractionListeners.add(
|
||||
0, IntType.NPC.ordinal, arrayOf("testoptlistener2"), method = { player: Player, node: Node ->
|
||||
return@add true
|
||||
})
|
||||
val opt = Option("testoptlistener2", 1)
|
||||
npc.interaction.set(opt)
|
||||
|
||||
|
|
@ -568,7 +666,8 @@ class PathfinderTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test fun npcShouldReliablyReturnToSpawnLocationIfTooFar() {
|
||||
@Test
|
||||
fun npcShouldReliablyReturnToSpawnLocationIfTooFar() {
|
||||
//spawn a player into the area just to make sure it ticks...
|
||||
TestUtils.getMockPlayer("areatest").use { p ->
|
||||
val npc = NPC(1, Location.create(3240, 3226, 0))
|
||||
|
|
@ -584,7 +683,8 @@ class PathfinderTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test fun npcReturnToSpawnShouldUseOverriddenWalkRadius() {
|
||||
@Test
|
||||
fun npcReturnToSpawnShouldUseOverriddenWalkRadius() {
|
||||
TestUtils.getMockPlayer("overriddenRadiusReturn").use {
|
||||
val spawn = ServerConstants.HOME_LOCATION!!
|
||||
val npc = object : NPC(1, spawn.transform(5, 0, 0)) {
|
||||
|
|
@ -606,10 +706,14 @@ class PathfinderTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test fun randomWalkingNpcShouldNotRouteAroundBlockedLocalDestination() {
|
||||
@Test
|
||||
fun randomWalkingNpcShouldUseSideStepWhenDirectNorthTileIsBlocked() {
|
||||
val origin = Location.create(3200, 3600, 0)
|
||||
val blocked = origin.transform(1, 0, 0)
|
||||
val destination = origin.transform(2, 0, 0)
|
||||
val blocked = origin.transform(0, 1, 0)
|
||||
val destination = origin.transform(0, 2, 0)
|
||||
val sidesteps = setOf(
|
||||
origin.transform(-1, 0, 0), origin.transform(1, 0, 0)
|
||||
)
|
||||
val npc = FixedDestinationNPC(origin, destination, 3)
|
||||
npc.isWalks = true
|
||||
npc.isNeverWalks = false
|
||||
|
|
@ -619,22 +723,66 @@ class PathfinderTests {
|
|||
try {
|
||||
npc.resetWalk()
|
||||
repeat(20) {
|
||||
npc.handleTickActions()
|
||||
if (!npc.walkingQueue.hasPath()) {
|
||||
npc.handleTickActions()
|
||||
}
|
||||
}
|
||||
|
||||
Assertions.assertFalse(
|
||||
Assertions.assertTrue(
|
||||
npc.walkingQueue.hasPath(),
|
||||
"Random-walking NPCs should not take an RSMOD detour around a clipped boundary tile."
|
||||
"Random-walking NPCs should use an open east/west sidestep when the direct north tile is blocked."
|
||||
)
|
||||
npc.walkingQueue.update()
|
||||
Assertions.assertEquals(origin, npc.location)
|
||||
Assertions.assertTrue(
|
||||
npc.location in sidesteps,
|
||||
"Random-walking NPC should only take a local sidestep, not route through the blocked north tile. " + "npc=${npc.location}"
|
||||
)
|
||||
} finally {
|
||||
RegionManager.removeClippingFlag(blocked.z, blocked.x, blocked.y, false, movementBlockFlag)
|
||||
npc.clear()
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun npcShouldReliablyReturnToSpawnEvenIfRegionUnloaded() {
|
||||
@Test
|
||||
fun randomWalkingNpcShouldNotFullyRouteAroundBlockedLocalDestination() {
|
||||
val origin = Location.create(3200, 3600, 0)
|
||||
val blocked = origin.transform(1, 0, 0)
|
||||
val destination = origin.transform(2, 0, 0)
|
||||
val sidesteps = setOf(
|
||||
origin.transform(0, -1, 0), origin.transform(0, 1, 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) {
|
||||
if (!npc.walkingQueue.hasPath()) {
|
||||
npc.handleTickActions()
|
||||
}
|
||||
}
|
||||
|
||||
Assertions.assertTrue(
|
||||
npc.walkingQueue.hasPath(),
|
||||
"Random-walking NPCs should use a local sidestep instead of taking an RSMOD detour."
|
||||
)
|
||||
npc.walkingQueue.update()
|
||||
Assertions.assertTrue(
|
||||
npc.location in sidesteps,
|
||||
"Random-walking NPC should not fully route around a clipped boundary tile. npc=${npc.location}"
|
||||
)
|
||||
Assertions.assertNotEquals(destination, 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 ->
|
||||
val npc = NPC(1, Location.create(3240, 3226, 0))
|
||||
|
|
@ -654,9 +802,7 @@ class PathfinderTests {
|
|||
}
|
||||
|
||||
private class FixedDestinationNPC(
|
||||
location: Location,
|
||||
private val destination: Location,
|
||||
private val radius: Int
|
||||
location: Location, private val destination: Location, private val radius: Int
|
||||
) : NPC(1, location) {
|
||||
override fun getMovementDestination(): Location {
|
||||
return destination
|
||||
|
|
@ -667,8 +813,32 @@ class PathfinderTests {
|
|||
}
|
||||
}
|
||||
|
||||
private val movementBlockFlag = Pathfinder.PREVENT_NORTH or
|
||||
Pathfinder.PREVENT_EAST or
|
||||
Pathfinder.PREVENT_SOUTH or
|
||||
Pathfinder.PREVENT_WEST
|
||||
private val movementBlockFlag =
|
||||
Pathfinder.PREVENT_NORTH or Pathfinder.PREVENT_EAST or Pathfinder.PREVENT_SOUTH or Pathfinder.PREVENT_WEST
|
||||
|
||||
private fun openHorizontalInteractionOrigin(): Location {
|
||||
val start = Location.create(3200, 3600, 0)
|
||||
for (dy in -16..16) {
|
||||
for (dx in -16..16) {
|
||||
val candidate = start.transform(dx, dy, 0)
|
||||
if ((0..12).all { RegionManager.isTeleportPermitted(candidate.transform(it, 0, 0)) } && (0..11).all {
|
||||
Pathfinder.canInteract(
|
||||
candidate.x + it,
|
||||
candidate.y,
|
||||
1,
|
||||
candidate.x + it + 1,
|
||||
candidate.y,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
candidate.z,
|
||||
null
|
||||
)
|
||||
}) {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
}
|
||||
throw AssertionError("No open horizontal interaction test line found near $start.")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue