diff --git a/src/main/java/com/projectswg/holocore/resources/support/npc/spawn/NPCCreator.java b/src/main/java/com/projectswg/holocore/resources/support/npc/spawn/NPCCreator.java index 9f7445bd2..ec9e9877a 100644 --- a/src/main/java/com/projectswg/holocore/resources/support/npc/spawn/NPCCreator.java +++ b/src/main/java/com/projectswg/holocore/resources/support/npc/spawn/NPCCreator.java @@ -1,5 +1,5 @@ /*********************************************************************************** - * Copyright (c) 2018 /// Project SWG /// www.projectswg.com * + * Copyright (c) 2023 /// Project SWG /// www.projectswg.com * * * * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * @@ -53,14 +53,27 @@ import me.joshlarson.jlcommon.control.IntentChain; import me.joshlarson.jlcommon.log.Log; import me.joshlarson.jlcommon.utilities.Arguments; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.concurrent.ThreadLocalRandom; public class NPCCreator { - public static AIObject createNPC(Spawner spawner) { + public static Collection createNPCs(Spawner spawner) { Arguments.validate(spawner.getMinLevel() <= spawner.getMaxLevel(), "min level must be less than max level"); + int amount = spawner.getAmount(); + Collection npcs = new ArrayList<>(); + + for (int i = 0; i < amount; i++) { + npcs.add(createNPC(spawner)); + } + + return npcs; + } + + private static AIObject createNPC(Spawner spawner) { int combatLevel = ThreadLocalRandom.current().nextInt(spawner.getMinLevel(), spawner.getMaxLevel()+1); AIObject object = ObjectCreator.createObjectFromTemplate(spawner.getRandomIffTemplate(), AIObject.class); diff --git a/src/main/java/com/projectswg/holocore/services/gameplay/missions/DestroyMissionService.kt b/src/main/java/com/projectswg/holocore/services/gameplay/missions/DestroyMissionService.kt index ca64533e7..4be268630 100644 --- a/src/main/java/com/projectswg/holocore/services/gameplay/missions/DestroyMissionService.kt +++ b/src/main/java/com/projectswg/holocore/services/gameplay/missions/DestroyMissionService.kt @@ -31,6 +31,7 @@ import com.projectswg.common.data.encodables.oob.ProsePackage import com.projectswg.common.data.encodables.oob.StringId import com.projectswg.common.data.encodables.oob.waypoint.WaypointColor import com.projectswg.common.data.encodables.oob.waypoint.WaypointPackage +import com.projectswg.common.data.encodables.tangible.PvpFlag import com.projectswg.common.data.location.Location import com.projectswg.common.data.location.Terrain import com.projectswg.common.data.swgfile.ClientFactory @@ -38,7 +39,6 @@ import com.projectswg.common.network.packets.swg.zone.object_controller.MissionA import com.projectswg.common.network.packets.swg.zone.object_controller.MissionAcceptRequest import com.projectswg.common.network.packets.swg.zone.object_controller.MissionAcceptResponse import com.projectswg.common.network.packets.swg.zone.object_controller.MissionListRequest -import com.projectswg.holocore.intents.gameplay.combat.CreatureKilledIntent import com.projectswg.holocore.intents.support.global.chat.SystemMessageIntent import com.projectswg.holocore.intents.support.global.network.InboundPacketIntent import com.projectswg.holocore.intents.support.objects.swg.DestroyObjectIntent @@ -58,9 +58,11 @@ import com.projectswg.holocore.resources.support.objects.swg.SWGObject import com.projectswg.holocore.resources.support.objects.swg.ServerAttribute import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureDifficulty import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject -import com.projectswg.holocore.resources.support.objects.swg.custom.AIObject +import com.projectswg.holocore.resources.support.objects.swg.custom.AIBehavior import com.projectswg.holocore.resources.support.objects.swg.group.GroupObject import com.projectswg.holocore.resources.support.objects.swg.mission.MissionObject +import com.projectswg.holocore.resources.support.objects.swg.tangible.OptionFlag +import com.projectswg.holocore.resources.support.objects.swg.tangible.TangibleObject import com.projectswg.holocore.resources.support.objects.swg.waypoint.WaypointObject import com.projectswg.holocore.services.support.objects.ObjectStorageService.ObjectLookup import me.joshlarson.jlcommon.control.IntentHandler @@ -74,7 +76,7 @@ class DestroyMissionService : Service() { private val maxAcceptedMissions = 2 private val missionsToGenerate = 5 - private val npcToMission = mutableMapOf() + private val lairToMission = mutableMapOf() private val missionComplete = StringId("mission/mission_generic", "success_w_amount") @IntentHandler @@ -107,11 +109,9 @@ class DestroyMissionService : Service() { return } - npcToMission.filterValues { it == missionObject }.forEach { - val key = it.key - DestroyObjectIntent.broadcast(key) - DestroyObjectIntent.broadcast(npcToMission.remove(key)) - } + val lairs = lairToMission.filterValues { it == missionObject }.keys + lairs.forEach { lairToMission.remove(it) } + DestroyObjectIntent.broadcast(missionObject) val response = MissionAbort(creatureObject.objectId) response.missionObjectId = missionObjectId @@ -119,13 +119,13 @@ class DestroyMissionService : Service() { } @IntentHandler - private fun handleCreatureKilled(creatureKilledIntent: CreatureKilledIntent) { - val corpse = creatureKilledIntent.corpse - val missionObject = npcToMission.remove(corpse) + private fun handleDestroyObject(destroyObjectIntent: DestroyObjectIntent) { + val destroyedObject = destroyObjectIntent.`object` + val missionObject = lairToMission.remove(destroyedObject) if (missionObject != null) { val owner = missionObject.owner if (owner != null) { - handleMissionCompleted(owner, missionObject, corpse.location) + handleMissionCompleted(owner, missionObject, destroyedObject.location) } } } @@ -188,12 +188,14 @@ class DestroyMissionService : Service() { val location = missionObject.startLocation.toLocation() missionObject.waypointPackage = createWaypoint(location) - spawnNpc(location, missionObject) + val lair = createLair(missionObject, location) + lairToMission[lair] = missionObject + spawnNpcs(location, missionObject) StandardLog.onPlayerEvent(this, player, "accepted %s", missionObject) } } - private fun spawnNpc(location: Location, missionObject: MissionObject) { + private fun spawnNpcs(location: Location, missionObject: MissionObject) { val difficulty = missionObject.difficulty val egg = ObjectCreator.createObjectFromTemplate(SpawnerType.MISSION_EASY.objectTemplate) egg.containerPermissions = AdminPermissions.getPermissions() @@ -213,10 +215,20 @@ class DestroyMissionService : Service() { .withMinLevel(difficulty) .withMaxLevel(difficulty) .withLocation(location) + .withAmount(3) + .withBehavior(AIBehavior.LOITER) .build() - val npc = NPCCreator.createNPC(Spawner(spawnInfo, egg)) - npcToMission[npc] = missionObject + NPCCreator.createNPCs(Spawner(spawnInfo, egg)) + } + + private fun createLair(missionObject: MissionObject, location: Location): TangibleObject { + val lair = ObjectCreator.createObjectFromTemplate(missionObject.targetAppearance.string) as TangibleObject + lair.removeOptionFlags(OptionFlag.INVULNERABLE) + lair.setPvpFlags(PvpFlag.YOU_CAN_ATTACK) + lair.moveToContainer(null, location) + ObjectCreatedIntent.broadcast(lair) + return lair } private fun handleTooManyMissions(creatureObject: CreatureObject, missionId: Long, missionAcceptRequest: MissionAcceptRequest, player: Player) { diff --git a/src/main/java/com/projectswg/holocore/services/support/npc/spawn/SpawnerService.kt b/src/main/java/com/projectswg/holocore/services/support/npc/spawn/SpawnerService.kt index 51163fedd..883e6b084 100644 --- a/src/main/java/com/projectswg/holocore/services/support/npc/spawn/SpawnerService.kt +++ b/src/main/java/com/projectswg/holocore/services/support/npc/spawn/SpawnerService.kt @@ -1,29 +1,29 @@ /*********************************************************************************** - * Copyright (c) 2018 /// Project SWG /// www.projectswg.com * - * * + * Copyright (c) 2023 /// Project SWG /// www.projectswg.com * + * * * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * * Our goal is to create an emulator which will provide a server for players to * * continue playing a game similar to the one they used to play. We are basing * * it on the final publish of the game prior to end-game events. * - * * + * * * 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 //www.gnu.org/licenses/>. * - */ + * along with Holocore. If not, see . * + ***********************************************************************************/ package com.projectswg.holocore.services.support.npc.spawn import com.projectswg.common.data.location.Location @@ -153,7 +153,9 @@ class SpawnerService : Service() { val respawnDelay = spawner.respawnDelay if (respawnDelay > 0) { - executor.execute((respawnDelay * 1000).toLong()) { respawn(spawner) } + executor.execute((respawnDelay * 1000).toLong()) { + NPCCreator.createNPCs(spawner) + } } else { DestroyObjectIntent.broadcast(spawner.egg) } @@ -199,10 +201,8 @@ class SpawnerService : Service() { val egg = createEgg(spawn) val spawner = Spawner(spawn, egg) egg.setServerAttribute(ServerAttribute.EGG_SPAWNER, spawner) - - for (i in 0 until spawner.amount) { - NPCCreator.createNPC(spawner) - } + + NPCCreator.createNPCs(spawner) val patrolRoute = spawner.patrolRoute if (patrolRoute != null) { @@ -217,13 +217,7 @@ class SpawnerService : Service() { } } } - - private fun respawn(spawner: Spawner) { - for (i in spawner.npcs.size until spawner.amount) { - NPCCreator.createNPC(spawner) - } - } - + private fun createEgg(spawn: SpawnInfo): SWGObject { val spawnerType = SpawnerType.valueOf(spawn.spawnerType) val egg = ObjectCreator.createObjectFromTemplate(spawnerType.objectTemplate) diff --git a/src/test/java/com/projectswg/holocore/services/gameplay/combat/NpcDefaultAttackTest.kt b/src/test/java/com/projectswg/holocore/services/gameplay/combat/NpcDefaultAttackTest.kt index 733665e38..9a696a816 100644 --- a/src/test/java/com/projectswg/holocore/services/gameplay/combat/NpcDefaultAttackTest.kt +++ b/src/test/java/com/projectswg/holocore/services/gameplay/combat/NpcDefaultAttackTest.kt @@ -1,3 +1,29 @@ +/*********************************************************************************** + * Copyright (c) 2023 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * 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 . * + ***********************************************************************************/ package com.projectswg.holocore.services.gameplay.combat import com.projectswg.common.data.location.Location @@ -8,7 +34,6 @@ import com.projectswg.holocore.resources.support.npc.spawn.Spawner import com.projectswg.holocore.resources.support.objects.ObjectCreator import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureDifficulty import com.projectswg.holocore.resources.support.objects.swg.custom.AIObject -import com.projectswg.holocore.resources.support.objects.swg.weapon.WeaponObject import com.projectswg.holocore.test.runners.TestRunnerSynchronousIntents import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test @@ -55,7 +80,7 @@ class NpcDefaultAttackTest : TestRunnerSynchronousIntents() { .withLocation(inFrontOfMosEisleyStarport) .build() - return NPCCreator.createNPC(Spawner(spawnInfo, egg)) + return NPCCreator.createNPCs(Spawner(spawnInfo, egg)).first() } } \ No newline at end of file