mirror of
https://gitlab.com/2009scape/2009scape.git
synced 2026-08-28 05:45:10 -06:00
Some performance fixes
This commit is contained in:
parent
24fa2e29d0
commit
138d9d78a1
3 changed files with 137 additions and 34 deletions
|
|
@ -14,8 +14,7 @@ import core.game.world.map.Point
|
|||
import core.game.world.map.RegionManager
|
||||
import core.game.world.map.path.Path
|
||||
import core.game.world.map.path.Pathfinder
|
||||
import core.game.world.map.path.SmartPathfinder
|
||||
import core.game.world.repository.Repository
|
||||
import core.game.world.map.path.RsmodPathfinder
|
||||
import java.util.LinkedHashMap
|
||||
|
||||
/**
|
||||
|
|
@ -25,6 +24,7 @@ import java.util.LinkedHashMap
|
|||
object CombatMovementIntents {
|
||||
private const val MAX_RANGED_APPROACH_CANDIDATES = 128
|
||||
private const val MAX_PLAYER_COMBAT_PATH_DETOUR = 6.0
|
||||
private const val MAX_DIRECT_COMBAT_PATH_DISTANCE = 32
|
||||
|
||||
private data class Intent(val attacker: Entity, val target: Entity)
|
||||
private data class MovementDestination(
|
||||
|
|
@ -38,6 +38,7 @@ object CombatMovementIntents {
|
|||
private data class CandidatePath(val steps: List<Point>, val projectedLocation: Location)
|
||||
|
||||
private val intents = LinkedHashMap<Entity, Intent>()
|
||||
private val activeMeleeAttackers = LinkedHashMap<Entity, Entity>()
|
||||
|
||||
@JvmStatic
|
||||
fun request(attacker: Entity, target: Entity) {
|
||||
|
|
@ -47,16 +48,46 @@ object CombatMovementIntents {
|
|||
if (attacker is NPC && attacker.isNeverWalks) {
|
||||
return
|
||||
}
|
||||
trackActiveMelee(attacker, target)
|
||||
intents[attacker] = Intent(attacker, target)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun requestActiveMeleePressure() {
|
||||
for (attacker in Repository.players) {
|
||||
requestActiveMeleePressure(attacker)
|
||||
fun trackActiveMelee(attacker: Entity, target: Entity) {
|
||||
if (isActiveMeleeAttacker(attacker, target)) {
|
||||
activeMeleeAttackers[attacker] = target
|
||||
} else {
|
||||
activeMeleeAttackers.remove(attacker)
|
||||
}
|
||||
for (attacker in Repository.renderableNpcs) {
|
||||
requestActiveMeleePressure(attacker)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun untrack(attacker: Entity?) {
|
||||
if (attacker == null) {
|
||||
return
|
||||
}
|
||||
intents.remove(attacker)
|
||||
activeMeleeAttackers.remove(attacker)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun requestActiveMeleePressure() {
|
||||
if (activeMeleeAttackers.isEmpty()) {
|
||||
return
|
||||
}
|
||||
val iterator = activeMeleeAttackers.entries.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val entry = iterator.next()
|
||||
val attacker = entry.key
|
||||
val target = entry.value
|
||||
if (!isActiveMeleeAttacker(attacker, target)) {
|
||||
intents.remove(attacker)
|
||||
iterator.remove()
|
||||
continue
|
||||
}
|
||||
if (shouldMaintainMeleePressure(attacker, target)) {
|
||||
intents[attacker] = Intent(attacker, target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -82,6 +113,7 @@ object CombatMovementIntents {
|
|||
@JvmStatic
|
||||
fun clear() {
|
||||
intents.clear()
|
||||
activeMeleeAttackers.clear()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
|
|
@ -94,11 +126,14 @@ object CombatMovementIntents {
|
|||
if (attacker.locks.isMovementLocked) {
|
||||
return false
|
||||
}
|
||||
if (CombatMovementPlanner.movementStepsThisTick(target) == 0) {
|
||||
val targetSteps = CombatMovementPlanner.movementStepsThisTick(target)
|
||||
if (targetSteps == 0) {
|
||||
return false
|
||||
}
|
||||
val predictedTargetLocation = CombatMovementPlanner.predictedTargetLocation(target)
|
||||
val attackTiles = CombatMovementPlanner.candidateAttackTiles(attacker, target, predictedTargetLocation)
|
||||
val predictedTargetLocation = CombatMovementPlanner.predictTargetLocations(target, targetSteps).lastOrNull()
|
||||
?: target.location
|
||||
val borderTiles = CombatMovementPlanner.borderTiles(target, predictedTargetLocation, attacker.size())
|
||||
val attackTiles = borderTiles.filter { RegionManager.isTeleportPermitted(it) }.ifEmpty { borderTiles }
|
||||
if (attacker.location in attackTiles) {
|
||||
return false
|
||||
}
|
||||
|
|
@ -106,15 +141,18 @@ object CombatMovementIntents {
|
|||
return projectedAttackerLocation !in attackTiles
|
||||
}
|
||||
|
||||
private fun requestActiveMeleePressure(attacker: Entity) {
|
||||
private fun isActiveMeleeAttacker(attacker: Entity, target: Entity): Boolean {
|
||||
if (!attacker.isActive || !target.isActive || attacker.location == null || target.location == null) {
|
||||
return false
|
||||
}
|
||||
if (attacker.locks.isMovementLocked || attacker.location.z != target.location.z) {
|
||||
return false
|
||||
}
|
||||
if (attacker is NPC && attacker.isNeverWalks) {
|
||||
return false
|
||||
}
|
||||
val pulse = attacker.properties.combatPulse
|
||||
val target = pulse.getVictim() ?: return
|
||||
if (!pulse.isAttacking || pulse.style != CombatStyle.MELEE) {
|
||||
return
|
||||
}
|
||||
if (shouldMaintainMeleePressure(attacker, target)) {
|
||||
request(attacker, target)
|
||||
}
|
||||
return pulse.getVictim() === target && pulse.isAttacking && pulse.style == CombatStyle.MELEE
|
||||
}
|
||||
|
||||
private fun resolve(
|
||||
|
|
@ -138,7 +176,7 @@ object CombatMovementIntents {
|
|||
return
|
||||
}
|
||||
|
||||
val candidates = movementDestinationsFor(attacker, target)
|
||||
val candidates = movementDestinationsFor(attacker, target, targetLocation)
|
||||
val standingOnCandidate = candidates.any { it.location == attacker.location }
|
||||
if (standingOnCandidate && canAttackFrom(attacker, target, attacker.location, targetLocation)) {
|
||||
attacker.walkingQueue.reset()
|
||||
|
|
@ -209,11 +247,11 @@ object CombatMovementIntents {
|
|||
}
|
||||
}
|
||||
|
||||
private fun movementDestinationsFor(attacker: Entity, target: Entity): List<MovementDestination> {
|
||||
private fun movementDestinationsFor(attacker: Entity, target: Entity, targetLocation: Location): List<MovementDestination> {
|
||||
val pathfinder = pathfinderFor(attacker)
|
||||
if (attacker is NPC && pathfinder === Pathfinder.DUMB) {
|
||||
val destinations = if (occupiedTilesOverlap(attacker, target)) {
|
||||
CombatMovementPlanner.candidateAttackTiles(attacker, target, targetLocationFor(attacker, target))
|
||||
CombatMovementPlanner.candidateAttackTiles(attacker, target, targetLocation)
|
||||
} else {
|
||||
dumbNpcAttackDestinations(attacker, target)
|
||||
}
|
||||
|
|
@ -222,7 +260,6 @@ object CombatMovementIntents {
|
|||
}
|
||||
}
|
||||
|
||||
val targetLocation = targetLocationFor(attacker, target)
|
||||
val destinations = CombatMovementPlanner.candidateAttackTiles(attacker, target, targetLocation).map {
|
||||
MovementDestination(it, pathfinder, allowPartialPath = false)
|
||||
}
|
||||
|
|
@ -521,8 +558,12 @@ object CombatMovementIntents {
|
|||
if (attacker.location == destination.location) {
|
||||
return null
|
||||
}
|
||||
val directPath = directPathTo(attacker, destination)
|
||||
if (directPath != null) {
|
||||
return directPath
|
||||
}
|
||||
if (destination.pathfinder === Pathfinder.SMART &&
|
||||
!SmartPathfinder.canAttempt(attacker.location, destination.location)
|
||||
!RsmodPathfinder.canAttempt(attacker.location, destination.location)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
|
@ -545,6 +586,38 @@ object CombatMovementIntents {
|
|||
return CandidatePath(steps, projected)
|
||||
}
|
||||
|
||||
private fun directPathTo(attacker: Entity, destination: MovementDestination): CandidatePath? {
|
||||
if (attacker.size() != 1 || destination.node !is Location || attacker.location.z != destination.location.z) {
|
||||
return null
|
||||
}
|
||||
val maxSteps = movementStepsFor(attacker)
|
||||
val steps = ArrayList<Point>(maxSteps)
|
||||
var current = attacker.location
|
||||
var distance = 0
|
||||
while (current != destination.location) {
|
||||
if (++distance > MAX_DIRECT_COMBAT_PATH_DISTANCE) {
|
||||
return null
|
||||
}
|
||||
val direction = Direction.getDirection(current, destination.location) ?: return null
|
||||
if (!direction.canMoveFrom(current.z, current.x, current.y, RegionManager::getClippingFlag)) {
|
||||
return null
|
||||
}
|
||||
val next = current.transform(direction)
|
||||
if (!RegionManager.isTeleportPermitted(next)) {
|
||||
return null
|
||||
}
|
||||
if (steps.size < maxSteps) {
|
||||
steps.add(Point(next.x, next.y, direction, direction.stepX, direction.stepY))
|
||||
}
|
||||
current = next
|
||||
}
|
||||
if (steps.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
val projected = steps.last().let { Location.create(it.x, it.y, attacker.location.z) }
|
||||
return CandidatePath(steps, projected)
|
||||
}
|
||||
|
||||
private fun isExcessiveCombatDetour(attacker: Player, destination: MovementDestination, path: Path): Boolean {
|
||||
val pathLength = (path.points.size - 1).coerceAtLeast(0)
|
||||
val directDistance = attacker.location.getDistance(destination.location)
|
||||
|
|
@ -560,17 +633,23 @@ object CombatMovementIntents {
|
|||
if (!isSuccessful || isMoveNear) {
|
||||
return false
|
||||
}
|
||||
val terminal = points.lastOrNull() ?: return false
|
||||
val terminal = points.peekLast() ?: return false
|
||||
return terminal.x == destination.x && terminal.y == destination.y
|
||||
}
|
||||
|
||||
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 maxSteps = movementStepsFor(attacker)
|
||||
val steps = ArrayList<Point>(maxSteps)
|
||||
for (point in path.points) {
|
||||
if (point.x == attacker.location.x && point.y == attacker.location.y) {
|
||||
continue
|
||||
}
|
||||
steps.add(point)
|
||||
if (steps.size >= maxSteps) {
|
||||
break
|
||||
}
|
||||
}
|
||||
val steps = movementStepsFor(attacker).coerceAtMost(points.size)
|
||||
return points.take(steps)
|
||||
return steps
|
||||
}
|
||||
|
||||
private fun walkPath(attacker: Entity, path: CandidatePath) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,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.Direction
|
||||
import core.game.world.map.Location
|
||||
import core.game.world.map.Point
|
||||
import core.game.world.map.RegionManager
|
||||
|
|
@ -50,17 +51,22 @@ object CombatMovementPlanner {
|
|||
if (maxSteps <= 0) {
|
||||
return emptyList()
|
||||
}
|
||||
val queued = movementPoints(target)
|
||||
val queued = movementPoints(target, 2)
|
||||
if (queued.isEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
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) }
|
||||
val predicted = ArrayList<Location>(steps)
|
||||
for (i in 0 until steps) {
|
||||
val point = queued[i]
|
||||
predicted.add(Location.create(point.x, point.y, target.location.z))
|
||||
}
|
||||
return predicted
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun movementStepsThisTick(target: Entity): Int {
|
||||
val queued = movementPoints(target)
|
||||
val queued = movementPoints(target, 2)
|
||||
if (queued.isEmpty()) {
|
||||
return 0
|
||||
}
|
||||
|
|
@ -94,6 +100,10 @@ object CombatMovementPlanner {
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
return Pathfinder.canInteract(
|
||||
location.x,
|
||||
location.y,
|
||||
|
|
@ -125,8 +135,18 @@ object CombatMovementPlanner {
|
|||
return border.toList()
|
||||
}
|
||||
|
||||
private fun movementPoints(target: Entity): List<Point> {
|
||||
return target.walkingQueue.queue.filter { it.direction != null }
|
||||
private fun movementPoints(target: Entity, limit: Int): List<Point> {
|
||||
val points = ArrayList<Point>(limit)
|
||||
for (point in target.walkingQueue.queue) {
|
||||
if (point.direction == null) {
|
||||
continue
|
||||
}
|
||||
points.add(point)
|
||||
if (points.size >= limit) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return points
|
||||
}
|
||||
|
||||
private fun canMoveTwoStepsThisTick(target: Entity, queued: List<Point>): Boolean {
|
||||
|
|
|
|||
|
|
@ -216,6 +216,9 @@ class CombatPulse(
|
|||
}
|
||||
return false
|
||||
}
|
||||
if (style == CombatStyle.MELEE) {
|
||||
CombatMovementIntents.trackActiveMelee(attacker, target)
|
||||
}
|
||||
val type = canInteract()
|
||||
if (type == InteractionType.STILL_INTERACT) {
|
||||
if (shouldMaintainMeleePressure()) {
|
||||
|
|
@ -378,6 +381,7 @@ class CombatPulse(
|
|||
|
||||
override fun stop() {
|
||||
super.stop()
|
||||
CombatMovementIntents.untrack(entity)
|
||||
entity!!.setAttribute("combat-stop", GameWorld.ticks)
|
||||
if (victim != null) {
|
||||
lastVictim = victim
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue