mirror of
https://github.com/ProjectSWGCore/Holocore.git
synced 2026-09-12 22:45:18 -04:00
Fixed or improved a variety of CPU and RAM issues
This commit is contained in:
@@ -8,6 +8,7 @@ classes/
|
||||
.classpath
|
||||
.project
|
||||
.kotlin
|
||||
/.idea/AndroidProjectSystem.xml
|
||||
# IntelliJ-User Specific
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
|
||||
+1
-1
Submodule pswgcommon updated: fdad8e7ad7...6dfcf2213b
@@ -120,9 +120,13 @@ class NavigationPoint(val parent: SWGObject?, val location: Location, val speed:
|
||||
}
|
||||
|
||||
fun from(sourceParent: SWGObject?, source: Location, destinationParent: SWGObject?, destination: Location, speed: Double): List<NavigationPoint> {
|
||||
assert(sourceParent == null || sourceParent is CellObject) { "invalid source parent" }
|
||||
assert(destinationParent == null || destinationParent is CellObject) { "invalid destination parent" }
|
||||
assert(speed > 0) { "speed must be greater than zero, was $speed" }
|
||||
|
||||
if (sourceParent == destinationParent) return from(sourceParent, source, destination, speed)
|
||||
|
||||
var source = source
|
||||
assert(sourceParent == null || sourceParent is CellObject)
|
||||
assert(destinationParent == null || destinationParent is CellObject)
|
||||
val route = getBuildingRoute(sourceParent as CellObject?, destinationParent as CellObject?, source, destination) ?: return ArrayList()
|
||||
val points = createIntraBuildingRoute(route, sourceParent, source, speed)
|
||||
if (route.isNotEmpty()) source = if (destinationParent == null) buildWorldPortalLocation(route[route.size - 1]) else buildPortalLocation(route[route.size - 1])
|
||||
@@ -145,10 +149,14 @@ class NavigationPoint(val parent: SWGObject?, val location: Location, val speed:
|
||||
val totalDistance = source.distanceTo(destination)
|
||||
val path: MutableList<NavigationPoint> = ArrayList()
|
||||
|
||||
assert(speed > 0) { "speed must be greater than zero, was $speed" }
|
||||
assert(totalDistance < 5_000) { "distance between waypoints is too large ($totalDistance)" }
|
||||
|
||||
var currentDistance = speed
|
||||
while (currentDistance < totalDistance) {
|
||||
path.add(interpolate(parent, source, destination, speed, currentDistance / totalDistance))
|
||||
currentDistance += speed
|
||||
assert(path.size < 10_000) { "path length growing too large" }
|
||||
}
|
||||
path.add(interpolate(parent, source, destination, speed, 1.0))
|
||||
return path
|
||||
@@ -229,7 +237,7 @@ class NavigationPoint(val parent: SWGObject?, val location: Location, val speed:
|
||||
|
||||
private fun buildWorldPortalLocation(portal: Portal): Location {
|
||||
val building = portal.cell1!!.parent
|
||||
assert(building is BuildingObject)
|
||||
assert(building is BuildingObject) { "cell parent wasn't a building" }
|
||||
return Location.builder(buildPortalLocation(portal)).translateLocation(building!!.location).build()
|
||||
}
|
||||
|
||||
|
||||
@@ -51,15 +51,19 @@ class NpcPatrolMode(obj: AIObject, waypoints: List<ResolvedPatrolWaypoint>) : Np
|
||||
waypointBuilder.add(waypointBuilder[0])
|
||||
}
|
||||
|
||||
this.waypoints = ArrayList<NavigationPoint>(128)
|
||||
for (i in 1 until waypointBuilder.size) {
|
||||
val source = waypointBuilder[i - 1]
|
||||
val destination = waypointBuilder[i]
|
||||
this.waypoints.addAll(NavigationPoint.from(source.parent, source.location, destination.parent, destination.location, walkSpeed))
|
||||
if (destination.delay > 0)
|
||||
this.waypoints.addAll(NavigationPoint.nop(this.waypoints[this.waypoints.size - 1], destination.delay.toInt() - 1))
|
||||
if (waypointBuilder.isEmpty()) {
|
||||
this.waypoints = ArrayList<NavigationPoint>(128)
|
||||
} else {
|
||||
this.waypoints = ArrayList<NavigationPoint>(128)
|
||||
for (i in 1 until waypointBuilder.size) {
|
||||
val source = waypointBuilder[i - 1]
|
||||
val destination = waypointBuilder[i]
|
||||
this.waypoints.addAll(NavigationPoint.from(source.parent, source.location, destination.parent, destination.location, walkSpeed))
|
||||
if (destination.delay > 0)
|
||||
this.waypoints.addAll(NavigationPoint.nop(this.waypoints[this.waypoints.size - 1], destination.delay.toInt() - 1))
|
||||
}
|
||||
this.waypoints.addAll(NavigationPoint.from(waypointBuilder[waypointBuilder.size - 1].parent, waypointBuilder[waypointBuilder.size - 1].location, waypointBuilder[0].parent, waypointBuilder[0].location, walkSpeed))
|
||||
}
|
||||
this.waypoints.addAll(NavigationPoint.from(waypointBuilder[waypointBuilder.size - 1].parent, waypointBuilder[waypointBuilder.size - 1].location, waypointBuilder[0].parent, waypointBuilder[0].location, walkSpeed))
|
||||
}
|
||||
|
||||
override suspend fun onModeStart() {
|
||||
|
||||
+10
-7
@@ -39,13 +39,16 @@ import com.projectswg.holocore.resources.support.objects.permissions.AdminPermis
|
||||
import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureDifficulty
|
||||
import com.projectswg.holocore.resources.support.objects.swg.custom.AIBehavior
|
||||
import com.projectswg.holocore.resources.support.objects.swg.custom.AIObject
|
||||
import java.util.concurrent.ThreadLocalRandom
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.sin
|
||||
import kotlin.random.Random
|
||||
|
||||
class DynamicMovementObject(var location: Location, val name: String, val baseSpeed: Double = 0.0) {
|
||||
|
||||
var heading = ThreadLocalRandom.current().nextDouble() * 2 * Math.PI
|
||||
|
||||
private val random = Random(System.currentTimeMillis())
|
||||
var heading = random.nextDouble() * 2 * Math.PI
|
||||
private val groupMarker = ObjectCreator.createObjectFromTemplate("object/path_waypoint/shared_path_waypoint_droid.iff")
|
||||
private val npcs = ArrayList<AIObject>()
|
||||
private var lastUpdate = System.nanoTime()
|
||||
@@ -70,7 +73,6 @@ class DynamicMovementObject(var location: Location, val name: String, val baseSp
|
||||
val bossSpawner = Spawner(simpleSpawnInfo.withDifficulty(CreatureDifficulty.BOSS).build(), groupMarker)
|
||||
val eliteSpawner = Spawner(simpleSpawnInfo.withDifficulty(CreatureDifficulty.ELITE).build(), groupMarker)
|
||||
val normalSpawner = Spawner(simpleSpawnInfo.withDifficulty(CreatureDifficulty.NORMAL).build(), groupMarker)
|
||||
val random = ThreadLocalRandom.current()
|
||||
if (random.nextDouble() < 0.25)
|
||||
npcs.add(NPCCreator.createSingleNpc(bossSpawner))
|
||||
npcs.add(NPCCreator.createSingleNpc(eliteSpawner))
|
||||
@@ -102,7 +104,7 @@ class DynamicMovementObject(var location: Location, val name: String, val baseSp
|
||||
.setZ(location.z + radius * sin(angle))
|
||||
newLocationBuilder.setY(ServerData.terrains.getHeight(newLocationBuilder))
|
||||
val newLocation = newLocationBuilder.build()
|
||||
val speed = it.worldLocation.distanceTo(newLocation) / elapsedTime
|
||||
val speed = min(30.0, max(1.0, it.worldLocation.distanceTo(newLocation) / elapsedTime))
|
||||
it.moveTo(null, newLocationBuilder.build(), speed)
|
||||
}
|
||||
}
|
||||
@@ -114,7 +116,7 @@ class DynamicMovementObject(var location: Location, val name: String, val baseSp
|
||||
return
|
||||
}
|
||||
|
||||
val newHeading = heading + Math.PI * (1 + ThreadLocalRandom.current().nextDouble() - 0.5)
|
||||
val newHeading = heading + Math.PI * (1 + random.nextDouble() - 0.5)
|
||||
val secondProposed = calculateNextPosition(newHeading, distance)
|
||||
if (isValidNextPosition(secondProposed)) {
|
||||
location = secondProposed
|
||||
@@ -123,7 +125,7 @@ class DynamicMovementObject(var location: Location, val name: String, val baseSp
|
||||
}
|
||||
|
||||
// Brute Force Escape
|
||||
val randomRotationFromNorth = ThreadLocalRandom.current().nextDouble() * Math.TAU
|
||||
val randomRotationFromNorth = random.nextDouble() * Math.TAU
|
||||
for (clockwiseRotation in 0..35) {
|
||||
val bruteForceHeading = (clockwiseRotation * 10) * Math.PI / 180.0 + randomRotationFromNorth
|
||||
val proposed = calculateNextPosition(bruteForceHeading, distance)
|
||||
@@ -135,6 +137,7 @@ class DynamicMovementObject(var location: Location, val name: String, val baseSp
|
||||
}
|
||||
|
||||
// TODO: destroy this object, we got stuck
|
||||
location = firstProposed
|
||||
assert(false)
|
||||
}
|
||||
|
||||
|
||||
@@ -796,17 +796,62 @@ public abstract class SWGObject extends BaselineObject implements Comparable<SWG
|
||||
public Location getWorldLocation() {
|
||||
return location.getWorldLocation(this);
|
||||
}
|
||||
|
||||
|
||||
public double distanceTo(@NotNull SWGObject obj) {
|
||||
if (parent == obj.getParent())
|
||||
return getLocation().distanceTo(obj.getLocation());
|
||||
return getWorldLocation().distanceTo(obj.getWorldLocation());
|
||||
SWGObject tmp = this;
|
||||
double selfX = 0.0;
|
||||
double selfY = 0.0;
|
||||
double selfZ = 0.0;
|
||||
while (tmp != null) {
|
||||
Location loc = tmp.getLocation();
|
||||
selfX += loc.getX();
|
||||
selfY += loc.getY();
|
||||
selfZ += loc.getZ();
|
||||
tmp = tmp.getParent();
|
||||
}
|
||||
|
||||
tmp = obj;
|
||||
double otherX = 0.0;
|
||||
double otherY = 0.0;
|
||||
double otherZ = 0.0;
|
||||
while (tmp != null) {
|
||||
Location loc = tmp.getLocation();
|
||||
otherX += loc.getX();
|
||||
otherY += loc.getY();
|
||||
otherZ += loc.getZ();
|
||||
tmp = tmp.getParent();
|
||||
}
|
||||
|
||||
selfX -= otherX;
|
||||
selfY -= otherY;
|
||||
selfZ -= otherZ;
|
||||
return Math.sqrt(selfX * selfX + selfY * selfY + selfZ * selfZ);
|
||||
}
|
||||
|
||||
|
||||
public double flatDistanceTo(@NotNull SWGObject obj) {
|
||||
if (parent == obj.getParent())
|
||||
return getLocation().flatDistanceTo(obj.getLocation());
|
||||
return getWorldLocation().flatDistanceTo(obj.getWorldLocation());
|
||||
SWGObject tmp = this;
|
||||
double selfX = 0.0;
|
||||
double selfZ = 0.0;
|
||||
while (tmp != null) {
|
||||
Location loc = tmp.getLocation();
|
||||
selfX += loc.getX();
|
||||
selfZ += loc.getZ();
|
||||
tmp = tmp.getParent();
|
||||
}
|
||||
|
||||
tmp = obj;
|
||||
double otherX = 0.0;
|
||||
double otherZ = 0.0;
|
||||
while (tmp != null) {
|
||||
Location loc = tmp.getLocation();
|
||||
otherX += loc.getX();
|
||||
otherZ += loc.getZ();
|
||||
tmp = tmp.getParent();
|
||||
}
|
||||
|
||||
selfX -= otherX;
|
||||
selfZ -= otherZ;
|
||||
return Math.sqrt(selfX * selfX + selfZ * selfZ);
|
||||
}
|
||||
|
||||
public double getX() {
|
||||
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (c) 2025 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* ProjectSWG is an emulation project for Star Wars Galaxies founded on *
|
||||
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
|
||||
* Our goal is to create one or more emulators which will provide servers for *
|
||||
* players to continue playing a game similar to the one they used to play. *
|
||||
* *
|
||||
* This file is part of Holocore. *
|
||||
* *
|
||||
* --------------------------------------------------------------------------------*
|
||||
* *
|
||||
* Holocore is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as *
|
||||
* published by the Free Software Foundation, either version 3 of the *
|
||||
* License, or (at your option) any later version. *
|
||||
* *
|
||||
* Holocore is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
||||
***********************************************************************************/
|
||||
package com.projectswg.holocore.resources.support.data.server_info.loader
|
||||
|
||||
import com.projectswg.common.data.location.Location
|
||||
import com.projectswg.common.data.swgiff.parsers.SWGParser
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.npc.NpcPatrolRouteLoader
|
||||
import com.projectswg.holocore.resources.support.npc.ai.NavigationPoint
|
||||
import com.projectswg.holocore.resources.support.npc.spawn.Spawner
|
||||
import com.projectswg.holocore.services.support.objects.ObjectStorageService
|
||||
import com.projectswg.holocore.test.runners.TestRunnerNoIntents
|
||||
import org.junit.jupiter.api.AfterAll
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
class NpcPatrolRouteLoaderTest : TestRunnerNoIntents() {
|
||||
|
||||
@Test
|
||||
fun `test patrol route waypoints`() {
|
||||
fun checkRouteLeg(sourceWaypoint: Spawner.ResolvedPatrolWaypoint, destinationWaypoint: Spawner.ResolvedPatrolWaypoint) {
|
||||
val route = NavigationPoint.from(sourceWaypoint.parent, sourceWaypoint.location, destinationWaypoint.parent, destinationWaypoint.location, 1.0)
|
||||
assert(route.size < 500) { "distance between waypoints is too large (${route.size})" }
|
||||
assert(sourceWaypoint.location.terrain == destinationWaypoint.location.terrain) { "terrain mismatch along route" }
|
||||
}
|
||||
|
||||
val hasError = AtomicBoolean(false)
|
||||
ServerData.npcPatrolRoutes.forEach { route ->
|
||||
try {
|
||||
val resolvedRoute = route.map { Spawner.ResolvedPatrolWaypoint(it) }
|
||||
assert(route.isNotEmpty()) { "route is empty" }
|
||||
for (i in 1 until route.size) {
|
||||
checkRouteLeg(resolvedRoute[i - 1], resolvedRoute[i])
|
||||
}
|
||||
if (route[0].patrolType == NpcPatrolRouteLoader.PatrolType.LOOP) checkRouteLeg(resolvedRoute[route.size - 1], resolvedRoute[0])
|
||||
} catch (e: AssertionError) {
|
||||
System.err.println("Patrol group '${route[0].groupId}' error: ${e.message}")
|
||||
hasError.set(true)
|
||||
}
|
||||
}
|
||||
Assertions.assertFalse(hasError.get())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test NPC to patrol route start`() {
|
||||
val hasError = AtomicBoolean(false)
|
||||
ServerData.npcStaticSpawns.spawns.parallelStream().forEach { spawn ->
|
||||
if (spawn.patrolId.isEmpty() || spawn.patrolId == "0") return@forEach
|
||||
val spawnerLocation = Location.builder().setTerrain(spawn.terrain).setX(spawn.x).setY(spawn.y).setZ(spawn.z).build()
|
||||
val route = ServerData.npcPatrolRoutes[spawn.patrolId]
|
||||
val routeLocation = Location.builder().setTerrain(route[0].terrain).setX(route[0].x).setY(route[0].y).setZ(route[0].z).build()
|
||||
val distanceToRoute = spawnerLocation.distanceTo(routeLocation)
|
||||
try {
|
||||
assert(spawn.buildingId == route[0].buildingId) { "NPC not in same building as route" }
|
||||
assert(spawn.cellId == route[0].cellId) { "NPC not in same cell as route" }
|
||||
assert(distanceToRoute < 500) { "Spawner distance to route too large ($distanceToRoute)" }
|
||||
assert(spawnerLocation.terrain == routeLocation.terrain) { "terrain mismatch along route" }
|
||||
} catch (e: AssertionError) {
|
||||
System.err.println("Patrol spawner '${spawn.npcId}' with route '${spawn.patrolId}' error: ${e.message}")
|
||||
hasError.set(true)
|
||||
}
|
||||
}
|
||||
Assertions.assertFalse(hasError.get())
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private var objectStorageService = ObjectStorageService()
|
||||
|
||||
@BeforeAll
|
||||
@JvmStatic
|
||||
fun setup() {
|
||||
SWGParser.setBasePath("serverdata")
|
||||
objectStorageService.initialize()
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
@JvmStatic
|
||||
fun tearDown() {
|
||||
objectStorageService.terminate()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user