diff --git a/Server/data/configs/npc_spawns.json b/Server/data/configs/npc_spawns.json index 50e91a604..f8a68a451 100644 --- a/Server/data/configs/npc_spawns.json +++ b/Server/data/configs/npc_spawns.json @@ -157,7 +157,7 @@ }, { "npc_id": "47", - "loc_data": "{2821,3170,0,1,1}-{3076,3282,0,1,5}-{3089,3266,0,1,4}-{3091,3266,0,1,4}-{3341,3267,0,1,5}-{3097,3364,0,1,3}-{3102,3363,0,1,5}-{3127,3487,0,1,4}-{3125,3486,0,1,6}-{3127,3486,0,1,4}-{2339,9356,0,1,0}-{2354,9390,0,1,0}-{2361,9403,0,1,0}-{2362,9347,0,1,0}-{2603,9480,0,1,1}-{2600,9477,0,1,0}-{2579,9496,0,1,4}-{2580,9508,0,1,0}-{2571,9522,0,1,4}-{2565,9505,0,1,1}-{2566,9510,0,1,6}-{2594,9497,0,1,4}-{2852,9642,0,1,6}-{2858,9632,0,1,3}-{2568,9620,0,1,0}-{2573,9612,0,1,0}-{2579,9631,0,1,0}-{2580,9600,0,1,0}-{2580,9614,0,1,0}-{2580,9620,0,1,0}-{2580,9626,0,1,0}-{2583,9632,0,1,0}-{2584,9625,0,1,0}-{2584,9637,0,1,0}-{2589,9644,0,1,0}-{2590,9601,0,1,0}-{2590,9638,0,1,0}-{2591,9601,0,1,0}-{2591,9621,0,1,0}-{2594,9636,0,1,0}-{2594,9644,0,1,0}-{2597,9604,0,1,0}-{2607,9615,0,1,0}-{2608,9628,0,1,0}-{2614,9651,0,1,0}-{2614,9656,0,1,0}-{2615,9647,0,1,0}-{2615,9661,0,1,0}-{2616,9633,0,1,0}-{2618,9630,0,1,0}-{3108,9754,0,1,5}-{3110,9754,0,1,5}-{3108,9750,0,1,5}-{2592,9831,0,1,3}-{2588,9825,0,1,6}-{2583,9829,0,1,4}-{2581,9841,0,1,0}-{2597,9823,0,1,2}-{2579,9805,0,1,3}-{2576,9804,0,1,0}-{2573,9805,0,1,5}-{2571,9808,0,1,3}-{2576,9810,0,1,2}-{2587,9802,0,1,2}-{2592,9800,0,1,4}-{2596,9805,0,1,6}-{2601,9802,0,1,5}-{2585,9801,0,1,7}-{2594,9803,0,1,0}-{2590,9806,0,1,3}-{2612,9808,0,1,6}-{2604,9810,0,1,6}-{2579,9821,0,1,2}-{2576,9812,0,1,6}-{2580,9813,0,1,6}-{2600,9813,0,1,4}-{2599,9809,0,1,4}-{3158,3226,0,1,5}-{3160,3202,0,1,4}-{3192,3203,0,1,0}-{3194,3204,0,1,0}-{3196,3206,0,1,0}-{3197,3204,0,1,0}-{2654,9640,0,1,6}-{2655,9637,0,1,4}-{2656,9639,0,1,7}-{2651,9636,0,1,5}-{2648,9637,0,1,4}-{2651,9642,0,1,1}-{2654,9640,0,1,0}-{2654,9635,0,1,6}-{2655,9635,0,1,3}-{2664,9626,0,1,6}-{2664,9624,0,1,1}-{2661,9623,0,1,1}-{2663,9623,0,1,3}-{2664,9626,0,1,6}-{2930,9699,0,1,0}-{2933,9697,0,1,0}-{2932,9685,0,1,0}-{2930,9693,0,1,0}-{3235,3224,0,1,3}-{3229,3220,0,1,4}-{3211,3211,0,1,3}-{3225,3220,0,1,1}-{3237,3215,0,1,5}-{3211,3210,0,1,7}-{3227,3220,0,1,7}-{3233,3227,0,1,5}-{3227,3210,0,1,6}-{3228,3222,0,1,4}-{3229,3226,0,1,0}-{3236,3217,0,1,4}-{3259,3230,0,1,4}-{3233,3237,0,1,7}-{3205,3204,0,1,0}-{3206,3204,0,1,0}-{3205,3203,0,1,0}-{3206,3202,0,1,0}-{3207,3202,0,1,0}-{3208,3203,0,1,0}-{3001,3202,0,1,5}-{3243,3687,0,1,5}-{3249,3669,0,1,3}-{3252,3675,0,1,4}-{3252,3680,0,1,3}-{3259,3683,0,1,0}-{3475,9840,0,1,6}-{3481,9842,0,1,1}-{3486,9843,0,1,7}-{3483,9824,0,1,4}-{3496,9808,0,0,5}-{3490,9815,0,1,1}-{3478,9834,0,0,3}-{3490,9824,0,1,4}-{3225,9862,0,1,4}-{3222,9861,0,1,6}-{3220,9860,0,1,6}-{3219,9865,0,1,6}-{3237,9862,0,1,4}-{2536,2982,0,1,3}-{2531,2980,0,1,0}-{2522,2981,0,1,4}-{2545,2989,0,1,4}-{2523,2970,0,1,2}-{3026,3174,0,1,5}-{3019,3176,0,1,7}-{2801,3158,0,1,2}-{2514,3193,0,1,6}-{2518,3192,0,1,3}-{2507,3181,0,1,3}-{2508,3178,0,1,6}-{2511,3183,0,1,3}-{2515,3182,0,1,1}-{3021,3205,0,1,6}-{3019,3292,0,1,7}-{3018,3295,0,1,7}-{2531,3325,0,1,3}-{2530,3327,0,1,5}-{2521,3331,0,1,3}-{2526,3328,0,1,3}-{2523,3331,0,1,4}-{2523,3334,0,1,1}-{2531,3329,0,1,5}-{2532,3333,0,1,5}-{3276,9871,0,1,1}-{3277,9871,0,1,3}-" + "loc_data": "{2821,3170,0,1,1}-{3341,3267,0,1,5}-{3076,3282,0,1,5}-{3089,3266,0,1,4}-{3091,3266,0,1,4}-{3097,3364,0,1,3}-{3102,3363,0,1,5}-{3127,3487,0,1,4}-{3125,3486,0,1,6}-{3127,3486,0,1,4}-{2339,9356,0,1,0}-{2354,9390,0,1,0}-{2361,9403,0,1,0}-{2362,9347,0,1,0}-{2603,9480,0,1,1}-{2600,9477,0,1,0}-{2579,9496,0,1,4}-{2580,9508,0,1,0}-{2571,9522,0,1,4}-{2565,9505,0,1,1}-{2566,9510,0,1,6}-{2594,9497,0,1,4}-{2852,9642,0,1,6}-{2858,9632,0,1,3}-{2568,9620,0,1,0}-{2573,9612,0,1,0}-{2579,9631,0,1,0}-{2580,9600,0,1,0}-{2580,9614,0,1,0}-{2580,9620,0,1,0}-{2580,9626,0,1,0}-{2583,9632,0,1,0}-{2584,9625,0,1,0}-{2584,9637,0,1,0}-{2589,9644,0,1,0}-{2590,9601,0,1,0}-{2590,9638,0,1,0}-{2591,9601,0,1,0}-{2591,9621,0,1,0}-{2594,9636,0,1,0}-{2594,9644,0,1,0}-{2597,9604,0,1,0}-{2607,9615,0,1,0}-{2608,9628,0,1,0}-{2614,9651,0,1,0}-{2614,9656,0,1,0}-{2615,9647,0,1,0}-{2615,9661,0,1,0}-{2616,9633,0,1,0}-{2618,9630,0,1,0}-{3108,9754,0,1,5}-{3110,9754,0,1,5}-{3108,9750,0,1,5}-{2592,9831,0,1,3}-{2588,9825,0,1,6}-{2583,9829,0,1,4}-{2581,9841,0,1,0}-{2597,9823,0,1,2}-{2579,9805,0,1,3}-{2576,9804,0,1,0}-{2573,9805,0,1,5}-{2571,9808,0,1,3}-{2576,9810,0,1,2}-{2587,9802,0,1,2}-{2592,9800,0,1,4}-{2596,9805,0,1,6}-{2601,9802,0,1,5}-{2585,9801,0,1,7}-{2594,9803,0,1,0}-{2590,9806,0,1,3}-{2612,9808,0,1,6}-{2604,9810,0,1,6}-{2579,9821,0,1,2}-{2576,9812,0,1,6}-{2580,9813,0,1,6}-{2600,9813,0,1,4}-{2599,9809,0,1,4}-{3158,3226,0,1,5}-{3160,3202,0,1,4}-{3192,3203,0,1,0}-{3194,3204,0,1,0}-{3196,3206,0,1,0}-{3197,3204,0,1,0}-{2654,9640,0,1,6}-{2655,9637,0,1,4}-{2656,9639,0,1,7}-{2651,9636,0,1,5}-{2648,9637,0,1,4}-{2651,9642,0,1,1}-{2654,9640,0,1,0}-{2654,9635,0,1,6}-{2655,9635,0,1,3}-{2664,9626,0,1,6}-{2664,9624,0,1,1}-{2661,9623,0,1,1}-{2663,9623,0,1,3}-{2664,9626,0,1,6}-{2930,9699,0,1,0}-{2933,9697,0,1,0}-{2932,9685,0,1,0}-{2930,9693,0,1,0}-{3235,3224,0,1,3}-{3229,3220,0,1,4}-{3211,3211,0,1,3}-{3225,3220,0,1,1}-{3237,3215,0,1,5}-{3211,3210,0,1,7}-{3227,3220,0,1,7}-{3233,3227,0,1,5}-{3227,3210,0,1,6}-{3228,3222,0,1,4}-{3229,3226,0,1,0}-{3236,3217,0,1,4}-{3259,3230,0,1,4}-{3233,3237,0,1,7}-{3205,3204,0,1,0}-{3206,3204,0,1,0}-{3205,3203,0,1,0}-{3206,3202,0,1,0}-{3207,3202,0,1,0}-{3208,3203,0,1,0}-{3001,3202,0,1,5}-{3243,3687,0,1,5}-{3249,3669,0,1,3}-{3252,3675,0,1,4}-{3252,3680,0,1,3}-{3259,3683,0,1,0}-{3475,9840,0,1,6}-{3481,9842,0,1,1}-{3486,9843,0,1,7}-{3483,9824,0,1,4}-{3496,9808,0,0,5}-{3490,9815,0,1,1}-{3478,9834,0,0,3}-{3490,9824,0,1,4}-{3225,9862,0,1,4}-{3222,9861,0,1,6}-{3220,9860,0,1,6}-{3219,9865,0,1,6}-{3237,9862,0,1,4}-{2536,2982,0,1,3}-{2531,2980,0,1,0}-{2522,2981,0,1,4}-{2545,2989,0,1,4}-{2523,2970,0,1,2}-{3026,3174,0,1,5}-{3019,3176,0,1,7}-{2801,3158,0,1,2}-{2514,3193,0,1,6}-{2518,3192,0,1,3}-{2507,3181,0,1,3}-{2508,3178,0,1,6}-{2511,3183,0,1,3}-{2515,3182,0,1,1}-{3021,3205,0,1,6}-{3019,3292,0,1,7}-{3018,3295,0,1,7}-{2531,3325,0,1,3}-{2530,3327,0,1,5}-{2521,3331,0,1,3}-{2526,3328,0,1,3}-{2523,3331,0,1,4}-{2523,3334,0,1,1}-{2531,3329,0,1,5}-{2532,3333,0,1,5}-{3276,9871,0,1,1}-{3277,9871,0,1,3}-" }, { "npc_id": "48", @@ -8641,15 +8641,15 @@ }, { "npc_id": "5067", - "loc_data": "{1799,4889,0,1,4}-" + "loc_data": "{1799,4885,0,0,3}-" }, { "npc_id": "5068", - "loc_data": "{1807,4889,0,1,3}-" + "loc_data": "{1805,4885,0,0,3}-" }, { "npc_id": "5069", - "loc_data": "{1807,4893,0,1,3}-" + "loc_data": "{1807,4893,0,0,6}-" }, { "npc_id": "5070", diff --git a/Server/src/main/content/global/travel/balloon/BalloonCrashSites.kt b/Server/src/main/content/global/travel/balloon/BalloonCrashSites.kt index 690da7806..6415e606e 100644 --- a/Server/src/main/content/global/travel/balloon/BalloonCrashSites.kt +++ b/Server/src/main/content/global/travel/balloon/BalloonCrashSites.kt @@ -8,20 +8,25 @@ import core.api.delayScript import core.api.face import core.api.getAttribute import core.api.getRegionBorders +import core.api.inBorders +import core.api.openDialogue import core.api.openOverlay import core.api.playAudio import core.api.queueScript import core.api.removeAttribute -import core.api.sendMessage +import core.api.sendDialogue import core.api.sendPlayerDialogue import core.api.setAttribute import core.api.stopExecuting import core.api.teleport import core.api.unlock +import core.game.dialogue.DialogueFile import core.game.dialogue.DialogueLabeller +import core.game.dialogue.FacialExpression import core.game.interaction.IntType import core.game.interaction.InteractionListener import core.game.interaction.QueueStrength +import core.game.node.entity.Entity import core.game.node.entity.player.Player import core.game.world.map.Location import core.game.world.map.zone.ZoneBorders @@ -73,7 +78,7 @@ object BalloonCrashSites { fadeTransition(player) { closeFlightInterface(player) teleport(player, exit) - sendMessage(player, "You bail, but land safely on Entrana.") + sendDialogue(player, "You bail, but land safely on Entrana.") } } @@ -82,21 +87,23 @@ object BalloonCrashSites { fadeTransition(player) { closeFlightInterface(player) teleport(player, OCEAN_CRASH_LANDING) - oceanSwimmingAnimations(player) + oceanSwimmingAnimation(player) face(player, OCEAN_CRASH_FACE_LOCATION) - sendMessage(player, "The balloon crashes.") + sendDialogue(player, "The balloon crashes.") } } fun leaveOceanCrashSite(player: Player) { - sendMessage(player, "You climb onto the plank and swim to shore.") - fadeTransition(player, fadeToBlack = true) { - player.appearance.setAnimations() - player.appearance.sync() - teleport(player, OCEAN_CRASH_EXIT) - face(player, OCEAN_CRASH_EXIT_FACE_LOCATION) - sendMessage(player, "You manage to swim back to shore.") - } + player.appearance.setAnimations() + player.appearance.sync() + teleport(player, OCEAN_CRASH_EXIT) + face(player, OCEAN_CRASH_EXIT_FACE_LOCATION) + sendDialogue(player, "You manage to swim back to shore.") + } + + fun oceanSwimmingAnimation(player: Player) { + player.appearance.setAnimations(Animation(RENDER_ANIMATION_SWIM_188)) + player.appearance.sync() } fun crashAtWoodlandSite(player: Player, exit: WoodlandExit) { @@ -106,13 +113,12 @@ object BalloonCrashSites { closeFlightInterface(player) teleport(player, WOODLAND_CRASH_LANDING) face(player, WOODLAND_CRASH_FACE_LOCATION) - sendMessage(player, "The balloon crashes.") + sendDialogue(player, "The balloon crashes.") } } fun leaveWoodlandCrashSite(player: Player) { val exit = getWoodlandExit(player) - sendMessage(player, "You walk away from the crash site.") fadeTransition(player, fadeToBlack = true) { when (exit) { WoodlandExit.FALADOR -> { @@ -177,23 +183,21 @@ object BalloonCrashSites { return@queueScript stopExecuting(player) } } - - private fun oceanSwimmingAnimations(player: Player) { - player.appearance.setAnimations(Animation(RENDER_ANIMATION_SWIM_188)) - player.appearance.sync() - } - } class BalloonCrashSiteListener : InteractionListener, MapArea { override fun defineListeners() { on(BalloonCrashSites.SCENERY_OCEAN_PLANK_19126, IntType.SCENERY, "use") { player, _ -> - BalloonCrashSites.leaveOceanCrashSite(player) + openDialogue(player, BalloonCrashExitDialogue("You climb onto the plank and swim to shore.") { + BalloonCrashSites.leaveOceanCrashSite(it) + }) return@on true } on(BalloonCrashSites.SCENERY_WOODLAND_DIRT_19125, IntType.SCENERY, "leave") { player, _ -> - BalloonCrashSites.leaveWoodlandCrashSite(player) + openDialogue(player, BalloonCrashExitDialogue("You walk away from the crash site.") { + BalloonCrashSites.leaveWoodlandCrashSite(it) + }) return@on true } @@ -209,6 +213,15 @@ class BalloonCrashSiteListener : InteractionListener, MapArea { } + override fun areaEnter(entity: Entity) { + if (entity !is Player || !inBorders(entity, 1795, 4883, 1810, 4898)) return + + queueScript(entity, 0, QueueStrength.SOFT) { + BalloonCrashSites.oceanSwimmingAnimation(entity) + return@queueScript stopExecuting(entity) + } + } + override fun defineAreaBorders(): Array { return arrayOf(getRegionBorders(BalloonCrashSites.CRASH_SITE_REGION_7244)) } @@ -218,6 +231,20 @@ class BalloonCrashSiteListener : InteractionListener, MapArea { } } +/** Displays a crash site exit message and continues the exit after the player clicks Continue. */ +private class BalloonCrashExitDialogue(private val message: String, private val onContinue: (Player) -> Unit) : DialogueFile() { + override fun handle(componentID: Int, buttonID: Int) { + when (stage) { + 0 -> sendDialogue(player!!, message).also { stage++ } + 1 -> { + val player = player!! + end() + onContinue(player) + } + } + } +} + class BalloonCrashDialogue : DialogueLabeller() { override fun addConversation() { assignToIds(BalloonCrashSites.NPC_OCEAN_AUGUSTE_5051) @@ -230,18 +257,17 @@ class BalloonCrashDialogue : DialogueLabeller() { } label("ocean_crash") - npc("Great Scott! Ocean! Ahhh!") - player("What happened?") - npc("Crashed, off course. Ocean! Oh dear... I feel very...") - player("Contemplative? Concerned for your life? Desperate?") - npc("No...more along the lines of ill...You might want to swim away now. Use the plank...") - player("How are you going to get back? What about the balloon?") - npc("I'm sure a fishing trawler will come by and give me a lift. Don't worry, I shall return.") + npc(FacialExpression.SCARED, "Great Scott! Ocean! Ahhh!") + player(FacialExpression.SCARED, "What happened?") + npc(FacialExpression.SCARED, "Crashed, off course. Ocean! Oh dear... I feel very...") + player(FacialExpression.ASKING, "Contemplative? Concerned for your life? Desperate?") + npc(FacialExpression.SCARED, "No...more along the lines of ill...You might want to swim away now. Use the plank...") + player(FacialExpression.SCARED, "How are you going to get back? What about the balloon?") + npc(FacialExpression.THINKING, "I'm sure a fishing trawler will come by and give me a lift. Don't worry, I shall return.") label("woodland_crash") - npc("Well, that didn't go as planned.") + npc(FacialExpression.ANGRY, "Well, that didn't go as planned.") player("So what now? How will we get the balloon back?") - npc("I'll have to get one of the monks to come pick it up. You head back on your own; just follow the path. I'll meet you back at Entrana.") + npc(FacialExpression.SAD, "I'll have to get one of the monks to come pick it up. You head back on your own; just follow the path. I'll meet you back at Entrana.") } - } \ No newline at end of file diff --git a/Server/src/main/content/global/travel/balloon/BalloonFlightGrid.kt b/Server/src/main/content/global/travel/balloon/BalloonFlightGrid.kt new file mode 100644 index 000000000..56fb98a67 --- /dev/null +++ b/Server/src/main/content/global/travel/balloon/BalloonFlightGrid.kt @@ -0,0 +1,219 @@ +package content.global.travel.balloon + +/** Row and column within the balloon interface grid, starting from the top-left. */ +data class BalloonGridPosition(val row: Int, val column: Int) + +/** + * Defines the interface grid and collision points for the balloon minigame. + * @author Edith + */ +object BalloonFlightGrid { + private const val GRID_ROWS = 13 + private const val GRID_COLUMNS = 20 + private const val TOP_LEFT_GRID_CHILD = 258 + private val FLIGHT_AREA_ROWS = 2..9 + + const val SCREEN_ONE = 1 + const val SCREEN_TWO = 2 + const val SCREEN_THREE = 3 + + private val childGrid = Array(GRID_ROWS) { row -> + IntArray(GRID_COLUMNS) { column -> TOP_LEFT_GRID_CHILD - row * GRID_COLUMNS + column } + }.apply { + // Children 98 and 99 are reversed in interface 470. + this[8][0] = 99 + this[8][1] = 98 + } + + /** Interface model children that cause a collision on each flight screen. */ + private val collisionGrids = mapOf( + BalloonTravelLocation.TAVERLEY to mapOf( + SCREEN_ONE to setOf( + 78, 82, 83, 84, 85, 86, 87, 88, 89, 91, + 92, 102, 103, 104, 105, 106, 107, 108, 109, 123, + 124, 125, 127, 128, 145, 148, 155, 156, 172, 173, + 174, 175, 177, 209, 210, 211, 212, 213, 227, 228, + 229, 230 + ), + SCREEN_TWO to setOf( + 83, 84, 90, 91, 92, 93, 94, 95, 96, 97, + 103, 111, 112, 113, 114, 115, 116, 117, 123, 133, + 134, 135, 136, 137, 154, 155, 156, 157, 158, 161, + 179, 180, 181, 185, 186, 187, 188, 201, 202, 203, + 204, 219 + ), + SCREEN_THREE to setOf( + 78, 79, 80, 81, 82, 83, 84, 85, 86, 92, + 93, 97, 98, 99, 100, 101, 102, 103, 106, 113, + 118, 119, 120, 121, 126, 138, 146, 151, 168, 169, + 170, 171, 174, 175, 176, 191, 192, 207, 208, 212, + 213, 214, 215 + ) + ), + BalloonTravelLocation.CRAFTING_GUILD to mapOf( + SCREEN_ONE to setOf( + 78, 79, 80, 81, 82, 83, 84, 88, 89, 90, + 91, 93, 96, 97, 98, 99, 100, 102, 103, 104, + 108, 109, 110, 111, 113, 116, 117, 118, 119, 122, + 124, 128, 129, 130, 131, 133, 137, 138, 148, 151, + 162, 163, 164, 171, 175, 183, 192, 193, 194, 206, + 220, 221, 233, 234, 235 + ), + SCREEN_TWO to setOf( + 78, 79, 83, 84, 87, 89, 92, 96, 97, 98, + 99, 103, 104, 107, 109, 112, 119, 124, 127, 132, + 161, 168, 169, 170, 171, 187, 188, 193, 194, 195, + 200, 201, 202, 205, 206, 207, 216, 217 + ), + SCREEN_THREE to setOf( + 78, 79, 80, 83, 84, 85, 86, 87, 88, 89, + 90, 97, 100, 103, 104, 105, 107, 108, 110, 120, + 123, 130, 143, 163, 166, 167, 168, 173, 174, 175, + 198, 199, 209, 210, 211, 212, 222, 223, 224, 228, + 229, 230 + ) + ), + BalloonTravelLocation.VARROCK to mapOf( + SCREEN_ONE to setOf( + 78, 79, 80, 83, 86, 87, 88, 89, 90, 91, + 93, 94, 95, 97, 99, 100, 107, 108, 109, 110, + 111, 113, 114, 115, 117, 120, 122, 127, 129, 131, + 135, 137, 144, 145, 146, 157, 161, 163, 170, 174, + 188, 189, 190, 193, 194, 195, 198, 199, 200, 206, + 207, 208, 216, 232 + ), + SCREEN_TWO to setOf( + 79, 81, 82, 83, 85, 86, 87, 88, 89, 93, + 94, 95, 97, 98, 101, 102, 103, 105, 106, 107, + 108, 109, 113, 114, 115, 117, 119, 122, 125, 126, + 127, 128, 129, 134, 135, 137, 139, 145, 146, 147, + 148, 149, 154, 166, 167, 168, 171, 172, 173, 187, + 190, 192, 200, 201, 202, 209, 214, 215, 216, 217, + 218, 224, 225, 226, 236 + ), + SCREEN_THREE to setOf( + 79, 80, 81, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 97, 98, 100, 101, 103, + 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, + 114, 119, 120, 123, 124, 125, 126, 127, 128, 129, + 130, 131, 132, 133, 134, 141, 143, 144, 145, 146, + 147, 148, 149, 150, 151, 152, 153, 154, 163, 164, + 167, 168, 171, 172, 183, 184, 187, 188, 191, 192, + 198, 199, 201, 202, 225, 230 + ) + ), + BalloonTravelLocation.CASTLE_WARS to mapOf( + SCREEN_ONE to setOf( + 78, 80, 83, 84, 85, 92, 93, 97, 100, 105, + 112, 120, 130, 136, 140, 147, 164, 165, 166, 169, + 170, 175, 176, 177, 181, 182, 183, 193, 207, 212, + 213, 214, 227, 228, 229 + ), + SCREEN_TWO to setOf( + 79, 85, 86, 87, 88, 89, 90, 91, 92, 93, + 95, 96, 107, 108, 109, 110, 111, 112, 113, 115, + 116, 118, 129, 130, 131, 132, 133, 135, 136, 140, + 141, 142, 152, 153, 156, 158, 159, 173, 180, 181, + 189, 190, 191, 197, 203, 204, 205, 215, 216, 217, + 228, 229, 230, 231 + ), + SCREEN_THREE to setOf( + 78, 80, 83, 84, 85, 86, 87, 92, 93, 97, + 99, 100, 103, 104, 105, 106, 107, 112, 113, 118, + 120, 123, 124, 125, 126, 127, 133, 138, 140, 147, + 153, 170, 171, 172, 179, 183, 184, 185, 186, 198, + 199, 208, 209, 210, 211, 215, 216, 217, 222, 223 + ) + ), + BalloonTravelLocation.GRAND_TREE to mapOf( + SCREEN_ONE to setOf( + 78, 79, 80, 81, 84, 85, 89, 90, 93, 94, + 97, 98, 99, 100, 104, 110, 118, 119, 130, 138, + 144, 186, 187, 188, 191, 192, 193, 194, 195, 201, + 202, 203, 214, 229, 230, 231, 232, 237 + ), + SCREEN_TWO to setOf( + 79, 80, 81, 82, 83, 84, 87, 88, 89, 90, + 91, 92, 94, 95, 96, 97, 100, 101, 102, 103, + 104, 107, 108, 109, 110, 111, 112, 114, 115, 116, + 117, 120, 122, 124, 127, 128, 129, 130, 132, 134, + 135, 140, 142, 144, 147, 149, 150, 152, 154, 167, + 172, 181, 183, 191, 192, 196, 197, 200, 201, 203, + 204, 205, 208, 209, 214, 215, 216, 217, 219, 220, + 224, 228 + ), + SCREEN_THREE to setOf( + 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 97, 98, 99, 100, 101, + 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 120, 121, 122, 123, 124, 125, 126, 127, 128, + 129, 130, 131, 132, 140, 141, 142, 143, 144, 145, + 146, 147, 148, 149, 150, 151, 161, 162, 163, 164, + 165, 166, 167, 168, 169, 170, 174, 175, 178, 182, + 183, 187, 188, 189, 198, 199, 200, 212, 213, 214, + 225 + ) + ) + ) + + /** Grid columns for crashing over ocean. */ + private val oceanColumns = mapOf( + BalloonTravelLocation.TAVERLEY to mapOf( + SCREEN_ONE to 17..19, + SCREEN_TWO to 0..7 + ), + BalloonTravelLocation.CRAFTING_GUILD to mapOf( + SCREEN_TWO to 4..19, + SCREEN_THREE to 0..2 + ), + BalloonTravelLocation.VARROCK to mapOf( + SCREEN_ONE to 2..6 + ), + BalloonTravelLocation.CASTLE_WARS to mapOf( + SCREEN_ONE to 4..19, + SCREEN_TWO to 0..5 + ), + BalloonTravelLocation.GRAND_TREE to mapOf( + SCREEN_ONE to 4..19 + ) + ) + + /** Checks whether balloon position is above the ocean. */ + fun isOverOcean(destination: BalloonTravelLocation, screen: Int, position: BalloonGridPosition): Boolean { + return oceanColumns[destination]?.get(screen)?.contains(position.column) == true + } + + /** Gets the interface child occupied by a grid position. */ + fun getGridChild(position: BalloonGridPosition): Int? { + if (position.row !in childGrid.indices) { + return null + } + if (position.column !in childGrid[position.row].indices) { + return null + } + return childGrid[position.row][position.column] + } + + /** Checks whether a grid position is inside the flight area. */ + fun isInFlightArea(position: BalloonGridPosition): Boolean { + return position.row in FLIGHT_AREA_ROWS && position.column in 0 until GRID_COLUMNS + } + + /** Checks whether either part of the balloon collides with the flight boundary or a model. */ + fun willBalloonCollide(destination: BalloonTravelLocation, screen: Int, position: BalloonGridPosition): Boolean { + val topPosition = position.copy(row = position.row - 1) + + if (position.column == GRID_COLUMNS) { + return topPosition.row !in FLIGHT_AREA_ROWS || position.row !in FLIGHT_AREA_ROWS + } + + if (!isInFlightArea(topPosition) || !isInFlightArea(position)) { + return true + } + + val collisionChildren = collisionGrids[destination]?.get(screen) ?: return true + val topChild = getGridChild(topPosition) ?: return true + val bottomChild = getGridChild(position) ?: return true + return topChild in collisionChildren || bottomChild in collisionChildren + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/travel/balloon/BalloonFlightInterface.kt b/Server/src/main/content/global/travel/balloon/BalloonFlightInterface.kt index eded3b846..ef5bb1197 100644 --- a/Server/src/main/content/global/travel/balloon/BalloonFlightInterface.kt +++ b/Server/src/main/content/global/travel/balloon/BalloonFlightInterface.kt @@ -7,6 +7,7 @@ import core.api.closeOverlay import core.api.closeTabInterface import core.api.face import core.api.getAttribute +import core.api.getVarbit import core.api.openInterface import core.api.openOverlay import core.api.openSingleTab @@ -29,7 +30,6 @@ import core.net.packet.context.MinimapStateContext import core.net.packet.out.MinimapState import core.plugin.Initializable import org.rs09.consts.Components -import kotlin.math.abs /** * Handles the balloon flight minigame interface. @@ -54,13 +54,18 @@ class BalloonFlightInterface : InterfaceListener { private const val BALLOON_FLIGHT_INTERFACE_470 = 470 private const val BALLOON_CONTROL_INTERFACE_471 = 471 - private const val ATTRIBUTE_BALLOON_TOP = "enlightenedjourney_balloon_top" - private const val ATTRIBUTE_BALLOON_BOTTOM = "enlightenedjourney_balloon_bottom" + private const val ATTRIBUTE_BALLOON_ROW = "enlightenedjourney_balloon_row" + private const val ATTRIBUTE_BALLOON_COLUMN = "enlightenedjourney_balloon_column" private const val ATTRIBUTE_FLIGHT_PROGRESS = "enlightenedjourney_balloon_flight_progress" private const val ATTRIBUTE_FLIGHT_SCREEN = "enlightenedjourney_balloon_flight_screen" private const val ATTRIBUTE_FLIGHT_DESTINATION = "enlightenedjourney_balloon_flight_destination" + private const val VARBIT_SANDBAGS_REMAINING_2880 = 2880 + private const val VARBIT_LOGS_REMAINING_2881 = 2881 + private const val STARTING_SANDBAGS = 8 + private const val STARTING_LOGS = 10 + private const val SOUND_SANDBAG_3249 = 3249 private const val SOUND_BURN_LOG_3251 = 3251 private const val SOUND_RELAX_3247 = 3247 @@ -87,7 +92,8 @@ class BalloonFlightInterface : InterfaceListener { setAttribute(player, ATTRIBUTE_FLIGHT_DESTINATION, BalloonTravelLocation.TAVERLEY) setAttribute(player, ATTRIBUTE_FLIGHT_PROGRESS, 0) setAttribute(player, ATTRIBUTE_FLIGHT_SCREEN, 1) - setBalloonPosition(player, startPosition.top, startPosition.bottom) + setBalloonPosition(player, startPosition) + updateFlightSupplies(player) closeInterface(player) hideGameTabs(player) hideMinimap(player) @@ -101,7 +107,8 @@ class BalloonFlightInterface : InterfaceListener { setAttribute(player, ATTRIBUTE_FLIGHT_DESTINATION, destination) setAttribute(player, ATTRIBUTE_FLIGHT_PROGRESS, 0) setAttribute(player, ATTRIBUTE_FLIGHT_SCREEN, 1) - setBalloonPosition(player, startPosition.top, startPosition.bottom) + setBalloonPosition(player, startPosition) + updateFlightSupplies(player) closeInterface(player) hideGameTabs(player) hideMinimap(player) @@ -113,7 +120,7 @@ class BalloonFlightInterface : InterfaceListener { return getAttribute(player, ATTRIBUTE_FLIGHT_DESTINATION, BalloonTravelLocation.TAVERLEY) } - private fun getScreenPosition(destination: BalloonTravelLocation, screen: Int): BalloonPosition { + private fun getScreenPosition(destination: BalloonTravelLocation, screen: Int): BalloonGridPosition { return when (destination) { BalloonTravelLocation.TAVERLEY -> when (screen) { 1 -> BalloonFlightRoutes.TAVERLEY_START @@ -170,70 +177,85 @@ class BalloonFlightInterface : InterfaceListener { } } - private fun setBalloonPosition(player: Player, topChild: Int, bottomChild: Int) { - setAttribute(player, ATTRIBUTE_BALLOON_TOP, topChild) - setAttribute(player, ATTRIBUTE_BALLOON_BOTTOM, bottomChild) + private fun setBalloonPosition(player: Player, position: BalloonGridPosition) { + setAttribute(player, ATTRIBUTE_BALLOON_ROW, position.row) + setAttribute(player, ATTRIBUTE_BALLOON_COLUMN, position.column) } - /** Moves the balloon model on the interface according to the selected control. */ - private fun moveBalloon(player: Player, move: BalloonMove) { - val startPosition = BalloonFlightRoutes.getStartPosition(getFlightDestination(player)) - val oldTop = getAttribute(player, ATTRIBUTE_BALLOON_TOP, startPosition.top) - val oldBottom = getAttribute(player, ATTRIBUTE_BALLOON_BOTTOM, startPosition.bottom) - var newTop = oldTop - var newBottom = oldBottom - - if (newBottom == 98) { - newBottom += 2 - } - - newTop += move.east - newBottom += move.east - - repeat(abs(move.vertical)) { - if (move.vertical < 0) { - newTop = moveNorth(newTop) - newBottom = moveNorth(newBottom) - } else { - newTop = moveSouth(newTop) - newBottom = moveSouth(newBottom) - } - } - - if (move.vertical < 0 && newTop - newBottom == 21) { - newBottom += 1 - } - - BalloonFlightScreens.clearBalloon(player, oldTop, oldBottom) - setBalloonPosition(player, newTop, newBottom) - BalloonFlightScreens.drawBalloon(player, newTop, newBottom) + private fun getBalloonPosition(player: Player, destination: BalloonTravelLocation, screen: Int): BalloonGridPosition { + val startPosition = getScreenPosition(destination, screen) + return BalloonGridPosition( + getAttribute(player, ATTRIBUTE_BALLOON_ROW, startPosition.row), + getAttribute(player, ATTRIBUTE_BALLOON_COLUMN, startPosition.column) + ) } - private fun moveNorth(child: Int) = child + if (child >= 111) 20 else 19 - private fun moveSouth(child: Int) = child - 20 + /** Moves the balloon model on the interface according to the selected control, or returns false if it would collide. */ + private fun moveBalloon(player: Player, move: BalloonMove): Boolean { + val destination = getFlightDestination(player) + val screen = getAttribute(player, ATTRIBUTE_FLIGHT_SCREEN, 1) + val oldPosition = getBalloonPosition(player, destination, screen) + val newPosition = oldPosition.copy(row = oldPosition.row + move.vertical, column = oldPosition.column + move.east) + + if (BalloonFlightGrid.willBalloonCollide(destination, screen, newPosition)) { + handleBalloonCrash(player, getCrashLocation(destination, screen, newPosition)) + return false + } + + BalloonFlightScreens.clearBalloon(player, oldPosition) + setBalloonPosition(player, newPosition) + BalloonFlightScreens.drawBalloon(player, newPosition) + return true + } + + /** Sets the starting amount of sandbags and logs, and deducts them when used. */ + private fun updateFlightSupplies(player: Player, buttonId: Int? = null): Boolean { + if (buttonId == null) { + setVarbit(player, VARBIT_SANDBAGS_REMAINING_2880, STARTING_SANDBAGS, true) + setVarbit(player, VARBIT_LOGS_REMAINING_2881, STARTING_LOGS, true) + return true + } + + val varbit = when (buttonId) { + BalloonFlightRoutes.BUTTON_SANDBAG -> VARBIT_SANDBAGS_REMAINING_2880 + BalloonFlightRoutes.BUTTON_BURN_LOG -> VARBIT_LOGS_REMAINING_2881 + else -> return true + } + + val remaining = getVarbit(player, varbit) + if (remaining <= 0) { + return false + } + + setVarbit(player, varbit, remaining - 1, true) + return true + } /** Handles a control button press and either advances, lands or crashes the route. */ - private fun handleRouteButton(player: Player, buttonId: Int) { + private fun handleControlButton(player: Player, buttonId: Int) { val destination = getFlightDestination(player) val screen = getAttribute(player, ATTRIBUTE_FLIGHT_SCREEN, 1) if (buttonId == BalloonFlightRoutes.BUTTON_BAIL) { - handleBalloonCrash(player, getCrashLocation(destination, screen)) + val position = getBalloonPosition(player, destination, screen) + handleBalloonCrash(player, getCrashLocation(destination, screen, position)) return } - val route = BalloonFlightRoutes.getSequence(destination, screen) + val move = controlMoves[buttonId] ?: return + val routeLength = BalloonFlightRoutes.getSequence(destination, screen).size val progress = getAttribute(player, ATTRIBUTE_FLIGHT_PROGRESS, 0) - val expectedButton = route.getOrNull(progress) - val move = controlMoves[buttonId] - if (buttonId != expectedButton || move == null) { - handleBalloonCrash(player, getCrashLocation(destination, screen)) + if (!updateFlightSupplies(player, buttonId)) { return } + playControlSound(player, buttonId) - moveBalloon(player, move) - if (progress + 1 >= route.size) { + if (!moveBalloon(player, move)) { + return + } + + if (progress + 1 >= routeLength) { advanceFlightScreen(player, destination, screen) } else { setAttribute(player, ATTRIBUTE_FLIGHT_PROGRESS, progress + 1) @@ -255,14 +277,24 @@ class BalloonFlightInterface : InterfaceListener { BalloonFlightScreens.draw(player, destination, screen) } - private fun getCrashLocation(destination: BalloonTravelLocation, screen: Int): BalloonCrashLocation { + private fun getCrashLocation(destination: BalloonTravelLocation, screen: Int, position: BalloonGridPosition): BalloonCrashLocation { + if (BalloonFlightGrid.isOverOcean(destination, screen, position)) { + return BalloonCrashLocation.OCEAN + } + + if (screen == BalloonFlightGrid.SCREEN_ONE) { + return BalloonCrashLocation.ENTRANA + } + return when (destination) { - BalloonTravelLocation.TAVERLEY -> getTaverleyCrashLocation(screen) - BalloonTravelLocation.CRAFTING_GUILD -> getCraftingGuildCrashLocation(screen) - BalloonTravelLocation.VARROCK -> getVarrockCrashLocation(screen) - BalloonTravelLocation.CASTLE_WARS -> getCastleWarsCrashLocation(screen) - BalloonTravelLocation.GRAND_TREE -> getGrandTreeCrashLocation(screen) - else -> getTaverleyCrashLocation(screen) + BalloonTravelLocation.TAVERLEY, + BalloonTravelLocation.CRAFTING_GUILD, + BalloonTravelLocation.VARROCK -> BalloonCrashLocation.WOODLAND_FALADOR + + BalloonTravelLocation.CASTLE_WARS, + BalloonTravelLocation.GRAND_TREE -> BalloonCrashLocation.WOODLAND_ARDOUGNE + + else -> BalloonCrashLocation.ENTRANA } } @@ -274,13 +306,13 @@ class BalloonFlightInterface : InterfaceListener { } } - private fun startTaverleyScreen(player: Player, screen: Int, position: BalloonPosition) { + private fun startTaverleyScreen(player: Player, screen: Int, position: BalloonGridPosition) { BalloonFlightScreens.clear(player) BalloonFlightScreens.draw(player, BalloonTravelLocation.TAVERLEY, screen) setAttribute(player, ATTRIBUTE_FLIGHT_SCREEN, screen) setAttribute(player, ATTRIBUTE_FLIGHT_PROGRESS, 0) - setBalloonPosition(player, position.top, position.bottom) - BalloonFlightScreens.drawBalloon(player, position.top, position.bottom) + setBalloonPosition(player, position) + BalloonFlightScreens.drawBalloon(player, position) } private fun landInTaverley(player: Player) { @@ -298,14 +330,6 @@ class BalloonFlightInterface : InterfaceListener { } } - private fun getTaverleyCrashLocation(screen: Int): BalloonCrashLocation { - return when (screen) { - 1 -> BalloonCrashLocation.ENTRANA - 2 -> BalloonCrashLocation.OCEAN - else -> BalloonCrashLocation.WOODLAND_FALADOR - } - } - private fun advanceCraftingGuildScreen(player: Player, screen: Int) { when (screen) { 1 -> startCraftingGuildScreen(player, 2, BalloonFlightRoutes.CRAFTING_GUILD_SECOND) @@ -314,13 +338,13 @@ class BalloonFlightInterface : InterfaceListener { } } - private fun startCraftingGuildScreen(player: Player, screen: Int, position: BalloonPosition) { + private fun startCraftingGuildScreen(player: Player, screen: Int, position: BalloonGridPosition) { BalloonFlightScreens.clear(player) BalloonFlightScreens.draw(player, BalloonTravelLocation.CRAFTING_GUILD, screen) setAttribute(player, ATTRIBUTE_FLIGHT_SCREEN, screen) setAttribute(player, ATTRIBUTE_FLIGHT_PROGRESS, 0) - setBalloonPosition(player, position.top, position.bottom) - BalloonFlightScreens.drawBalloon(player, position.top, position.bottom) + setBalloonPosition(player, position) + BalloonFlightScreens.drawBalloon(player, position) } private fun landAtCraftingGuild(player: Player) { @@ -335,14 +359,6 @@ class BalloonFlightInterface : InterfaceListener { } } - private fun getCraftingGuildCrashLocation(screen: Int): BalloonCrashLocation { - return when (screen) { - 1 -> BalloonCrashLocation.ENTRANA - 2 -> BalloonCrashLocation.OCEAN - else -> BalloonCrashLocation.WOODLAND_FALADOR - } - } - private fun advanceVarrockScreen(player: Player, screen: Int) { when (screen) { 1 -> startVarrockScreen(player, 2, BalloonFlightRoutes.VARROCK_SECOND) @@ -351,13 +367,13 @@ class BalloonFlightInterface : InterfaceListener { } } - private fun startVarrockScreen(player: Player, screen: Int, position: BalloonPosition) { + private fun startVarrockScreen(player: Player, screen: Int, position: BalloonGridPosition) { BalloonFlightScreens.clear(player) BalloonFlightScreens.draw(player, BalloonTravelLocation.VARROCK, screen) setAttribute(player, ATTRIBUTE_FLIGHT_SCREEN, screen) setAttribute(player, ATTRIBUTE_FLIGHT_PROGRESS, 0) - setBalloonPosition(player, position.top, position.bottom) - BalloonFlightScreens.drawBalloon(player, position.top, position.bottom) + setBalloonPosition(player, position) + BalloonFlightScreens.drawBalloon(player, position) } private fun landAtVarrock(player: Player) { @@ -372,13 +388,6 @@ class BalloonFlightInterface : InterfaceListener { } } - private fun getVarrockCrashLocation(screen: Int): BalloonCrashLocation { - return when (screen) { - 1 -> BalloonCrashLocation.ENTRANA - else -> BalloonCrashLocation.WOODLAND_FALADOR - } - } - private fun advanceCastleWarsScreen(player: Player, screen: Int) { when (screen) { 1 -> startCastleWarsScreen(player, 2, BalloonFlightRoutes.CASTLE_WARS_SECOND) @@ -387,13 +396,13 @@ class BalloonFlightInterface : InterfaceListener { } } - private fun startCastleWarsScreen(player: Player, screen: Int, position: BalloonPosition) { + private fun startCastleWarsScreen(player: Player, screen: Int, position: BalloonGridPosition) { BalloonFlightScreens.clear(player) BalloonFlightScreens.draw(player, BalloonTravelLocation.CASTLE_WARS, screen) setAttribute(player, ATTRIBUTE_FLIGHT_SCREEN, screen) setAttribute(player, ATTRIBUTE_FLIGHT_PROGRESS, 0) - setBalloonPosition(player, position.top, position.bottom) - BalloonFlightScreens.drawBalloon(player, position.top, position.bottom) + setBalloonPosition(player, position) + BalloonFlightScreens.drawBalloon(player, position) } private fun landAtCastleWars(player: Player) { @@ -408,13 +417,6 @@ class BalloonFlightInterface : InterfaceListener { } } - private fun getCastleWarsCrashLocation(screen: Int): BalloonCrashLocation { - return when (screen) { - 1, 2 -> BalloonCrashLocation.OCEAN - else -> BalloonCrashLocation.WOODLAND_ARDOUGNE - } - } - private fun advanceGrandTreeScreen(player: Player, screen: Int) { when (screen) { 1 -> startGrandTreeScreen(player, 2, BalloonFlightRoutes.GRAND_TREE_SECOND) @@ -423,13 +425,13 @@ class BalloonFlightInterface : InterfaceListener { } } - private fun startGrandTreeScreen(player: Player, screen: Int, position: BalloonPosition) { + private fun startGrandTreeScreen(player: Player, screen: Int, position: BalloonGridPosition) { BalloonFlightScreens.clear(player) BalloonFlightScreens.draw(player, BalloonTravelLocation.GRAND_TREE, screen) setAttribute(player, ATTRIBUTE_FLIGHT_SCREEN, screen) setAttribute(player, ATTRIBUTE_FLIGHT_PROGRESS, 0) - setBalloonPosition(player, position.top, position.bottom) - BalloonFlightScreens.drawBalloon(player, position.top, position.bottom) + setBalloonPosition(player, position) + BalloonFlightScreens.drawBalloon(player, position) } private fun landAtGrandTree(player: Player) { @@ -444,17 +446,10 @@ class BalloonFlightInterface : InterfaceListener { } } - private fun getGrandTreeCrashLocation(screen: Int): BalloonCrashLocation { - return when (screen) { - 1 -> BalloonCrashLocation.OCEAN - else -> BalloonCrashLocation.WOODLAND_ARDOUGNE - } - } - private fun clearFlightAttributes(player: Player) { removeAttributes( - player, ATTRIBUTE_BALLOON_TOP, - ATTRIBUTE_BALLOON_BOTTOM, + player, ATTRIBUTE_BALLOON_ROW, + ATTRIBUTE_BALLOON_COLUMN, ATTRIBUTE_FLIGHT_DESTINATION, ATTRIBUTE_FLIGHT_SCREEN, ATTRIBUTE_FLIGHT_PROGRESS @@ -511,7 +506,7 @@ class BalloonFlightInterface : InterfaceListener { openSingleTab(player, BALLOON_CONTROL_INTERFACE_471) BalloonFlightScreens.clear(player) drawFlightScreen(player, destination, screen) - BalloonFlightScreens.drawBalloon(player, startPosition.top, startPosition.bottom) + BalloonFlightScreens.drawBalloon(player, startPosition) openOverlay(player, 333) true } @@ -526,7 +521,7 @@ class BalloonFlightInterface : InterfaceListener { } on(BALLOON_CONTROL_INTERFACE_471) { player, _, _, buttonId, _, _ -> - handleRouteButton(player, buttonId) + handleControlButton(player, buttonId) true } } diff --git a/Server/src/main/content/global/travel/balloon/BalloonFlightRoutes.kt b/Server/src/main/content/global/travel/balloon/BalloonFlightRoutes.kt index 0a688f1c3..7bea83a6a 100644 --- a/Server/src/main/content/global/travel/balloon/BalloonFlightRoutes.kt +++ b/Server/src/main/content/global/travel/balloon/BalloonFlightRoutes.kt @@ -7,8 +7,6 @@ import core.game.world.map.Location * @author Edith */ -data class BalloonPosition(val top: Int, val bottom: Int) - object BalloonFlightRoutes { const val BUTTON_SANDBAG = 4 const val BUTTON_RELAX = 5 @@ -17,26 +15,26 @@ object BalloonFlightRoutes { const val BUTTON_BURN_LOG = 9 const val BUTTON_RED_ROPE = 10 - /** Child slots used by the top and bottom balloon models. */ - val TAVERLEY_START = BalloonPosition(118, 99) - val TAVERLEY_SECOND = BalloonPosition(119, 98) - val TAVERLEY_THIRD = BalloonPosition(179, 159) + /** Balloon starting grid positions for each flight screen. */ + val TAVERLEY_START = BalloonGridPosition(8, 0) + val TAVERLEY_SECOND = BalloonGridPosition(8, 1) + val TAVERLEY_THIRD = BalloonGridPosition(5, 1) - val CRAFTING_GUILD_START = BalloonPosition(178, 158) - val CRAFTING_GUILD_SECOND = BalloonPosition(159, 139) - val CRAFTING_GUILD_THIRD = BalloonPosition(159, 139) + val CRAFTING_GUILD_START = BalloonGridPosition(5, 0) + val CRAFTING_GUILD_SECOND = BalloonGridPosition(6, 1) + val CRAFTING_GUILD_THIRD = BalloonGridPosition(6, 1) - val VARROCK_START = BalloonPosition(138, 118) - val VARROCK_SECOND = BalloonPosition(179, 159) - val VARROCK_THIRD = BalloonPosition(179, 159) + val VARROCK_START = BalloonGridPosition(7, 0) + val VARROCK_SECOND = BalloonGridPosition(5, 1) + val VARROCK_THIRD = BalloonGridPosition(5, 1) - val CASTLE_WARS_START = BalloonPosition(118, 99) - val CASTLE_WARS_SECOND = BalloonPosition(199, 179) - val CASTLE_WARS_THIRD = BalloonPosition(159, 139) + val CASTLE_WARS_START = BalloonGridPosition(8, 0) + val CASTLE_WARS_SECOND = BalloonGridPosition(4, 1) + val CASTLE_WARS_THIRD = BalloonGridPosition(6, 1) - val GRAND_TREE_START = BalloonPosition(178, 158) - val GRAND_TREE_SECOND = BalloonPosition(159, 139) - val GRAND_TREE_THIRD = BalloonPosition(159, 139) + val GRAND_TREE_START = BalloonGridPosition(5, 0) + val GRAND_TREE_SECOND = BalloonGridPosition(6, 1) + val GRAND_TREE_THIRD = BalloonGridPosition(6, 1) /** Player landing locations. */ val TAVERLEY_LANDING = Location.create(2941, 3420, 0) @@ -162,8 +160,8 @@ object BalloonFlightRoutes { } } - /** Gets the starting child slots for the balloon models on the first screen of a route. */ - fun getStartPosition(destination: BalloonTravelLocation): BalloonPosition { + /** Gets the starting grid position for the balloon models on the first screen of a route. */ + fun getStartPosition(destination: BalloonTravelLocation): BalloonGridPosition { return when (destination) { BalloonTravelLocation.TAVERLEY -> TAVERLEY_START BalloonTravelLocation.CRAFTING_GUILD -> CRAFTING_GUILD_START diff --git a/Server/src/main/content/global/travel/balloon/BalloonFlightScreens.kt b/Server/src/main/content/global/travel/balloon/BalloonFlightScreens.kt index 4bff34984..411857a3c 100644 --- a/Server/src/main/content/global/travel/balloon/BalloonFlightScreens.kt +++ b/Server/src/main/content/global/travel/balloon/BalloonFlightScreens.kt @@ -38,20 +38,30 @@ object BalloonFlightScreens { } } - /** Draws the top and bottom balloon models at the given child slots. */ - fun drawBalloon(player: Player, topChild: Int, bottomChild: Int) { + /** Draws the top and bottom balloon models at the given grid position. */ + fun drawBalloon(player: Player, position: BalloonGridPosition) { + val (topChild, bottomChild) = getBalloonChildren(position) ?: return player.packetDispatch.sendModelOnInterface(MODEL_BALLOON_TOP_19517, BALLOON_FLIGHT_INTERFACE_470, topChild, 2576) resetModelAngle(player, topChild) player.packetDispatch.sendModelOnInterface(MODEL_BALLOON_BOTTOM_19518, BALLOON_FLIGHT_INTERFACE_470, bottomChild, 2576) resetModelAngle(player, bottomChild) } - /** Clears the top and bottom balloon models at the given child slots. */ - fun clearBalloon(player: Player, topChild: Int, bottomChild: Int) { + /** Clears the top and bottom balloon models at the given grid position. */ + fun clearBalloon(player: Player, position: BalloonGridPosition) { + val (topChild, bottomChild) = getBalloonChildren(position) ?: return player.packetDispatch.sendModelOnInterface(-1, BALLOON_FLIGHT_INTERFACE_470, topChild, 0) player.packetDispatch.sendModelOnInterface(-1, BALLOON_FLIGHT_INTERFACE_470, bottomChild, 0) } + /** Gets the top and bottom interface children for a balloon grid position. */ + private fun getBalloonChildren(position: BalloonGridPosition): Pair? { + val topPosition = position.copy(row = position.row - 1) + val topChild = BalloonFlightGrid.getGridChild(topPosition) ?: return null + val bottomChild = BalloonFlightGrid.getGridChild(position) ?: return null + return topChild to bottomChild + } + private fun resetModelAngle(player: Player, child: Int) { player.packetDispatch.sendAngleOnInterface(BALLOON_FLIGHT_INTERFACE_470, child, 2576, 0, 0) } diff --git a/Server/src/main/content/global/travel/balloon/OceanCrashSharkPatrol.kt b/Server/src/main/content/global/travel/balloon/OceanCrashSharkPatrol.kt new file mode 100644 index 000000000..0ac049cd3 --- /dev/null +++ b/Server/src/main/content/global/travel/balloon/OceanCrashSharkPatrol.kt @@ -0,0 +1,62 @@ +package content.global.travel.balloon + +import core.api.getAttribute +import core.api.setAttribute +import core.game.node.entity.npc.NPC +import core.game.node.entity.npc.NPCBehavior +import core.game.world.map.Location +import org.rs09.consts.NPCs + +/** + * Makes the sharks in the ocean crash site swim around in a clockwise patrol. + * All three should have their own slightly different route instead of a shared one but honestly I cba. + * @author Edith + */ +class OceanCrashSharkPatrol : NPCBehavior(NPCs.SHARK_5067, NPCs.SHARK_5068, NPCs.SHARK_5069) { + companion object { + private const val ATTRIBUTE_NEXT_WAYPOINT = "balloon_shark_next_waypoint" + + private fun route(vararg coordinates: Pair) = + Array(coordinates.size) { Location.create(coordinates[it].first, coordinates[it].second) } + + private val PATROL_ROUTE = route( + 1799 to 4885, + 1798 to 4886, + 1797 to 4887, + 1797 to 4892, + 1798 to 4893, + 1798 to 4894, + 1799 to 4894, + 1803 to 4894, + 1804 to 4893, + 1805 to 4893, + 1807 to 4893, + 1807 to 4887, + 1806 to 4886, + 1805 to 4885 + ) + } + + override fun onCreation(self: NPC) { + val startingWaypoint = PATROL_ROUTE.indexOf(self.location) + val nextWaypoint = if (startingWaypoint == -1) 0 else (startingWaypoint + 1) % PATROL_ROUTE.size + setAttribute(self, ATTRIBUTE_NEXT_WAYPOINT, nextWaypoint) + } + + override fun tick(self: NPC): Boolean { + if (!self.walkingQueue.hasPath()) { + var nextWaypoint = getAttribute(self, ATTRIBUTE_NEXT_WAYPOINT, 0) + + if (self.location == PATROL_ROUTE[nextWaypoint]) { + nextWaypoint = (nextWaypoint + 1) % PATROL_ROUTE.size + setAttribute(self, ATTRIBUTE_NEXT_WAYPOINT, nextWaypoint) + } + + val destination = PATROL_ROUTE[nextWaypoint] + self.walkingQueue.reset() + self.walkingQueue.addPath(destination.x, destination.y) + } + + return false + } +} \ No newline at end of file