From 7709c3bb04376de2a940297bdc70f5a86c34b571 Mon Sep 17 00:00:00 2001 From: Ceikry Date: Sun, 27 Mar 2022 05:14:16 +0000 Subject: [PATCH] Implemented a brand new cutscene system Converted Lost Tribe's cutscene to the new system --- .../game/node/entity/combat/DeathTask.java | 2 + Server/src/main/kotlin/Cutscene.kt | 356 ++++++++++++++++++ Server/src/main/kotlin/api/ContentAPI.kt | 9 + Server/src/main/kotlin/api/events/Events.kt | 4 +- .../src/main/kotlin/rs09/ServerConstants.kt | 3 + Server/src/main/kotlin/rs09/game/Event.kt | 1 + .../game/content/ame/RandomEventManager.kt | 6 + .../members/thelosttribe/LostTribeCutscene.kt | 327 ++++++---------- .../members/thelosttribe/MistagLTDialogue.kt | 4 +- .../game/system/config/ServerConfigParser.kt | 1 + 10 files changed, 488 insertions(+), 225 deletions(-) create mode 100644 Server/src/main/kotlin/Cutscene.kt diff --git a/Server/src/main/java/core/game/node/entity/combat/DeathTask.java b/Server/src/main/java/core/game/node/entity/combat/DeathTask.java index 3e58d7d89..e1ef0d543 100644 --- a/Server/src/main/java/core/game/node/entity/combat/DeathTask.java +++ b/Server/src/main/java/core/game/node/entity/combat/DeathTask.java @@ -1,5 +1,6 @@ package core.game.node.entity.combat; +import api.events.SelfDeath; import core.game.container.Container; import core.game.container.ContainerType; import core.game.node.Node; @@ -84,6 +85,7 @@ public final class DeathTask extends NodeTask { e.getImpactHandler().getImpactLog().clear();// check if this needs to be // before finalize e.getImpactHandler().setDisabledTicks(4); + e.dispatch(new SelfDeath(killer)); } @Override diff --git a/Server/src/main/kotlin/Cutscene.kt b/Server/src/main/kotlin/Cutscene.kt new file mode 100644 index 000000000..cd8d7d75f --- /dev/null +++ b/Server/src/main/kotlin/Cutscene.kt @@ -0,0 +1,356 @@ +import api.* +import api.events.EventHook +import api.events.SelfDeath +import core.game.component.Component +import core.game.content.dialogue.FacialExpression +import core.game.interaction.MovementPulse +import core.game.node.entity.Entity +import core.game.node.entity.npc.NPC +import core.game.node.entity.player.Player +import core.game.node.scenery.Scenery +import core.game.system.task.Pulse +import core.game.world.map.Direction +import core.game.world.map.Location +import core.game.world.map.Region +import core.game.world.map.RegionManager +import core.game.world.map.build.DynamicRegion +import org.rs09.consts.Components +import rs09.ServerConstants +import rs09.game.Event +import rs09.game.camerautils.PlayerCamera +import rs09.game.system.SystemLogger +import rs09.game.world.GameWorld + +/** + * A utility class for making cutscenes. + * @author Ceikry + */ +abstract class Cutscene(val player: Player) { + lateinit var region: Region + lateinit var base: Location + var exitLocation: Location = player.location.transform(0,0,0) + var ended = false + + val camera = PlayerCamera(player) + private val addedNPCs = HashMap>() + + abstract fun setup() + abstract fun runStage(stage: Int) + + /** + * Creates a new dynamic copy of the region identified by regionId, sets this cutscene's region as this new copy, and clears any cutscene-spawned NPCs from the previous region. + * @param regionId the region ID to duplicate. + */ + fun loadRegion(regionId: Int) + { + clearNPCs() + logCutscene("Creating new instance of region $regionId for ${player.username}.") + region = DynamicRegion.create(regionId) + logCutscene("Dynamic region instantiated for ${player.username}. Global coordinates: ${region.baseLocation}.") + base = region.baseLocation + } + + /** + * Fade the player's view to black. This process can be safely assumed to take about 8 ticks to complete. + */ + fun fadeToBlack() + { + logCutscene("Fading ${player.username}'s screen to black.") + player.interfaceManager.closeOverlay() + player.interfaceManager.openOverlay(Component(Components.FADE_TO_BLACK_120)) + } + + /** + * Fade the player's view from black back to normal. This process can be safely assumed to take about 6 ticks to complete. + */ + fun fadeFromBlack() + { + logCutscene("Fading ${player.username}'s screen from black to normal.") + player.interfaceManager.closeOverlay() + player.interfaceManager.openOverlay(Component(Components.FADE_FROM_BLACK_170)) + } + + /** + * Teleports an entity to a pair of coordinates within the currently active cutscene region + * @param entity the entity to teleport + * @param regionX the region X coordinate to teleport the entity to (0-63) + * @param regionY the region Y coordinate to teleport the entity to (0-63) + * @param plane (optional) the plane to teleport to (0-3) + */ + fun teleport(entity: Entity, regionX: Int, regionY: Int, plane: Int = 0) + { + val newLoc = base.transform(regionX, regionY, plane) + logCutscene("Teleporting ${entity.username} to coordinates: LOCAL[$regionX,$regionY,$plane] GLOBAL[${newLoc.x},${newLoc.y},$plane].") + entity.properties.teleportLocation = newLoc + } + + /** + * Moves an entity to the given coordinates within the currently active cutscene region + * @param entity the entity to move + * @param regionX the region X coordinate to move the entity to (0-63) + * @param regionY the region Y coordinate to move the entity to (0-63) + */ + fun move(entity: Entity, regionX: Int, regionY: Int) + { + logCutscene("Moving ${entity.username} to LOCAL[$regionX,$regionY].") + entity.pulseManager.run(object : MovementPulse(entity, base.transform(regionX,regionY,0)) { + override fun pulse(): Boolean { + return true + } + }) + } + + /** + * Sends a dialogue to the player using the given NPC ID, which updates the cutscene stage by default when continued. + * @param npcId the ID of the NPC to send a dialogue for + * @param expression the FacialExpression the NPC should use + * @param message the message to send + * @param onContinue (optional) a method that runs when the dialogue is "continued." Increments the cutscene stage by default. + */ + fun dialogueUpdate(npcId: Int, expression: FacialExpression, message: String, onContinue: () -> Unit = {incrementStage()}) + { + logCutscene("Sending NPC dialogue update.") + sendNPCDialogue(player, npcId, message, expression) + player.dialogueInterpreter.addAction { _,_ -> onContinue.invoke() } + } + + /** + * Sends a non-NPC dialogue to the player, which updates the cutscene stage by default when continued + * @param message the message to send + * @param onContinue (optional) a method that runs when the dialogue is "continued." Increments the cutscene stage by default. + */ + fun dialogueUpdate(message: String, onContinue: () -> Unit = {incrementStage()}) + { + logCutscene("Sending standard dialogue update.") + sendDialogue(player, message) + player.dialogueInterpreter.addAction {_,_ -> onContinue.invoke()} + } + + /** + * Sends a player dialogue, which updates the cutscene stage by default when continued + * @param expression the FacialExpression to use + * @param message the message to send + * @param onContinue (optional) a method that runs when the dialogue is "continued." Increments the cutscene stage by default. + */ + fun playerDialogueUpdate(expression: FacialExpression, message: String, onContinue: () -> Unit = {incrementStage()}) + { + logCutscene("Sending player dialogue update") + sendPlayerDialogue(player, message, expression) + player.dialogueInterpreter.addAction{_,_ -> onContinue.invoke()} + } + + /** + * Updates the stage after a set number of ticks. + * @param ticks the number of ticks to wait + * @param newStage (optional) the new stage to update to. If not passed, stage is incremented instead. + */ + fun timedUpdate(ticks: Int, newStage: Int = -1) + { + logCutscene("Executing timed updated for $ticks ticks.") + GameWorld.Pulser.submit(object : Pulse(ticks) + { + override fun pulse(): Boolean { + if(newStage == -1) + incrementStage() + else + updateStage(newStage) + return true + } + }) + } + + /** + * Retrieves the first NPC from the added-to-cutscene NPCs with a matching ID. + * @param id the ID to grab for. + * @return the first NPC, or null if there are no matching NPCs. + */ + fun getNPC(id: Int): NPC? + { + return addedNPCs[id]?.firstOrNull() + } + + /** + * Retrieves all NPCs from the added-to-cutscene NPCs with a matching ID. + * @param id the ID to grab for. + * @return an ArrayList of the matching NPCs, which will be empty if there were no matches. + */ + fun getNPCs(id: Int): ArrayList + { + return addedNPCs[id] ?: ArrayList() + } + + /** + * Retrieves the object in the region at the given regionX and regionY + * @param regionX the region-local X coordinate to grab the object from (0-63) + * @param regionY the region-local Y coordinate to grab the object from (0-63) + * @param plane the plane to grab the object from (0-3) + */ + fun getObject(regionX: Int, regionY: Int, plane: Int = 0): Scenery? + { + val obj = RegionManager.getObject(base.transform(regionX,regionY,plane)) + logCutscene("Retrieving object at LOCAL[$regionX,$regionY], GOT: ${obj?.definition?.name ?: "null"}.") + return obj + } + + /** + * Adds an NPC to the cutscene with the given ID, at the region-local X and Y coordinates. + * @param id the ID of the NPC to add. + * @param regionX the region-local X coordinate to place the NPC at + * @param regionY the region-local Y coordinate to place the NPC at + * @param direction the direction the NPC faces when it is initially placed. + * @param plane the plane to place te NPC on. Default is 0. + */ + fun addNPC(id: Int, regionX: Int, regionY: Int, direction: Direction, plane: Int = 0) + { + val npc = NPC(id) + npc.isRespawn = false + npc.isAggressive = false + npc.isWalks = false + npc.location = base.transform(regionX, regionY, plane) + npc.init() + npc.faceLocation(npc.location.transform(direction)) + + val npcs = addedNPCs[id] ?: ArrayList() + npcs.add(npc) + addedNPCs[id] = npcs + logCutscene("Added NPC $id at location LOCAL[$regionX,$regionY,$plane] GLOBAL[${npc.location.x},${npc.location.y},$plane]") + } + + fun start() + { + logCutscene("Starting cutscene for ${player.username}.") + region = RegionManager.forId(player.location.regionId) + base = RegionManager.forId(player.location.regionId).baseLocation + setup() + runStage(player.getCutsceneStage()) + setAttribute(player, ATTRIBUTE_CUTSCENE, this) + setAttribute(player, ATTRIBUTE_CUTSCENE_STAGE, 0) + player.interfaceManager.removeTabs(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14) + player.properties.isSafeZone = true + player.properties.safeRespawn = player.location + player.lock() + player.hook(Event.SelfDeath, CUTSCENE_DEATH_HOOK) + player.logoutListeners["cutscene"] = {player -> player.location = exitLocation; player.getCutscene()?.end() } + player.antiMacroHandler.enabled = false + } + + /** + * Ends this cutscene, fading the screen to black, teleporting the player to the exit location, and then fading it back in and executing the endActions passed to this method. + * @param endActions (optional) a method that executes when the cutscene fully completes + */ + fun end(endActions: (() -> Unit)? = null) + { + ended = true + fadeToBlack() + GameWorld.Pulser.submit(object : Pulse(){ + var tick: Int = 0 + override fun pulse(): Boolean { + when(tick++) + { + 8 -> player.properties.teleportLocation = exitLocation + 9 -> fadeFromBlack() + 16 -> { + endActions?.invoke() + player.removeAttribute(ATTRIBUTE_CUTSCENE) + player.removeAttribute(ATTRIBUTE_CUTSCENE_STAGE) + player.properties.isSafeZone = false + player.properties.safeRespawn = ServerConstants.HOME_LOCATION + player.interfaceManager.restoreTabs() + player.unlock() + clearNPCs() + player.unhook(CUTSCENE_DEATH_HOOK) + player.logoutListeners.remove("cutscene") + player.antiMacroHandler.enabled = true + return true + } + } + return false + } + }) + } + + /** + * Moves the camera to the specified regionX and regionY + * @param regionX the region-local X coordinate to move the camera to (0-63) + * @param regionY the region-local Y coordinate to move the camera to (0-63) + * @param height (optional) the height of the camera, defaults to 300. + * @param speed (optional) the speed of the camera transition, defaults to 100. + */ + fun moveCamera(regionX: Int, regionY: Int, height: Int = 300, speed: Int = 100) + { + val globalLoc = base.transform(regionX, regionY, 0) + camera.panTo(globalLoc.x, globalLoc.y, height, speed) + } + + /** + * Rotates the camera to face the given region-local X and Y coordinates + * @param regionX the region-local X coordinate to rotate the camera to (0-63) + * @param regionY the region-local Y coordinate to rotate the camera to (0-63) + * @param height (optional) the height of the camera, defaults to 300. + * @param speed (optional) the speed of the camera transition, defaults to 100. + */ + fun rotateCamera(regionX: Int, regionY: Int, height: Int = 300, speed: Int = 100) + { + val globalLoc = base.transform(regionX, regionY, 0) + camera.rotateTo(globalLoc.x, globalLoc.y, height, speed) + } + + /** + * Sets the location the player is placed at when the cutscene ends. + */ + fun setExit(location: Location) + { + exitLocation = location + } + + private fun loadCurrentStage() + { + if(ended) return + runStage(player.getCutsceneStage()) + } + + /** + * Increments the player's stage attribute by 1 + */ + fun incrementStage() + { + setAttribute(player, ATTRIBUTE_CUTSCENE_STAGE, player.getCutsceneStage() + 1) + loadCurrentStage() + } + + /** + * Sets the player's stage attribute to a new stage + */ + fun updateStage(newStage: Int) + { + setAttribute(player, ATTRIBUTE_CUTSCENE_STAGE, newStage) + loadCurrentStage() + } + + fun logCutscene(message: String) + { + if(ServerConstants.LOG_CUTSCENE) + SystemLogger.logInfo("[CUTSCENE: ${this.javaClass.simpleName}] $message") + } + + fun clearNPCs() { + for(entry in addedNPCs.entries) + { + logCutscene("Clearing ${entry.value.size} NPCs with ID ${entry.key} for ${player.username}.") + for(npc in entry.value) npc.clear() + } + addedNPCs.clear() + } + + companion object { + const val ATTRIBUTE_CUTSCENE = "cutscene" + const val ATTRIBUTE_CUTSCENE_STAGE = "cutscene:stage" + object CUTSCENE_DEATH_HOOK : EventHook + { + override fun process(entity: Entity, event: SelfDeath) { + if(entity !is Player) return + entity.getCutscene()?.end() ?: entity.unhook(this) + } + } + } +} \ No newline at end of file diff --git a/Server/src/main/kotlin/api/ContentAPI.kt b/Server/src/main/kotlin/api/ContentAPI.kt index 8da520f99..d02ed59b7 100644 --- a/Server/src/main/kotlin/api/ContentAPI.kt +++ b/Server/src/main/kotlin/api/ContentAPI.kt @@ -1,5 +1,6 @@ package api +import Cutscene import core.cache.def.impl.ItemDefinition import core.cache.def.impl.SceneryDefinition import core.game.component.Component @@ -1438,4 +1439,12 @@ fun dumpContainer(player: Player, container: core.game.container.Container) { fun hasHouse(player: Player): Boolean { return player.houseManager.hasHouse() +} + +fun Player.getCutscene(): Cutscene? { + return getAttribute(this, Cutscene.ATTRIBUTE_CUTSCENE, null) +} + +fun Player.getCutsceneStage(): Int { + return getAttribute(this, Cutscene.ATTRIBUTE_CUTSCENE_STAGE, 0) } \ No newline at end of file diff --git a/Server/src/main/kotlin/api/events/Events.kt b/Server/src/main/kotlin/api/events/Events.kt index a7bae484e..3ac1d9387 100644 --- a/Server/src/main/kotlin/api/events/Events.kt +++ b/Server/src/main/kotlin/api/events/Events.kt @@ -1,6 +1,7 @@ package api.events import core.game.node.Node +import core.game.node.entity.Entity import core.game.node.entity.npc.NPC import core.game.node.entity.player.link.TeleportManager.TeleportType @@ -10,4 +11,5 @@ data class TeleportEvent(val type: TeleportType, val source: Int = -1) : Event data class LitFireEvent(val logId: Int) : Event data class InteractionEvent(val target: Node, val option: String) : Event data class ButtonClickedEvent(val iface: Int, val buttonId: Int) : Event -data class UsedWithEvent(val used: Int, val with: Int) : Event \ No newline at end of file +data class UsedWithEvent(val used: Int, val with: Int) : Event +data class SelfDeath(val killer: Entity) : Event \ No newline at end of file diff --git a/Server/src/main/kotlin/rs09/ServerConstants.kt b/Server/src/main/kotlin/rs09/ServerConstants.kt index 295d076fc..5843419ca 100644 --- a/Server/src/main/kotlin/rs09/ServerConstants.kt +++ b/Server/src/main/kotlin/rs09/ServerConstants.kt @@ -113,6 +113,9 @@ class ServerConstants { @JvmField var MS_SECRET_KEY = "" + @JvmField + var LOG_CUTSCENE = true + //location names for the ::to command. val TELEPORT_DESTINATIONS = arrayOf( arrayOf(Location.create(2974, 4383, 2), "corp", "corporal", "corporeal"), diff --git a/Server/src/main/kotlin/rs09/game/Event.kt b/Server/src/main/kotlin/rs09/game/Event.kt index 697d90dd0..7b519f795 100644 --- a/Server/src/main/kotlin/rs09/game/Event.kt +++ b/Server/src/main/kotlin/rs09/game/Event.kt @@ -10,4 +10,5 @@ object Event { val Interaction = InteractionEvent::class.java val ButtonClicked = ButtonClickedEvent::class.java val UsedWith = UsedWithEvent::class.java + val SelfDeath = SelfDeath::class.java } \ No newline at end of file diff --git a/Server/src/main/kotlin/rs09/game/content/ame/RandomEventManager.kt b/Server/src/main/kotlin/rs09/game/content/ame/RandomEventManager.kt index 3bfe2e50c..9215236d7 100644 --- a/Server/src/main/kotlin/rs09/game/content/ame/RandomEventManager.kt +++ b/Server/src/main/kotlin/rs09/game/content/ame/RandomEventManager.kt @@ -13,6 +13,7 @@ private const val MIN_DELAY_TICKS = AVG_DELAY_TICKS / 2 private const val MAX_DELAY_TICKS = MIN_DELAY_TICKS + AVG_DELAY_TICKS // window of 60 min centered on 60 min (30 to 90 min) class RandomEventManager(val player: Player) { var event: RandomEventNPC? = null + var enabled: Boolean = false var nextSpawn = 0 val skills = arrayOf("WoodcuttingSkillPulse","FishingPulse","MiningSkillPulse","BoneBuryingOptionPlugin") @@ -22,6 +23,10 @@ class RandomEventManager(val player: Player) { } fun fireEvent() { + if(!enabled){ + rollNextSpawn() + return + } if (player.zoneMonitor.isRestricted(ZoneRestriction.RANDOM_EVENTS)) { nextSpawn = GameWorld.ticks + 3000 return @@ -60,6 +65,7 @@ class RandomEventManager(val player: Player) { if (player.isArtificial) return rollNextSpawn() SystemLogger.logRE("Initialized REManager for ${player.username}") + enabled = true } private fun rollNextSpawn() { diff --git a/Server/src/main/kotlin/rs09/game/content/quest/members/thelosttribe/LostTribeCutscene.kt b/Server/src/main/kotlin/rs09/game/content/quest/members/thelosttribe/LostTribeCutscene.kt index f456f163b..b3f0fcba8 100644 --- a/Server/src/main/kotlin/rs09/game/content/quest/members/thelosttribe/LostTribeCutscene.kt +++ b/Server/src/main/kotlin/rs09/game/content/quest/members/thelosttribe/LostTribeCutscene.kt @@ -1,239 +1,122 @@ package rs09.game.content.quest.members.thelosttribe -import core.game.content.activity.ActivityPlugin -import core.game.content.activity.CutscenePlugin +import Cutscene +import api.animate +import api.face import core.game.content.dialogue.FacialExpression import core.game.content.global.action.DoorActionHandler -import core.game.interaction.MovementPulse -import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.node.entity.player.link.emote.Emotes -import core.game.system.task.Pulse +import core.game.world.map.Direction import core.game.world.map.Location -import core.game.world.map.RegionManager -import core.game.world.map.build.DynamicRegion -import core.game.world.update.flag.context.Animation -import core.plugin.Initializable -import rs09.game.camerautils.PlayerCamera -import rs09.game.world.GameWorld.Pulser -private const val DUKE = 2088 -private const val MISTAG = 2089 -private const val SIGMUND = 2090 -private const val URTAG = 5858 -private const val BOW_ANIM = 6000 -@Initializable -/** - * Final cutscene for the lost tribe - * @author Ceikry - */ -class LostTribeCutscene(val pl: Player? = null) : CutscenePlugin("Lost Tribe Cutscene",true) { - var camera: PlayerCamera = PlayerCamera(null) - - init { - this.player = pl - } - - override fun configure() { - region = DynamicRegion.create(12850) - setRegionBase() - registerRegion(region.id) - } - - override fun getStartLocation(): Location { - return base.transform(7,17,0) - } - - override fun newInstance(p: Player?): ActivityPlugin { - return LostTribeCutscene(player) - } - - override fun getSpawnLocation(): Location { - return Location.create(3208, 3213, 0) - } - - override fun open() { - player.setAttribute("cutscene:original-loc",player.location) - player.setAttribute("real-end",Location.create(3207, 3221, 0)) - player.interfaceManager.removeTabs(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14) +class LostTribeCutscene(player: Player) : Cutscene(player) { + override fun setup() { + setExit(Location.create(3207, 3221, 0)) if(player.settings.isRunToggled){ player.settings.toggleRun() } + loadRegion(12850) + addNPC(DUKE, 6, 23, Direction.SOUTH) + addNPC(MISTAG, 8, 17, Direction.NORTH) + addNPC(URTAG, 8, 15, Direction.NORTH) + addNPC(SIGMUND, 13, 22, Direction.NORTH_WEST) + } - camera = PlayerCamera(player) - var loc = coords(8,26) - camera.panTo(loc.x,loc.y,300,100) - loc = coords(5,18) - camera.rotateTo(loc.x,loc.y,300,100) - - player.faceLocation(coords(7,18)) - - val mistag = npcs.get(1) - val urtag = npcs.get(2) - val duke = npcs.get(0) - val sigmund = npcs.get(3) - - Pulser.submit(object: Pulse(){ - var counter = 0 - var stage = 0 - override fun pulse(): Boolean { - if(!player.isPlaying || !player.isActive) return true - when(counter++){ - 5 -> DoorActionHandler.handleDoor(player, RegionManager.getObject(coords(7,17))) - 7 -> { - player.pulseManager.run(object: MovementPulse(player,coords(7,22),false){ - override fun pulse(): Boolean { - return true - } - }, "movement") - mistag.pulseManager.run(object : MovementPulse(mistag,coords(7,19)){//coords(7,20)){ - override fun pulse(): Boolean { - return true - } - }, "movement") - urtag.pulseManager.run(object : MovementPulse(urtag,coords(7,18)){ - override fun pulse(): Boolean { - return true - } - }) - } - 12 -> { - mistag.pulseManager.run(object : MovementPulse(mistag,coords(7,20)){//coords(7,20)){ - override fun pulse(): Boolean { - return true - } - }, "movement") - urtag.pulseManager.run(object : MovementPulse(urtag,coords(6,21)){ - override fun pulse(): Boolean { - return true - } - }) - } - 15 -> { - player.faceLocation(duke.location) - player.dialogueInterpreter.sendDialogues(player,FacialExpression.FRIENDLY,"Your grace, I present Ur-tag, headman of the","Dorgeshuun.") - Emotes.BOW.play(player) - player.dialogueInterpreter.addAction { player, buttonId -> - Pulser.submit(object: MovementPulse(player,coords(7,24)){ - override fun pulse(): Boolean { - return true - } - }) - Pulser.submit(object : Pulse(5){ - var counter = 0 - override fun pulse(): Boolean { - duke.animator.animate(Emotes.BOW.animation) - urtag.animator.animate(Animation(BOW_ANIM)) - player.dialogueInterpreter.sendDialogues(duke,FacialExpression.FRIENDLY,"Welcome, Ur-tag. I am sorry that your race came","under suspicion.") - player.dialogueInterpreter.addAction { player, buttonId -> - player.dialogueInterpreter.sendDialogues(duke,FacialExpression.ANGRY,"I assure you that the warmongering element has been","dealt with.") - loc = coords(9,22) - camera.panTo(loc.x,loc.y,300,2) - loc = coords(6,22) - camera.rotateTo(loc.x,loc.y,300,2) - player.dialogueInterpreter.addAction { player, buttonId -> - player.dialogueInterpreter.sendDialogues(urtag,FacialExpression.OLD_NORMAL,"I apologize for the damage to your cellar. I will send","workers to repair the hole.") - player.dialogueInterpreter.addAction { player, buttonId -> - player.dialogueInterpreter.sendDialogues(duke,FacialExpression.FRIENDLY,"No, let it stay. It can be a route of commerce between","our lands.") - player.dialogueInterpreter.addAction { player, buttonId -> - duke.faceLocation(player.location) - player.faceLocation(duke.location) - camera.rotateTo(duke.location.x,duke.location.y,350,4) - loc = coords(11,22) - camera.panTo(loc.x,loc.y,325,3) - loc = coords(6,22) - camera.rotateTo(loc.x,loc.y,300,3) - player.dialogueInterpreter.sendDialogues(duke,FacialExpression.FRIENDLY,"${player.name}, Lumbridge is in your debt. Please accept","this ring as a token of my thanks.") - player.dialogueInterpreter.addAction { player, buttonId -> - player.dialogueInterpreter.sendDialogues(duke,FacialExpression.FRIENDLY,"It is enchanted to save you in your hour of need.") - player.dialogueInterpreter.addAction { player, buttonId -> - urtag.pulseManager.run(object: MovementPulse(urtag,coords(7,23)){ - override fun pulse(): Boolean { - return true - } - }) - urtag.faceLocation(player.location) - player.dialogueInterpreter.sendDialogues(urtag,FacialExpression.OLD_NORMAL,"I too thank you. Accept the freedom of the Dorgeshuun","mines.") - player.dialogueInterpreter.addAction { player, buttonId -> - player.dialogueInterpreter.sendDialogues(urtag,FacialExpression.OLD_NORMAL,"These are strange times. I never dreamed that I would","see the surface, still less that I would be on friendly","terms with its people.") - loc = coords(16,21) - camera.panTo(loc.x,loc.y,300,3) - loc = coords(6,22) - camera.rotateTo(loc.x,loc.y,300,2) - player.dialogueInterpreter.addAction { player, buttonId -> - Pulser.submit(object : Pulse(5) { - override fun pulse(): Boolean { - player.dialogueInterpreter.sendDialogues(sigmund,FacialExpression.ANGRY,"Prattle on, goblin.") - player.dialogueInterpreter.addAction { player, buttonId -> - player.dialogueInterpreter.sendDialogues(sigmund,FacialExpression.EVIL_LAUGH,"Soon you will be destroyed!") - sigmund.animator.animate(Emotes.LAUGH.animation) - player.dialogueInterpreter.addAction { player, buttonId -> - loc = coords(16,17) - sigmund.pulseManager.run(object: MovementPulse(sigmund,loc){ - override fun pulse(): Boolean { - return true - } - }) - Pulser.submit(object : Pulse(4){ - override fun pulse(): Boolean { - Pulser.submit(endPulse) - Pulser.submit(object : Pulse(7){ - override fun pulse(): Boolean { - player.questRepository.getQuest("Lost Tribe").setStage(player,100) - player.questRepository.getQuest("Lost Tribe").finish(player) - return true - } - }) - return true - } - }) - } - } - return true - } - }) - } - } - } - } - } - } - } - } - return true - } - }) - } - loc = coords(7,25) - camera.panTo(loc.x,loc.y,300,3) - return true - } - } - return false + override fun runStage(stage: Int) { + when(stage) + { + 0 -> { + fadeToBlack() + timedUpdate(6) } - }) + 1 -> { + teleport(player, 7, 17) + timedUpdate(2) + } + 2 -> { + fadeFromBlack() + moveCamera(8, 26) + rotateCamera(5, 18) + timedUpdate(6) + } + 3 -> { + DoorActionHandler.handleDoor(player, getObject(7, 17)) + timedUpdate(3) + } + 4 -> { + move(player, 7, 22) + move(getNPC(MISTAG)!!, 7, 19) + move(getNPC(URTAG)!!, 7, 18) + timedUpdate(5) + } + 5 -> { + move(getNPC(MISTAG)!!, 7, 20) + move(getNPC(URTAG)!!, 6, 21) + timedUpdate(3) + } + 6 -> { + player.faceLocation(getNPC(DUKE)!!.location) + playerDialogueUpdate(FacialExpression.FRIENDLY, "Your grace, I present Ur-tag, headman of the Dorgeshuun.") + Emotes.BOW.play(player) + } + 7 -> { + move(player, 7, 24) + timedUpdate(5) + } + 8 -> { + animate(getNPC(DUKE)!!, Emotes.BOW.animation) + animate(getNPC(URTAG)!!, URTAG_BOW_ANIM) + dialogueUpdate(DUKE, FacialExpression.FRIENDLY, "Welcome, Ur-tag. I am sorry that your race came under suspicion.") + } + 9 -> { + dialogueUpdate(DUKE, FacialExpression.ANGRY, "I assure you that the warmongering element has been dealt with.") + moveCamera(9, 22) + rotateCamera(6, 22) + } + 10 -> dialogueUpdate(URTAG, FacialExpression.OLD_NORMAL, "I apologize for the damage to your cellar. I will send workers to repair the hole.") + 11 -> dialogueUpdate(DUKE, FacialExpression.FRIENDLY, "No, let it stay. It can be a route of commerce between our lands.") + 12 -> { + val duke = getNPC(DUKE)!! + face(duke, player.location) + face(player, duke.location) + rotateCamera(6, 22, 300, 3) + moveCamera(11, 22, 325, 3) + dialogueUpdate(DUKE, FacialExpression.FRIENDLY, "${player.username}, Lumbridge is in your debt. Please accept this ring as a token of my thanks.") + } + 13 -> dialogueUpdate(DUKE, FacialExpression.FRIENDLY, "It is enchanted to save you in your hour of need.") + 14 -> { + move(getNPC(URTAG)!!, 7, 23) + dialogueUpdate(URTAG, FacialExpression.OLD_NORMAL,"I too thank you. Accept the freedom of the Dorgeshuun mines." ) + } + 15 -> { + dialogueUpdate(URTAG, FacialExpression.OLD_NORMAL, "These are strange times. I never dreamed that I would see the surface, still less that I would be on friendly terms with its people." ) + moveCamera(16, 21, 300, 3) + rotateCamera(6, 22, 300, 2) + } + 16 -> dialogueUpdate(SIGMUND, FacialExpression.ANGRY, "Prattle on, goblin.") + 17 -> { + animate(getNPC(SIGMUND)!!, Emotes.LAUGH.animation) + dialogueUpdate(SIGMUND, FacialExpression.EVIL_LAUGH, "Soon you will be destroyed!") + } + 18 -> { + move(getNPC(SIGMUND)!!, 16, 17) + timedUpdate(4) + } + 19 -> { + end { + player.questRepository.getQuest("Lost Tribe").setStage(player, 100) + player.questRepository.getQuest("Lost Tribe").finish(player) + } + } + } } - override fun start(player: Player?, login: Boolean, vararg args: Any?): Boolean { - val duke = NPC(DUKE,coords(6,23)) - val mistag = NPC(MISTAG,coords(8,17)) - val urtag = NPC(URTAG,coords(8,15)) - val sigmund = NPC(SIGMUND,coords(13,22)) - duke.init() - duke.faceLocation(coords(6,22)) - mistag.init() - urtag.init() - sigmund.init() - sigmund.faceLocation(coords(11,18)) - npcs.add(duke) - npcs.add(mistag) - npcs.add(urtag) - npcs.add(sigmund) - return super.start(player, login, *args) + companion object { + private const val DUKE = 2088 + private const val MISTAG = 2089 + private const val SIGMUND = 2090 + private const val URTAG = 5858 + private const val URTAG_BOW_ANIM = 6000 } - - fun coords(x: Int, y: Int): Location { - return base.transform(x,y,0) - } - } \ No newline at end of file diff --git a/Server/src/main/kotlin/rs09/game/content/quest/members/thelosttribe/MistagLTDialogue.kt b/Server/src/main/kotlin/rs09/game/content/quest/members/thelosttribe/MistagLTDialogue.kt index 8ae776b8d..cd3d47b9b 100644 --- a/Server/src/main/kotlin/rs09/game/content/quest/members/thelosttribe/MistagLTDialogue.kt +++ b/Server/src/main/kotlin/rs09/game/content/quest/members/thelosttribe/MistagLTDialogue.kt @@ -1,7 +1,6 @@ package rs09.game.content.quest.members.thelosttribe import core.game.component.Component -import core.game.content.activity.ActivityManager import core.game.content.dialogue.FacialExpression import rs09.game.content.dialogue.DialogueFile import rs09.tools.END_DIALOGUE @@ -45,7 +44,8 @@ class MistagLTDialogue(val isGreeting: Boolean, val questStage: Int) : DialogueF 3 -> npc("I will summon Ur-tag, our headman, at once.").also { stage++ } 4 -> { end() - ActivityManager.start(player,"Lost Tribe Cutscene",false) + LostTribeCutscene(player!!).start() + //ActivityManager.start(player,"Lost Tribe Cutscene",false) } } } diff --git a/Server/src/main/kotlin/rs09/game/system/config/ServerConfigParser.kt b/Server/src/main/kotlin/rs09/game/system/config/ServerConfigParser.kt index 3475a845a..d55443dd3 100644 --- a/Server/src/main/kotlin/rs09/game/system/config/ServerConfigParser.kt +++ b/Server/src/main/kotlin/rs09/game/system/config/ServerConfigParser.kt @@ -98,6 +98,7 @@ object ServerConfigParser { ServerConstants.HOME_LOCATION = parseLocation(data.getString("world.home_location")) ServerConstants.START_LOCATION = parseLocation(data.getString("world.new_player_location")) ServerConstants.DAILY_RESTART = data.getBoolean("world.daily_restart") + ServerConstants.LOG_CUTSCENE = data.getBoolean("world.verbose_cutscene", false) ServerConstants.GRAND_EXCHANGE_DATA_PATH = data.getPath("paths.eco_data") ServerConstants.CELEDT_DATA_PATH = data.getPath("paths.cele_drop_table_path") ServerConstants.SERVER_GE_NAME = data.getString("world.name_ge") ?: ServerConstants.SERVER_NAME