2626package com.projectswg.holocore.resources.support.npc.ai.dynamic
2727
2828import com.projectswg.common.data.location.Location
29+ import com.projectswg.holocore.intents.support.objects.DestroyObjectIntent
30+ import com.projectswg.holocore.intents.support.objects.ObjectCreatedIntent
2931import com.projectswg.holocore.resources.support.data.server_info.loader.ServerData
32+ import com.projectswg.holocore.resources.support.data.server_info.loader.npc.NpcStaticSpawnLoader
33+ import com.projectswg.holocore.resources.support.npc.spawn.NPCCreator
34+ import com.projectswg.holocore.resources.support.npc.spawn.SimpleSpawnInfo
35+ import com.projectswg.holocore.resources.support.npc.spawn.Spawner
36+ import com.projectswg.holocore.resources.support.npc.spawn.SpawnerType
37+ import com.projectswg.holocore.resources.support.objects.ObjectCreator
38+ import com.projectswg.holocore.resources.support.objects.permissions.AdminPermissions
39+ import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureDifficulty
40+ import com.projectswg.holocore.resources.support.objects.swg.custom.AIBehavior
41+ import com.projectswg.holocore.resources.support.objects.swg.custom.AIObject
3042import java.util.concurrent.ThreadLocalRandom
3143import kotlin.math.cos
3244import kotlin.math.sin
3345
34- class DynamicMovementObject (var location : Location ) {
46+ class DynamicMovementObject (var location : Location , val name : String , val baseSpeed : Double = 0.0 ) {
3547
3648 var heading = ThreadLocalRandom .current().nextDouble() * 2 * Math .PI
49+ private val groupMarker = ObjectCreator .createObjectFromTemplate(" object/path_waypoint/shared_path_waypoint_droid.iff" )
50+ private val npcs = ArrayList <AIObject >()
51+ private var lastUpdate = System .nanoTime()
3752
38- fun move () {
53+ init {
54+ groupMarker.location = location
55+ groupMarker.objectName = name
56+ groupMarker.containerPermissions = AdminPermissions .getPermissions()
57+ }
58+
59+ fun launch () {
60+ ObjectCreatedIntent (groupMarker).broadcast()
61+ val simpleSpawnInfo = SimpleSpawnInfo .builder()
62+ .withNpcId(" humanoid_tusken_soldier" )
63+ .withDifficulty(CreatureDifficulty .NORMAL )
64+ .withSpawnerType(SpawnerType .WAYPOINT_AUTO_SPAWN )
65+ .withMinLevel(10 )
66+ .withMaxLevel(30 )
67+ .withSpawnerFlag(NpcStaticSpawnLoader .SpawnerFlag .AGGRESSIVE )
68+ .withBehavior(AIBehavior .IDLE )
69+ .withLocation(location)
70+ val bossSpawner = Spawner (simpleSpawnInfo.withDifficulty(CreatureDifficulty .BOSS ).build(), groupMarker)
71+ val eliteSpawner = Spawner (simpleSpawnInfo.withDifficulty(CreatureDifficulty .ELITE ).build(), groupMarker)
72+ val normalSpawner = Spawner (simpleSpawnInfo.withDifficulty(CreatureDifficulty .NORMAL ).build(), groupMarker)
73+ val random = ThreadLocalRandom .current()
74+ if (random.nextDouble() < 0.25 )
75+ npcs.add(NPCCreator .createSingleNpc(bossSpawner))
76+ npcs.add(NPCCreator .createSingleNpc(eliteSpawner))
77+ repeat(15 ) {
78+ npcs.add(NPCCreator .createSingleNpc(normalSpawner))
79+ }
80+ }
81+
82+ fun destroy () {
83+ DestroyObjectIntent (groupMarker).broadcast()
84+ }
85+
86+ fun act () {
3987 assert (! isOutOfBounds(location))
4088 assert (heading in 0.0 .. Math .TAU )
41- moveNextPosition()
89+ val currentTime = System .nanoTime()
90+ val npcSpeed = if (baseSpeed != 0.0 ) baseSpeed else npcs[0 ].walkSpeed.toDouble()
91+ val elapsedTime = (currentTime - lastUpdate) / 1E9
92+ val distance = elapsedTime * npcSpeed // TODO: scale based on terrain, somewhat
93+ lastUpdate = currentTime
94+ moveNextPosition(distance)
95+ groupMarker.moveToLocation(location)
96+ npcs.forEachIndexed { i, it ->
97+ // Spiral shape for now
98+ val radius = 1 + i * 0.5
99+ val angle = i * (Math .PI * 0.61 )
100+ val newLocationBuilder = Location .builder(location)
101+ .setX(location.x + radius * cos(angle))
102+ .setZ(location.z + radius * sin(angle))
103+ newLocationBuilder.setY(ServerData .terrains.getHeight(newLocationBuilder))
104+ val newLocation = newLocationBuilder.build()
105+ val speed = it.worldLocation.distanceTo(newLocation) / elapsedTime
106+ it.moveTo(null , newLocationBuilder.build(), speed)
107+ }
42108 }
43109
44- private fun moveNextPosition () {
45- val firstProposed = calculateNextPosition(heading)
110+ private fun moveNextPosition (distance : Double ) {
111+ val firstProposed = calculateNextPosition(heading, distance )
46112 if (isValidNextPosition(firstProposed)) {
47113 location = firstProposed
48114 return
49115 }
50116
51117 val newHeading = heading + Math .PI * (1 + ThreadLocalRandom .current().nextDouble() - 0.5 )
52- val secondProposed = calculateNextPosition(newHeading)
118+ val secondProposed = calculateNextPosition(newHeading, distance )
53119 if (isValidNextPosition(secondProposed)) {
54120 location = secondProposed
55121 heading = if (newHeading >= Math .TAU ) newHeading - Math .TAU else newHeading
@@ -60,7 +126,7 @@ class DynamicMovementObject(var location: Location) {
60126 val randomRotationFromNorth = ThreadLocalRandom .current().nextDouble() * Math .TAU
61127 for (clockwiseRotation in 0 .. 35 ) {
62128 val bruteForceHeading = (clockwiseRotation * 10 ) * Math .PI / 180.0 + randomRotationFromNorth
63- val proposed = calculateNextPosition(bruteForceHeading)
129+ val proposed = calculateNextPosition(bruteForceHeading, distance )
64130 if (isValidNextPosition(proposed)) {
65131 location = proposed
66132 heading = if (bruteForceHeading >= Math .TAU ) bruteForceHeading - Math .TAU else bruteForceHeading
@@ -80,10 +146,10 @@ class DynamicMovementObject(var location: Location) {
80146 return location.x < - 7000 || location.x > 7000 || location.z < - 7000 || location.z > 7000
81147 }
82148
83- private fun calculateNextPosition (heading : Double ): Location {
149+ private fun calculateNextPosition (heading : Double , distance : Double ): Location {
84150 val nextLocationBuilder = Location .builder(location)
85- .setX(location.x + 50 * cos(heading))
86- .setZ(location.z + 50 * sin(heading))
151+ .setX(location.x + distance * cos(heading))
152+ .setZ(location.z + distance * sin(heading))
87153 .setHeading(heading)
88154 nextLocationBuilder.setY(ServerData .terrains.getHeight(nextLocationBuilder))
89155 return nextLocationBuilder.build()
0 commit comments