From 24d7e9ced0ad3377b5f4b785e1265417c6631861 Mon Sep 17 00:00:00 2001 From: Ziggy Date: Sat, 13 Jan 2024 11:52:16 +0100 Subject: [PATCH] Added support for quest.task.ground.go_to_location task type, so that a wider selection of quests can be added to the game --- .../data/server_info/loader/QuestLoader.java | 103 +++++++++++++++++- .../gameplay/player/quest/QuestService.kt | 73 +++++++++++-- .../player/quest/QuestTaskTypeTest.kt | 22 +++- 3 files changed, 189 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/projectswg/holocore/resources/support/data/server_info/loader/QuestLoader.java b/src/main/java/com/projectswg/holocore/resources/support/data/server_info/loader/QuestLoader.java index 92054643d..9730b3134 100644 --- a/src/main/java/com/projectswg/holocore/resources/support/data/server_info/loader/QuestLoader.java +++ b/src/main/java/com/projectswg/holocore/resources/support/data/server_info/loader/QuestLoader.java @@ -1,5 +1,5 @@ /*********************************************************************************** - * Copyright (c) 2023 /// Project SWG /// www.projectswg.com * + * Copyright (c) 2024 /// 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. * @@ -168,6 +168,37 @@ public class QuestLoader extends DataLoader { if (columns.contains("music_on_failure")) { musicOnFailure = set.getText("music_on_failure"); } + boolean createWaypoint = false; + if (columns.contains("create_waypoint")) { + createWaypoint = set.getBoolean("create_waypoint"); + } + String planetName = null; + if (columns.contains("planet_name")) { + planetName = set.getText("planet_name"); + } + double locationX = 0; + if (columns.contains("location_x")) { + locationX = set.getReal("location_x"); + } + double locationY = 0; + if (columns.contains("location_y")) { + locationY = set.getReal("location_y"); + } + double locationZ = 0; + if (columns.contains("location_z")) { + locationZ = set.getReal("location_z"); + } + String waypointName = null; + if (columns.contains("waypoint_name")) { + waypointName = set.getText("waypoint_name"); + } + double radius = 0; + if (columns.contains("radius")) { + String radiusText = set.getText("radius"); // Is seen as empty string if not set + if (!radiusText.isBlank()) { + radius = Double.parseDouble(radiusText); + } + } QuestTaskInfo questTaskInfo = new QuestTaskInfo(); @@ -208,6 +239,13 @@ public class QuestLoader extends DataLoader { questTaskInfo.setMusicOnActivate(musicOnActivate); questTaskInfo.setMusicOnComplete(musicOnComplete); questTaskInfo.setMusicOnFailure(musicOnFailure); + questTaskInfo.setCreateWaypoint(createWaypoint); + questTaskInfo.setPlanetName(planetName); + questTaskInfo.setLocationX(locationX); + questTaskInfo.setLocationY(locationY); + questTaskInfo.setLocationZ(locationZ); + questTaskInfo.setWaypointName(waypointName); + questTaskInfo.setRadius(radius); questTaskInfos.add(questTaskInfo); } @@ -325,6 +363,13 @@ public class QuestLoader extends DataLoader { private String musicOnActivate; private String musicOnComplete; private String musicOnFailure; + private boolean createWaypoint; + private String planetName; + private double locationX; + private double locationY; + private double locationZ; + private String waypointName; + private double radius; private QuestTaskInfo() { nextTasksOnComplete = new ArrayList<>(); @@ -571,6 +616,62 @@ public class QuestLoader extends DataLoader { this.musicOnFailure = musicOnFailure; } + public boolean isCreateWaypoint() { + return createWaypoint; + } + + public void setCreateWaypoint(boolean createWaypoint) { + this.createWaypoint = createWaypoint; + } + + public String getPlanetName() { + return planetName; + } + + public void setPlanetName(String planetName) { + this.planetName = planetName; + } + + public double getLocationX() { + return locationX; + } + + public void setLocationX(double locationX) { + this.locationX = locationX; + } + + public double getLocationY() { + return locationY; + } + + public void setLocationY(double locationY) { + this.locationY = locationY; + } + + public double getLocationZ() { + return locationZ; + } + + public void setLocationZ(double locationZ) { + this.locationZ = locationZ; + } + + public String getWaypointName() { + return waypointName; + } + + public void setWaypointName(String waypointName) { + this.waypointName = waypointName; + } + + public double getRadius() { + return radius; + } + + public void setRadius(double radius) { + this.radius = radius; + } + @Override public String toString() { return "QuestTaskInfo{" + "index=" + index + ", type='" + type + '\'' + ", name='" + name + '\'' + '}'; diff --git a/src/main/java/com/projectswg/holocore/services/gameplay/player/quest/QuestService.kt b/src/main/java/com/projectswg/holocore/services/gameplay/player/quest/QuestService.kt index 5b88a1901..a1b7ac342 100644 --- a/src/main/java/com/projectswg/holocore/services/gameplay/player/quest/QuestService.kt +++ b/src/main/java/com/projectswg/holocore/services/gameplay/player/quest/QuestService.kt @@ -1,5 +1,5 @@ /*********************************************************************************** - * Copyright (c) 2023 /// Project SWG /// www.projectswg.com * + * Copyright (c) 2024 /// 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. * @@ -30,6 +30,9 @@ import com.projectswg.common.data.CRC import com.projectswg.common.data.encodables.oob.OutOfBandPackage 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.location.Location +import com.projectswg.common.data.location.Terrain import com.projectswg.common.data.swgfile.ClientFactory import com.projectswg.common.network.packets.swg.zone.CommPlayerMessage import com.projectswg.common.network.packets.swg.zone.PlayMusicMessage @@ -44,7 +47,10 @@ import com.projectswg.holocore.intents.gameplay.player.quest.CompleteQuestIntent import com.projectswg.holocore.intents.gameplay.player.quest.GrantQuestIntent import com.projectswg.holocore.intents.gameplay.player.quest.GrantQuestIntent.Companion.broadcast import com.projectswg.holocore.intents.support.global.chat.SystemMessageIntent +import com.projectswg.holocore.intents.support.global.zone.PlayerTransformedIntent +import com.projectswg.holocore.intents.support.objects.swg.DestroyObjectIntent import com.projectswg.holocore.intents.support.objects.swg.ObjectCreatedIntent +import com.projectswg.holocore.intents.support.objects.swg.ObjectTeleportIntent import com.projectswg.holocore.resources.support.data.server_info.StandardLog import com.projectswg.holocore.resources.support.data.server_info.loader.QuestLoader.QuestTaskInfo import com.projectswg.holocore.resources.support.data.server_info.loader.ServerData @@ -56,6 +62,7 @@ import com.projectswg.holocore.resources.support.objects.StaticItemCreator.creat import com.projectswg.holocore.resources.support.objects.swg.SWGObject import com.projectswg.holocore.resources.support.objects.swg.custom.AIObject import com.projectswg.holocore.resources.support.objects.swg.player.PlayerObject +import com.projectswg.holocore.resources.support.objects.swg.waypoint.WaypointObject import com.projectswg.holocore.resources.support.random.Die import com.projectswg.holocore.resources.support.random.RandomDie import me.joshlarson.jlcommon.concurrency.ScheduledThreadPool @@ -126,12 +133,9 @@ class QuestService(private val destroyMultiAndLootDie: Die = RandomDie()) : Serv val killer = intent.killer val owner = killer.owner ?: return val playerObject = killer.playerObject - val entries = playerObject.quests.entries - for ((key, quest) in entries) { - if (quest.isComplete) { - continue - } - val questName = key.string + val incompleteQuests = playerObject.quests.entries.filter { !it.value.isComplete }.map { it.key } + for (incompleteQuest in incompleteQuests) { + val questName = incompleteQuest.string val activeTaskListInfos = getActiveTaskInfos(questName, playerObject) for (activeTaskListInfo in activeTaskListInfos) { val type = activeTaskListInfo.type @@ -142,6 +146,44 @@ class QuestService(private val destroyMultiAndLootDie: Die = RandomDie()) : Serv } } } + + @IntentHandler + private fun handlePlayerTransformedIntent(intent: PlayerTransformedIntent) { + val player = intent.player.ownerShallow ?: return + handlePlayerChangeLocation(player, intent.newLocation) + } + + @IntentHandler + private fun handleObjectTeleportIntent(intent: ObjectTeleportIntent) { + val player = intent.`object`.ownerShallow ?: return + handlePlayerChangeLocation(player, intent.newLocation) + } + + private fun handlePlayerChangeLocation(player: Player, newLocation: Location) { + val playerObject = player.playerObject + val incompleteQuests = playerObject.quests.entries.filter { !it.value.isComplete }.map { it.key } + for (incompleteQuest in incompleteQuests) { + val questName = incompleteQuest.string + val activeTaskListInfos = getActiveTaskInfos(questName, playerObject) + for (activeTaskListInfo in activeTaskListInfos) { + val type = activeTaskListInfo.type + if (type == "quest.task.ground.go_to_location") { + val terrain = Terrain.getTerrainFromName(activeTaskListInfo.planetName) + val radius = activeTaskListInfo.radius + + if (newLocation.isWithinDistance(terrain, activeTaskListInfo.locationX, activeTaskListInfo.locationY, activeTaskListInfo.locationZ, radius)) { + StandardLog.onPlayerTrace(this, player, "arrived at location for task %d of quest %s", activeTaskListInfo.index, questName) + completeTask(questName, player, activeTaskListInfo) + player.sendPacket(PlayMusicMessage(0, "sound/ui_objective_reached.snd", 1, false)) + playerObject.waypoints.values.find { it.name == activeTaskListInfo.waypointName }?.let { // if you bothered with renaming the waypoint, you get to keep it + playerObject.removeWaypoint(it.objectId) + DestroyObjectIntent(it).broadcast() + } + } + } + } + } + } private fun handleKillDestroyMultiAndLoot(activeTaskListInfo: QuestTaskInfo, questName: String, owner: Player, corpse: AIObject) { if (!isKillPartOfTask(activeTaskListInfo, corpse)) { @@ -297,9 +339,26 @@ class QuestService(private val destroyMultiAndLootDie: Die = RandomDie()) : Serv "quest.task.ground.destroy_multi_and_loot" -> handleDestroyMultiAndLoot(player, questName, currentTask) "quest.task.ground.reward" -> handleReward(player, questName, playerObject, currentTask) "quest.task.ground.nothing" -> handleNothing(player, questName, currentTask) + "quest.task.ground.go_to_location" -> handleGoToLocation(player, currentTask) } } + private fun handleGoToLocation(player: Player, currentTask: QuestTaskInfo) { + if (currentTask.isCreateWaypoint) { + createQuestWaypoint(currentTask, player) + } + } + + private fun createQuestWaypoint(currentTask: QuestTaskInfo, player: Player) { + val waypoint = ObjectCreator.createObjectFromTemplate("object/waypoint/shared_waypoint.iff") as WaypointObject + waypoint.setPosition(Terrain.getTerrainFromName(currentTask.planetName), currentTask.locationX, currentTask.locationY, currentTask.locationZ) + waypoint.color = WaypointColor.YELLOW + waypoint.name = currentTask.waypointName + waypoint.isActive = true + player.playerObject.addWaypoint(waypoint) + ObjectCreatedIntent(waypoint).broadcast() + } + private fun completeTask(questName: String, player: Player, currentTask: QuestTaskInfo) { val playerObject = player.getPlayerObject() playerObject.removeActiveQuestTask(questName, currentTask.index) diff --git a/src/test/java/com/projectswg/holocore/services/gameplay/player/quest/QuestTaskTypeTest.kt b/src/test/java/com/projectswg/holocore/services/gameplay/player/quest/QuestTaskTypeTest.kt index f16158967..271573628 100644 --- a/src/test/java/com/projectswg/holocore/services/gameplay/player/quest/QuestTaskTypeTest.kt +++ b/src/test/java/com/projectswg/holocore/services/gameplay/player/quest/QuestTaskTypeTest.kt @@ -1,5 +1,5 @@ /*********************************************************************************** - * Copyright (c) 2023 /// Project SWG /// www.projectswg.com * + * Copyright (c) 2024 /// 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. * @@ -27,6 +27,8 @@ package com.projectswg.holocore.services.gameplay.player.quest import com.projectswg.common.data.location.Location +import com.projectswg.common.data.location.Location.LocationBuilder +import com.projectswg.common.data.location.Terrain import com.projectswg.common.network.packets.swg.login.creation.ClientCreateCharacter import com.projectswg.common.network.packets.swg.zone.CommPlayerMessage import com.projectswg.common.network.packets.swg.zone.object_controller.quest.QuestCompletedMessage @@ -206,6 +208,24 @@ class QuestTaskTypeTest : TestRunnerSynchronousIntents() { assertNotNull(questTaskTimerData, "Quest task time should have been sent") } + @Test + @DisplayName("quest.task.ground.go_to_location") + fun goToLocation() { + val player = createPlayer() + + GrantQuestIntent.broadcast(player, "quest/test_go_to_location") + val desiredLocation = LocationBuilder() + .setTerrain(Terrain.TATOOINE) + .setX(-1200.0) + .setY(0.0) + .setZ(-3600.0) + .build() + player.creatureObject.moveToLocation(desiredLocation) + val questCompletedMessage = player.waitForNextPacket(QuestCompletedMessage::class.java) + + assertNotNull(questCompletedMessage, "Quest not completed in time") + } + private class CharacterSnapshot(private val player: GenericPlayer) { // Helper class to snapshot a character's state val xp = player.playerObject.getExperiencePoints("dance") val rebelFactionPoints = player.playerObject.getFactionPoints()["rebel"] ?: 0