Some performance fixes

This commit is contained in:
dam 2026-05-06 18:31:48 +03:00
parent 24fa2e29d0
commit 138d9d78a1
No known key found for this signature in database
GPG key ID: 4AF4E722399663FB
3 changed files with 137 additions and 34 deletions

View file

@ -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) {

View file

@ -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 {

View file

@ -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