Merge branch 'Enlightened-Journey' into 'master'

Draft: Implemented Enlightened Journey quest

Closes #277

See merge request 2009scape/2009scape!2388
This commit is contained in:
Edie 2026-07-31 17:27:57 +00:00
commit 38f4d53006
26 changed files with 4418 additions and 26 deletions

View file

@ -457,7 +457,7 @@
},
{
"item_id": "1923",
"loc_data": "{1,3208,3214,0,80}-{1,3140,3452,1,90}-"
"loc_data": "{1,3208,3214,0,80}-{1,2816,3351,1,80}-{1,3140,3452,1,90}-"
},
{
"item_id": "1925",

View file

@ -54811,39 +54811,48 @@
},
{
"id": "5049",
"name": "Auguste"
"name": "Auguste",
"examine": "Balloonist extraordinaire."
},
{
"id": "5050",
"name": "Auguste"
"name": "Auguste",
"examine": ""
},
{
"id": "5051",
"name": "Auguste"
"name": "Auguste",
"examine": "Balloonist extraordinaire."
},
{
"id": "5052",
"name": "Auguste"
"name": "Auguste",
"examine": "Balloonist extraordinaire."
},
{
"id": "5053",
"name": "Assistant Serf"
"name": "Assistant Serf",
"examine": "A helpful assistant."
},
{
"id": "5054",
"name": "Assistant Brock"
"name": "Assistant Brock",
"examine": "A helpful assistant."
},
{
"id": "5055",
"name": "Assistant Marrow"
"name": "Assistant Marrow",
"examine": "A helpful assistant."
},
{
"id": "5056",
"name": "Assistant Le Smith"
"name": "Assistant Le Smith",
"examine": "A helpful assistant."
},
{
"id": "5057",
"name": "Assistant Stan"
"name": "Assistant Stan",
"examine": "A helpful assistant."
},
{
"id": "5058",
@ -54863,7 +54872,23 @@
},
{
"id": "5062",
"name": "Shark"
"name": "Auguste/Assistant Stan wrapper"
},
{
"id": "5063",
"name": "Assistant Marrow wrapper"
},
{
"id": "5064",
"name": "Assistant Le Smith wrapper"
},
{
"id": "5065",
"name": "Assistant Brock wrapper"
},
{
"id": "5066",
"name": "Assistant Serf wrapper"
},
{
"id": "5067",
@ -54883,11 +54908,13 @@
},
{
"id": "5068",
"name": "Shark"
"name": "Shark",
"examine": "Time to run away..."
},
{
"id": "5069",
"name": "Shark"
"name": "Shark",
"examine": "Time to run away..."
},
{
"id": "5070",

View file

@ -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",
@ -8611,17 +8611,45 @@
"npc_id": "5049",
"loc_data": "{2808,3355,0,1,6}-"
},
{
"npc_id": "5051",
"loc_data": "{1800,4893,0,0,4}-"
},
{
"npc_id": "5052",
"loc_data": "{1842,4921,0,0,7}-"
},
{
"npc_id": "5062",
"loc_data": "{2927,3410,0,0,0}-"
"loc_data": "{2938,3421,0,1,6}-"
},
{
"npc_id": "5063",
"loc_data": "{2460,3108,0,1,3}-"
"loc_data": "{2462,3109,0,1,6}-"
},
{
"npc_id": "5064",
"loc_data": "{2480,3458,0,1,6}-"
},
{
"npc_id": "5065",
"loc_data": "{2925,3303,0,0,0}-"
"loc_data": "{2925,3303,0,1,0}-"
},
{
"npc_id": "5066",
"loc_data": "{3298,3482,0,1,6}-"
},
{
"npc_id": "5067",
"loc_data": "{1799,4885,0,0,3}-"
},
{
"npc_id": "5068",
"loc_data": "{1805,4885,0,0,3}-"
},
{
"npc_id": "5069",
"loc_data": "{1807,4893,0,0,6}-"
},
{
"npc_id": "5070",

View file

@ -80,7 +80,8 @@ class EmptyOptionListener : InteractionListener {
BURNT_GNOMEBOWL(Items.BURNT_GNOMEBOWL_2175, Items.GNOMEBOWL_MOULD_2166, "You empty the contents of the gnomebowl onto the floor."),
BURNT_EGG(Items.BURNT_EGG_7090, Items.BOWL_1923, "You empty the contents of the bowl onto the floor."),
BURNT_ONION(Items.BURNT_ONION_7092, Items.BOWL_1923, "You empty the contents of the bowl onto the floor."),
BURNT_MUSHROOM(Items.BURNT_MUSHROOM_7094, Items.BOWL_1923, "You empty the contents of the bowl onto the floor.");
BURNT_MUSHROOM(Items.BURNT_MUSHROOM_7094, Items.BOWL_1923, "You empty the contents of the bowl onto the floor."),
SANDBAG(Items.SANDBAG_9943, Items.EMPTY_SACK_5418, "You empty the sandbag.");
companion object {
var emptyItemMap = HashMap<Int, Int?>()

View file

@ -20,7 +20,8 @@ class SandSourceListener : InteractionListener {
private val SAND_PITS = intArrayOf(
Scenery.SAND_PIT_2645,
Scenery.SAND_PIT_4373,
Scenery.SANDPIT_10814
Scenery.SANDPIT_10814,
Scenery.SANDPIT_22726
)
}

View file

@ -27,7 +27,7 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
harvestAmt = when (plantable) {
Plantable.LIMPWURT_SEED, Plantable.WOAD_SEED -> 3
Plantable.MUSHROOM_SPORE -> 6
Plantable.WILLOW_SAPLING -> 0
Plantable.WILLOW_SAPLING, Plantable.AUGUSTES_SAPLING -> 0
else -> 1
}
if(plantable != null
@ -451,7 +451,7 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
// restocking their fruit should take 40 minutes per fruit
minutes = 40
}
else if(plantable == Plantable.WILLOW_SAPLING && isGrown()) {
else if((plantable == Plantable.WILLOW_SAPLING || plantable == Plantable.AUGUSTES_SAPLING) && isGrown()) {
// Willow Branches grow back in only 5 minutes
minutes = 5
}

View file

@ -82,7 +82,8 @@ enum class Plantable(val itemID: Int, val displayName: String, val value: Int, v
CACTUS_SEED(Items.CACTUS_SEED_5280, "cactus seed", 8, 7, 66.5, 25.0, 374.0, 55, PatchType.CACTUS_PATCH, Items.CACTUS_SPINE_6016),
EVIL_TURNIP_SEED(Items.EVIL_TURNIP_SEED_12148, "evil turnip seed", 4, 1, 41.0, 46.0, 0.0, 42, PatchType.EVIL_TURNIP_PATCH, Items.EVIL_TURNIP_12134),
CALQUAT_SAPLING(Items.CALQUAT_SAPLING_5503, "calquat tree sapling", 4, 8, 130.5, 48.5, 12096.0, 72, PatchType.CALQUAT_TREE_PATCH, Items.CALQUAT_FRUIT_5980, Item(Items.POISON_IVY_BERRIES_6018, 8)),
SPIRIT_SAPLING(Items.SPIRIT_SAPLING_5375, "spirit tree sapling", 8, 12, 199.5, 0.0, 19301.0, 83, PatchType.SPIRIT_TREE_PATCH, 0, Item(Items.MONKEY_NUTS_4012, 5), null, Item(Items.MONKEY_BAR_4014, 1), Item(Items.GROUND_TOOTH_9082, 1) )
SPIRIT_SAPLING(Items.SPIRIT_SAPLING_5375, "spirit tree sapling", 8, 12, 199.5, 0.0, 19301.0, 83, PatchType.SPIRIT_TREE_PATCH, 0, Item(Items.MONKEY_NUTS_4012, 5), null, Item(Items.MONKEY_BAR_4014, 1), Item(Items.GROUND_TOOTH_9082, 1)),
AUGUSTES_SAPLING(Items.AUGUSTES_SAPLING_9932, "Auguste's sapling", 15, 6, 25.0, 0.0, 1456.5, 30, PatchType.TREE_PATCH, Items.WILLOW_ROOTS_6045, Item(Items.APPLES5_5386)),
;
constructor(itemID: Int, displayName: String, value: Int, stages: Int, plantingXP: Double, harvestXP: Double, checkHealthXP: Double, requiredLevel: Int, applicablePatch: PatchType, harvestItem: Int, protectionFlower: Plantable)

View file

@ -13,6 +13,7 @@ import core.tools.StringUtils
import core.tools.prependArticle
import org.rs09.consts.Sounds
import content.data.Quests
import content.region.misc.entrana.quest.enlightenedjourney.quest.EnlightenedJourney
class UseWithPatchHandler : InteractionListener {
val RAKE = Items.RAKE_5341
@ -89,7 +90,7 @@ class UseWithPatchHandler : InteractionListener {
return true
}
})
} else if (p.plantable == Plantable.WILLOW_SAPLING && p.harvestAmt > 0) {
} else if ((p.plantable == Plantable.WILLOW_SAPLING || p.plantable == Plantable.AUGUSTES_SAPLING) && p.harvestAmt > 0) {
val pulse = CropHarvester.harvestPulse(player, with, Items.WILLOW_BRANCH_5933) ?: return@onUseWith false
submitIndividualPulse(player, pulse)
}
@ -293,6 +294,10 @@ class UseWithPatchHandler : InteractionListener {
if (plantable == Plantable.JUTE_SEED && patch == FarmingPatch.MCGRUBOR_HOPS && !player.achievementDiaryManager.hasCompletedTask(DiaryType.SEERS_VILLAGE, 0, 7)) {
player.achievementDiaryManager.finishTask(player, DiaryType.SEERS_VILLAGE, 0, 7)
}
if (plantable == Plantable.AUGUSTES_SAPLING) {
setAttribute(player, EnlightenedJourney.ATTRIBUTE_SAPLING_PLANTED, true)
sendMessage(player, "You have now planted Auguste's sapling. He will not give you any more.")
}
p.plant(plantable)
rewardXP(player, Skills.FARMING, plantable.plantingXP)
p.setNewHarvestAmount()
@ -311,6 +316,8 @@ class UseWithPatchHandler : InteractionListener {
val patchName = p.patch.type.displayName()
if (plantable == Plantable.SCARECROW) {
sendMessage(player, "You place the scarecrow in the $patchName.")
} else if (plantable == Plantable.AUGUSTES_SAPLING) {
sendMessage(player, "You plant the special willow sapling in the tree patch.")
} else {
sendMessage(player, "You plant $itemAmount $itemName in the $patchName.")
}

View file

@ -57,7 +57,7 @@ class CropGrowth : PersistTimer (500, "farming:crops", isSoft = true) {
private fun runOfflineCatchupLogic() {
for ((_, patch) in patchMap) {
val type = patch.patch.type
val shouldPlayCatchup = !patch.isGrown() || (type == PatchType.BUSH_PATCH && patch.getFruitOrBerryCount() < 4) || (type == PatchType.FRUIT_TREE_PATCH && patch.getFruitOrBerryCount() < 6) || (patch.plantable == Plantable.WILLOW_SAPLING && patch.harvestAmt < 6)
val shouldPlayCatchup = !patch.isGrown() || (type == PatchType.BUSH_PATCH && patch.getFruitOrBerryCount() < 4) || (type == PatchType.FRUIT_TREE_PATCH && patch.getFruitOrBerryCount() < 6) || ((patch.plantable == Plantable.WILLOW_SAPLING || patch.plantable == Plantable.AUGUSTES_SAPLING) && patch.harvestAmt < 6)
if (shouldPlayCatchup && !patch.isDead && !patch.isChoppedFruitTree()) {
var stagesToSimulate = if (!patch.isGrown()) {
if (patch.isWeedy() || patch.isEmptyAndWeeded()) patch.currentGrowthStage % 4
@ -69,7 +69,7 @@ class CropGrowth : PersistTimer (500, "farming:crops", isSoft = true) {
stagesToSimulate += Math.min(4, 4 - patch.getFruitOrBerryCount())
if (type == PatchType.FRUIT_TREE_PATCH)
stagesToSimulate += Math.min(6, 6 - patch.getFruitOrBerryCount())
if (patch.plantable == Plantable.WILLOW_SAPLING)
if (patch.plantable == Plantable.WILLOW_SAPLING || patch.plantable == Plantable.AUGUSTES_SAPLING)
stagesToSimulate += Math.min(6, 6 - patch.harvestAmt)
}

View file

@ -0,0 +1,67 @@
package content.global.travel.balloon
import core.game.dialogue.DialogueLabeller
import core.game.dialogue.DialogueOption
/**
* Dialogue for the balloon transport assistants.
* @author Edith
*/
class BalloonAssistantsDialogue(private val start: BalloonTravelLocation) : DialogueLabeller() {
override fun addConversation() {
npc("Do you want to use the balloon? Just so you know, some locations require special logs and high Firemaking skills.")
options(
DialogueOption("yes_fly_balloon", "Yes."),
DialogueOption("nowhere", "No."),
DialogueOption("who_are_you", "Who are you?")
)
label("yes_fly_balloon")
exec { player, _ ->
BalloonTravelInterface.open(player, start)
}
label("who_are_you")
exec { player, _ ->
when (start) {
BalloonTravelLocation.TAVERLEY -> loadLabel(player, "who_stan")
BalloonTravelLocation.CRAFTING_GUILD -> loadLabel(player, "who_brock")
BalloonTravelLocation.VARROCK -> loadLabel(player, "who_serf")
BalloonTravelLocation.CASTLE_WARS -> loadLabel(player, "who_marrow")
BalloonTravelLocation.GRAND_TREE -> loadLabel(player, "who_le_smith")
else -> return@exec
}
}
label("who_stan")
npc("I am Stan. Auguste hired me to look after this balloon. I make sure people are prepared to fly.")
goto("ask_fly")
label("who_brock")
npc("I am Assistant Brock. I serve under Auguste as his number two assistant.")
goto("ask_fly")
label("who_serf")
npc("I am a serf. Assistant Serf to you! Auguste freed me and gave me this job.")
goto("ask_fly")
label("who_marrow")
npc("I am Assistant Marrow. I'm working here part time while I study to be a doctor.")
goto("ask_fly")
label("who_le_smith")
npc("I am Assistant Le Smith. I used to work as a glider pilot, but they kicked me off.")
player("Why?")
npc("They said I was too full of hot air.")
goto("ask_fly")
label("ask_fly")
npc("Do you want to use the balloon?")
options(
DialogueOption("yes_fly_balloon", "Yes."),
DialogueOption("nowhere", "No.")
)
}
}

View file

@ -0,0 +1,273 @@
package content.global.travel.balloon
import core.api.MapArea
import core.api.closeInterface
import core.api.closeOverlay
import core.api.closeTabInterface
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.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
import core.game.world.map.zone.ZoneRestriction
import core.game.world.update.flag.context.Animation
import core.net.packet.PacketRepository
import core.net.packet.context.MinimapStateContext
import core.net.packet.out.MinimapState
import org.rs09.consts.Components
/**
* Handles balloon crash site travel and dialogue.
* @author Edith
*/
object BalloonCrashSites {
const val CRASH_SITE_REGION_7244 = 7244
const val SCENERY_OCEAN_PLANK_19126 = 19126
const val SCENERY_WOODLAND_DIRT_19125 = 19125
const val NPC_OCEAN_AUGUSTE_5051 = 5051
const val NPC_WOODLAND_AUGUSTE_5052 = 5052
private const val SOUND_CRASH_3244 = 3244
private const val RENDER_ANIMATION_SWIM_188 = 188
private val OCEAN_CRASH_LANDING = Location.create(1805, 4889, 0)
private val OCEAN_CRASH_FACE_LOCATION = Location.create(1804, 4889, 0)
private val OCEAN_CRASH_EXIT = Location.create(2867, 3368, 0)
private val OCEAN_CRASH_EXIT_FACE_LOCATION = Location.create(2866, 3368, 0)
private val WOODLAND_CRASH_LANDING = Location.create(1844, 4920, 0)
private val WOODLAND_CRASH_FACE_LOCATION = Location.create(1843, 4921, 0)
private val WOODLAND_FALADOR_EXIT = Location.create(3068, 3358, 0)
private val WOODLAND_FALADOR_EXIT_FACE_LOCATION = Location.create(3068, 3357, 0)
private val WOODLAND_ARDOUGNE_EXIT = Location.create(2625, 3243, 0)
private val WOODLAND_ARDOUGNE_EXIT_FACE_LOCATION = Location.create(2625, 3242, 0)
private const val ATTRIBUTE_WOODLAND_EXIT = "/save:enlightenedjourney_woodland_crash_exit"
enum class WoodlandExit {
FALADOR,
ARDOUGNE
}
fun returnToEntrana(player: Player) {
val exit = Location.create(player.location)
fadeTransition(player) {
closeFlightInterface(player)
teleport(player, exit)
sendDialogue(player, "You bail, but land safely on Entrana.")
}
}
fun crashAtOceanSite(player: Player) {
playAudio(player, SOUND_CRASH_3244)
fadeTransition(player) {
closeFlightInterface(player)
teleport(player, OCEAN_CRASH_LANDING)
oceanSwimmingAnimation(player)
face(player, OCEAN_CRASH_FACE_LOCATION)
sendDialogue(player, "The balloon crashes.")
}
}
fun leaveOceanCrashSite(player: Player) {
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) {
playAudio(player, SOUND_CRASH_3244)
setAttribute(player, ATTRIBUTE_WOODLAND_EXIT, exit.name)
fadeTransition(player) {
closeFlightInterface(player)
teleport(player, WOODLAND_CRASH_LANDING)
face(player, WOODLAND_CRASH_FACE_LOCATION)
sendDialogue(player, "The balloon crashes.")
}
}
fun leaveWoodlandCrashSite(player: Player) {
val exit = getWoodlandExit(player)
fadeTransition(player, fadeToBlack = true) {
when (exit) {
WoodlandExit.FALADOR -> {
teleport(player, WOODLAND_FALADOR_EXIT)
face(player, WOODLAND_FALADOR_EXIT_FACE_LOCATION)
sendPlayerDialogue(player, "The crash must have really sent us off course; I'm near Falador!")
}
WoodlandExit.ARDOUGNE -> {
teleport(player, WOODLAND_ARDOUGNE_EXIT)
face(player, WOODLAND_ARDOUGNE_EXIT_FACE_LOCATION)
// TODO: Placeholder message, find 09 source.
sendPlayerDialogue(player, "The crash must have really sent us off course; I'm near Ardougne!")
}
}
removeAttribute(player, ATTRIBUTE_WOODLAND_EXIT)
}
}
private fun getWoodlandExit(player: Player): WoodlandExit {
return when (getAttribute(player, ATTRIBUTE_WOODLAND_EXIT, WoodlandExit.FALADOR.name)) {
WoodlandExit.ARDOUGNE.name -> WoodlandExit.ARDOUGNE
else -> WoodlandExit.FALADOR
}
}
private fun closeFlightInterface(player: Player) {
closeInterface(player)
closeTabInterface(player)
player.interfaceManager.restoreTabs()
PacketRepository.send(MinimapState::class.java, MinimapStateContext(player, 0))
}
private fun fadeTransition(player: Player, fadeToBlack: Boolean = false, whileBlack: () -> Unit) {
if (fadeToBlack) {
player.lock()
openOverlay(player, Components.FADE_TO_BLACK_120)
queueScript(player, 4, QueueStrength.SOFT) { stage ->
when (stage) {
0 -> {
whileBlack()
closeOverlay(player)
openOverlay(player, Components.FADE_FROM_BLACK_170)
return@queueScript delayScript(player, 3)
}
else -> {
closeOverlay(player)
unlock(player)
return@queueScript stopExecuting(player)
}
}
}
return
}
whileBlack()
closeOverlay(player)
openOverlay(player, Components.FADE_FROM_BLACK_170)
queueScript(player, 3, QueueStrength.SOFT) {
closeOverlay(player)
unlock(player)
return@queueScript stopExecuting(player)
}
}
}
class BalloonCrashSiteListener : InteractionListener, MapArea {
override fun defineListeners() {
on(BalloonCrashSites.SCENERY_OCEAN_PLANK_19126, IntType.SCENERY, "use") { 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, _ ->
openDialogue(player, BalloonCrashExitDialogue("You walk away from the crash site.") {
BalloonCrashSites.leaveWoodlandCrashSite(it)
})
return@on true
}
on(BalloonCrashSites.NPC_OCEAN_AUGUSTE_5051, IntType.NPC, "talk-to") { player, node ->
DialogueLabeller.open(player, BalloonCrashDialogue(), node.asNpc())
return@on true
}
on(BalloonCrashSites.NPC_WOODLAND_AUGUSTE_5052, IntType.NPC, "talk-to") { player, node ->
DialogueLabeller.open(player, BalloonCrashDialogue(), node.asNpc())
return@on true
}
}
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<ZoneBorders> {
return arrayOf(getRegionBorders(BalloonCrashSites.CRASH_SITE_REGION_7244))
}
override fun getRestrictions(): Array<ZoneRestriction> {
return arrayOf(ZoneRestriction.TELEPORT)
}
}
/** 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)
exec { player, npc ->
when (npc.id) {
BalloonCrashSites.NPC_OCEAN_AUGUSTE_5051 -> loadLabel(player, "ocean_crash")
BalloonCrashSites.NPC_WOODLAND_AUGUSTE_5052 -> loadLabel(player, "woodland_crash")
}
}
label("ocean_crash")
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(FacialExpression.ANGRY, "Well, that didn't go as planned.")
player("So what now? How will we get the balloon back?")
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.")
}
}

View file

@ -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
}
}

View file

@ -0,0 +1,528 @@
package content.global.travel.balloon
import content.data.Quests
import content.region.misc.entrana.quest.enlightenedjourney.quest.EnlightenedJourney
import core.api.closeInterface
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
import core.api.playAudio
import core.api.queueScript
import core.api.removeAttributes
import core.api.sendMessage
import core.api.setAttribute
import core.api.setQuestStage
import core.api.setVarbit
import core.api.stopExecuting
import core.api.teleport
import core.api.unlock
import core.game.interaction.InterfaceListener
import core.game.interaction.QueueStrength
import core.game.node.entity.player.Player
import core.game.world.map.Location
import core.net.packet.PacketRepository
import core.net.packet.context.MinimapStateContext
import core.net.packet.out.MinimapState
import core.plugin.Initializable
import org.rs09.consts.Components
/**
* Handles the balloon flight minigame interface.
* @author Edith
*/
/** Balloon movement when the player presses one of the balloon controls. */
private data class BalloonMove(val east: Int, val vertical: Int)
/** Crash destinations for when the player bails or enters an incorrect route input. */
private enum class BalloonCrashLocation {
ENTRANA,
OCEAN,
WOODLAND_FALADOR,
WOODLAND_ARDOUGNE
}
@Initializable
class BalloonFlightInterface : InterfaceListener {
companion object {
private const val BALLOON_FLIGHT_INTERFACE_470 = 470
private const val BALLOON_CONTROL_INTERFACE_471 = 471
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_RED_ROPE_3239 = 3239
private const val SOUND_WIND_3240 = 3240
private const val SOUND_ROPE_3242 = 3242
private const val SOUND_BALLOON_MOVE_3246 = 3246
private const val SOUND_RELAX_3247 = 3247
private const val SOUND_SANDBAG_3249 = 3249
private const val SOUND_BURN_LOG_3251 = 3251
private const val MUSIC_FLOATING_FREE_206 = 206
private val controlMoves = mapOf(
BalloonFlightRoutes.BUTTON_SANDBAG to BalloonMove(1, -2),
BalloonFlightRoutes.BUTTON_BURN_LOG to BalloonMove(1, -1),
BalloonFlightRoutes.BUTTON_RELAX to BalloonMove(1, 0),
BalloonFlightRoutes.BUTTON_NORMAL_ROPE to BalloonMove(1, 1),
BalloonFlightRoutes.BUTTON_RED_ROPE to BalloonMove(1, 2)
)
/** Quest flight from Entrana to Taverley. */
fun startQuestFlight(player: Player) {
val startPosition = BalloonFlightRoutes.getStartPosition(BalloonTravelLocation.TAVERLEY)
setAttribute(player, ATTRIBUTE_FLIGHT_DESTINATION, BalloonTravelLocation.TAVERLEY)
setAttribute(player, ATTRIBUTE_FLIGHT_PROGRESS, 0)
setAttribute(player, ATTRIBUTE_FLIGHT_SCREEN, 1)
setBalloonPosition(player, startPosition)
updateFlightSupplies(player)
closeInterface(player)
hideGameTabs(player)
hideMinimap(player)
player.musicPlayer.unlock(MUSIC_FLOATING_FREE_206)
openInterface(player, BALLOON_FLIGHT_INTERFACE_470)
}
/** Post-quest flight from Entrana to a new unlockable destination. */
fun startUnlockFlight(player: Player, destination: BalloonTravelLocation) {
val startPosition = BalloonFlightRoutes.getStartPosition(destination)
setAttribute(player, ATTRIBUTE_FLIGHT_DESTINATION, destination)
setAttribute(player, ATTRIBUTE_FLIGHT_PROGRESS, 0)
setAttribute(player, ATTRIBUTE_FLIGHT_SCREEN, 1)
setBalloonPosition(player, startPosition)
updateFlightSupplies(player)
closeInterface(player)
hideGameTabs(player)
hideMinimap(player)
player.musicPlayer.unlock(MUSIC_FLOATING_FREE_206)
openInterface(player, BALLOON_FLIGHT_INTERFACE_470)
}
private fun getFlightDestination(player: Player): BalloonTravelLocation {
return getAttribute(player, ATTRIBUTE_FLIGHT_DESTINATION, BalloonTravelLocation.TAVERLEY)
}
private fun getScreenPosition(destination: BalloonTravelLocation, screen: Int): BalloonGridPosition {
return when (destination) {
BalloonTravelLocation.TAVERLEY -> when (screen) {
1 -> BalloonFlightRoutes.TAVERLEY_START
2 -> BalloonFlightRoutes.TAVERLEY_SECOND
3 -> BalloonFlightRoutes.TAVERLEY_THIRD
else -> BalloonFlightRoutes.TAVERLEY_START
}
BalloonTravelLocation.CRAFTING_GUILD -> when (screen) {
1 -> BalloonFlightRoutes.CRAFTING_GUILD_START
2 -> BalloonFlightRoutes.CRAFTING_GUILD_SECOND
3 -> BalloonFlightRoutes.CRAFTING_GUILD_THIRD
else -> BalloonFlightRoutes.CRAFTING_GUILD_START
}
BalloonTravelLocation.VARROCK -> when (screen) {
1 -> BalloonFlightRoutes.VARROCK_START
2 -> BalloonFlightRoutes.VARROCK_SECOND
3 -> BalloonFlightRoutes.VARROCK_THIRD
else -> BalloonFlightRoutes.VARROCK_START
}
BalloonTravelLocation.CASTLE_WARS -> when (screen) {
1 -> BalloonFlightRoutes.CASTLE_WARS_START
2 -> BalloonFlightRoutes.CASTLE_WARS_SECOND
3 -> BalloonFlightRoutes.CASTLE_WARS_THIRD
else -> BalloonFlightRoutes.CASTLE_WARS_START
}
BalloonTravelLocation.GRAND_TREE -> when (screen) {
1 -> BalloonFlightRoutes.GRAND_TREE_START
2 -> BalloonFlightRoutes.GRAND_TREE_SECOND
3 -> BalloonFlightRoutes.GRAND_TREE_THIRD
else -> BalloonFlightRoutes.GRAND_TREE_START
}
else -> BalloonFlightRoutes.TAVERLEY_START
}
}
private fun handleBalloonCrash(player: Player, location: BalloonCrashLocation) {
when (location) {
BalloonCrashLocation.ENTRANA -> BalloonCrashSites.returnToEntrana(player)
BalloonCrashLocation.OCEAN -> BalloonCrashSites.crashAtOceanSite(player)
BalloonCrashLocation.WOODLAND_FALADOR -> BalloonCrashSites.crashAtWoodlandSite(
player,
BalloonCrashSites.WoodlandExit.FALADOR
)
BalloonCrashLocation.WOODLAND_ARDOUGNE -> BalloonCrashSites.crashAtWoodlandSite(
player,
BalloonCrashSites.WoodlandExit.ARDOUGNE
)
}
}
private fun setBalloonPosition(player: Player, position: BalloonGridPosition) {
setAttribute(player, ATTRIBUTE_BALLOON_ROW, position.row)
setAttribute(player, ATTRIBUTE_BALLOON_COLUMN, position.column)
}
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)
)
}
/** 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 handleControlButton(player: Player, buttonId: Int) {
val destination = getFlightDestination(player)
val screen = getAttribute(player, ATTRIBUTE_FLIGHT_SCREEN, 1)
if (buttonId == BalloonFlightRoutes.BUTTON_BAIL) {
val position = getBalloonPosition(player, destination, screen)
handleBalloonCrash(player, getCrashLocation(destination, screen, position))
return
}
val move = controlMoves[buttonId] ?: return
val routeLength = BalloonFlightRoutes.getSequence(destination, screen).size
val progress = getAttribute(player, ATTRIBUTE_FLIGHT_PROGRESS, 0)
if (!updateFlightSupplies(player, buttonId)) {
return
}
playControlSounds(player, buttonId)
if (!moveBalloon(player, move)) {
return
}
if (progress + 1 >= routeLength) {
advanceFlightScreen(player, destination, screen)
} else {
setAttribute(player, ATTRIBUTE_FLIGHT_PROGRESS, progress + 1)
}
}
private fun advanceFlightScreen(player: Player, destination: BalloonTravelLocation, screen: Int) {
when (destination) {
BalloonTravelLocation.TAVERLEY -> advanceTaverleyScreen(player, screen)
BalloonTravelLocation.CRAFTING_GUILD -> advanceCraftingGuildScreen(player, screen)
BalloonTravelLocation.VARROCK -> advanceVarrockScreen(player, screen)
BalloonTravelLocation.CASTLE_WARS -> advanceCastleWarsScreen(player, screen)
BalloonTravelLocation.GRAND_TREE -> advanceGrandTreeScreen(player, screen)
else -> {}
}
}
private fun drawFlightScreen(player: Player, destination: BalloonTravelLocation, screen: Int) {
BalloonFlightScreens.draw(player, destination, screen)
}
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,
BalloonTravelLocation.CRAFTING_GUILD,
BalloonTravelLocation.VARROCK -> BalloonCrashLocation.WOODLAND_FALADOR
BalloonTravelLocation.CASTLE_WARS,
BalloonTravelLocation.GRAND_TREE -> BalloonCrashLocation.WOODLAND_ARDOUGNE
else -> BalloonCrashLocation.ENTRANA
}
}
private fun advanceTaverleyScreen(player: Player, screen: Int) {
when (screen) {
1 -> startTaverleyScreen(player, 2, BalloonFlightRoutes.TAVERLEY_SECOND)
2 -> startTaverleyScreen(player, 3, BalloonFlightRoutes.TAVERLEY_THIRD)
3 -> landInTaverley(player)
}
}
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)
BalloonFlightScreens.drawBalloon(player, position)
}
private fun landInTaverley(player: Player) {
fadeTransition(player) {
clearFlightAttributes(player)
closeInterface(player)
closeTabInterface(player)
showGameTabs(player)
showMinimap(player)
teleport(player, BalloonFlightRoutes.TAVERLEY_LANDING)
face(player, Location.create(2941, 3421, 0))
sendMessage(player, "You arrive safely in Taverley.")
setVarbit(player, EnlightenedJourney.VARBIT_TAVERLEY_BALLOON_2868, EnlightenedJourney.TAVERLEY_BALLOON_AUGUSTE_1, true)
setQuestStage(player, Quests.ENLIGHTENED_JOURNEY, EnlightenedJourney.STAGE_CLAIM_REWARD)
}
}
private fun advanceCraftingGuildScreen(player: Player, screen: Int) {
when (screen) {
1 -> startCraftingGuildScreen(player, 2, BalloonFlightRoutes.CRAFTING_GUILD_SECOND)
2 -> startCraftingGuildScreen(player, 3, BalloonFlightRoutes.CRAFTING_GUILD_THIRD)
3 -> landAtCraftingGuild(player)
}
}
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)
BalloonFlightScreens.drawBalloon(player, position)
}
private fun landAtCraftingGuild(player: Player) {
fadeTransition(player) {
clearFlightAttributes(player)
closeInterface(player)
closeTabInterface(player)
showGameTabs(player)
showMinimap(player)
teleport(player, BalloonFlightRoutes.CRAFTING_GUILD_LANDING)
BalloonTravelInterface.unlockDestination(player, BalloonTravelLocation.CRAFTING_GUILD)
}
}
private fun advanceVarrockScreen(player: Player, screen: Int) {
when (screen) {
1 -> startVarrockScreen(player, 2, BalloonFlightRoutes.VARROCK_SECOND)
2 -> startVarrockScreen(player, 3, BalloonFlightRoutes.VARROCK_THIRD)
3 -> landAtVarrock(player)
}
}
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)
BalloonFlightScreens.drawBalloon(player, position)
}
private fun landAtVarrock(player: Player) {
fadeTransition(player) {
clearFlightAttributes(player)
closeInterface(player)
closeTabInterface(player)
showGameTabs(player)
showMinimap(player)
teleport(player, BalloonFlightRoutes.VARROCK_LANDING)
BalloonTravelInterface.unlockDestination(player, BalloonTravelLocation.VARROCK)
}
}
private fun advanceCastleWarsScreen(player: Player, screen: Int) {
when (screen) {
1 -> startCastleWarsScreen(player, 2, BalloonFlightRoutes.CASTLE_WARS_SECOND)
2 -> startCastleWarsScreen(player, 3, BalloonFlightRoutes.CASTLE_WARS_THIRD)
3 -> landAtCastleWars(player)
}
}
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)
BalloonFlightScreens.drawBalloon(player, position)
}
private fun landAtCastleWars(player: Player) {
fadeTransition(player) {
clearFlightAttributes(player)
closeInterface(player)
closeTabInterface(player)
showGameTabs(player)
showMinimap(player)
teleport(player, BalloonFlightRoutes.CASTLE_WARS_LANDING)
BalloonTravelInterface.unlockDestination(player, BalloonTravelLocation.CASTLE_WARS)
}
}
private fun advanceGrandTreeScreen(player: Player, screen: Int) {
when (screen) {
1 -> startGrandTreeScreen(player, 2, BalloonFlightRoutes.GRAND_TREE_SECOND)
2 -> startGrandTreeScreen(player, 3, BalloonFlightRoutes.GRAND_TREE_THIRD)
3 -> landAtGrandTree(player)
}
}
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)
BalloonFlightScreens.drawBalloon(player, position)
}
private fun landAtGrandTree(player: Player) {
fadeTransition(player) {
clearFlightAttributes(player)
closeInterface(player)
closeTabInterface(player)
showGameTabs(player)
showMinimap(player)
teleport(player, BalloonFlightRoutes.GRAND_TREE_LANDING)
BalloonTravelInterface.unlockDestination(player, BalloonTravelLocation.GRAND_TREE)
}
}
private fun clearFlightAttributes(player: Player) {
removeAttributes(
player, ATTRIBUTE_BALLOON_ROW,
ATTRIBUTE_BALLOON_COLUMN,
ATTRIBUTE_FLIGHT_DESTINATION,
ATTRIBUTE_FLIGHT_SCREEN,
ATTRIBUTE_FLIGHT_PROGRESS
)
}
private fun playControlSounds(player: Player, buttonId: Int) {
val sounds = when (buttonId) {
BalloonFlightRoutes.BUTTON_SANDBAG -> intArrayOf(SOUND_SANDBAG_3249)
BalloonFlightRoutes.BUTTON_BURN_LOG -> intArrayOf(SOUND_BURN_LOG_3251)
BalloonFlightRoutes.BUTTON_RELAX -> intArrayOf(SOUND_BALLOON_MOVE_3246, SOUND_RELAX_3247)
BalloonFlightRoutes.BUTTON_NORMAL_ROPE -> intArrayOf(SOUND_ROPE_3242, SOUND_WIND_3240)
BalloonFlightRoutes.BUTTON_RED_ROPE -> intArrayOf(SOUND_RED_ROPE_3239)
else -> return
}
sounds.forEach { playAudio(player, it) }
}
private fun hideMinimap(player: Player) {
PacketRepository.send(MinimapState::class.java, MinimapStateContext(player, 2))
}
private fun showMinimap(player: Player) {
PacketRepository.send(MinimapState::class.java, MinimapStateContext(player, 0))
}
private fun hideGameTabs(player: Player) {
player.interfaceManager.removeTabs(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)
}
private fun showGameTabs(player: Player) {
player.interfaceManager.restoreTabs()
}
private fun fadeTransition(player: Player, whileBlack: () -> Unit) {
whileBlack()
closeOverlay(player)
openOverlay(player, Components.FADE_FROM_BLACK_170)
queueScript(player, 3, QueueStrength.SOFT) {
closeOverlay(player)
unlock(player)
return@queueScript stopExecuting(player)
}
}
}
override fun defineInterfaceListeners() {
onOpen(BALLOON_FLIGHT_INTERFACE_470) { player, _ ->
val destination = getFlightDestination(player)
val screen = getAttribute(player, ATTRIBUTE_FLIGHT_SCREEN, 1)
val startPosition = getScreenPosition(destination, screen)
player.lock()
openSingleTab(player, BALLOON_CONTROL_INTERFACE_471)
BalloonFlightScreens.clear(player)
drawFlightScreen(player, destination, screen)
BalloonFlightScreens.drawBalloon(player, startPosition)
openOverlay(player, 333)
true
}
onClose(BALLOON_FLIGHT_INTERFACE_470) { player, _ ->
BalloonFlightScreens.clear(player)
clearFlightAttributes(player)
closeTabInterface(player)
showGameTabs(player)
showMinimap(player)
true
}
on(BALLOON_CONTROL_INTERFACE_471) { player, _, _, buttonId, _, _ ->
handleControlButton(player, buttonId)
true
}
}
}

View file

@ -0,0 +1,220 @@
package content.global.travel.balloon
import core.game.world.map.Location
/**
* Flight route values used by the balloon flight minigame.
* @author Edith
*/
object BalloonFlightRoutes {
const val BUTTON_SANDBAG = 4
const val BUTTON_RELAX = 5
const val BUTTON_NORMAL_ROPE = 6
const val BUTTON_BAIL = 8
const val BUTTON_BURN_LOG = 9
const val BUTTON_RED_ROPE = 10
/** 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 = BalloonGridPosition(5, 0)
val CRAFTING_GUILD_SECOND = BalloonGridPosition(6, 1)
val CRAFTING_GUILD_THIRD = BalloonGridPosition(6, 1)
val VARROCK_START = BalloonGridPosition(7, 0)
val VARROCK_SECOND = BalloonGridPosition(5, 1)
val VARROCK_THIRD = BalloonGridPosition(5, 1)
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 = 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)
val CRAFTING_GUILD_LANDING = Location.create(2924, 3303, 0)
val VARROCK_LANDING = Location.create(3298, 3481, 0)
val CASTLE_WARS_LANDING = Location.create(2462, 3108, 0)
val GRAND_TREE_LANDING = Location.create(2480, 3458, 0)
private val taverleyRouteFirstScreen = listOf(
BUTTON_SANDBAG, BUTTON_BURN_LOG, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_RED_ROPE, BUTTON_RELAX, BUTTON_RELAX, BUTTON_NORMAL_ROPE,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX
)
private val taverleyRouteSecondScreen = listOf(
BUTTON_BURN_LOG, BUTTON_RELAX, BUTTON_BURN_LOG, BUTTON_RELAX, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_BURN_LOG, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX
)
private val taverleyRouteThirdScreen = listOf(
BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_RED_ROPE, BUTTON_NORMAL_ROPE, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_BURN_LOG, BUTTON_RELAX, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_NORMAL_ROPE
)
private val craftingGuildRouteFirstScreen = listOf(
BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_BURN_LOG, BUTTON_BURN_LOG,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_RED_ROPE, BUTTON_RELAX, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_BURN_LOG, BUTTON_RED_ROPE, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_NORMAL_ROPE, BUTTON_RELAX, BUTTON_BURN_LOG, BUTTON_RELAX
)
private val craftingGuildRouteSecondScreen = listOf(
BUTTON_RELAX, BUTTON_NORMAL_ROPE, BUTTON_RELAX, BUTTON_BURN_LOG, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_NORMAL_ROPE, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_BURN_LOG, BUTTON_RELAX, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX
)
private val craftingGuildRouteThirdScreen = listOf(
BUTTON_RELAX, BUTTON_RELAX, BUTTON_SANDBAG, BUTTON_RELAX, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RED_ROPE,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_NORMAL_ROPE, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_NORMAL_ROPE
)
private val varrockRouteFirstScreen = listOf(
BUTTON_BURN_LOG, BUTTON_BURN_LOG, BUTTON_BURN_LOG, BUTTON_BURN_LOG, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_NORMAL_ROPE, BUTTON_NORMAL_ROPE, BUTTON_NORMAL_ROPE, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_NORMAL_ROPE, BUTTON_BURN_LOG, BUTTON_RELAX, BUTTON_RELAX,
BUTTON_NORMAL_ROPE, BUTTON_BURN_LOG, BUTTON_BURN_LOG, BUTTON_RELAX, BUTTON_RELAX
)
private val varrockRouteSecondScreen = listOf(
BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_BURN_LOG, BUTTON_BURN_LOG, BUTTON_NORMAL_ROPE, BUTTON_NORMAL_ROPE,
BUTTON_NORMAL_ROPE, BUTTON_NORMAL_ROPE, BUTTON_RELAX, BUTTON_RELAX, BUTTON_SANDBAG,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX
)
private val varrockRouteThirdScreen = listOf(
BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_SANDBAG, BUTTON_RELAX,
BUTTON_NORMAL_ROPE, BUTTON_NORMAL_ROPE, BUTTON_SANDBAG, BUTTON_RELAX, BUTTON_NORMAL_ROPE,
BUTTON_NORMAL_ROPE, BUTTON_SANDBAG, BUTTON_RELAX, BUTTON_RED_ROPE, BUTTON_RELAX,
BUTTON_RED_ROPE, BUTTON_RELAX, BUTTON_NORMAL_ROPE
)
private val castleWarsRouteFirstScreen = listOf(
BUTTON_SANDBAG, BUTTON_SANDBAG, BUTTON_BURN_LOG, BUTTON_RELAX, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RED_ROPE, BUTTON_BURN_LOG,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_RED_ROPE, BUTTON_RELAX, BUTTON_RELAX,
BUTTON_BURN_LOG, BUTTON_BURN_LOG, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX
)
private val castleWarsRouteSecondScreen = listOf(
BUTTON_BURN_LOG, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RED_ROPE, BUTTON_NORMAL_ROPE,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_SANDBAG, BUTTON_RELAX, BUTTON_NORMAL_ROPE,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_NORMAL_ROPE, BUTTON_RELAX
)
private val castleWarsRouteThirdScreen = listOf(
BUTTON_BURN_LOG, BUTTON_RELAX, BUTTON_RELAX, BUTTON_NORMAL_ROPE, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_BURN_LOG, BUTTON_RED_ROPE, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_SANDBAG, BUTTON_RED_ROPE,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_NORMAL_ROPE
)
private val grandTreeRouteFirstScreen = listOf(
BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_NORMAL_ROPE, BUTTON_RELAX, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX
)
private val grandTreeRouteSecondScreen = listOf(
BUTTON_BURN_LOG, BUTTON_NORMAL_ROPE, BUTTON_BURN_LOG, BUTTON_NORMAL_ROPE, BUTTON_BURN_LOG,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_BURN_LOG, BUTTON_NORMAL_ROPE, BUTTON_RELAX,
BUTTON_SANDBAG, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RED_ROPE, BUTTON_RELAX,
BUTTON_NORMAL_ROPE, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX
)
private val grandTreeRouteThirdScreen = listOf(
BUTTON_BURN_LOG, BUTTON_BURN_LOG, BUTTON_BURN_LOG, BUTTON_RELAX, BUTTON_RELAX,
BUTTON_NORMAL_ROPE, BUTTON_BURN_LOG, BUTTON_RELAX, BUTTON_RELAX, BUTTON_RELAX,
BUTTON_RELAX, BUTTON_RELAX, BUTTON_RED_ROPE, BUTTON_RELAX, BUTTON_RED_ROPE,
BUTTON_NORMAL_ROPE, BUTTON_RELAX, BUTTON_RELAX, BUTTON_NORMAL_ROPE
)
/** Gets the matching button sequence for the selected route and current flight screen. */
fun getSequence(destination: BalloonTravelLocation, screen: Int): List<Int> {
return when (destination) {
BalloonTravelLocation.TAVERLEY -> getTaverleySequence(screen)
BalloonTravelLocation.CRAFTING_GUILD -> getCraftingGuildSequence(screen)
BalloonTravelLocation.VARROCK -> getVarrockSequence(screen)
BalloonTravelLocation.CASTLE_WARS -> getCastleWarsSequence(screen)
BalloonTravelLocation.GRAND_TREE -> getGrandTreeSequence(screen)
else -> emptyList()
}
}
/** 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
BalloonTravelLocation.VARROCK -> VARROCK_START
BalloonTravelLocation.CASTLE_WARS -> CASTLE_WARS_START
BalloonTravelLocation.GRAND_TREE -> GRAND_TREE_START
else -> TAVERLEY_START
}
}
private fun getTaverleySequence(screen: Int): List<Int> {
return when (screen) {
1 -> taverleyRouteFirstScreen
2 -> taverleyRouteSecondScreen
3 -> taverleyRouteThirdScreen
else -> emptyList()
}
}
private fun getCraftingGuildSequence(screen: Int): List<Int> {
return when (screen) {
1 -> craftingGuildRouteFirstScreen
2 -> craftingGuildRouteSecondScreen
3 -> craftingGuildRouteThirdScreen
else -> emptyList()
}
}
private fun getVarrockSequence(screen: Int): List<Int> {
return when (screen) {
1 -> varrockRouteFirstScreen
2 -> varrockRouteSecondScreen
3 -> varrockRouteThirdScreen
else -> emptyList()
}
}
private fun getCastleWarsSequence(screen: Int): List<Int> {
return when (screen) {
1 -> castleWarsRouteFirstScreen
2 -> castleWarsRouteSecondScreen
3 -> castleWarsRouteThirdScreen
else -> emptyList()
}
}
private fun getGrandTreeSequence(screen: Int): List<Int> {
return when (screen) {
1 -> grandTreeRouteFirstScreen
2 -> grandTreeRouteSecondScreen
3 -> grandTreeRouteThirdScreen
else -> emptyList()
}
}
}

View file

@ -0,0 +1,824 @@
package content.global.travel.balloon
import core.api.sendAnimationOnInterface
import core.game.node.entity.player.Player
/**
* Draws the scenery and balloon models for the balloon flight minigame interface.
* @author Edith
*/
object BalloonFlightScreens {
private const val BALLOON_FLIGHT_INTERFACE_470 = 470
private const val MODEL_WHITE_BIRD_19779 = 19779
private const val MODEL_BROWN_BIRD_19780 = 19780
private const val ANIMATION_BIRD_341 = 341
private const val MODEL_CLOUD_ANOMALY_19781 = 19781
private const val ANIMATION_CLOUD_ANOMALY_5141 = 5141
private const val MODEL_BALLOON_TOP_19517 = 19517
private const val MODEL_BALLOON_BOTTOM_19518 = 19518
/** Clears all model child slots used by the flight interface. */
fun clear(player: Player) {
for (child in 78..237) {
player.packetDispatch.sendModelOnInterface(-1, BALLOON_FLIGHT_INTERFACE_470, child, 0)
}
}
/** Draws the scenery for the selected route and screen. */
fun draw(player: Player, destination: BalloonTravelLocation, screen: Int) {
when (destination) {
BalloonTravelLocation.TAVERLEY -> drawTaverleyScreen(player, screen)
BalloonTravelLocation.CRAFTING_GUILD -> drawCraftingGuildScreen(player, screen)
BalloonTravelLocation.VARROCK -> drawVarrockScreen(player, screen)
BalloonTravelLocation.CASTLE_WARS -> drawCastleWarsScreen(player, screen)
BalloonTravelLocation.GRAND_TREE -> drawGrandTreeScreen(player, screen)
else -> {}
}
}
/** 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 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<Int, Int>? {
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)
}
private fun drawModels(player: Player, model: Int, vararg children: Int) {
for (child in children) {
player.packetDispatch.sendModelOnInterface(model, BALLOON_FLIGHT_INTERFACE_470, child, 2576)
resetModelAngle(player, child)
}
}
private fun drawAnimatedModels(
player: Player,
model: Int,
animation: Int,
zoom: Int,
pitch: Int,
yaw: Int,
vararg children: Int
) {
for (child in children) {
player.packetDispatch.sendModelOnInterface(model, BALLOON_FLIGHT_INTERFACE_470, child, zoom)
player.packetDispatch.sendAngleOnInterface(BALLOON_FLIGHT_INTERFACE_470, child, zoom, pitch, yaw)
sendAnimationOnInterface(player, animation, BALLOON_FLIGHT_INTERFACE_470, child)
}
}
private fun drawTaverleyScreen(player: Player, screen: Int) {
when (screen) {
1 -> drawTaverleyScreenOne(player)
2 -> drawTaverleyScreenTwo(player)
3 -> drawTaverleyScreenThree(player)
}
}
private fun drawTaverleyScreenOne(player: Player) {
// Ground
drawModels(player, 19558, 40)
drawModels(player, 19559, 45)
drawModels(player, 19560, 50)
drawModels(player, 19561, 55)
// Trees
drawModels(player, 19580, 123)
drawModels(player, 19578, 124)
drawModels(player, 19582, 125)
drawModels(player, 19579, 103)
drawModels(player, 19576, 104)
drawModels(player, 19581, 105)
drawModels(player, 19575, 83)
drawModels(player, 19574, 84)
drawModels(player, 19577, 85)
drawModels(player, 19521, 102, 107, 106, 109)
drawModels(player, 19519, 82, 87, 86, 89)
drawModels(player, 19522, 91, 92)
drawModels(player, 19570, 127, 128)
drawModels(player, 19569, 107, 108)
drawModels(player, 19568, 87, 88)
// Clouds
drawModels(player, 19525, 155, 210, 227, 172)
drawModels(player, 19524, 211, 212, 228, 173)
drawModels(player, 19526, 156, 213, 229, 174)
// Launch base
drawModels(player, 19572, 78)
// Birds
drawAnimatedModels(player, MODEL_BROWN_BIRD_19780, ANIMATION_BIRD_341, 2567, 100, 300, 145, 148, 177, 209)
// Cloud anomaly
drawAnimatedModels(player, MODEL_CLOUD_ANOMALY_19781, ANIMATION_CLOUD_ANOMALY_5141, 2567, 0, 300, 175, 230)
}
private fun drawTaverleyScreenTwo(player: Player) {
// Ground
drawModels(player, 19562, 40)
drawModels(player, 19563, 45)
drawModels(player, 19564, 50)
drawModels(player, 19565, 55)
// Clouds
drawModels(player, 19526, 158)
// Palm tree
drawModels(player, 19551, 83)
drawModels(player, 19553, 84)
drawModels(player, 19552, 103)
drawModels(player, 19527, 123)
// Rock
drawModels(player, 19616, 90)
// Trees
drawModels(player, 19530, 111, 112)
drawModels(player, 19523, 91, 92)
drawModels(player, 19570, 133)
drawModels(player, 19569, 113)
drawModels(player, 19568, 93)
// Building
drawModels(player, 19534, 154)
drawModels(player, 19533, 134)
drawModels(player, 19532, 114)
drawModels(player, 19531, 94)
drawModels(player, 19544, 155)
drawModels(player, 19543, 135)
drawModels(player, 19542, 115)
drawModels(player, 19541, 95)
drawModels(player, 19545, 156)
drawModels(player, 19546, 136)
drawModels(player, 19540, 116)
drawModels(player, 19539, 96)
drawModels(player, 19550, 157)
drawModels(player, 19549, 137)
drawModels(player, 19548, 117)
drawModels(player, 19547, 97)
// Clouds
drawModels(player, 19525, 180, 201, 186)
drawModels(player, 19524, 202, 187)
drawModels(player, 19526, 181, 203, 188)
// Birds
drawAnimatedModels(player, MODEL_WHITE_BIRD_19779, ANIMATION_BIRD_341, 2567, 100, 300, 161, 179, 185, 204, 219)
}
private fun drawTaverleyScreenThree(player: Player) {
// Ground
drawModels(player, 19566, 40)
drawModels(player, 19554, 45)
drawModels(player, 19555, 50)
drawModels(player, 19556, 55)
// Building
drawModels(player, 19538, 138)
drawModels(player, 19537, 118)
drawModels(player, 19536, 99)
drawModels(player, 19535, 78)
// Trees
drawModels(player, 19580, 119)
drawModels(player, 19578, 120)
drawModels(player, 19582, 121)
drawModels(player, 19579, 98)
drawModels(player, 19576, 100)
drawModels(player, 19581, 101)
drawModels(player, 19575, 79)
drawModels(player, 19574, 80)
drawModels(player, 19577, 81)
drawModels(player, 19521, 102, 103, 113)
drawModels(player, 19519, 82, 83, 93)
drawModels(player, 19522, 84, 85, 92)
drawModels(player, 19570, 126)
drawModels(player, 19569, 106)
drawModels(player, 19568, 86)
// Landing base
drawModels(player, 19567, 97)
// Clouds
drawModels(player, 19525, 168, 174, 212)
drawModels(player, 19524, 169, 170, 175, 213)
drawModels(player, 19526, 171, 176, 214)
// Birds
drawAnimatedModels(player, MODEL_BROWN_BIRD_19780, ANIMATION_BIRD_341, 2567, 100, 300, 146, 151, 192, 207, 208, 215)
// Cloud anomaly
drawAnimatedModels(player, MODEL_CLOUD_ANOMALY_19781, ANIMATION_CLOUD_ANOMALY_5141, 2567, 0, 300, 191)
}
private fun drawCraftingGuildScreen(player: Player, screen: Int) {
when (screen) {
1 -> drawCraftingGuildScreenOne(player)
2 -> drawCraftingGuildScreenTwo(player)
3 -> drawCraftingGuildScreenThree(player)
}
}
private fun drawCraftingGuildScreenOne(player: Player) {
// Ground
drawModels(player, 19558, 40)
drawModels(player, 19559, 45)
drawModels(player, 19604, 50)
drawModels(player, 19605, 55)
// Launch base
drawModels(player, 19572, 138)
// Mountain
drawModels(player, 19607, 118)
drawModels(player, 19611, 119)
drawModels(player, 19608, 98, 99)
drawModels(player, 19612, 100)
drawModels(player, 19609, 78, 79, 80)
drawModels(player, 19613, 81)
// Trees
drawModels(player, 19570, 122, 124, 133, 137)
drawModels(player, 19569, 102, 104, 113, 117)
drawModels(player, 19568, 82, 84, 93, 97)
drawModels(player, 19521, 103, 116)
drawModels(player, 19519, 83, 96)
// Building
drawModels(player, 19618, 128)
drawModels(player, 19633, 108)
drawModels(player, 19630, 88)
drawModels(player, 19619, 129)
drawModels(player, 19603, 109)
drawModels(player, 19602, 89)
drawModels(player, 19620, 130)
drawModels(player, 19629, 110)
drawModels(player, 19628, 90)
drawModels(player, 19621, 131)
drawModels(player, 19632, 111)
drawModels(player, 19631, 91)
// Birds
drawAnimatedModels(player, MODEL_BROWN_BIRD_19780, ANIMATION_BIRD_341, 2567, 100, 300, 220, 221, 206, 175, 151, 171, 148)
// Cloud anomaly
drawAnimatedModels(player, MODEL_CLOUD_ANOMALY_19781, ANIMATION_CLOUD_ANOMALY_5141, 2567, 0, 300, 183)
// Clouds
drawModels(player, 19525, 162, 192, 233)
drawModels(player, 19524, 163, 193, 234)
drawModels(player, 19526, 164, 194, 235)
}
private fun drawCraftingGuildScreenTwo(player: Player) {
// Ground
drawModels(player, 19606, 40)
drawModels(player, 19561, 45)
drawModels(player, 19562, 50, 55)
// Clouds
drawModels(player, 19525, 168, 200, 193, 205)
drawModels(player, 19524, 169, 201, 206, 194)
drawModels(player, 19526, 170, 202, 207, 195)
// Trees
drawModels(player, 19521, 99)
drawModels(player, 19519, 78)
drawModels(player, 19570, 119)
drawModels(player, 19569, 98)
drawModels(player, 19568, 79)
// Ship
drawModels(player, 19625, 103)
drawModels(player, 19623, 83)
drawModels(player, 19626, 124)
drawModels(player, 19627, 104)
drawModels(player, 19624, 84)
// Rocks
drawModels(player, 19617, 107, 109, 112)
drawModels(player, 19615, 87, 89, 92)
drawModels(player, 19616, 96)
// Shark
drawModels(player, 19717, 97)
// Birds
drawAnimatedModels(player, MODEL_WHITE_BIRD_19779, ANIMATION_BIRD_341, 2567, 100, 300, 161, 187, 216, 217, 171)
// Cloud anomaly
drawAnimatedModels(player, MODEL_CLOUD_ANOMALY_19781, ANIMATION_CLOUD_ANOMALY_5141, 2567, 0, 300, 127, 132, 188)
}
private fun drawCraftingGuildScreenThree(player: Player) {
// Ground
drawModels(player, 19595, 40)
drawModels(player, 19596, 45)
drawModels(player, 19597, 50)
drawModels(player, 19598, 55)
// Landing base
drawModels(player, 19567, 97)
// Rocks
drawModels(player, 19616, 79, 84)
drawModels(player, 19617, 100)
drawModels(player, 19615, 80)
// Shark
drawModels(player, 19717, 78)
// Trees
drawModels(player, 19601, 123, 130)
drawModels(player, 19600, 103, 110)
drawModels(player, 19599, 83, 90)
drawModels(player, 19521, 105, 107, 108)
drawModels(player, 19519, 85, 87, 88)
drawModels(player, 19522, 86)
drawModels(player, 19622, 89)
// Clouds
drawModels(player, 19526, 198, 224, 168, 175, 230, 212)
drawModels(player, 19525, 222, 166, 173, 228, 210)
drawModels(player, 19524, 223, 167, 174, 229, 211)
// Birds
drawAnimatedModels(player, MODEL_BROWN_BIRD_19780, ANIMATION_BIRD_341, 2567, 100, 300, 199, 120, 163, 209)
// Cloud anomaly
drawAnimatedModels(player, MODEL_CLOUD_ANOMALY_19781, ANIMATION_CLOUD_ANOMALY_5141, 2567, 0, 300, 143, 104)
}
private fun drawVarrockScreen(player: Player, screen: Int) {
when (screen) {
1 -> drawVarrockScreenOne(player)
2 -> drawVarrockScreenTwo(player)
3 -> drawVarrockScreenThree(player)
}
}
private fun drawVarrockScreenOne(player: Player) {
// Ground
drawModels(player, 19731, 40)
drawModels(player, 19735, 45)
drawModels(player, 19736, 50)
drawModels(player, 19737, 55)
// Launch base
drawModels(player, 19572, 99)
// Hill
drawModels(player, 19607, 78)
drawModels(player, 19611, 79)
// Trees
drawModels(player, 19570, 120, 135, 137)
drawModels(player, 19569, 100, 115, 117)
drawModels(player, 19568, 80, 95, 97)
drawModels(player, 19616, 83, 86)
drawModels(player, 19529, 127, 129, 131)
drawModels(player, 19528, 107, 109, 111)
drawModels(player, 19523, 87, 88, 89, 90, 91)
drawModels(player, 19530, 108, 110)
drawModels(player, 19521, 113, 114)
drawModels(player, 19519, 93, 94)
// Clouds
drawModels(player, 19525, 198, 144, 206, 188, 193)
drawModels(player, 19524, 199, 145, 207, 189, 194)
drawModels(player, 19526, 200, 146, 208, 190, 195)
// Birds
drawAnimatedModels(
player,
MODEL_WHITE_BIRD_19779,
ANIMATION_BIRD_341,
2567,
100,
300,
122,
161,
163,
170,
174,
157,
216,
232
)
}
private fun drawVarrockScreenTwo(player: Player) {
// Ground
drawModels(player, 19738, 40)
drawModels(player, 19739, 45)
drawModels(player, 19740, 50)
drawModels(player, 19741, 55)
// Birds
drawAnimatedModels(player, MODEL_BROWN_BIRD_19780, ANIMATION_BIRD_341, 2567, 100, 300, 139, 218, 214, 209, 190, 214, 154)
// Trees
drawModels(player, 19570, 119, 122, 134, 135, 137)
drawModels(player, 19569, 98, 102, 114, 115, 117)
drawModels(player, 19568, 79, 82, 94, 95, 97)
drawModels(player, 19521, 101, 103, 113)
drawModels(player, 19519, 81, 83, 93)
// Clouds
drawModels(player, 19525, 200, 224, 171, 215)
drawModels(player, 19524, 201, 225, 172, 216)
drawModels(player, 19526, 202, 226, 173, 217)
// Building
drawModels(player, 19753, 85)
drawModels(player, 19744, 105, 125)
drawModels(player, 19749, 145)
drawModels(player, 19760, 86, 88)
drawModels(player, 19763, 106)
drawModels(player, 19745, 126, 128)
drawModels(player, 19750, 146)
drawModels(player, 19755, 166)
drawModels(player, 19759, 87)
drawModels(player, 19764, 107)
drawModels(player, 19746, 127)
drawModels(player, 19751, 147)
drawModels(player, 19756, 167)
drawModels(player, 19765, 108)
drawModels(player, 19752, 148)
drawModels(player, 19757, 168)
drawModels(player, 19761, 89)
drawModels(player, 19743, 109, 129)
drawModels(player, 19754, 149)
// Cloud anomaly
drawAnimatedModels(player, MODEL_CLOUD_ANOMALY_19781, ANIMATION_CLOUD_ANOMALY_5141, 2567, 0, 300, 187, 192, 236)
}
private fun drawVarrockScreenThree(player: Player) {
// Ground
drawModels(player, 19742, 40)
drawModels(player, 19732, 45)
drawModels(player, 19733, 50)
drawModels(player, 19734, 55)
// Trees
drawModels(player, 19570, 119, 120)
drawModels(player, 19569, 98, 100)
drawModels(player, 19568, 79, 80)
drawModels(player, 19521, 101)
drawModels(player, 19519, 81)
// Birds
drawAnimatedModels(player, MODEL_BROWN_BIRD_19780, ANIMATION_BIRD_341, 2567, 100, 300, 141, 199, 225, 230)
// Clouds
drawModels(player, 19526, 198, 202)
drawModels(player, 19525, 201)
// Building
drawModels(player, 19729, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94)
drawModels(player, 19729, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114)
drawModels(player, 19729, 123, 124, 126, 127, 129, 130, 132, 133, 134)
drawModels(player, 19730, 125, 128, 131)
drawModels(player, 19724, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154)
drawModels(player, 19725, 163, 167, 171)
drawModels(player, 19726, 164, 168, 172)
drawModels(player, 19727, 183, 187, 191)
drawModels(player, 19728, 184, 188, 192)
// Landing base
drawModels(player, 19567, 97)
}
private fun drawCastleWarsScreen(player: Player, screen: Int) {
when (screen) {
1 -> drawCastleWarsScreenOne(player)
2 -> drawCastleWarsScreenTwo(player)
3 -> drawCastleWarsScreenThree(player)
}
}
private fun drawCastleWarsScreenOne(player: Player) {
// Ground
drawModels(player, 19583, 40)
drawModels(player, 19588, 45, 50)
drawModels(player, 19589, 55)
// Launch base
drawModels(player, 19572, 78)
// Trees
drawModels(player, 19570, 120)
drawModels(player, 19569, 100)
drawModels(player, 19568, 80)
// Rocks
drawModels(player, 19616, 83, 84, 93)
drawModels(player, 19615, 85, 92)
drawModels(player, 19617, 105, 112)
// Shark
drawModels(player, 19717, 97)
// Clouds
drawModels(player, 19525, 227, 181, 164, 169, 212, 175)
drawModels(player, 19524, 228, 182, 165, 213, 176)
drawModels(player, 19526, 229, 183, 166, 170, 214, 177)
// Birds
drawAnimatedModels(player, MODEL_WHITE_BIRD_19779, ANIMATION_BIRD_341, 2567, 100, 300, 140, 147, 207, 130, 193, 136)
}
private fun drawCastleWarsScreenTwo(player: Player) {
// Ground
drawModels(player, 19590, 40)
drawModels(player, 19591, 45)
drawModels(player, 19592, 50)
drawModels(player, 19593, 55)
// Trees
drawModels(player, 19568, 95)
drawModels(player, 19569, 115)
drawModels(player, 19570, 135)
drawModels(player, 19519, 87, 88)
drawModels(player, 19521, 107, 108)
drawModels(player, 19575, 89)
drawModels(player, 19574, 90)
drawModels(player, 19577, 91)
drawModels(player, 19579, 109)
drawModels(player, 19576, 110)
drawModels(player, 19581, 111)
drawModels(player, 19580, 129)
drawModels(player, 19578, 130)
drawModels(player, 19582, 131)
drawModels(player, 19599, 92, 96)
drawModels(player, 19600, 112, 116)
drawModels(player, 19601, 132, 136)
drawModels(player, 19639, 93)
drawModels(player, 19640, 113)
drawModels(player, 19641, 133)
drawModels(player, 19642, 153)
drawModels(player, 19643, 173)
// Rocks
drawModels(player, 19616, 85, 86)
// Shark
drawModels(player, 19717, 79)
// Clouds
drawModels(player, 19525, 140, 180, 189, 203, 215, 228)
drawModels(player, 19524, 141, 158, 190, 204, 216, 229, 230)
drawModels(player, 19526, 142, 159, 181, 191, 205, 217, 231)
// Cloud anomaly
drawAnimatedModels(player, MODEL_CLOUD_ANOMALY_19781, ANIMATION_CLOUD_ANOMALY_5141, 2567, 0, 300, 152, 156)
// Birds
drawAnimatedModels(player, MODEL_WHITE_BIRD_19779, ANIMATION_BIRD_341, 2567, 100, 300, 118, 197)
}
private fun drawCastleWarsScreenThree(player: Player) {
// Ground
drawModels(player, 19594, 40)
drawModels(player, 19584, 45)
drawModels(player, 19585, 50)
drawModels(player, 19586, 55)
// Landing base
drawModels(player, 19567, 97)
// Trees
drawModels(player, 19599, 78, 86, 87)
drawModels(player, 19600, 99, 106, 107)
drawModels(player, 19601, 118, 126, 127)
drawModels(player, 19568, 80, 93)
drawModels(player, 19569, 100, 113)
drawModels(player, 19570, 120, 133)
drawModels(player, 19575, 83)
drawModels(player, 19574, 84)
drawModels(player, 19577, 85)
drawModels(player, 19579, 103)
drawModels(player, 19576, 104)
drawModels(player, 19581, 105)
drawModels(player, 19580, 123)
drawModels(player, 19578, 124)
drawModels(player, 19582, 125)
drawModels(player, 19519, 92)
drawModels(player, 19521, 112)
// Clouds
drawModels(player, 19525, 170, 183, 208, 215, 222)
drawModels(player, 19524, 171, 184, 185, 198, 209, 210, 216)
drawModels(player, 19526, 172, 186, 199, 211, 217, 223)
// Birds
drawAnimatedModels(player, MODEL_WHITE_BIRD_19779, ANIMATION_BIRD_341, 2567, 100, 300, 140, 153, 179)
// Cloud anomaly
drawAnimatedModels(player, MODEL_CLOUD_ANOMALY_19781, ANIMATION_CLOUD_ANOMALY_5141, 2567, 0, 300, 138, 147)
}
private fun drawGrandTreeScreen(player: Player, screen: Int) {
when (screen) {
1 -> drawGrandTreeScreenOne(player)
2 -> drawGrandTreeScreenTwo(player)
3 -> drawGrandTreeScreenThree(player)
}
}
private fun drawGrandTreeScreenOne(player: Player) {
// Ground
drawModels(player, 19560, 40)
drawModels(player, 19561, 45)
drawModels(player, 19562, 50, 55)
// Launch base
drawModels(player, 19572, 138)
// Mountain
drawModels(player, 19607, 118)
drawModels(player, 19611, 119)
drawModels(player, 19608, 98, 99)
drawModels(player, 19612, 100)
drawModels(player, 19609, 78, 79, 80)
drawModels(player, 19613, 81)
// Rocks
drawModels(player, 19616, 93, 97)
drawModels(player, 19615, 84, 90)
drawModels(player, 19617, 104, 110)
drawModels(player, 19622, 89)
// Shark
drawModels(player, 19717, 85, 94)
// Clouds
drawModels(player, 19525, 186, 192, 201, 230)
drawModels(player, 19524, 187, 193, 194, 202, 231)
drawModels(player, 19526, 188, 195, 203, 232)
// Birds
drawAnimatedModels(player, MODEL_WHITE_BIRD_19779, ANIMATION_BIRD_341, 2567, 100, 300, 191, 229, 237, 144, 130)
// Cloud anomaly
drawAnimatedModels(player, MODEL_CLOUD_ANOMALY_19781, ANIMATION_CLOUD_ANOMALY_5141, 2567, 0, 300, 214)
}
private fun drawGrandTreeScreenTwo(player: Player) {
// Ground
drawModels(player, 19634, 40)
drawModels(player, 19636, 45)
drawModels(player, 19637, 50)
drawModels(player, 19638, 55)
// Trees
drawModels(player, 19522, 79)
drawModels(player, 19639, 80, 82, 84, 87, 92)
drawModels(player, 19640, 100, 102, 104, 107, 112)
drawModels(player, 19641, 127, 132)
drawModels(player, 19642, 120, 122, 124, 147, 152)
drawModels(player, 19643, 140, 142, 144, 167, 172)
drawModels(player, 19519, 81, 83, 91)
drawModels(player, 19521, 101, 103, 111)
drawModels(player, 19575, 88)
drawModels(player, 19574, 89)
drawModels(player, 19577, 90)
drawModels(player, 19579, 108)
drawModels(player, 19576, 109)
drawModels(player, 19581, 110)
drawModels(player, 19580, 128)
drawModels(player, 19578, 129)
drawModels(player, 19582, 130)
drawModels(player, 19599, 94, 95)
drawModels(player, 19600, 114, 115)
drawModels(player, 19601, 134, 135)
drawModels(player, 19523, 96, 97)
drawModels(player, 19529, 116, 117)
// Clouds
drawModels(player, 19525, 191, 200, 203, 208, 214)
drawModels(player, 19524, 204, 215, 216)
drawModels(player, 19526, 192, 201, 205, 209, 217)
// Birds
drawAnimatedModels(player, MODEL_WHITE_BIRD_19779, ANIMATION_BIRD_341, 2567, 100, 300, 219, 183, 154, 196, 197, 149, 150)
// Cloud anomaly
drawAnimatedModels(player, MODEL_CLOUD_ANOMALY_19781, ANIMATION_CLOUD_ANOMALY_5141, 2567, 0, 300, 181, 220, 224, 228)
}
private fun drawGrandTreeScreenThree(player: Player) {
// Ground
drawModels(player, 19713, 40)
drawModels(player, 19714, 45)
drawModels(player, 19715, 50)
drawModels(player, 19716, 55)
// Landing base
drawModels(player, 19567, 97)
// Waterfall
drawModels(player, 19644, 87)
drawModels(player, 19645, 88)
drawModels(player, 19646, 89)
drawModels(player, 19647, 90)
drawModels(player, 19648, 91)
drawModels(player, 19649, 92)
drawModels(player, 19650, 99)
drawModels(player, 19651, 98)
drawModels(player, 19652, 100)
drawModels(player, 19653, 101)
drawModels(player, 19654, 78)
drawModels(player, 19655, 102)
drawModels(player, 19656, 103)
drawModels(player, 19657, 104)
drawModels(player, 19658, 105)
drawModels(player, 19659, 106)
drawModels(player, 19660, 107, 108)
drawModels(player, 19661, 109)
drawModels(player, 19662, 110)
drawModels(player, 19663, 111)
drawModels(player, 19664, 112)
drawModels(player, 19665, 79)
drawModels(player, 19666, 120)
drawModels(player, 19667, 121)
drawModels(player, 19668, 122)
drawModels(player, 19669, 123)
drawModels(player, 19670, 124)
drawModels(player, 19671, 125)
drawModels(player, 19672, 126)
drawModels(player, 19673, 127)
drawModels(player, 19674, 128)
drawModels(player, 19675, 129)
drawModels(player, 19676, 80)
drawModels(player, 19677, 130)
drawModels(player, 19678, 131)
drawModels(player, 19679, 132)
drawModels(player, 19680, 140)
drawModels(player, 19681, 141)
drawModels(player, 19682, 142)
drawModels(player, 19683, 143)
drawModels(player, 19684, 144)
drawModels(player, 19685, 145)
drawModels(player, 19686, 146)
drawModels(player, 19687, 81)
drawModels(player, 19688, 147)
drawModels(player, 19689, 148)
drawModels(player, 19690, 149)
drawModels(player, 19691, 150)
drawModels(player, 19692, 151)
drawModels(player, 19693, 161)
drawModels(player, 19694, 162)
drawModels(player, 19695, 163)
drawModels(player, 19696, 164)
drawModels(player, 19697, 165)
drawModels(player, 19698, 82)
drawModels(player, 19699, 166)
drawModels(player, 19700, 167)
drawModels(player, 19701, 168)
drawModels(player, 19702, 169)
drawModels(player, 19703, 170)
drawModels(player, 19704, 182)
drawModels(player, 19705, 183)
drawModels(player, 19706, 187)
drawModels(player, 19707, 188)
drawModels(player, 19708, 189)
drawModels(player, 19709, 83)
drawModels(player, 19710, 84)
drawModels(player, 19711, 85)
drawModels(player, 19712, 86)
// Clouds
drawModels(player, 19525, 174, 198, 212)
drawModels(player, 19524, 199, 213)
drawModels(player, 19526, 175, 200, 214)
// Birds
drawAnimatedModels(player, MODEL_WHITE_BIRD_19779, ANIMATION_BIRD_341, 2567, 100, 300, 178, 225)
}
}

View file

@ -0,0 +1,421 @@
package content.global.travel.balloon
import content.data.Quests
import content.region.misc.entrana.quest.enlightenedjourney.dialogue.AugusteDialogueFile
import content.region.misc.entrana.quest.enlightenedjourney.quest.EnlightenedJourney
import content.region.misthalin.varrock.diary.VarrockAchivementDiary.Companion.MediumTasks.HOTAIR_BALLOON_TRAVEL_SOMEWHERE
import core.api.amountInInventory
import core.api.animateInterface
import core.api.animationDuration
import core.api.closeInterface
import core.api.findLocalNPC
import core.api.getAttribute
import core.api.getVarbit
import core.api.hasLevelStat
import core.api.isQuestComplete
import core.api.lock
import core.api.openDialogue
import core.api.openInterface
import core.api.playJingle
import core.api.queueScript
import core.api.removeItem
import core.api.rewardXP
import core.api.sendDialogue
import core.api.sendMessage
import core.api.setAttribute
import core.api.setComponentVisibility
import core.api.setVarbit
import core.api.stopExecuting
import core.api.teleport
import core.api.unlock
import core.cache.def.impl.ItemDefinition
import core.game.dialogue.DialogueFile
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.interaction.InterfaceListener
import core.game.interaction.QueueStrength
import core.game.node.entity.player.Player
import core.game.node.entity.player.link.diary.DiaryType
import core.game.node.entity.skill.Skills
import core.game.node.item.Item
import core.game.world.map.Location
import core.game.world.update.flag.context.Animation
import core.plugin.Initializable
import org.rs09.consts.Items
import org.rs09.consts.NPCs
/**
* Handles the balloon transportation map interface.
* @author Edith
*/
private const val ASSISTANT_STAN_WRAPPER_5062 = 5062
private const val ASSISTANT_MARROW_WRAPPER_5063 = 5063
private const val ASSISTANT_LE_SMITH_WRAPPER_5064 = 5064
private const val ASSISTANT_BROCK_WRAPPER_5065 = 5065
private const val ASSISTANT_SERF_WRAPPER_5066 = 5066
private const val VARROCK_MEDIUM_DIARY = 1
private val BALLOON_VISIBLE_NPCS = intArrayOf(
NPCs.AUGUSTE_5049,
NPCs.ASSISTANT_STAN_5057,
NPCs.ASSISTANT_MARROW_5055,
NPCs.ASSISTANT_LE_SMITH_5056,
NPCs.ASSISTANT_BROCK_5054,
NPCs.ASSISTANT_SERF_5053
)
private val BALLOON_ASSISTANT_NPCS = intArrayOf(
NPCs.ASSISTANT_STAN_5057,
NPCs.ASSISTANT_MARROW_5055,
NPCs.ASSISTANT_LE_SMITH_5056,
NPCs.ASSISTANT_BROCK_5054,
NPCs.ASSISTANT_SERF_5053
)
private val BALLOON_BASKET_LOCS = intArrayOf(19128, 19129)
@Initializable
class BalloonTravelInterface : InterfaceListener, InteractionListener {
companion object {
private const val JINGLE_A_HOT_AIR_JOURNEY_118 = 118
private const val BALLOON_MAP_INTERFACE_469 = 469
private const val BALLOON_MAP_MODEL_12 = 12
private const val ATTRIBUTE_BALLOON_START = "balloon_travel_start"
/** Opens the balloon map interface. */
fun open(player: Player, start: BalloonTravelLocation) {
setAttribute(player, ATTRIBUTE_BALLOON_START, start)
openInterface(player, BALLOON_MAP_INTERFACE_469)
setComponentVisibility(player, BALLOON_MAP_INTERFACE_469, start.mapComponent, false)
}
/** Checks the saved route unlock state. */
private fun hasRouteUnlocked(player: Player, destination: BalloonTravelLocation): Boolean {
return getVarbit(player, destination.varbit) > EnlightenedJourney.BALLOON_ROUTE_LOCKED_0
}
/** Checks if the player can use an unlocked route. */
private fun canUseRoute(player: Player, destination: BalloonTravelLocation): Boolean {
if (!hasRouteUnlocked(player, destination)) {
return false
}
if (!hasLevelStat(player, Skills.FIREMAKING, destination.requiredFiremakingLevel)) {
sendMessage(
player,
"You require a Firemaking level of ${destination.requiredFiremakingLevel} to travel there."
)
return false
}
if (amountInInventory(player, destination.log) < 1) {
sendMessage(player, "You need some logs to fuel the balloon.")
return false
}
if (destination == BalloonTravelLocation.ENTRANA) {
if (!ItemDefinition.canEnterEntrana(player)) {
sendMessage(
player,
"I'm sorry, no weapons or armour may be worn to Entrana. Come back when you have laid them down."
)
return false
}
sendMessage(player, "You are quickly searched.")
}
return true
}
private fun canAttemptRouteUnlock(
player: Player,
start: BalloonTravelLocation,
destination: BalloonTravelLocation
): Boolean {
if (start != BalloonTravelLocation.ENTRANA) {
return false
}
if (destination == BalloonTravelLocation.ENTRANA || destination == BalloonTravelLocation.TAVERLEY) {
return false
}
if (!hasLevelStat(player, Skills.FIREMAKING, destination.requiredFiremakingLevel)) {
sendMessage(
player,
"You require a Firemaking level of ${destination.requiredFiremakingLevel} to travel there."
)
return false
}
if (amountInInventory(player, destination.log) < destination.unlockLogAmount) {
sendMessage(player, "You need some logs to fuel the balloon.")
return false
}
return true
}
/** Completes a normal balloon trip. */
private fun balloonTravel(player: Player, start: BalloonTravelLocation, destination: BalloonTravelLocation) {
if (removeItem(player, Item(destination.log, 1))) {
val animation = BalloonTravelLocation.getMapAnimation(start, destination)
val duration = animationDuration(Animation(animation))
lock(player, duration)
playJingle(player, JINGLE_A_HOT_AIR_JOURNEY_118)
setComponentVisibility(player, BALLOON_MAP_INTERFACE_469, start.mapComponent, true)
setComponentVisibility(player, BALLOON_MAP_INTERFACE_469, BALLOON_MAP_MODEL_12, false)
animateInterface(player, BALLOON_MAP_INTERFACE_469, BALLOON_MAP_MODEL_12, animation)
queueScript(player, duration, QueueStrength.SOFT) {
closeInterface(player)
teleport(player, destination.destination)
if (start == BalloonTravelLocation.VARROCK) {
player.achievementDiaryManager.finishTask(
player,
DiaryType.VARROCK,
VARROCK_MEDIUM_DIARY,
HOTAIR_BALLOON_TRAVEL_SOMEWHERE
)
}
unlock(player)
return@queueScript stopExecuting(player)
}
}
}
private fun unlockRoute(player: Player, destination: BalloonTravelLocation) {
closeInterface(player)
openDialogue(player, BalloonBoardingDialogue(destination))
}
fun unlockDestination(player: Player, destination: BalloonTravelLocation) {
if (hasRouteUnlocked(player, destination)) {
return
}
setVarbit(player, destination.varbit, EnlightenedJourney.BALLOON_ROUTE_UNLOCKED_1, true)
rewardXP(player, Skills.FIREMAKING, 2000.0)
}
}
override fun defineInterfaceListeners() {
on(BALLOON_MAP_INTERFACE_469) { player, _, _, selectId, _, _ ->
val destination = BalloonTravelLocation.fromSelectId(selectId) ?: return@on false
val start = getAttribute(player, ATTRIBUTE_BALLOON_START, BalloonTravelLocation.ENTRANA)
if (destination == start) {
return@on true
}
if (hasRouteUnlocked(player, destination)) {
if (canUseRoute(player, destination)) {
balloonTravel(player, start, destination)
}
return@on true
}
if (canAttemptRouteUnlock(player, start, destination)) {
unlockRoute(player, destination)
return@on true
}
return@on true
}
}
override fun defineListeners() {
on(BALLOON_VISIBLE_NPCS, IntType.NPC, "fly") { player, node ->
if (!isQuestComplete(player, Quests.ENLIGHTENED_JOURNEY)) {
sendMessage(player, "You should speak to Auguste first.")
return@on true
}
val start = BalloonTravelLocation.fromNpcId(node.id) ?: return@on false
open(player, start)
return@on true
}
on(BALLOON_ASSISTANT_NPCS, IntType.NPC, "talk-to") { player, node ->
if (!isQuestComplete(player, Quests.ENLIGHTENED_JOURNEY)) {
return@on true
}
val start = BalloonTravelLocation.fromNpcId(node.id) ?: return@on false
openDialogue(player, BalloonAssistantsDialogue(start), node.asNpc())
return@on true
}
on(BALLOON_BASKET_LOCS, IntType.SCENERY, "use") { player, node ->
val start = BalloonTravelLocation.fromSceneryId(node.asScenery().wrapper.id) ?: return@on false
val routeNpc = findLocalNPC(player, start.npc) ?: return@on true
if (routeNpc.getShownNPC(player).id == NPCs.AUGUSTE_5049) {
openDialogue(player, AugusteDialogueFile(), routeNpc)
} else {
if (!isQuestComplete(player, Quests.ENLIGHTENED_JOURNEY)) {
return@on true
}
openDialogue(player, BalloonAssistantsDialogue(start), routeNpc)
}
return@on true
}
}
}
/** Balloon locations used by the transportation map. */
enum class BalloonTravelLocation(
val selection: Int,
val mapComponent: Int,
val destination: Location,
val log: Int,
val unlockLogAmount: Int,
val requiredFiremakingLevel: Int,
val varbit: Int,
val npc: Int,
val scenery: Int
) {
CASTLE_WARS(
14,
24,
Location.create(2462, 3108, 0),
Items.YEW_LOGS_1515,
10,
50,
EnlightenedJourney.VARBIT_CASTLEWARS_BALLOON_2869,
ASSISTANT_MARROW_WRAPPER_5063,
19137,
),
GRAND_TREE(
15,
23,
Location.create(2480, 3458, 0),
Items.MAGIC_LOGS_1513,
3,
60,
EnlightenedJourney.VARBIT_GRANDTREE_BALLOON_2870,
ASSISTANT_LE_SMITH_WRAPPER_5064,
19139,
),
CRAFTING_GUILD(
16,
20,
Location.create(2924, 3303, 0),
Items.OAK_LOGS_1521,
10,
30,
EnlightenedJourney.VARBIT_CRAFTINGGUILD_BALLOON_2871,
ASSISTANT_BROCK_WRAPPER_5065,
19141,
),
ENTRANA(
17,
25,
Location.create(2809, 3356, 0),
Items.LOGS_1511,
1,
20,
EnlightenedJourney.VARBIT_ENTRANA_BALLOON_2867,
NPCs.AUGUSTE_5049,
19133,
),
TAVERLEY(
18,
22,
Location.create(2941, 3420, 0),
Items.LOGS_1511,
1,
20,
EnlightenedJourney.VARBIT_TAVERLEY_BALLOON_2868,
ASSISTANT_STAN_WRAPPER_5062,
19135,
),
VARROCK(
19,
21,
Location.create(3298, 3481, 0),
Items.WILLOW_LOGS_1519,
10,
40,
EnlightenedJourney.VARBIT_VARROCK_BALLOON_2872,
ASSISTANT_SERF_WRAPPER_5066,
19143,
);
companion object {
private val selectionMap = values().associateBy { it.selection }
private val npcMap = values().associateBy { it.npc }
private val sceneryMap = values().associateBy { it.scenery }
private val mapAnimation = mapOf(
ENTRANA to TAVERLEY to 5110,
TAVERLEY to ENTRANA to 5111,
ENTRANA to CRAFTING_GUILD to 5112,
CRAFTING_GUILD to ENTRANA to 5113,
ENTRANA to VARROCK to 5114,
VARROCK to ENTRANA to 5115,
ENTRANA to GRAND_TREE to 5116,
GRAND_TREE to ENTRANA to 5117,
ENTRANA to CASTLE_WARS to 5118,
CASTLE_WARS to ENTRANA to 5119,
VARROCK to CRAFTING_GUILD to 5120,
CRAFTING_GUILD to VARROCK to 5121,
VARROCK to TAVERLEY to 5122,
TAVERLEY to VARROCK to 5123,
TAVERLEY to CRAFTING_GUILD to 5124,
CRAFTING_GUILD to TAVERLEY to 5125,
TAVERLEY to CASTLE_WARS to 5126,
CASTLE_WARS to TAVERLEY to 5127,
CRAFTING_GUILD to CASTLE_WARS to 5128,
CASTLE_WARS to CRAFTING_GUILD to 5129,
VARROCK to CASTLE_WARS to 5130,
CASTLE_WARS to VARROCK to 5131,
GRAND_TREE to CASTLE_WARS to 5132,
CASTLE_WARS to GRAND_TREE to 5133,
GRAND_TREE to CRAFTING_GUILD to 5134,
CRAFTING_GUILD to GRAND_TREE to 5135,
TAVERLEY to GRAND_TREE to 5136,
GRAND_TREE to TAVERLEY to 5137,
VARROCK to GRAND_TREE to 5138,
GRAND_TREE to VARROCK to 5139
)
fun getMapAnimation(start: BalloonTravelLocation, destination: BalloonTravelLocation): Int {
return mapAnimation[start to destination] ?: 0
}
fun fromSelectId(selectId: Int): BalloonTravelLocation? {
return selectionMap[selectId]
}
fun fromNpcId(npc: Int): BalloonTravelLocation? {
return npcMap[npc]
}
fun fromSceneryId(scenery: Int): BalloonTravelLocation? {
return sceneryMap[scenery]
}
}
}
private class BalloonBoardingDialogue(private val destination: BalloonTravelLocation) : DialogueFile() {
override fun handle(componentID: Int, buttonID: Int) {
when (stage) {
0 -> sendDialogue(player!!, "You board the balloon.").also { stage++ }
1 -> {
if (removeItem(player!!, Item(destination.log, destination.unlockLogAmount))) {
end()
BalloonFlightInterface.startUnlockFlight(player!!, destination)
} else {
end()
sendMessage(player!!, "You need some logs to fuel the balloon.")
}
}
}
}
}

View file

@ -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<Int, Int>) =
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
}
}

View file

@ -0,0 +1,79 @@
package content.region.misc.entrana.quest.enlightenedjourney.cutscene
import content.data.Quests
import content.region.misc.entrana.quest.enlightenedjourney.quest.EnlightenedJourney.Companion.ENTRANA_BALLOON_COMPLETE_2
import content.region.misc.entrana.quest.enlightenedjourney.quest.EnlightenedJourney.Companion.STAGE_FLIGHT
import content.region.misc.entrana.quest.enlightenedjourney.quest.EnlightenedJourney.Companion.VARBIT_ENTRANA_BALLOON_2867
import core.api.face
import core.api.playJingle
import core.api.setQuestStage
import core.api.setVarbit
import core.game.activity.Cutscene
import core.game.node.entity.player.Player
import core.game.world.map.Direction
import core.game.world.map.Location
import org.rs09.consts.NPCs
/**
* Enlightened Journey cutscene where the main balloon is constructed.
* @author Edith
*/
class BalloonConstructionCutscene(player: Player) : Cutscene(player) {
companion object {
private const val REGION_ENTRANA_11060 = 11060
private const val JINGLE_A_HOT_AIR_JOURNEY_116 = 116
}
override fun setup() {
setExit(Location.create(player.location))
loadRegion(REGION_ENTRANA_11060)
}
override fun runStage(stage: Int) {
when (stage) {
0 -> {
fadeToBlack()
timedUpdate(5)
}
1 -> {
teleport(player, 56, 26, 0)
face(player, base.transform(55, 26, 0))
addNPC(NPCs.AUGUSTE_5049, 57, 27, Direction.SOUTH)
moveCamera(48, 25, 1100)
rotateCamera(60, 31)
setVarbit(player, VARBIT_ENTRANA_BALLOON_2867, ENTRANA_BALLOON_COMPLETE_2, true)
timedUpdate(5)
}
2 -> {
fadeFromBlack()
move(getNPC(NPCs.AUGUSTE_5049)!!, 56, 27)
timedUpdate(2)
}
3 -> {
playJingle(player, JINGLE_A_HOT_AIR_JOURNEY_116)
timedUpdate(2)
}
4 -> {
moveCamera(39, 20, 2000, 8)
timedUpdate(6)
}
5 -> {
fadeToBlack()
timedUpdate(3)
}
6 -> {
endWithoutFade {
fadeFromBlack()
setQuestStage(player, Quests.ENLIGHTENED_JOURNEY, STAGE_FLIGHT)
}
}
}
}
}

View file

@ -0,0 +1,128 @@
package content.region.misc.entrana.quest.enlightenedjourney.cutscene
import content.data.Quests
import content.region.misc.entrana.quest.enlightenedjourney.quest.EnlightenedJourney.Companion.STAGE_FIRST_EXPERIMENT
import core.api.face
import core.api.playAudio
import core.api.playJingle
import core.api.setQuestStage
import core.api.visualize
import core.game.activity.Cutscene
import core.game.dialogue.FacialExpression
import core.game.node.entity.impl.Projectile
import core.game.node.entity.player.Player
import core.game.world.map.Direction
import core.game.world.map.Location
import org.rs09.consts.NPCs
/**
* Enlightened Journey cutscene where the balloon catches on fire.
* @author Edith
*/
class BalloonExperimentCutscene(player: Player) : Cutscene(player) {
companion object {
private const val REGION_ENTRANA_11060 = 11060
private const val JINGLE_A_HOT_AIR_JOURNEY_116 = 116
private const val SOUND_ORIGAMI_BALLOON_BURNS_3243 = 3243
private const val SOUND_RELEASE_BALLOON_3245 = 3245
private const val ANIMATION_RELEASE_ORIGAMI_BALLOON_5142 = 5142
private const val GRAPHIC_RELEASE_ORIGAMI_BALLOON_880 = 880
private const val GRAPHIC_FLYING_ORIGAMI_BALLOON_882 = 882
}
override fun setup() {
setExit(Location.create(player.location))
loadRegion(REGION_ENTRANA_11060)
}
override fun runStage(stage: Int) {
when (stage) {
0 -> {
fadeToBlack()
timedUpdate(5)
}
1 -> {
teleport(player, 56, 27, 0)
face(player, base.transform(56, 25, 0))
addNPC(NPCs.AUGUSTE_5049, 57, 27, Direction.SOUTH_WEST)
moveCamera(58, 30, 400)
rotateCamera(50, 6)
timedUpdate(5)
}
2 -> {
fadeFromBlack()
timedUpdate(2)
}
3 -> {
visualize(player, ANIMATION_RELEASE_ORIGAMI_BALLOON_5142, GRAPHIC_RELEASE_ORIGAMI_BALLOON_880)
playAudio(player, SOUND_RELEASE_BALLOON_3245)
playJingle(player, JINGLE_A_HOT_AIR_JOURNEY_116)
timedUpdate(3)
}
4 -> {
val origamiBalloonStart = base.transform(56, 26, 0)
val origamiBalloonEnd = base.transform(56, 21, 0)
Projectile
.create(
origamiBalloonStart,
origamiBalloonEnd,
GRAPHIC_FLYING_ORIGAMI_BALLOON_882,
0,
50,
0,
250,
0,
0
)
.send()
moveCamera(63, 22, 500, 8)
rotateCamera(47, 26, speed = 8)
timedUpdate(7)
}
5 -> {
face(player, getNPC(NPCs.AUGUSTE_5049)!!)
dialogueUpdate(NPCs.AUGUSTE_5049, FacialExpression.HAPPY, "That was perfect. My hypothesis was right!")
timedUpdate(1)
}
6 -> {
playAudio(player, SOUND_ORIGAMI_BALLOON_BURNS_3243)
face(getNPC(NPCs.AUGUSTE_5049)!!, player)
}
7 -> playerDialogueUpdate(FacialExpression.HALF_WORRIED, "Did you not see the burning?")
8 -> dialogueUpdate(NPCs.AUGUSTE_5049, FacialExpression.HAPPY, "One more test. Then we shall proceed.")
9 -> playerDialogueUpdate(FacialExpression.HALF_WORRIED, "Burning? Fire? Hello?")
10 -> dialogueUpdate(
NPCs.AUGUSTE_5049,
FacialExpression.HAPPY,
"We shall meekly go! No...no...it needs to sound grander. We shall cautiously go..."
)
11 -> playerDialogueUpdate(FacialExpression.HALF_WORRIED, "We're doomed.")
12 -> {
fadeToBlack()
timedUpdate(3)
}
13 -> {
endWithoutFade {
fadeFromBlack()
setQuestStage(player, Quests.ENLIGHTENED_JOURNEY, STAGE_FIRST_EXPERIMENT)
}
}
}
}
}

View file

@ -0,0 +1,142 @@
package content.region.misc.entrana.quest.enlightenedjourney.cutscene
import content.data.Quests
import content.region.misc.entrana.quest.enlightenedjourney.quest.EnlightenedJourney.Companion.STAGE_SECOND_EXPERIMENT
import core.api.animate
import core.api.face
import core.api.playAudio
import core.api.playJingle
import core.api.sendChat
import core.api.sendGraphics
import core.api.setQuestStage
import core.api.visualize
import core.game.activity.Cutscene
import core.game.dialogue.FacialExpression
import core.game.node.entity.impl.Projectile
import core.game.node.entity.player.Player
import core.game.world.map.Direction
import core.game.world.map.Location
import core.game.world.update.flag.context.Graphics
import org.rs09.consts.NPCs
/**
* Enlightened Journey cutscene where the balloon catches on fire AND is chased by the four stooges.
* @author Edith
*/
class SecondBalloonExperimentCutscene(player: Player) : Cutscene(player) {
companion object {
private const val REGION_ENTRANA_11060 = 11060
private const val JINGLE_A_HOT_AIR_JOURNEY_116 = 116
private const val SOUND_ORIGAMI_BALLOON_BURNS_3243 = 3243
private const val SOUND_RELEASE_BALLOON_3245 = 3245
private const val ANIMATION_RELEASE_ORIGAMI_BALLOON_5142 = 5142
private const val GRAPHIC_RELEASE_BIGGER_ORIGAMI_BALLOON_877 = 877
private const val GRAPHIC_FLYING_BIGGER_ORIGAMI_BALLOON_878 = 878
private const val GRAPHIC_BURNING_BIGGER_ORIGAMI_BALLOON_879 = 879
}
override fun setup() {
setExit(Location.create(player.location))
loadRegion(REGION_ENTRANA_11060)
}
override fun runStage(stage: Int) {
when (stage) {
0 -> {
fadeToBlack()
timedUpdate(5)
}
1 -> {
teleport(player, 56, 27, 0)
face(player, base.transform(56, 28, 0))
addNPC(NPCs.AUGUSTE_5049, 57, 27, Direction.SOUTH_WEST)
moveCamera(58, 30, 400)
rotateCamera(50, 6)
timedUpdate(5)
}
2 -> {
fadeFromBlack()
timedUpdate(2)
}
3 -> {
face(player, base.transform(56, 26, 0))
visualize(player, ANIMATION_RELEASE_ORIGAMI_BALLOON_5142, GRAPHIC_RELEASE_BIGGER_ORIGAMI_BALLOON_877)
playAudio(player, SOUND_RELEASE_BALLOON_3245)
playJingle(player, JINGLE_A_HOT_AIR_JOURNEY_116)
timedUpdate(3)
}
4 -> {
val origamiBalloonStart = base.transform(56, 27, 0)
val origamiBalloonEnd = base.transform(56, 21, 0)
Projectile
.create(
origamiBalloonStart,
origamiBalloonEnd,
GRAPHIC_FLYING_BIGGER_ORIGAMI_BALLOON_878,
50,
50,
0,
250,
0,
0
)
.send()
moveCamera(63, 22, 550, 8)
rotateCamera(47, 26, speed = 8)
addNPC(NPCs.BOB_5058, 60, 22, Direction.WEST)
addNPC(NPCs.CURLY_5059, 61, 23, Direction.WEST)
addNPC(NPCs.MOE_5060, 62, 22, Direction.WEST)
addNPC(NPCs.LARRY_5061, 61, 21, Direction.WEST)
timedUpdate(3)
}
5 -> {
move(getNPC(NPCs.BOB_5058)!!, 53, 22)
move(getNPC(NPCs.CURLY_5059)!!, 54, 23)
move(getNPC(NPCs.MOE_5060)!!, 55, 22)
move(getNPC(NPCs.LARRY_5061)!!, 54, 21)
timedUpdate(5)
}
6 -> {
sendChat(getNPC(NPCs.BOB_5058)!!, "Destroy the floating thing!")
sendGraphics(
Graphics(GRAPHIC_BURNING_BIGGER_ORIGAMI_BALLOON_879, 200, 0),
base.transform(56, 21, 0)
)
playAudio(player, SOUND_ORIGAMI_BALLOON_BURNS_3243)
timedUpdate(3)
}
7 -> {
moveCamera(56, 23, 450)
rotateCamera(57, 32)
timedUpdate(1)
}
8 -> {
animate(player, 2113)
playerDialogueUpdate(FacialExpression.NEUTRAL, "Well, that went down like a lead balloon.")
}
9 -> {
fadeToBlack()
timedUpdate(3)
}
10 -> {
endWithoutFade {
fadeFromBlack()
setQuestStage(player, Quests.ENLIGHTENED_JOURNEY, STAGE_SECOND_EXPERIMENT)
}
}
}
}
}

View file

@ -0,0 +1,913 @@
package content.region.misc.entrana.quest.enlightenedjourney.dialogue
import content.data.Quests
import content.global.travel.balloon.BalloonFlightInterface
import content.global.travel.balloon.BalloonTravelInterface
import content.global.travel.balloon.BalloonTravelLocation
import content.region.misc.entrana.quest.enlightenedjourney.cutscene.BalloonExperimentCutscene
import content.region.misc.entrana.quest.enlightenedjourney.cutscene.SecondBalloonExperimentCutscene
import content.region.misc.entrana.quest.enlightenedjourney.quest.EnlightenedJourney
import core.api.addItem
import core.api.amountInBank
import core.api.amountInInventory
import core.api.finishQuest
import core.api.freeSlots
import core.api.getAttribute
import core.api.getQuest
import core.api.getQuestStage
import core.api.hasAnItem
import core.api.inEquipment
import core.api.openDialogue
import core.api.openInterface
import core.api.removeItem
import core.api.sendMessage
import core.api.setAttribute
import core.api.setQuestStage
import core.api.teleport
import core.cache.def.impl.ItemDefinition
import core.game.dialogue.DialogueLabeller
import core.game.dialogue.DialogueOption
import core.game.dialogue.DialoguePlugin
import core.game.dialogue.FacialExpression
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.node.entity.player.Player
import core.game.node.item.Item
import core.game.world.map.Location
import core.plugin.Initializable
import org.rs09.consts.Items
import org.rs09.consts.NPCs
/**
* Dialogue between Auguste and the player for the Enlightened Journey quest.
* @author Edith
*/
@Initializable
class AugusteDialogue(player: Player? = null) : DialoguePlugin(player) {
override fun newInstance(player: Player): DialoguePlugin {
return AugusteDialogue(player)
}
override fun handle(interfaceId: Int, buttonId: Int): Boolean {
openDialogue(player, AugusteDialogueFile(), npc)
return false
}
override fun getIds(): IntArray {
return intArrayOf(NPCs.AUGUSTE_5049)
}
}
class AugusteTalkListener : InteractionListener {
override fun defineListeners() {
on(NPCs.AUGUSTE_5049, IntType.NPC, "talk-to") { player, node ->
openDialogue(player, AugusteDialogueFile(), node.asNpc())
return@on true
}
}
}
private fun checkAllItemsDelivered(player: Player): Boolean {
val allDelivered = getAttribute(player, EnlightenedJourney.ATTRIBUTE_YELLOW_DYE_DELIVERED, false) &&
getAttribute(player, EnlightenedJourney.ATTRIBUTE_RED_DYE_DELIVERED, false) &&
getAttribute(player, EnlightenedJourney.ATTRIBUTE_SANDBAGS_DELIVERED, false) &&
getAttribute(player, EnlightenedJourney.ATTRIBUTE_SILK_DELIVERED, false) &&
getAttribute(player, EnlightenedJourney.ATTRIBUTE_BOWL_DELIVERED, false)
if (allDelivered) {
setQuestStage(player, Quests.ENLIGHTENED_JOURNEY, EnlightenedJourney.STAGE_WILLOW_BASKET)
}
return allDelivered
}
private fun questStageLabel(stage: Int) = "quest_stage_$stage"
private fun hasBomberCap(player: Player): Boolean {
return hasAnItem(player, arrayOf(Items.BOMBER_CAP_9945, Items.CAP_AND_GOGGLES_9946), false).exists()
}
private fun hasBomberJacket(player: Player): Boolean {
return hasAnItem(player, Items.BOMBER_JACKET_9944).exists()
}
private fun hasLostBomberReward(player: Player): Boolean {
return !hasBomberCap(player) || !hasBomberJacket(player)
}
private fun hasCapAndGoggles(player: Player): Boolean {
val hasCap = amountInInventory(player, Items.BOMBER_CAP_9945) >= 1 || inEquipment(player, Items.BOMBER_CAP_9945)
val hasGoggles = amountInInventory(player, Items.GNOME_GOGGLES_9472) >= 1 || inEquipment(player, Items.GNOME_GOGGLES_9472)
return hasCap && hasGoggles
}
class AugusteDialogueFile : DialogueLabeller() {
override fun addConversation() {
assignToIds(NPCs.AUGUSTE_5049)
exec { player, _ ->
loadLabel(player, "quest_stage_" + getQuestStage(player, Quests.ENLIGHTENED_JOURNEY))
}
label("quest_stage_0")
npc(FacialExpression.HAPPY, "Greetings! Would you like to be my number one accomplice? I mean, assistant?")
player(FacialExpression.ASKING, "Who are you?")
npc(FacialExpression.HAPPY, "I am Auguste. I am going to be the first balloonist in all of Gielinor!")
player(FacialExpression.HALF_THINKING, "Balloo-what? I thought only monks lived on Entrana.")
npc(FacialExpression.SUSPICIOUS, "Well, they do...I was a monk. But I'm so sick of living on this island! I want to explore new frontiers!")
player(FacialExpression.LAUGH, "And go where no man has gone before?")
npc(FacialExpression.HAPPY, "Maybe! That sounds good. I was thinking just over to Taverley, though.")
player(FacialExpression.ASKING, "Why not take the boat then?")
npc(FacialExpression.ANGRY, "Ugh! Ocean! Don't talk to me about the ocean...oh dear...I think I feel ill.")
player(FacialExpression.AMAZED_TALKING, "Whoa, ok, no ocean. How exactly are you going to get off the island, then?")
npc(FacialExpression.NEUTRAL, "I have devised a new way to travel. But I need an assistant balloonist to help me build my design.")
npc(FacialExpression.HALF_ASKING, "Are you an experienced adventurer willing to help me?")
options(
DialogueOption("sign_me_up", "Yes! Sign me up.", expression = FacialExpression.FRIENDLY),
DialogueOption("nowhere", "Not right now.")
)
label("sign_me_up")
exec { player, _ ->
if (!getQuest(player, Quests.ENLIGHTENED_JOURNEY).hasRequirements(player)) {
loadLabel(player, "requirements_not_met")
}
}
npc(FacialExpression.HAPPY, "Wonderful! Let's get started.")
player(FacialExpression.HALF_THINKING, "Wait. I still don't know exactly what we're doing.")
npc(FacialExpression.AMAZED_TALKING, "Of course, of course, how foolish of me. Well, we are going to make a balloon.")
player(FacialExpression.HALF_THINKING, "Which is what exactly?")
npc(FacialExpression.HAPPY, "Let me show you!")
exec { player, _ ->
openInterface(player, 472)
setQuestStage(player, Quests.ENLIGHTENED_JOURNEY, EnlightenedJourney.STAGE_QUEST_STARTED)
}
label("requirements_not_met")
exec { player, _ ->
sendMessage(player, "You do not meet the requirements to start : 'Enlightened Journey'.")
}
label(questStageLabel(EnlightenedJourney.STAGE_QUEST_STARTED))
player(FacialExpression.LAUGH, "How exactly do you plan to get to Taverley in that? How will it work?")
npc(FacialExpression.FRIENDLY, "Have you noticed how ashes float above fires for long periods of time?")
goto("ashes_float")
label("ashes_float")
options(
DialogueOption("whats_your_point", "Umm, yes. What's your point?", "What's your point?", FacialExpression.THINKING),
DialogueOption("cant_see_detail", "Err, no. I can't see that kind of detail.")
)
label("cant_see_detail")
npc("You don't? Oh...well...it's just, they rise, you know, from the fire... Maybe you should pay more attention to things!")
goto("ashes_float")
label("whats_your_point")
npc(FacialExpression.HAPPY, "Don't you see? It's the hot air! It rises, taking the ashes with it.")
npc(FacialExpression.HAPPY, "I had this epiphany while I was at the glass blower's house. If we pump hot air into an envelope it will rise because it is lighter than the cold air around it.")
player(FacialExpression.HALF_THINKING, "Come again?")
npc("We are going to sew a big sack and light a fire under it. Once the sack fills with hot air it will begin to rise, taking us along with it in the basket.")
player(FacialExpression.HALF_WORRIED, "You seem pretty confident about this. Have you tested it?")
npc(FacialExpression.SAD_TALKING, "Well, no. You see, I don't have the materials here to make any test balloons.")
npc(FacialExpression.HAPPY, "That's where you come in!")
npc(FacialExpression.HAPPY, "You are going to collect materials for two test runs of the balloon.")
npc(FacialExpression.HAPPY, "You will need to get three sheets of papyrus, one ball of wool, one full sack of potatoes, and one unlit candle.")
player(FacialExpression.THINKING, "What am I supposed to do with all that junk?")
npc("When you have all of it, bring it to me and I'll explain what to do next. Any questions?")
player("Where do I get all this stuff?")
npc(FacialExpression.ANGRY, "You're the adventurer, you should know! Think of logical places, like, churches; they have tons of candles.")
exec { player, _ ->
setQuestStage(player, Quests.ENLIGHTENED_JOURNEY, EnlightenedJourney.STAGE_GATHER_MATERIALS)
}
label(questStageLabel(EnlightenedJourney.STAGE_GATHER_MATERIALS))
npc(FacialExpression.ASKING, "Have you gotten the materials?")
options(
DialogueOption("are_materials_gathered?", "Yes.", expression = FacialExpression.HAPPY),
DialogueOption("failure_not_option", "No."),
DialogueOption("what_materials_needed", "What do I need to get again?")
)
label("are_materials_gathered?")
exec { player, _ ->
if (amountInInventory(player, Items.PAPYRUS_970) >= 3
&& amountInInventory(player, Items.BALL_OF_WOOL_1759) >= 1
&& amountInInventory(player, Items.POTATOES10_5438) >= 1
&& amountInInventory(player, Items.CANDLE_36) >= 1
) {
setQuestStage(player, Quests.ENLIGHTENED_JOURNEY, EnlightenedJourney.STAGE_ORIGAMI_BALLOON)
loadLabel(player, "materials_are_gathered")
} else {
loadLabel(player, "missing_materials")
}
}
label("failure_not_option")
npc("Failure is not an option.")
label("what_materials_needed")
npc("You will need to get three sheets of papyrus, one ball of wool, one full sack of potatoes, and one unlit candle.")
label("missing_materials")
exec { player, _ ->
val papyrus = amountInInventory(player, Items.PAPYRUS_970)
val wool = amountInInventory(player, Items.BALL_OF_WOOL_1759) >= 1
val potatoes = amountInInventory(player, Items.POTATOES10_5438) >= 1
val candle = amountInInventory(player, Items.CANDLE_36) >= 1
val hasAllPapyrus = papyrus >= 3
val hasSomePapyrus = papyrus in 1..2
val hasNoPapyrus = papyrus == 0
when {
hasNoPapyrus && !wool && !potatoes && !candle -> loadLabel(player, "missing_all_materials")
hasSomePapyrus && !wool && !potatoes && !candle -> loadLabel(player, "missing_papyrus_wool_candle_and_potatoes")
!hasAllPapyrus && wool && potatoes && candle -> loadLabel(player, "missing_more_papyrus")
hasAllPapyrus && !wool && potatoes && candle -> loadLabel(player, "missing_wool")
hasAllPapyrus && wool && potatoes && !candle -> loadLabel(player, "missing_candle")
hasAllPapyrus && wool && !potatoes && candle -> loadLabel(player, "missing_potatoes")
!hasAllPapyrus && !wool && potatoes && candle -> loadLabel(player, "missing_papyrus_and_wool")
!hasAllPapyrus && wool && potatoes && !candle -> loadLabel(player, "missing_papyrus_and_candle")
!hasAllPapyrus && wool && !potatoes && candle -> loadLabel(player, "missing_papyrus_and_potatoes")
hasAllPapyrus && !wool && potatoes && !candle -> loadLabel(player, "missing_wool_and_candle")
hasAllPapyrus && !wool && !potatoes && candle -> loadLabel(player, "missing_wool_and_potatoes")
hasAllPapyrus && wool && !potatoes && !candle -> loadLabel(player, "missing_candle_and_potatoes")
!hasAllPapyrus && !wool && potatoes && !candle -> loadLabel(player, "missing_wool_papyrus_and_candle")
!hasAllPapyrus && !wool && !potatoes && candle -> loadLabel(player, "missing_wool_papyrus_and_potatoes")
!hasAllPapyrus && wool && !potatoes && !candle -> loadLabel(player, "missing_papyrus_potatoes_and_candle")
hasAllPapyrus && !wool && !potatoes && !candle -> loadLabel(player, "missing_wool_candle_and_potatoes")
}
}
label("missing_all_materials")
npc(FacialExpression.SAD_TALKING, "You don't seem to have any of the materials.")
npc(FacialExpression.SAD_TALKING, "You need a ball of wool, three sheets of papyrus, an unlit candle, and a full sack of potatoes.")
label("missing_more_papyrus")
npc(FacialExpression.SAD_TALKING, "You need more papyrus.")
label("missing_papyrus_wool_candle_and_potatoes")
npc(FacialExpression.SAD_TALKING, "You need more papyrus, a ball of wool, an unlit candle, and a full sack of potatoes.")
label("missing_wool")
npc(FacialExpression.SAD_TALKING, "You need a ball of wool.")
label("missing_candle")
npc(FacialExpression.SAD_TALKING, "You need an unlit candle.")
label("missing_potatoes")
npc(FacialExpression.SAD_TALKING, "You need a full sack of potatoes.")
label("missing_papyrus_and_wool")
npc(FacialExpression.SAD_TALKING, "You need more papyrus and a ball of wool.")
label("missing_papyrus_and_candle")
npc(FacialExpression.SAD_TALKING, "You need papyrus and an unlit candle.")
label("missing_papyrus_and_potatoes")
npc(FacialExpression.SAD_TALKING, "You need more papyrus and a full sack of potatoes.")
label("missing_wool_and_candle")
npc(FacialExpression.SAD_TALKING, "You need a ball of wool and an unlit candle.")
label("missing_wool_and_potatoes")
npc(FacialExpression.SAD_TALKING, "You need a ball of wool and a full sack of potatoes.")
label("missing_candle_and_potatoes")
npc(FacialExpression.SAD_TALKING, "You need an unlit candle and a full sack of potatoes.")
label("missing_wool_papyrus_and_candle")
npc(FacialExpression.SAD_TALKING, "You need a ball of wool, an unlit candle, and papyrus.")
label("missing_wool_papyrus_and_potatoes")
npc(FacialExpression.SAD_TALKING, "You need a ball of wool, papyrus, and a full sack of potatoes.")
label("missing_papyrus_potatoes_and_candle")
npc(FacialExpression.SAD_TALKING, "You need more papyrus, a full sack of potatoes, and an unlit candle.")
label("missing_wool_candle_and_potatoes")
npc(FacialExpression.SAD_TALKING, "You need a ball of wool, an unlit candle, and a full sack of potatoes.")
label("materials_are_gathered")
npc(FacialExpression.HAPPY, "Good, you have everything! Now, I need you to create an origami balloon.")
player(FacialExpression.HALF_THINKING, "How do you make the origami balloon?")
npc(FacialExpression.HAPPY, "First, use the papyrus on the ball of wool. The papyrus is folded into an origami box and the yarn will support the heat source.")
npc(FacialExpression.HAPPY, "Next, add the unlit candle to the balloon structure. It will act as the heat source.")
npc(FacialExpression.HAPPY, "Once you have done that let me know and we will begin our experiment.")
label(questStageLabel(EnlightenedJourney.STAGE_ORIGAMI_BALLOON))
exec { player, _ ->
if (amountInInventory(player, Items.ORIGAMI_BALLOON_9934) >= 1) {
loadLabel(player, "first_experiment")
} else {
loadLabel(player, "no_origami_balloon")
}
}
label("no_origami_balloon")
player("How do you make the origami balloon?")
npc("First, use the papyrus on the ball of wool. The papyrus is folded into an origami box and the yarn will support the heat source.")
npc("Next, add the unlit candle to the balloon structure. It will act as the heat source.")
npc("Once you have done that let me know and we will begin our experiment.")
label("first_experiment")
player(FacialExpression.HAPPY, "I finished the origami balloon!")
npc(FacialExpression.HAPPY, "Wonderful! I'll take that, and we'll conduct our first experiment.")
exec { player, _ ->
removeItem(player, Item(Items.ORIGAMI_BALLOON_9934, 1))
BalloonExperimentCutscene(player).start()
}
label(questStageLabel(EnlightenedJourney.STAGE_FIRST_EXPERIMENT))
npc(FacialExpression.HAPPY, "Do you have the other two sheets of papyrus and a full sack of potatoes?")
options(
DialogueOption("has_second_materials", "Yes, I have them here."),
DialogueOption("misplaced_second_materials", "Oh, I've misplaced them.")
)
label("has_second_materials")
exec { player, _ ->
val hasPapyrus = amountInInventory(player, Items.PAPYRUS_970) >= 2
val hasPotatoes = amountInInventory(player, Items.POTATOES10_5438) >= 1
when {
hasPapyrus && hasPotatoes -> loadLabel(player, "second_experiment")
!hasPapyrus && hasPotatoes -> loadLabel(player, "missing_papyrus_second")
hasPapyrus && !hasPotatoes -> loadLabel(player, "missing_potatoes_second")
else -> loadLabel(player, "missing_both_second")
}
}
label("misplaced_second_materials")
npc("Failure is not an option.")
label("missing_papyrus_second")
npc(FacialExpression.SAD_TALKING, "You need more papyrus.")
label("missing_potatoes_second")
npc(FacialExpression.SAD_TALKING, "You need a full sack of potatoes.")
label("missing_both_second")
npc(FacialExpression.SAD_TALKING, "You need a full sack of potatoes and more papyrus.")
label("second_experiment")
npc(FacialExpression.HAPPY, "Commendable. If I may have those, I will construct this experiment.")
exec { player, _ ->
removeItem(player, Item(Items.PAPYRUS_970, 2))
removeItem(player, Item(Items.POTATOES10_5438, 1))
SecondBalloonExperimentCutscene(player).start()
}
label(questStageLabel(EnlightenedJourney.STAGE_SECOND_EXPERIMENT))
player(FacialExpression.HALF_THINKING, "Those peasants... where did they come from?")
npc("Ahh, the flash mob phenomenon. Many have hypothesized that they are beings of great power sent to smite those who question the gods.")
player(FacialExpression.HALF_THINKING, "And this isn't worrying because...?")
npc(FacialExpression.HAPPY, "Don't worry! I know exactly what I'm doing.")
npc(FacialExpression.HAPPY, "Those experiments went extraordinarily well.")
player(FacialExpression.HALF_WORRIED, "Was I the ONLY one who saw them burning?", "BURNING???")
npc(FacialExpression.HAPPY, "Yes, very well indeed. Now we will start building the balloon that will carry us off the island.")
npc(FacialExpression.HAPPY, "This task will be much greater than the last two. I hope you are prepared for it.")
player("This is madness.")
npc(FacialExpression.HAPPY, "You need to get the following items:")
npc(FacialExpression.HAPPY, "Yellow dye", "Red dye", "Ten pieces of silk", "A clay bowl")
npc(FacialExpression.HAPPY, "and eight sandbags.")
npc(FacialExpression.HAPPY, "Sandbags can be made by getting empty sacks and filling them at the sand pit here on Entrana.")
npc(FacialExpression.HAPPY, "However, there are other sand pits around the world that will work as well; there is one in Yanille, Rellekka, and Zanaris.")
npc("You can bring items back to me as you get them, while you are waiting for the tree to grow.")
player(FacialExpression.HALF_THINKING, "What tree?")
npc("I am going to give you a willow sapling and a basket of apples. You must plant the willow sapling at a tree patch.")
npc("If you give the basket of apples to the gardener near the patch, he will look after the tree for you while it grows.")
npc("Don't lose the sapling! It took me a long time to save up enough for one. If you do, you'll have to pay me 30,000 coins for a new one.")
npc("Once the tree is fully grown, cut twelve branches from it using secateurs. Bring the branches back here and use them on the metal frame on the platform to create the basket.")
npc(FacialExpression.HAPPY, "Here you go. Now be very careful not to lose it!")
exec { player, _ ->
addItem(player, Items.AUGUSTES_SAPLING_9932)
addItem(player, Items.APPLES5_5386)
setQuestStage(player, Quests.ENLIGHTENED_JOURNEY, EnlightenedJourney.STAGE_MAIN_BALLOON)
}
label(questStageLabel(EnlightenedJourney.STAGE_MAIN_BALLOON))
npc(FacialExpression.HAPPY, "Do you have anything for me?")
options(
DialogueOption("yes_give_items", "Yes, I want to give you some items."),
DialogueOption("trouble_finding_items", "I'm having trouble finding some items."),
DialogueOption("lost_sapling_check", "I have lost my willow sapling. Can I buy a replacement?",
optionIf = { player, _ ->
!getAttribute(player, EnlightenedJourney.ATTRIBUTE_SAPLING_PLANTED, false)
}),
)
label("yes_give_items")
options(
DialogueOption(
"give_dye", "Dye.", "Here's the dye.", FacialExpression.NEUTRAL, false,
optionIf = { player, _ ->
!getAttribute(player, EnlightenedJourney.ATTRIBUTE_YELLOW_DYE_DELIVERED, false) ||
!getAttribute(player, EnlightenedJourney.ATTRIBUTE_RED_DYE_DELIVERED, false)
}),
DialogueOption(
"give_sandbags", "Sandbags.", "Here are the sandbags.", FacialExpression.NEUTRAL, false,
optionIf = { player, _ ->
!getAttribute(player, EnlightenedJourney.ATTRIBUTE_SANDBAGS_DELIVERED, false)
}),
DialogueOption(
"give_silk", "Silk.", "Here's the silk.", FacialExpression.NEUTRAL, false,
optionIf = { player, _ ->
!getAttribute(player, EnlightenedJourney.ATTRIBUTE_SILK_DELIVERED, false)
}),
DialogueOption(
"give_bowl", "Bowl.", "Here's the bowl.", FacialExpression.NEUTRAL, false,
optionIf = { player, _ ->
!getAttribute(player, EnlightenedJourney.ATTRIBUTE_BOWL_DELIVERED, false)
}),
DialogueOption("nowhere", "Never mind."),
)
label("all_items_delivered")
npc(FacialExpression.HAPPY, "That's the last of it!")
goto(questStageLabel(EnlightenedJourney.STAGE_WILLOW_BASKET))
label("noted_item")
npc(FacialExpression.SAD, "What am I supposed to do with a note? I can't make a balloon from notes!")
goto("yes_give_items")
label("give_dye")
exec { player, _ ->
val needsYellow = !getAttribute(player, EnlightenedJourney.ATTRIBUTE_YELLOW_DYE_DELIVERED, false)
val needsRed = !getAttribute(player, EnlightenedJourney.ATTRIBUTE_RED_DYE_DELIVERED, false)
val hasNoted = (needsYellow && amountInInventory(player, Items.YELLOW_DYE_1766) >= 1) ||
(needsRed && amountInInventory(player, Items.RED_DYE_1764) >= 1)
val hasYellow = needsYellow && amountInInventory(player, Items.YELLOW_DYE_1765) >= 1
val hasRed = needsRed && amountInInventory(player, Items.RED_DYE_1763) >= 1
when {
hasYellow && hasRed -> loadLabel(player, "give_both_dyes")
hasYellow -> loadLabel(player, "give_yellow_dye")
hasRed -> loadLabel(player, "give_red_dye")
hasNoted -> loadLabel(player, "noted_item")
else -> loadLabel(player, "no_dye")
}
}
label("give_both_dyes")
npc(FacialExpression.HAPPY, "Ah, wonderful, yellow dye. Thank you.")
exec { player, _ ->
removeItem(player, Item(Items.YELLOW_DYE_1765, 1))
setAttribute(player, EnlightenedJourney.ATTRIBUTE_YELLOW_DYE_DELIVERED, true)
}
npc(FacialExpression.HAPPY, "Red dye. Thank you.")
exec { player, _ ->
removeItem(player, Item(Items.RED_DYE_1763, 1))
setAttribute(player, EnlightenedJourney.ATTRIBUTE_RED_DYE_DELIVERED, true)
if (checkAllItemsDelivered(player)) {
loadLabel(player, "all_items_delivered")
} else {
loadLabel(player, "yes_give_items")
}
}
label("give_yellow_dye")
npc(FacialExpression.HAPPY, "Ah, wonderful, yellow dye. Thank you.")
exec { player, _ ->
removeItem(player, Item(Items.YELLOW_DYE_1765, 1))
setAttribute(player, EnlightenedJourney.ATTRIBUTE_YELLOW_DYE_DELIVERED, true)
if (checkAllItemsDelivered(player)) {
loadLabel(player, "all_items_delivered")
} else {
loadLabel(player, "yes_give_items")
}
}
label("give_red_dye")
npc(FacialExpression.HAPPY, "Red dye! Thank you.")
exec { player, _ ->
removeItem(player, Item(Items.RED_DYE_1763, 1))
setAttribute(player, EnlightenedJourney.ATTRIBUTE_RED_DYE_DELIVERED, true)
if (checkAllItemsDelivered(player)) {
loadLabel(player, "all_items_delivered")
} else {
loadLabel(player, "yes_give_items")
}
}
label("no_dye")
npc("You don't have any dye with you.")
npc("I need red and yellow dye for the balloon.")
goto("yes_give_items")
label("give_sandbags")
exec { player, _ ->
val hasSandbags = amountInInventory(player, Items.SANDBAG_9943) >= 8
when {
hasSandbags -> loadLabel(player, "take_sandbags")
else -> loadLabel(player, "not_enough_sandbags")
}
}
label("take_sandbags")
npc(FacialExpression.HAPPY, "Sandbags, thank you. This will allow us to change height.")
exec { player, _ ->
removeItem(player, Item(Items.SANDBAG_9943, 8))
setAttribute(player, EnlightenedJourney.ATTRIBUTE_SANDBAGS_DELIVERED, true)
if (checkAllItemsDelivered(player)) {
loadLabel(player, "all_items_delivered")
} else {
loadLabel(player, "yes_give_items")
}
}
label("not_enough_sandbags")
npc(FacialExpression.SAD, "You don't have enough sandbags. Please bring me eight.")
npc(FacialExpression.HAPPY, "Sandbags can be made by getting empty sacks and filling them at the sandpit here on Entrana.")
npc(FacialExpression.HAPPY, "However, there are other sand pits around the world that will work as well.")
goto("yes_give_items")
label("give_silk")
exec { player, _ ->
val hasEnough = amountInInventory(player, Items.SILK_950) >= 10
val hasNoted = amountInInventory(player, Items.SILK_951) >= 1
when {
hasEnough -> loadLabel(player, "take_silk")
hasNoted -> loadLabel(player, "noted_item")
else -> loadLabel(player, "not_enough_silk")
}
}
label("take_silk")
npc(FacialExpression.HAPPY, "Silk for the balloon, thank you.")
exec { player, _ ->
removeItem(player, Item(Items.SILK_950, 10))
setAttribute(player, EnlightenedJourney.ATTRIBUTE_SILK_DELIVERED, true)
if (checkAllItemsDelivered(player)) {
loadLabel(player, "all_items_delivered")
} else {
loadLabel(player, "yes_give_items")
}
}
label("not_enough_silk")
npc("You don't have enough silk. Please bring me ten pieces.")
goto("yes_give_items")
label("give_bowl")
exec { player, _ ->
val hasBowl = amountInInventory(player, Items.BOWL_1923) >= 1
val hasNoted = amountInInventory(player, Items.BOWL_1924) >= 1
when {
hasBowl -> loadLabel(player, "take_bowl")
hasNoted -> loadLabel(player, "noted_item")
else -> loadLabel(player, "no_bowl")
}
}
label("take_bowl")
npc(FacialExpression.HAPPY, "Ah the bowl. This will be used to hold the fuel while it heats the air in the balloon.")
exec { player, _ ->
removeItem(player, Item(Items.BOWL_1923, 1))
setAttribute(player, EnlightenedJourney.ATTRIBUTE_BOWL_DELIVERED, true)
if (checkAllItemsDelivered(player)) {
loadLabel(player, "all_items_delivered")
} else {
loadLabel(player, "yes_give_items")
}
}
label("no_bowl")
npc("I need a plain clay-fired bowl; they're quite ease to come by.") //"ease" seems to be an authentic typo.
goto("yes_give_items")
label("lost_sapling_check")
exec { player, _ ->
val hasSapling = amountInInventory(player, Items.AUGUSTES_SAPLING_9932) >= 1 ||
amountInBank(player, Items.AUGUSTES_SAPLING_9932) >= 1 ||
getAttribute(player, EnlightenedJourney.ATTRIBUTE_SAPLING_PLANTED, false)
if (hasSapling) {
loadLabel(player, "dont_be_greedy")
} else {
loadLabel(player, "sapling_replacement_offer")
}
}
label("dont_be_greedy")
npc("You already have one! Don't be greedy.")
label("sapling_replacement_offer")
npc("It will cost you 30,000 gold coins to replace it, do you want to pay that?")
options(
DialogueOption("sapling_replacement_yes", "Yes."),
DialogueOption("nowhere", "No way!"),
)
label("sapling_replacement_yes")
exec { player, _ ->
if (amountInInventory(player, Items.COINS_995) >= 30000) {
loadLabel(player, "sapling_replacement_success")
} else {
loadLabel(player, "sapling_replacement_failure")
}
}
label("sapling_replacement_success")
npc("Here you go. Now be very careful not to lose it again!")
exec { player, _ ->
removeItem(player, Item(Items.COINS_995, 30000))
addItem(player, Items.AUGUSTES_SAPLING_9932, 1)
}
//fallback, no source for what happens when the player has insufficient funds.
label("sapling_replacement_failure")
exec { player, _ ->
sendMessage(player, "You need 30,000 coins.")
}
label("trouble_finding_items")
npc("What do you need help with?")
options(
DialogueOption(
"hint_dye", "Dye.", skipPlayer = true,
optionIf = { player, _ ->
!getAttribute(player, EnlightenedJourney.ATTRIBUTE_YELLOW_DYE_DELIVERED, false) ||
!getAttribute(player, EnlightenedJourney.ATTRIBUTE_RED_DYE_DELIVERED, false)
}),
DialogueOption(
"hint_sandbags", "Sandbags.", skipPlayer = true,
optionIf = { player, _ ->
!getAttribute(player, EnlightenedJourney.ATTRIBUTE_SANDBAGS_DELIVERED, false)
}),
DialogueOption(
"hint_silk", "Silk.", skipPlayer = true,
optionIf = { player, _ ->
!getAttribute(player, EnlightenedJourney.ATTRIBUTE_SILK_DELIVERED, false)
}),
DialogueOption(
"hint_bowl", "Bowl.", skipPlayer = true,
optionIf = { player, _ ->
!getAttribute(player, EnlightenedJourney.ATTRIBUTE_BOWL_DELIVERED, false)
}),
DialogueOption("nowhere", "Never mind.", skipPlayer = true)
)
label("hint_dye")
npc("I was told a while ago that there was a witch who made dye in Draynor Village. Maybe you should start by looking there.")
label("hint_sandbags")
npc("Sandbags can be made by getting empty sacks and filling them at the sandpit here on Entrana.")
npc("However, there are other sand pits around the world that will work as well; there is one in Yanille, Rellekka, and Zanaris.")
label("hint_silk")
npc("Hmm, I believe silk is imported from the desert. Perhaps someone there can tell you where to find it.")
label("hint_bowl")
npc("I think there is a spare one in the glass blower's house. I rent a room from him there, so I don't think he'll mind you taking it.")
label(questStageLabel(EnlightenedJourney.STAGE_WILLOW_BASKET))
npc(FacialExpression.HAPPY, "You just need to build the basket and I can finish the balloon! How are you getting on with the willow?")
options(
DialogueOption("lost_sapling_check", "I have lost my willow sapling. Can I buy a replacement?",
optionIf = { player, _ ->
!getAttribute(player, EnlightenedJourney.ATTRIBUTE_SAPLING_PLANTED, false)
}),
DialogueOption("build_the_basket", "What do I do again?", expression = FacialExpression.HALF_THINKING),
DialogueOption("nowhere", "Fine thanks."),
)
label("build_the_basket")
npc(FacialExpression.HAPPY, "Use the willow sapling I gave you to grow a willow tree. Cut twelve branches from it using secateurs. Use the branches on the platform here.")
label(questStageLabel(EnlightenedJourney.STAGE_FLIGHT))
npc(FacialExpression.HAPPY, "Well, let's get going!")
options(
DialogueOption("wait_tell_me", "Wait; tell me what we're doing."),
DialogueOption("maiden_voyage", "Okay."),
DialogueOption("nowhere", "No, I'm not ready.")
)
label("wait_tell_me")
npc("Theoretically, we are ready for the maiden voyage.")
player("Theoretically?")
npc("Well, of course. With you piloting I am sure we will survive.")
player("WHAT!? This is your balloon! Why aren't you going to pilot it? And what do mean 'survive'? You never said anything about me flying this bird!")
npc("Don't be silly. I'm not going to pilot it. We will be safe in the basket, the only thing we will lose are the logs. You will pilot it, won't you?")
player("Fine, I will. So how do I control the balloon?")
npc("Wonderful! Let me explain my hypothesis on how to control a balloon.")
player("Oh no, not another hypothesis!")
npc("The balloon needs ten normal logs for fuel. I believe the balloon will be controlled based on weight, so you must not have more than 40kg with you.")
npc("Also, I seem to have lost my tinderbox, do you think you could bring one? We need it to light the fire.")
player("And here, I thought all the legwork was done. Fine, I'll bring a tinderbox.")
npc("Right! So, the balloon mechanics.")
npc("Your prime direction will always be to land the balloon on the target at the end of our route. I've written to some friends in Taverley who have kindly painted one on the ground there.")
npc("We must avoid everything, because the balloon is very fragile. Even clouds will be dangerous! But if my calculations are correct, we will be able to squeeze through some surprising spaces, if you pilot it well.")
npc("Those sandbags you made earlier will give the balloon a big lift when dropped, whereas adding the logs will only make it rise a little bit. But don't use them up too quickly! We have no way of replenishing them.")
npc("If you do run out of logs and sandbags you will not be able to go up any longer. Just make the best of it and hope we can make it to our destination without going upwards.")
npc("I've added two ropes, the red one is an emergency rope, it will let the hot air out of the balloon and we will drop quickly. The other rope will only drop us a little.")
npc("If we get into tribb...beg your pardon, trouble, in all likelihood we will crash. But do not fear! We should be fine. Just make sure you come back to Entrana so we can try again.")
npc("If it all goes horribly wrong, you can always bail. If we're still over Entrana, we can land quickly and try again. However, once past the island, we will crash.")
npc("Are you ready to go?")
options(
DialogueOption("maiden_voyage", "Okay."),
DialogueOption("nowhere", "No, I'm not ready.")
)
label("maiden_voyage")
exec { player, _ ->
when {
player.settings.weight > 40.0 -> loadLabel(player, "maiden_voyage_heavy")
amountInInventory(player, Items.TINDERBOX_590) < 1 -> loadLabel(player, "maiden_voyage_tinderbox")
amountInInventory(player, Items.LOGS_1511) < 10 -> loadLabel(player, "maiden_voyage_logs")
else -> loadLabel(player, "maiden_voyage_start")
}
}
label("maiden_voyage_heavy")
npc("You have too much weight in your bag. You may only bring 40kg of weight.")
label("maiden_voyage_tinderbox")
npc("You need a tinderbox.")
label("maiden_voyage_logs")
npc("You don't have enough logs. You need ten normal logs.")
label("maiden_voyage_start")
exec { player, _ ->
if (removeItem(player, Item(Items.LOGS_1511, 10))) {
BalloonFlightInterface.startQuestFlight(player)
} else {
loadLabel(player, "maiden_voyage_logs")
}
}
label(questStageLabel(EnlightenedJourney.STAGE_CLAIM_REWARD))
npc(FacialExpression.HAPPY, "We have travelled in a way that no man has travelled before! The next step will be to go where no men have gone before.")
npc(FacialExpression.HAPPY, "@name, it has been a privilege flying with you.")
npc(FacialExpression.HAPPY, "For all your hard work, I give you this jacket and cap for future ballooning adventures.")
exec { player, _ ->
if (freeSlots(player) < 2) {
loadLabel(player, "reward_no_space")
} else {
finishQuest(player, Quests.ENLIGHTENED_JOURNEY)
}
}
label("reward_no_space")
npc("You don't have enough space in your inventory. Speak to me again when you do.")
label(questStageLabel(EnlightenedJourney.STAGE_COMPLETE))
exec { player, talkingNpc ->
if (talkingNpc.originalId == 5062) {
loadLabel(player, "post_quest_taverley")
} else {
loadLabel(player, "post_quest_entrana")
}
}
label("post_quest_taverley")
player("So what are you going to do now?")
npc("I am considering starting a balloon enterprise. People all over Gielinor will be able to travel in a new, exciting way.")
npc("As my first assistant, you will always be welcome to use a balloon. You'll have to bring your own fuel, though.")
player("Thanks!")
npc("I will base my operations in Entrana. If you'd like to travel to new places, come see me there.")
npc("Do you want to use the balloon? Just so you know, some locations require special logs and high Firemaking skills.")
options(
DialogueOption("post_quest_yes", "Yes."),
DialogueOption("nowhere", "No."),
DialogueOption("post_quest_origami", "Could you tell me how to make an origami balloon?")
)
label("post_quest_yes")
npc("Weapons are not allowed on Entrana. I must search you first.")
line("You are quickly searched.")
exec { player, _ ->
if (ItemDefinition.canEnterEntrana(player)) {
loadLabel(player, "enter_entrana_allowed")
} else {
loadLabel(player, "enter_entrana_disallowed")
}
}
label("enter_entrana_allowed")
exec { player, _ ->
teleport(player, Location.create(2808, 3354, 0))
}
label("enter_entrana_disallowed")
npc("I'm sorry, no weapons or armour may be worn to Entrana. Come back when you have laid them down.")
label("post_quest_origami")
npc("First, use the papyrus on the ball of wool. The papyrus is folded into an origami box and the yarn will support the heat source.")
npc("Next, add the unlit candle to the balloon structure. It will act as the heat source.")
npc("Now you have an origami balloon!")
npc("If you use your origami balloon with any dye, you can change the colour.")
npc("You can release the balloon and watch it float in the air, just use a tinderbox on it.")
label("post_quest_entrana")
exec { player, _ ->
if (getAttribute(player, EnlightenedJourney.ATTRIBUTE_AUGUSTE_POST_EXPLANATION, false)) {
loadLabel(player, "post_quest_options")
} else {
loadLabel(player, "post_quest_greeting")
}
}
label("post_quest_greeting")
npc("Hello, @name. It is good to see you again. My enterprise has grown since we last met. I've expanded all over Gielinor!")
npc("If you'd like to go to a new location, you will have to help fly the balloon again. Some of the routes are very dangerous!")
npc("But each route you complete will earn you Firemaking experience.")
npc("Once you've opened a route, it only costs 1 log of the corresponding type to travel there again, from any location you've already opened!")
npc("I have also hired assistants to watch over the balloons at each new location we go to. They will help you get where you want to go.")
exec { player, _ ->
setAttribute(player, EnlightenedJourney.ATTRIBUTE_AUGUSTE_POST_EXPLANATION, true)
}
goto("post_quest_options")
label("post_quest_options")
npc("Do you want to use the balloon? Just so you know, some locations require special logs and high Firemaking skills.")
options(
DialogueOption("yes_fly_balloon", "Yes."),
DialogueOption("nowhere", "No."),
DialogueOption("post_quest_origami", "Could you tell me how to make an origami balloon?"),
DialogueOption("replace_bomber_clothes", "Could you replace some items for me?") { player, _ -> hasLostBomberReward(player) },
DialogueOption("combine_cap_goggles", "Could you combine my cap and goggles?") { player, _ -> hasCapAndGoggles(player) }
)
label("replace_bomber_clothes")
exec { player, _ ->
if (hasBomberCap(player)) {
loadLabel(player, "bomber_cap_owned")
} else if (freeSlots(player) < 1) {
loadLabel(player, "bomber_cap_no_space")
} else {
loadLabel(player, "bomber_cap_replace")
}
}
label("bomber_cap_owned")
npc("You still seem to have your cap.")
goto("replace_bomber_jacket")
label("bomber_cap_no_space")
npc("You don't have enough free space for your cap.")
goto("replace_bomber_jacket")
label("bomber_cap_replace")
npc("Here's your cap. Try to keep better track of it.")
exec { player, _ ->
addItem(player, Items.BOMBER_CAP_9945)
}
goto("replace_bomber_jacket")
label("replace_bomber_jacket")
exec { player, _ ->
if (hasBomberJacket(player)) {
loadLabel(player, "bomber_jacket_owned")
} else if (freeSlots(player) < 1) {
loadLabel(player, "bomber_jacket_no_space")
} else {
loadLabel(player, "bomber_jacket_replace")
}
}
label("bomber_jacket_owned")
npc("You don't need a new jacket; you still have your original one.")
label("bomber_jacket_no_space")
npc("You don't have enough free space for your jacket.")
label("bomber_jacket_replace")
npc("Here's your jacket, to keep you warm during flight.")
exec { player, _ ->
addItem(player, Items.BOMBER_JACKET_9944)
}
label("combine_cap_goggles")
exec { player, _ ->
if (inEquipment(player, Items.BOMBER_CAP_9945) || inEquipment(player, Items.GNOME_GOGGLES_9472)) {
loadLabel(player, "combine_cap_goggles_worn")
} else if (amountInInventory(player, Items.BOMBER_CAP_9945) < 1 || amountInInventory(player, Items.GNOME_GOGGLES_9472) < 1) {
loadLabel(player, "combine_cap_goggles_no_space")
} else {
loadLabel(player, "combine_cap_goggles_success")
}
}
label("combine_cap_goggles_worn")
npc("I can't combine them if you are wearing them.")
label("combine_cap_goggles_no_space")
npc("You don't have enough free space for the cap and goggles.")
label("combine_cap_goggles_success")
npc("There you go! You look like a true airship pilot now.")
exec { player, _ ->
removeItem(player, Item(Items.BOMBER_CAP_9945, 1))
removeItem(player, Item(Items.GNOME_GOGGLES_9472, 1))
addItem(player, Items.CAP_AND_GOGGLES_9946, 1)
}
label("yes_fly_balloon")
exec { player, _ ->
BalloonTravelInterface.open(player, BalloonTravelLocation.ENTRANA)
}
}
}

View file

@ -0,0 +1,281 @@
package content.region.misc.entrana.quest.enlightenedjourney.quest
import content.data.Quests
import core.api.addItemOrDrop
import core.api.getAttribute
import core.api.getQuestPoints
import core.api.getQuestStage
import core.api.hasLevelStat
import core.api.inInventory
import core.api.removeAttributes
import core.api.rewardXP
import core.api.sendItemZoomOnInterface
import core.api.setInterfaceText
import core.api.setVarbit
import core.game.node.entity.player.Player
import core.game.node.entity.player.link.quest.Quest
import core.game.node.entity.skill.Skills
import core.plugin.Initializable
import org.rs09.consts.Items
/**
* Enlightened Journey quest.
* @author Edith
*/
@Initializable
class EnlightenedJourney : Quest(Quests.ENLIGHTENED_JOURNEY, 55, 54, 1, 0,
VARBIT_QUEST_PROGRESS_2866, QUEST_PROGRESS_NOT_STARTED_0, QUEST_PROGRESS_STARTED_1, QUEST_PROGRESS_COMPLETED_200) {
companion object {
val questName = Quests.ENLIGHTENED_JOURNEY
/** Quest progress varbit and states used by the quest tab. */
const val VARBIT_QUEST_PROGRESS_2866 = 2866
const val QUEST_PROGRESS_NOT_STARTED_0 = 0
const val QUEST_PROGRESS_STARTED_1 = 1
const val QUEST_PROGRESS_COMPLETED_200 = 200
/** Entrana balloon scenery state varbit. */
const val VARBIT_ENTRANA_BALLOON_2867 = 2867
const val ENTRANA_BALLOON_COMPLETE_2 = 2
/** Taverley balloon scenery and NPC state varbit. */
const val VARBIT_TAVERLEY_BALLOON_2868 = 2868
const val TAVERLEY_BALLOON_AUGUSTE_1 = 1
const val TAVERLEY_BALLOON_STAN_2 = 2
/** Standard balloon route states. */
const val BALLOON_ROUTE_LOCKED_0 = 0
const val BALLOON_ROUTE_UNLOCKED_1 = 1
/** Completing a ballooning route unlocks the respective destination. */
const val VARBIT_CASTLEWARS_BALLOON_2869 = 2869
const val VARBIT_GRANDTREE_BALLOON_2870 = 2870
const val VARBIT_CRAFTINGGUILD_BALLOON_2871 = 2871
const val VARBIT_VARROCK_BALLOON_2872 = 2872
/** Attributes used during the quest. */
const val ATTRIBUTE_YELLOW_DYE_DELIVERED = "/save:enlightenedjourney_yellow_dye_delivered"
const val ATTRIBUTE_RED_DYE_DELIVERED = "/save:enlightenedjourney_red_dye_delivered"
const val ATTRIBUTE_SANDBAGS_DELIVERED = "/save:enlightenedjourney_sandbags_delivered"
const val ATTRIBUTE_SILK_DELIVERED = "/save:enlightenedjourney_silk_delivered"
const val ATTRIBUTE_BOWL_DELIVERED = "/save:enlightenedjourney_bowl_delivered"
const val ATTRIBUTE_SAPLING_PLANTED = "/save:enlightenedjourney_sapling_planted"
/** Player has started the quest and Auguste is explaining what a "balloo-what?" is. */
const val STAGE_QUEST_STARTED = 5
/** Player has agreed to help Auguste and needs to gather the materials for the first balloon experiment. */
const val STAGE_GATHER_MATERIALS = 10
/** Player has gathered the first experiment materials and needs to make an origami balloon. */
const val STAGE_ORIGAMI_BALLOON = 20
/** Player has made an origami balloon and needs to give it to Auguste for the first balloon experiment. */
const val STAGE_FIRST_EXPERIMENT = 30
/** Player has completed the first balloon experiment and needs to prepare the second balloon experiment. */
const val STAGE_SECOND_EXPERIMENT = 40
/** Player has completed both balloon experiments and needs to gather the materials for the main balloon. */
const val STAGE_MAIN_BALLOON = 50
/** Player has delivered the main balloon materials to Auguste and needs to prepare the willow basket. */
const val STAGE_WILLOW_BASKET = 60
/** Player has built the main balloon and needs to fly it to Taverley. */
const val STAGE_FLIGHT = 70
/** Player has flown the balloon to Taverley and can now claim the quest reward from Auguste. */
const val STAGE_CLAIM_REWARD = 80
/** Player has completed Enlightened Journey. */
const val STAGE_COMPLETE = 100
/** Taverley balloon NPC state: Auguste "returns" to Entrana and Assistant Stan now manages the route from Taverley. */
const val ATTRIBUTE_TAVERLEY_AUGUSTE_REPLACED = "/save:enlightenedjourney_taverley_auguste_replaced"
/** Player has heard Augustes post-quest explanation of the expanded balloon network. */
const val ATTRIBUTE_AUGUSTE_POST_EXPLANATION = "/save:enlightenedjourney_auguste_post_explanation"
}
override fun drawJournal(player: Player, stage: Int) {
super.drawJournal(player, stage)
var line = 12
if (stage == 0) {
line(player, "I can start this quest by speaking to !!Auguste?? on !!Entrana??.", line++)
line(player, "Minimum Requirements:", line++)
line(player, "Level 36 Crafting", line++, hasLevelStat(player, Skills.CRAFTING, 36))
line(player, "Level 30 Farming", line++, hasLevelStat(player, Skills.FARMING, 30))
line(player, "Level 20 Firemaking", line++, hasLevelStat(player, Skills.FIREMAKING, 20))
line(player, "20 Quest Points", line, getQuestPoints(player) >= 20)
}
if (stage >= STAGE_QUEST_STARTED) {
line(player, "I have agreed to help Auguste build an !!air balloon??.", line++, stage > STAGE_QUEST_STARTED)
line(player, "I have no idea what he's talking about.", line++, stage > STAGE_QUEST_STARTED)
line(player, "Auguste thinks if he pumps hot air into a sack it will rise and", line++, stage > STAGE_QUEST_STARTED)
line(player, "take us along with it.", line++, stage > STAGE_QUEST_STARTED)
line(player, "But we're going to run some tests first. Thank goodness.", line++, stage > STAGE_QUEST_STARTED)
}
if (stage in STAGE_GATHER_MATERIALS until STAGE_ORIGAMI_BALLOON) {
line(player, "I need to gather the materials Auguste required:", line++, stage > STAGE_GATHER_MATERIALS)
line(player, "three sheets of papyrus", line++, stage > STAGE_GATHER_MATERIALS || inInventory(player, Items.PAPYRUS_970, 3))
line(player, "a ball of wool", line++, stage > STAGE_GATHER_MATERIALS || inInventory(player, Items.BALL_OF_WOOL_1759))
line(player, "a full sack of potatoes", line++, stage > STAGE_GATHER_MATERIALS || inInventory(player, Items.POTATOES10_5438))
line(player, "one unlit candle.", line++, stage > STAGE_GATHER_MATERIALS || inInventory(player, Items.CANDLE_36))
}
if (stage >= STAGE_ORIGAMI_BALLOON) {
line(player, "I gathered all the materials Auguste required:", line++, true)
line(player, "three sheets of papyrus, a ball of wool,", line++, true)
line(player, "a full sack of potatoes and one unlit candle.", line++, true)
}
if (stage in STAGE_ORIGAMI_BALLOON until STAGE_FIRST_EXPERIMENT) {
line(player, "I need to make the first test balloon with the ball of wool,", line++)
line(player, "a sheet of papyrus and an unlit candle and then give it to", line++)
line(player, "Auguste.", line++)
}
if (stage >= STAGE_FIRST_EXPERIMENT) {
line(player, "I made an !!origami balloon??.", line++, true)
line(player, "Auguste said I could make these any time I want if", line++, true)
line(player, "I have the materials.", line++, true)
}
if (stage >= STAGE_FIRST_EXPERIMENT) {
line(player, "Auguste conducted the first experiment.", line++, stage > STAGE_FIRST_EXPERIMENT)
line(player, "There was an awful lot of fire.", line++, stage > STAGE_FIRST_EXPERIMENT)
}
if (stage in STAGE_FIRST_EXPERIMENT until STAGE_SECOND_EXPERIMENT) {
line(player, "I need to give Auguste two sheets of papyrus and a full", line++)
line(player, "sack of potatoes.", line++)
}
if (stage >= STAGE_SECOND_EXPERIMENT) {
line(player, "Auguste conducted the second experiment.", line++, stage > STAGE_SECOND_EXPERIMENT)
line(player, "A flash mob appeared. They seem to have a grudge against", line++, stage > STAGE_SECOND_EXPERIMENT)
line(player, "science.", line++, stage > STAGE_SECOND_EXPERIMENT)
}
if (stage in STAGE_MAIN_BALLOON until STAGE_WILLOW_BASKET) {
line(player, "I need to gather the supplies for Auguste's main balloon:", line++)
line(player, "red dye", line++, getAttribute(player, ATTRIBUTE_RED_DYE_DELIVERED, false) || inInventory(player, Items.RED_DYE_1763))
line(player, "yellow dye", line++, getAttribute(player, ATTRIBUTE_YELLOW_DYE_DELIVERED, false) || inInventory(player, Items.YELLOW_DYE_1765))
line(player, "ten pieces of silk", line++, getAttribute(player, ATTRIBUTE_SILK_DELIVERED, false) || inInventory(player, Items.SILK_950, 10))
line(player, "a bowl", line++, getAttribute(player, ATTRIBUTE_BOWL_DELIVERED, false) || inInventory(player, Items.BOWL_1923))
line(player, "eight sandbags", line++, getAttribute(player, ATTRIBUTE_SANDBAGS_DELIVERED, false) || inInventory(player, Items.SANDBAG_9943, 8))
}
if (stage in STAGE_WILLOW_BASKET until STAGE_FLIGHT) {
line(player, "I've given all the materials to Auguste, now I just need to", line++)
line(player, "use the 12 willow branches to build the basket.", line++)
}
if (stage >= STAGE_FLIGHT) {
line(player, "I gave Auguste all the supplies and made the basket for the", line++, stage > STAGE_FLIGHT)
line(player, "balloon.", line++, stage > STAGE_FLIGHT)
line(player, "The balloon is all made and looks impressive!", line++, stage > STAGE_FLIGHT)
line(player, "Let's hope it doesn't end the way the experiments did.", line++, stage > STAGE_FLIGHT)
}
if (stage >= STAGE_CLAIM_REWARD) {
line(player, "Whew! We survived our first balloon flight.", line++, stage > STAGE_CLAIM_REWARD)
line(player, "We successfully flew the first balloon to !!Taverley??!", line++, stage > STAGE_CLAIM_REWARD)
line++
}
if (stage >= STAGE_COMPLETE) {
line(player, "%%QUEST COMPLETE!&&", line++)
line++
line(player, "I can now make !!Origami balloons??.", line++)
line(player, "I can also use the !!balloon transport system??.", line++)
line(player, "To go to new locations I should speak to !!Auguste?? on", line++)
line(player, "!!Entrana??.", line)
}
}
override fun hasRequirements(player: Player): Boolean {
return hasLevelStat(player, Skills.CRAFTING, 36) &&
hasLevelStat(player, Skills.FARMING, 30) &&
hasLevelStat(player, Skills.FIREMAKING, 20) &&
getQuestPoints(player) >= 20
}
override fun finish(player: Player) {
super.finish(player)
var line = 10
setInterfaceText(player, "You have completed Enlightened Journey!", 277, 4)
sendItemZoomOnInterface(player, 277, 5, Items.BOMBER_CAP_9945)
drawReward(player, "1 Quest Point", line++)
drawReward(player, "2,000 Crafting XP", line++)
drawReward(player, "3,000 Farming XP", line++)
drawReward(player, "1,500 Woodcutting XP", line++)
drawReward(player, "4,000 Firemaking XP", line++)
drawReward(player, "Balloon Transport System", line++)
drawReward(player, "Origami Balloons", line)
rewardXP(player, Skills.CRAFTING, 2000.0)
rewardXP(player, Skills.FARMING, 3000.0)
rewardXP(player, Skills.WOODCUTTING, 1500.0)
rewardXP(player, Skills.FIREMAKING, 4000.0)
addItemOrDrop(player, Items.BOMBER_JACKET_9944)
addItemOrDrop(player, Items.BOMBER_CAP_9945)
setVarbit(player, VARBIT_QUEST_PROGRESS_2866, QUEST_PROGRESS_COMPLETED_200, true)
setVarbit(player, VARBIT_ENTRANA_BALLOON_2867, ENTRANA_BALLOON_COMPLETE_2, true)
setVarbit(player, VARBIT_TAVERLEY_BALLOON_2868, TAVERLEY_BALLOON_AUGUSTE_1, true)
}
override fun reset(player: Player) {
setVarbit(player, VARBIT_QUEST_PROGRESS_2866, QUEST_PROGRESS_NOT_STARTED_0, true)
setVarbit(player, VARBIT_ENTRANA_BALLOON_2867, BALLOON_ROUTE_LOCKED_0, true)
setVarbit(player, VARBIT_TAVERLEY_BALLOON_2868, BALLOON_ROUTE_LOCKED_0, true)
setVarbit(player, VARBIT_CASTLEWARS_BALLOON_2869, BALLOON_ROUTE_LOCKED_0, true)
setVarbit(player, VARBIT_GRANDTREE_BALLOON_2870, BALLOON_ROUTE_LOCKED_0, true)
setVarbit(player, VARBIT_CRAFTINGGUILD_BALLOON_2871, BALLOON_ROUTE_LOCKED_0, true)
setVarbit(player, VARBIT_VARROCK_BALLOON_2872, BALLOON_ROUTE_LOCKED_0, true)
removeAttributes(
player, ATTRIBUTE_YELLOW_DYE_DELIVERED,
ATTRIBUTE_RED_DYE_DELIVERED,
ATTRIBUTE_SANDBAGS_DELIVERED,
ATTRIBUTE_SILK_DELIVERED,
ATTRIBUTE_BOWL_DELIVERED,
ATTRIBUTE_SAPLING_PLANTED,
ATTRIBUTE_TAVERLEY_AUGUSTE_REPLACED,
ATTRIBUTE_AUGUSTE_POST_EXPLANATION
)
}
override fun updateVarps(player: Player) {
val stage = getQuestStage(player, questName)
setVarbit(
player, VARBIT_QUEST_PROGRESS_2866, when {
stage >= STAGE_COMPLETE -> QUEST_PROGRESS_COMPLETED_200
stage > 0 -> QUEST_PROGRESS_STARTED_1
else -> QUEST_PROGRESS_NOT_STARTED_0
}, true
)
setVarbit(player, VARBIT_ENTRANA_BALLOON_2867, if (stage >= STAGE_FLIGHT) ENTRANA_BALLOON_COMPLETE_2 else BALLOON_ROUTE_LOCKED_0, true)
val taverleyBalloonState = when {
stage >= STAGE_COMPLETE && getAttribute(player, ATTRIBUTE_TAVERLEY_AUGUSTE_REPLACED, false) -> TAVERLEY_BALLOON_STAN_2
stage >= STAGE_CLAIM_REWARD -> TAVERLEY_BALLOON_AUGUSTE_1
else -> BALLOON_ROUTE_LOCKED_0
}
setVarbit(player, VARBIT_TAVERLEY_BALLOON_2868, taverleyBalloonState, true)
}
override fun newInstance(`object`: Any?): Quest {
return this
}
}

View file

@ -0,0 +1,164 @@
package content.region.misc.entrana.quest.enlightenedjourney.quest
import content.data.Quests
import content.region.misc.entrana.quest.enlightenedjourney.cutscene.BalloonConstructionCutscene
import content.region.misc.entrana.quest.enlightenedjourney.quest.EnlightenedJourney.Companion.STAGE_WILLOW_BASKET
import core.api.MapArea
import core.api.addItem
import core.api.amountInInventory
import core.api.animate
import core.api.freeSlots
import core.api.getQuestStage
import core.api.openDialogue
import core.api.playAudio
import core.api.removeItem
import core.api.rewardXP
import core.api.sendMessage
import core.api.setAttribute
import core.game.dialogue.DialogueFile
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.node.entity.Entity
import core.game.node.entity.player.Player
import core.game.node.entity.skill.Skills
import core.game.node.item.Item
import core.game.world.map.zone.ZoneBorders
import org.rs09.consts.Items
import org.rs09.consts.NPCs
import org.rs09.consts.Scenery
/**
* Listeners for the Enlightened Journey quest.
* @author Edith
*/
class EnlightenedJourneyListeners : InteractionListener, MapArea {
companion object {
private const val SOUND_CONSTRUCT_3248 = 3248
}
override fun defineListeners() {
/** Make balloon structure. */
onUseWith(IntType.ITEM, Items.PAPYRUS_970, Items.BALL_OF_WOOL_1759) { player, _, _ ->
if (getQuestStage(player, Quests.ENLIGHTENED_JOURNEY) < EnlightenedJourney.STAGE_ORIGAMI_BALLOON) {
return@onUseWith false
}
if (amountInInventory(player, Items.PAPYRUS_970) >= 1 &&
amountInInventory(player, Items.BALL_OF_WOOL_1759) >= 1
) {
removeItem(player, Item(Items.PAPYRUS_970, 1))
removeItem(player, Item(Items.BALL_OF_WOOL_1759, 1))
addItem(player, Items.BALLOON_STRUCTURE_9933, 1)
playAudio(player, SOUND_CONSTRUCT_3248)
sendMessage(player, "You create the origami balloon structure.")
}
return@onUseWith true
}
/** Make origami balloon. */
onUseWith(IntType.ITEM, Items.CANDLE_36, Items.BALLOON_STRUCTURE_9933) { player, _, _ ->
if (getQuestStage(player, Quests.ENLIGHTENED_JOURNEY) < EnlightenedJourney.STAGE_ORIGAMI_BALLOON) {
return@onUseWith false
}
if (amountInInventory(player, Items.CANDLE_36) >= 1 &&
amountInInventory(player, Items.BALLOON_STRUCTURE_9933) >= 1
) {
removeItem(player, Item(Items.CANDLE_36, 1))
removeItem(player, Item(Items.BALLOON_STRUCTURE_9933, 1))
addItem(player, Items.ORIGAMI_BALLOON_9934, 1)
playAudio(player, SOUND_CONSTRUCT_3248)
rewardXP(player, Skills.CRAFTING, 35.0)
sendMessage(player, "You create an origami balloon.")
}
return@onUseWith true
}
/** Make sandbag from sandpit. Entrana/Zanaris, Rellekka, Yanille, Dorgesh-Kaan. */
onUseWith(IntType.SCENERY, Items.EMPTY_SACK_5418,
Scenery.SAND_PIT_2645,
Scenery.SAND_PIT_4373,
Scenery.SANDPIT_10814,
Scenery.SANDPIT_22726
) { player, _, _ ->
if (removeItem(player, Item(Items.EMPTY_SACK_5418, 1))) {
animate(player, 5155)
addItem(player, Items.SANDBAG_9943, 1)
sendMessage(player, "You fill the bag with sand.")
}
return@onUseWith true
}
/** Make sandbag from bucket of sand. */
onUseWith(IntType.ITEM, Items.EMPTY_SACK_5418, Items.BUCKET_OF_SAND_1783) { player, _, _ ->
if (amountInInventory(player, Items.EMPTY_SACK_5418) >= 1 && amountInInventory(player, Items.BUCKET_OF_SAND_1783) >= 1) {
removeItem(player, Item(Items.EMPTY_SACK_5418, 1))
removeItem(player, Item(Items.BUCKET_OF_SAND_1783, 1))
addItem(player, Items.SANDBAG_9943, 1)
addItem(player, Items.BUCKET_1925, 1)
sendMessage(player, "You fill the bag with sand.")
}
return@onUseWith true
}
/** Using willow branches on the basket frame to construct the Entrana balloon. */
onUseWith(IntType.SCENERY, Items.WILLOW_BRANCH_5933, Scenery.BASKET_19132) { player, _, _ ->
if (getQuestStage(player, Quests.ENLIGHTENED_JOURNEY) != STAGE_WILLOW_BASKET) {
return@onUseWith false
}
if (amountInInventory(player, Items.WILLOW_BRANCH_5933) < 12) {
sendMessage(player, "You do not have enough willow branches.")
return@onUseWith true
}
openDialogue(player, BalloonConstructionDialogue())
return@onUseWith true
}
/** Split cap and goggles back into bomber cap and gnome goggles. */
on(Items.CAP_AND_GOGGLES_9946, IntType.ITEM, "split") { player, _ ->
if (freeSlots(player) < 1) {
sendMessage(player, "You need some free inventory space to do that.")
return@on true
}
if (removeItem(player, Item(Items.CAP_AND_GOGGLES_9946, 1))) {
addItem(player, Items.BOMBER_CAP_9945, 1)
addItem(player, Items.GNOME_GOGGLES_9472, 1)
}
return@on true
}
}
override fun defineAreaBorders(): Array<ZoneBorders> {
return arrayOf(ZoneBorders(2909, 3388, 2973, 3452, 0))
}
override fun areaLeave(entity: Entity, logout: Boolean) {
val player = entity as? Player ?: return
if (getQuestStage(player, Quests.ENLIGHTENED_JOURNEY) >= EnlightenedJourney.STAGE_COMPLETE) {
setAttribute(player, EnlightenedJourney.ATTRIBUTE_TAVERLEY_AUGUSTE_REPLACED, true)
}
}
}
class BalloonConstructionDialogue : DialogueFile() {
override fun handle(componentID: Int, buttonID: Int) {
when (stage) {
0 -> npc(NPCs.AUGUSTE_5049, "Great! Let me just put it together and we'll be ready",
"for lift off! Speak to me again in a moment.").also { stage++ }
1 -> {
if (amountInInventory(player!!, Items.WILLOW_BRANCH_5933) < 12) {
sendMessage(player!!, "You do not have enough willow branches.")
end()
return
}
if (removeItem(player!!, Item(Items.WILLOW_BRANCH_5933, 12))) {
end()
BalloonConstructionCutscene(player!!).start()
}
}
}
}
}

View file

@ -53,10 +53,12 @@ public enum FacialExpression {
WORRIED(9743),
HALF_WORRIED(9745), //Not on the wiki, first half of worried
AMAZED(9746),
AMAZED_TALKING(9749),
EXTREMELY_SHOCKED(9750),
GUILTY(9758),
HALF_GUILTY(9760), //Not on the wiki, first half of guilty
SAD(9761),
SAD_TALKING(9764),
CRYING(9765),
HALF_CRYING(9768), //Not on the wiki, but plays the first half of the crying animation
AFRAID(9772),

View file

@ -125,7 +125,7 @@ public enum DiaryType {
"Get a Slayer task from Vannaka",
"Buy twenty mahogany planks from the Sawmill Operator in one<br><br>transaction",
"Pick a fruit from the White Tree", // TODO Garden of Tranquility
"Use the hot air balloon to travel from Varrock to somewhere<br><br>else", // TODO Enlightened Journey
"Use the hot air balloon to travel from Varrock to somewhere<br><br>else",
"Get a cat training medal from Gertrude", // TODO need rat catchers and cats fixed
"Dial to the fairy ring west of Varrock",
"Browse through Oziach's Armour Shop"

View file

@ -1,7 +1,11 @@
package core.game.system.command.sets
import core.api.delayScript
import core.api.playAudio
import core.api.playGlobalAudio
import core.api.queueScript
import core.api.stopExecuting
import core.game.interaction.QueueStrength
import core.game.node.entity.player.link.music.MusicEntry
import core.game.system.command.Privilege
import core.game.world.map.Location