mirror of
https://gitlab.com/2009scape/2009scape.git
synced 2026-08-28 05:45:10 -06:00
Forced run fix
This commit is contained in:
parent
4d50efbddc
commit
64b6d858a0
4 changed files with 129 additions and 30 deletions
|
|
@ -99,24 +99,23 @@ object CombatMovementIntents {
|
|||
return
|
||||
}
|
||||
|
||||
val forceRun = shouldForceRun(attacker, target)
|
||||
val plan = CombatMovementPlanner.plan(attacker, target)
|
||||
val candidates = CombatMovementPlanner.candidateAttackTiles(attacker, target, plan.targetLocation)
|
||||
val targetLocation = targetLocationFor(attacker, target)
|
||||
val candidates = CombatMovementPlanner.candidateAttackTiles(attacker, target, targetLocation)
|
||||
var blockedByReservation = false
|
||||
for (candidate in candidates) {
|
||||
val candidatePath = pathTo(attacker, candidate, forceRun) ?: continue
|
||||
val candidatePath = pathTo(attacker, candidate) ?: continue
|
||||
val projectedTiles = occupiedTiles(attacker, candidatePath.projectedLocation)
|
||||
if (projectedTiles.any { it in reservedTiles }) {
|
||||
blockedByReservation = true
|
||||
continue
|
||||
}
|
||||
|
||||
walkPath(attacker, candidatePath, forceRun)
|
||||
walkPath(attacker, candidatePath)
|
||||
attacker.face(target)
|
||||
reservedTiles.addAll(projectedTiles)
|
||||
return
|
||||
}
|
||||
if (!blockedByReservation) {
|
||||
if (!blockedByReservation && CombatMovementPlanner.movementStepsThisTick(target) == 0) {
|
||||
stopUnreachableCombat(attacker)
|
||||
}
|
||||
}
|
||||
|
|
@ -138,7 +137,7 @@ object CombatMovementIntents {
|
|||
return attacker !is NPC || !attacker.isNeverWalks
|
||||
}
|
||||
|
||||
private fun pathTo(attacker: Entity, destination: Location, forceRun: Boolean): CandidatePath? {
|
||||
private fun pathTo(attacker: Entity, destination: Location): CandidatePath? {
|
||||
if (attacker.location == destination) {
|
||||
return null
|
||||
}
|
||||
|
|
@ -147,7 +146,7 @@ object CombatMovementIntents {
|
|||
if (!path.reaches(destination)) {
|
||||
return null
|
||||
}
|
||||
val steps = immediateMovementSteps(attacker, path, forceRun)
|
||||
val steps = immediateMovementSteps(attacker, path)
|
||||
val projected = steps.lastOrNull()?.let { Location.create(it.x, it.y, attacker.location.z) } ?: return null
|
||||
return CandidatePath(steps, projected)
|
||||
}
|
||||
|
|
@ -160,41 +159,42 @@ object CombatMovementIntents {
|
|||
return terminal.x == destination.x && terminal.y == destination.y
|
||||
}
|
||||
|
||||
private fun immediateMovementSteps(attacker: Entity, path: Path, forceRun: Boolean): List<Point> {
|
||||
private fun immediateMovementSteps(attacker: Entity, path: Path): List<Point> {
|
||||
val points = path.points.filter { it.x != attacker.location.x || it.y != attacker.location.y }
|
||||
if (points.isEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
val steps = if (attacker is Player && (forceRun || attacker.walkingQueue.isRunningBoth) && points.size > 1) {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
}
|
||||
val steps = movementStepsFor(attacker).coerceAtMost(points.size)
|
||||
return points.take(steps)
|
||||
}
|
||||
|
||||
private fun walkPath(attacker: Entity, path: CandidatePath, forceRun: Boolean) {
|
||||
private fun walkPath(attacker: Entity, path: CandidatePath) {
|
||||
if (attacker.locks.isMovementLocked) {
|
||||
return
|
||||
}
|
||||
val run = attacker is Player && (forceRun || attacker.walkingQueue.isRunning)
|
||||
val run = attacker is Player && attacker.walkingQueue.isRunning && attacker.settings.runEnergy >= 1.0
|
||||
attacker.walkingQueue.reset(run)
|
||||
for (step in path.steps) {
|
||||
attacker.walkingQueue.addPath(step.x, step.y)
|
||||
}
|
||||
}
|
||||
|
||||
private fun shouldForceRun(attacker: Entity, target: Entity): Boolean {
|
||||
if (attacker !is Player) {
|
||||
return false
|
||||
private fun targetLocationFor(attacker: Entity, target: Entity): Location {
|
||||
val targetSteps = CombatMovementPlanner.movementStepsThisTick(target)
|
||||
val predictionSteps = targetSteps.coerceAtMost(movementStepsFor(attacker))
|
||||
return CombatMovementPlanner.predictTargetLocations(target, predictionSteps).lastOrNull() ?: target.location
|
||||
}
|
||||
|
||||
private fun movementStepsFor(attacker: Entity): Int {
|
||||
return if (canRun(attacker)) {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
}
|
||||
if (attacker.walkingQueue.isRunningBoth || attacker.walkingQueue.isRunDisabled) {
|
||||
return false
|
||||
}
|
||||
if (attacker.settings.runEnergy < 1.0) {
|
||||
return false
|
||||
}
|
||||
return CombatMovementPlanner.movementStepsThisTick(target) > 1
|
||||
}
|
||||
|
||||
private fun canRun(attacker: Entity): Boolean {
|
||||
return attacker is Player && attacker.walkingQueue.isRunningBoth && attacker.settings.runEnergy >= 1.0
|
||||
}
|
||||
|
||||
private fun stopUnreachableCombat(attacker: Entity) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package core.game.node.entity.combat
|
|||
|
||||
import core.ServerConstants
|
||||
import core.game.node.entity.Entity
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.world.map.Location
|
||||
import core.game.world.map.Point
|
||||
import core.game.world.map.RegionManager
|
||||
|
|
@ -52,7 +53,7 @@ object CombatMovementPlanner {
|
|||
if (queued.isEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
val steps = if (queued.first().isRunDisabled) 1 else maxSteps.coerceAtMost(2)
|
||||
val steps = if (canMoveTwoStepsThisTick(target, queued)) maxSteps.coerceAtMost(2) else 1
|
||||
return queued.take(steps).map { Location.create(it.x, it.y, target.location.z) }
|
||||
}
|
||||
|
||||
|
|
@ -62,7 +63,7 @@ object CombatMovementPlanner {
|
|||
if (queued.isEmpty()) {
|
||||
return 0
|
||||
}
|
||||
return if (target.walkingQueue.isRunningBoth && !queued.first().isRunDisabled && queued.size > 1) {
|
||||
return if (canMoveTwoStepsThisTick(target, queued)) {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
|
|
@ -111,4 +112,11 @@ object CombatMovementPlanner {
|
|||
private fun movementPoints(target: Entity): List<Point> {
|
||||
return target.walkingQueue.queue.filter { it.direction != null }
|
||||
}
|
||||
|
||||
private fun canMoveTwoStepsThisTick(target: Entity, queued: List<Point>): Boolean {
|
||||
if (queued.size <= 1 || queued.first().isRunDisabled || !target.walkingQueue.isRunningBoth) {
|
||||
return false
|
||||
}
|
||||
return target !is Player || target.settings.runEnergy >= 1.0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class CombatMovementTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun meleeAttackerShouldMirrorRunningVictimAndKeepAttackPressure() {
|
||||
fun meleeAttackerShouldMirrorRunningVictimWhenRunEnabledAndKeepAttackPressure() {
|
||||
TestUtils.getMockPlayer("combat_mirror_attacker").use { attacker ->
|
||||
TestUtils.getMockPlayer("combat_mirror_victim").use { victim ->
|
||||
val origin = arenaOrigin()
|
||||
|
|
@ -38,6 +38,8 @@ class CombatMovementTests {
|
|||
place(victim, origin.transform(1, 0, 0))
|
||||
configureMelee(attacker)
|
||||
configureMelee(victim)
|
||||
enableRun(attacker)
|
||||
enableRun(victim)
|
||||
enablePvp(attacker, victim)
|
||||
|
||||
attacker.attack(victim)
|
||||
|
|
@ -54,7 +56,7 @@ class CombatMovementTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun meleeAttackerShouldQueueRunStepWhenCurrentTargetIsLeavingMeleeRange() {
|
||||
fun meleeAttackerShouldQueueRunStepWhenRunEnabledAndCurrentTargetIsLeavingMeleeRange() {
|
||||
TestUtils.getMockPlayer("combat_active_mirror_attacker").use { attacker ->
|
||||
TestUtils.getMockPlayer("combat_active_mirror_victim").use { victim ->
|
||||
val origin = arenaOrigin()
|
||||
|
|
@ -62,6 +64,8 @@ class CombatMovementTests {
|
|||
place(victim, origin.transform(1, 0, 0))
|
||||
configureMelee(attacker)
|
||||
configureMelee(victim)
|
||||
enableRun(attacker)
|
||||
enableRun(victim)
|
||||
enablePvp(attacker, victim)
|
||||
CombatMovementIntents.clear()
|
||||
|
||||
|
|
@ -81,6 +85,69 @@ class CombatMovementTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun meleeAttackerShouldNotForceRunWhenChasingRunningPvpTarget() {
|
||||
TestUtils.getMockPlayer("combat_walk_attacker").use { attacker ->
|
||||
TestUtils.getMockPlayer("combat_walk_victim").use { victim ->
|
||||
val origin = arenaOrigin()
|
||||
place(attacker, origin.transform(0, -1, 0))
|
||||
place(victim, origin.transform(1, 0, 0))
|
||||
configureMelee(attacker)
|
||||
configureMelee(victim)
|
||||
disableRun(attacker)
|
||||
enableRun(victim)
|
||||
enablePvp(attacker, victim)
|
||||
CombatMovementIntents.clear()
|
||||
|
||||
attacker.attack(victim)
|
||||
queueRun(victim, origin.transform(8, 0, 0))
|
||||
GameWorld.Pulser.updateAll()
|
||||
CombatMovementIntents.resolve()
|
||||
|
||||
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.")
|
||||
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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun walkingMeleeAttackerShouldNotStopWhenRunningTargetTemporarilyBlocksFirstStep() {
|
||||
TestUtils.getMockPlayer("combat_walk_blocked_attacker").use { attacker ->
|
||||
TestUtils.getMockPlayer("combat_walk_blocked_victim").use { victim ->
|
||||
val origin = arenaOrigin()
|
||||
place(attacker, origin)
|
||||
place(victim, origin.transform(1, 0, 0))
|
||||
configureMelee(attacker)
|
||||
configureMelee(victim)
|
||||
disableRun(attacker)
|
||||
enableRun(victim)
|
||||
enablePvp(attacker, victim)
|
||||
CombatMovementIntents.clear()
|
||||
|
||||
attacker.attack(victim)
|
||||
queueRun(victim, origin.transform(8, 0, 0))
|
||||
GameWorld.Pulser.updateAll()
|
||||
CombatMovementIntents.resolve()
|
||||
|
||||
assertTrue(
|
||||
attacker.properties.combatPulse.isAttacking,
|
||||
"A moving target temporarily blocking the first walking step should not stop combat."
|
||||
)
|
||||
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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun meleeAttackerShouldFollowVictimRunQueuedByLiveWalkPacket() {
|
||||
TestUtils.getMockPlayer("combat_packet_mirror_attacker").use { attacker ->
|
||||
|
|
@ -438,6 +505,12 @@ class CombatMovementTests {
|
|||
player.settings.setRunToggled(true)
|
||||
}
|
||||
|
||||
private fun disableRun(player: Player) {
|
||||
player.settings.runEnergy = 100.0
|
||||
player.settings.setRunToggled(false)
|
||||
player.walkingQueue.setRunning(false)
|
||||
}
|
||||
|
||||
private fun enablePvp(first: Entity, second: Entity) {
|
||||
core.game.world.GameWorld.settings!!.wild_pvp_enabled = true
|
||||
first.asPlayer().skullManager.isWilderness = true
|
||||
|
|
|
|||
|
|
@ -170,6 +170,24 @@ class CombatTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun combatMovementPlannerTreatsZeroEnergyRunningPlayerAsWalking() {
|
||||
TestUtils.getMockPlayer("combatPlannerNoEnergyRunner").use { target ->
|
||||
val origin = ServerConstants.HOME_LOCATION!!.transform(32, 32, 0)
|
||||
target.location = origin
|
||||
target.settings.runEnergy = 0.0
|
||||
target.walkingQueue.reset(true)
|
||||
target.walkingQueue.addPath(origin.x + 3, origin.y)
|
||||
|
||||
Assertions.assertEquals(1, CombatMovementPlanner.movementStepsThisTick(target))
|
||||
Assertions.assertEquals(
|
||||
listOf(origin.transform(1, 0, 0)),
|
||||
CombatMovementPlanner.predictTargetLocations(target)
|
||||
)
|
||||
Assertions.assertEquals(origin.transform(1, 0, 0), CombatMovementPlanner.predictedTargetLocation(target))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun combatMovementPlannerChoosesClosestTargetBorderTile() {
|
||||
TestUtils.getMockPlayer("combatPlannerAttacker").use { attacker ->
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue