Skip to content

Commit 826b24a

Browse files
committed
Added initial version of the dynamic spawn system
1 parent 8e030e0 commit 826b24a

8 files changed

Lines changed: 297 additions & 9 deletions

File tree

src/main/java/com/projectswg/holocore/resources/support/data/server_info/loader/NoSpawnZoneLoader.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
/***********************************************************************************
2-
* Copyright (c) 2024 /// Project SWG /// www.projectswg.com *
2+
* Copyright (c) 2025 /// Project SWG /// www.projectswg.com *
33
* *
4-
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
4+
* ProjectSWG is an emulation project for Star Wars Galaxies founded on *
55
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
6-
* Our goal is to create an emulator which will provide a server for players to *
7-
* continue playing a game similar to the one they used to play. We are basing *
8-
* it on the final publish of the game prior to end-game events. *
6+
* Our goal is to create one or more emulators which will provide servers for *
7+
* players to continue playing a game similar to the one they used to play. *
98
* *
109
* This file is part of Holocore. *
1110
* *
@@ -33,7 +32,6 @@ import com.projectswg.holocore.resources.support.data.server_info.SdbLoader.SdbR
3332
import java.io.File
3433
import java.io.IOException
3534
import java.util.*
36-
import kotlin.collections.ArrayList
3735

3836
class NoSpawnZoneLoader : DataLoader() {
3937
private val noSpawnZoneMap: MutableMap<Terrain, MutableCollection<NoSpawnZoneInfo>> = EnumMap(Terrain::class.java)
@@ -89,6 +87,7 @@ class NoSpawnZoneLoader : DataLoader() {
8987
}
9088

9189
class NoSpawnZoneInfo(set: SdbResultSet) {
90+
val name: String = set.getText("comment")
9291
val x: Long = set.getInt("x")
9392
val z: Long = set.getInt("z")
9493
val radius: Long = set.getInt("radius")
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/***********************************************************************************
2+
* Copyright (c) 2025 /// Project SWG /// www.projectswg.com *
3+
* *
4+
* ProjectSWG is an emulation project for Star Wars Galaxies founded on *
5+
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
6+
* Our goal is to create one or more emulators which will provide servers for *
7+
* players to continue playing a game similar to the one they used to play. *
8+
* *
9+
* This file is part of Holocore. *
10+
* *
11+
* --------------------------------------------------------------------------------*
12+
* *
13+
* Holocore is free software: you can redistribute it and/or modify *
14+
* it under the terms of the GNU Affero General Public License as *
15+
* published by the Free Software Foundation, either version 3 of the *
16+
* License, or (at your option) any later version. *
17+
* *
18+
* Holocore is distributed in the hope that it will be useful, *
19+
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
20+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
21+
* GNU Affero General Public License for more details. *
22+
* *
23+
* You should have received a copy of the GNU Affero General Public License *
24+
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
25+
***********************************************************************************/
26+
package com.projectswg.holocore.resources.support.npc.ai.dynamic
27+
28+
import com.projectswg.common.data.location.Location
29+
import com.projectswg.holocore.resources.support.data.server_info.loader.ServerData
30+
import java.util.concurrent.ThreadLocalRandom
31+
import kotlin.math.cos
32+
import kotlin.math.sin
33+
34+
class DynamicMovementObject(var location: Location) {
35+
36+
var heading = ThreadLocalRandom.current().nextDouble() * 2 * Math.PI
37+
38+
fun move() {
39+
assert(!isOutOfBounds(location))
40+
assert(heading in 0.0..Math.TAU)
41+
moveNextPosition()
42+
}
43+
44+
private fun moveNextPosition() {
45+
val firstProposed = calculateNextPosition(heading)
46+
if (isValidNextPosition(firstProposed)) {
47+
location = firstProposed
48+
return
49+
}
50+
51+
val newHeading = heading + Math.PI * (1 + ThreadLocalRandom.current().nextDouble() - 0.5)
52+
val secondProposed = calculateNextPosition(newHeading)
53+
if (isValidNextPosition(secondProposed)) {
54+
location = secondProposed
55+
heading = if (newHeading >= Math.TAU) newHeading - Math.TAU else newHeading
56+
return
57+
}
58+
59+
// Brute Force Escape
60+
val randomRotationFromNorth = ThreadLocalRandom.current().nextDouble() * Math.TAU
61+
for (clockwiseRotation in 0..35) {
62+
val bruteForceHeading = (clockwiseRotation * 10) * Math.PI / 180.0 + randomRotationFromNorth
63+
val proposed = calculateNextPosition(bruteForceHeading)
64+
if (isValidNextPosition(proposed)) {
65+
location = proposed
66+
heading = if (bruteForceHeading >= Math.TAU) bruteForceHeading - Math.TAU else bruteForceHeading
67+
return
68+
}
69+
}
70+
71+
// TODO: destroy this object, we got stuck
72+
assert(false)
73+
}
74+
75+
private fun isValidNextPosition(proposed: Location): Boolean {
76+
return !DynamicMovementProcessor.isIntersectingProtectedZone(location, proposed) && !isOutOfBounds(proposed)
77+
}
78+
79+
private fun isOutOfBounds(location: Location): Boolean {
80+
return location.x < -7000 || location.x > 7000 || location.z < -7000 || location.z > 7000
81+
}
82+
83+
private fun calculateNextPosition(heading: Double): Location {
84+
val nextLocationBuilder = Location.builder(location)
85+
.setX(location.x + 50 * cos(heading))
86+
.setZ(location.z + 50 * sin(heading))
87+
.setHeading(heading)
88+
nextLocationBuilder.setY(ServerData.terrains.getHeight(nextLocationBuilder))
89+
return nextLocationBuilder.build()
90+
}
91+
92+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/***********************************************************************************
2+
* Copyright (c) 2025 /// Project SWG /// www.projectswg.com *
3+
* *
4+
* ProjectSWG is an emulation project for Star Wars Galaxies founded on *
5+
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
6+
* Our goal is to create one or more emulators which will provide servers for *
7+
* players to continue playing a game similar to the one they used to play. *
8+
* *
9+
* This file is part of Holocore. *
10+
* *
11+
* --------------------------------------------------------------------------------*
12+
* *
13+
* Holocore is free software: you can redistribute it and/or modify *
14+
* it under the terms of the GNU Affero General Public License as *
15+
* published by the Free Software Foundation, either version 3 of the *
16+
* License, or (at your option) any later version. *
17+
* *
18+
* Holocore is distributed in the hope that it will be useful, *
19+
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
20+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
21+
* GNU Affero General Public License for more details. *
22+
* *
23+
* You should have received a copy of the GNU Affero General Public License *
24+
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
25+
***********************************************************************************/
26+
package com.projectswg.holocore.resources.support.npc.ai.dynamic
27+
28+
import com.projectswg.common.data.location.Location
29+
import com.projectswg.common.data.location.Point2f
30+
import com.projectswg.holocore.resources.support.data.server_info.loader.ServerData
31+
import kotlin.math.sqrt
32+
33+
object DynamicMovementProcessor {
34+
35+
fun isIntersectingProtectedZone(start: Location, end: Location): Boolean {
36+
assert(start.terrain == end.terrain)
37+
val zones = ServerData.noSpawnZones.getNoSpawnZoneInfos(start.terrain)
38+
val startPoint = Point2f(start.x.toFloat(), start.z.toFloat())
39+
val endPoint = Point2f(end.x.toFloat(), end.z.toFloat())
40+
41+
for (zone in zones) {
42+
val zoneCenter = Point2f(zone.x.toFloat(), zone.z.toFloat())
43+
val closestPoint = startPoint.getClosestPointOnLineSegment(endPoint, zoneCenter)
44+
val distance = sqrt(closestPoint.squaredDistanceTo(zoneCenter))
45+
if (distance < zone.radius.toFloat()) {
46+
return true
47+
}
48+
}
49+
return false
50+
}
51+
52+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/***********************************************************************************
2+
* Copyright (c) 2025 /// Project SWG /// www.projectswg.com *
3+
* *
4+
* ProjectSWG is an emulation project for Star Wars Galaxies founded on *
5+
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
6+
* Our goal is to create one or more emulators which will provide servers for *
7+
* players to continue playing a game similar to the one they used to play. *
8+
* *
9+
* This file is part of Holocore. *
10+
* *
11+
* --------------------------------------------------------------------------------*
12+
* *
13+
* Holocore is free software: you can redistribute it and/or modify *
14+
* it under the terms of the GNU Affero General Public License as *
15+
* published by the Free Software Foundation, either version 3 of the *
16+
* License, or (at your option) any later version. *
17+
* *
18+
* Holocore is distributed in the hope that it will be useful, *
19+
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
20+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
21+
* GNU Affero General Public License for more details. *
22+
* *
23+
* You should have received a copy of the GNU Affero General Public License *
24+
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
25+
***********************************************************************************/
26+
package com.projectswg.holocore.services.support.npc.ai
27+
28+
import me.joshlarson.jlcommon.control.Service
29+
30+
class AIDynamicMovementService : Service() {
31+
32+
33+
34+
}

src/main/java/com/projectswg/holocore/services/support/npc/ai/AIManager.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/***********************************************************************************
2-
* Copyright (c) 2024 /// Project SWG /// www.projectswg.com *
2+
* Copyright (c) 2025 /// Project SWG /// www.projectswg.com *
33
* *
44
* ProjectSWG is an emulation project for Star Wars Galaxies founded on *
55
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
@@ -28,5 +28,5 @@ package com.projectswg.holocore.services.support.npc.ai
2828
import me.joshlarson.jlcommon.control.Manager
2929
import me.joshlarson.jlcommon.control.ManagerStructure
3030

31-
@ManagerStructure(children = [AIService::class, AIMovementService::class])
31+
@ManagerStructure(children = [AIService::class, AIDynamicMovementService::class, AIMovementService::class])
3232
class AIManager : Manager()
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/***********************************************************************************
2+
* Copyright (c) 2025 /// Project SWG /// www.projectswg.com *
3+
* *
4+
* ProjectSWG is an emulation project for Star Wars Galaxies founded on *
5+
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
6+
* Our goal is to create one or more emulators which will provide servers for *
7+
* players to continue playing a game similar to the one they used to play. *
8+
* *
9+
* This file is part of Holocore. *
10+
* *
11+
* --------------------------------------------------------------------------------*
12+
* *
13+
* Holocore is free software: you can redistribute it and/or modify *
14+
* it under the terms of the GNU Affero General Public License as *
15+
* published by the Free Software Foundation, either version 3 of the *
16+
* License, or (at your option) any later version. *
17+
* *
18+
* Holocore is distributed in the hope that it will be useful, *
19+
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
20+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
21+
* GNU Affero General Public License for more details. *
22+
* *
23+
* You should have received a copy of the GNU Affero General Public License *
24+
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
25+
***********************************************************************************/
26+
package com.projectswg.holocore.resources.support.npc.ai.dynamic
27+
28+
import com.mongodb.assertions.Assertions
29+
import com.projectswg.common.data.location.Location
30+
import com.projectswg.common.data.location.Terrain
31+
import com.projectswg.holocore.resources.support.data.server_info.loader.ServerData
32+
import com.projectswg.holocore.test.runners.TestRunnerNoIntents
33+
import org.junit.jupiter.api.Test
34+
35+
class TestDynamicMovement : TestRunnerNoIntents() {
36+
37+
@Test
38+
fun testWanderNoCollide() {
39+
val obj = DynamicMovementObject(Location.builder()
40+
.setTerrain(Terrain.TATOOINE)
41+
.setX(1024.toDouble())
42+
.setY(ServerData.terrains.getHeight(Terrain.TATOOINE, 1024.toDouble(), 1024.toDouble()))
43+
.setZ(1024.toDouble())
44+
.build())
45+
46+
var previousLocation = obj.location
47+
for (i in 0 until 1000) {
48+
obj.move()
49+
val newLocation = obj.location
50+
Assertions.assertFalse(DynamicMovementProcessor.isIntersectingProtectedZone(previousLocation, newLocation))
51+
previousLocation = newLocation
52+
}
53+
}
54+
55+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/***********************************************************************************
2+
* Copyright (c) 2025 /// Project SWG /// www.projectswg.com *
3+
* *
4+
* ProjectSWG is an emulation project for Star Wars Galaxies founded on *
5+
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
6+
* Our goal is to create one or more emulators which will provide servers for *
7+
* players to continue playing a game similar to the one they used to play. *
8+
* *
9+
* This file is part of Holocore. *
10+
* *
11+
* --------------------------------------------------------------------------------*
12+
* *
13+
* Holocore is free software: you can redistribute it and/or modify *
14+
* it under the terms of the GNU Affero General Public License as *
15+
* published by the Free Software Foundation, either version 3 of the *
16+
* License, or (at your option) any later version. *
17+
* *
18+
* Holocore is distributed in the hope that it will be useful, *
19+
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
20+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
21+
* GNU Affero General Public License for more details. *
22+
* *
23+
* You should have received a copy of the GNU Affero General Public License *
24+
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
25+
***********************************************************************************/
26+
package com.projectswg.holocore.resources.support.npc.ai.dynamic
27+
28+
import com.mongodb.assertions.Assertions
29+
import com.projectswg.common.data.location.Location
30+
import com.projectswg.common.data.location.Terrain
31+
import com.projectswg.holocore.test.runners.TestRunnerNoIntents
32+
import org.junit.jupiter.api.Test
33+
34+
class TestDynamicMovementProcessor : TestRunnerNoIntents() {
35+
36+
@Test
37+
fun testNoBuildZoneCheck() {
38+
// No intersections
39+
Assertions.assertFalse(DynamicMovementProcessor.isIntersectingProtectedZone(tatooine(5391, -3185), tatooine(-559, -1197)))
40+
Assertions.assertFalse(DynamicMovementProcessor.isIntersectingProtectedZone(tatooine(5391, -3185), tatooine(5710, -5478)))
41+
Assertions.assertFalse(DynamicMovementProcessor.isIntersectingProtectedZone(tatooine(5391, -3185), tatooine(4433, -4013)))
42+
Assertions.assertFalse(DynamicMovementProcessor.isIntersectingProtectedZone(tatooine(3577, -1067), tatooine(-2141, 4485)))
43+
Assertions.assertFalse(DynamicMovementProcessor.isIntersectingProtectedZone(tatooine(3577, -1067), tatooine(5812, 5580)))
44+
45+
// Intersections
46+
Assertions.assertTrue(DynamicMovementProcessor.isIntersectingProtectedZone(tatooine(5391, -3185), tatooine(5812, 5580)))
47+
Assertions.assertTrue(DynamicMovementProcessor.isIntersectingProtectedZone(tatooine(5391, -3185), tatooine(3505, -4811)))
48+
Assertions.assertTrue(DynamicMovementProcessor.isIntersectingProtectedZone(tatooine(5391, -3185), tatooine(2503, -5798)))
49+
Assertions.assertTrue(DynamicMovementProcessor.isIntersectingProtectedZone(tatooine(5391, -3185), tatooine(-5246, -3984)))
50+
}
51+
52+
private fun tatooine(x: Int, z: Int): Location {
53+
return Location.builder().setTerrain(Terrain.TATOOINE).setX(x.toDouble()).setZ(z.toDouble()).build()
54+
}
55+
56+
}

0 commit comments

Comments
 (0)