diff --git a/Server/data/configs/ground_spawns.json b/Server/data/configs/ground_spawns.json index 1893b6df3..a84f7bd97 100644 --- a/Server/data/configs/ground_spawns.json +++ b/Server/data/configs/ground_spawns.json @@ -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", diff --git a/Server/data/configs/npc_configs.json b/Server/data/configs/npc_configs.json index 44c10bdcb..8a470270f 100644 --- a/Server/data/configs/npc_configs.json +++ b/Server/data/configs/npc_configs.json @@ -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", diff --git a/Server/data/configs/npc_spawns.json b/Server/data/configs/npc_spawns.json index 77ef33f02..f8a68a451 100644 --- a/Server/data/configs/npc_spawns.json +++ b/Server/data/configs/npc_spawns.json @@ -157,7 +157,7 @@ }, { "npc_id": "47", - "loc_data": "{2821,3170,0,1,1}-{3076,3282,0,1,5}-{3089,3266,0,1,4}-{3091,3266,0,1,4}-{3341,3267,0,1,5}-{3097,3364,0,1,3}-{3102,3363,0,1,5}-{3127,3487,0,1,4}-{3125,3486,0,1,6}-{3127,3486,0,1,4}-{2339,9356,0,1,0}-{2354,9390,0,1,0}-{2361,9403,0,1,0}-{2362,9347,0,1,0}-{2603,9480,0,1,1}-{2600,9477,0,1,0}-{2579,9496,0,1,4}-{2580,9508,0,1,0}-{2571,9522,0,1,4}-{2565,9505,0,1,1}-{2566,9510,0,1,6}-{2594,9497,0,1,4}-{2852,9642,0,1,6}-{2858,9632,0,1,3}-{2568,9620,0,1,0}-{2573,9612,0,1,0}-{2579,9631,0,1,0}-{2580,9600,0,1,0}-{2580,9614,0,1,0}-{2580,9620,0,1,0}-{2580,9626,0,1,0}-{2583,9632,0,1,0}-{2584,9625,0,1,0}-{2584,9637,0,1,0}-{2589,9644,0,1,0}-{2590,9601,0,1,0}-{2590,9638,0,1,0}-{2591,9601,0,1,0}-{2591,9621,0,1,0}-{2594,9636,0,1,0}-{2594,9644,0,1,0}-{2597,9604,0,1,0}-{2607,9615,0,1,0}-{2608,9628,0,1,0}-{2614,9651,0,1,0}-{2614,9656,0,1,0}-{2615,9647,0,1,0}-{2615,9661,0,1,0}-{2616,9633,0,1,0}-{2618,9630,0,1,0}-{3108,9754,0,1,5}-{3110,9754,0,1,5}-{3108,9750,0,1,5}-{2592,9831,0,1,3}-{2588,9825,0,1,6}-{2583,9829,0,1,4}-{2581,9841,0,1,0}-{2597,9823,0,1,2}-{2579,9805,0,1,3}-{2576,9804,0,1,0}-{2573,9805,0,1,5}-{2571,9808,0,1,3}-{2576,9810,0,1,2}-{2587,9802,0,1,2}-{2592,9800,0,1,4}-{2596,9805,0,1,6}-{2601,9802,0,1,5}-{2585,9801,0,1,7}-{2594,9803,0,1,0}-{2590,9806,0,1,3}-{2612,9808,0,1,6}-{2604,9810,0,1,6}-{2579,9821,0,1,2}-{2576,9812,0,1,6}-{2580,9813,0,1,6}-{2600,9813,0,1,4}-{2599,9809,0,1,4}-{3158,3226,0,1,5}-{3160,3202,0,1,4}-{3192,3203,0,1,0}-{3194,3204,0,1,0}-{3196,3206,0,1,0}-{3197,3204,0,1,0}-{2654,9640,0,1,6}-{2655,9637,0,1,4}-{2656,9639,0,1,7}-{2651,9636,0,1,5}-{2648,9637,0,1,4}-{2651,9642,0,1,1}-{2654,9640,0,1,0}-{2654,9635,0,1,6}-{2655,9635,0,1,3}-{2664,9626,0,1,6}-{2664,9624,0,1,1}-{2661,9623,0,1,1}-{2663,9623,0,1,3}-{2664,9626,0,1,6}-{2930,9699,0,1,0}-{2933,9697,0,1,0}-{2932,9685,0,1,0}-{2930,9693,0,1,0}-{3235,3224,0,1,3}-{3229,3220,0,1,4}-{3211,3211,0,1,3}-{3225,3220,0,1,1}-{3237,3215,0,1,5}-{3211,3210,0,1,7}-{3227,3220,0,1,7}-{3233,3227,0,1,5}-{3227,3210,0,1,6}-{3228,3222,0,1,4}-{3229,3226,0,1,0}-{3236,3217,0,1,4}-{3259,3230,0,1,4}-{3233,3237,0,1,7}-{3205,3204,0,1,0}-{3206,3204,0,1,0}-{3205,3203,0,1,0}-{3206,3202,0,1,0}-{3207,3202,0,1,0}-{3208,3203,0,1,0}-{3001,3202,0,1,5}-{3243,3687,0,1,5}-{3249,3669,0,1,3}-{3252,3675,0,1,4}-{3252,3680,0,1,3}-{3259,3683,0,1,0}-{3475,9840,0,1,6}-{3481,9842,0,1,1}-{3486,9843,0,1,7}-{3483,9824,0,1,4}-{3496,9808,0,0,5}-{3490,9815,0,1,1}-{3478,9834,0,0,3}-{3490,9824,0,1,4}-{3225,9862,0,1,4}-{3222,9861,0,1,6}-{3220,9860,0,1,6}-{3219,9865,0,1,6}-{3237,9862,0,1,4}-{2536,2982,0,1,3}-{2531,2980,0,1,0}-{2522,2981,0,1,4}-{2545,2989,0,1,4}-{2523,2970,0,1,2}-{3026,3174,0,1,5}-{3019,3176,0,1,7}-{2801,3158,0,1,2}-{2514,3193,0,1,6}-{2518,3192,0,1,3}-{2507,3181,0,1,3}-{2508,3178,0,1,6}-{2511,3183,0,1,3}-{2515,3182,0,1,1}-{3021,3205,0,1,6}-{3019,3292,0,1,7}-{3018,3295,0,1,7}-{2531,3325,0,1,3}-{2530,3327,0,1,5}-{2521,3331,0,1,3}-{2526,3328,0,1,3}-{2523,3331,0,1,4}-{2523,3334,0,1,1}-{2531,3329,0,1,5}-{2532,3333,0,1,5}-{3276,9871,0,1,1}-{3277,9871,0,1,3}-" + "loc_data": "{2821,3170,0,1,1}-{3341,3267,0,1,5}-{3076,3282,0,1,5}-{3089,3266,0,1,4}-{3091,3266,0,1,4}-{3097,3364,0,1,3}-{3102,3363,0,1,5}-{3127,3487,0,1,4}-{3125,3486,0,1,6}-{3127,3486,0,1,4}-{2339,9356,0,1,0}-{2354,9390,0,1,0}-{2361,9403,0,1,0}-{2362,9347,0,1,0}-{2603,9480,0,1,1}-{2600,9477,0,1,0}-{2579,9496,0,1,4}-{2580,9508,0,1,0}-{2571,9522,0,1,4}-{2565,9505,0,1,1}-{2566,9510,0,1,6}-{2594,9497,0,1,4}-{2852,9642,0,1,6}-{2858,9632,0,1,3}-{2568,9620,0,1,0}-{2573,9612,0,1,0}-{2579,9631,0,1,0}-{2580,9600,0,1,0}-{2580,9614,0,1,0}-{2580,9620,0,1,0}-{2580,9626,0,1,0}-{2583,9632,0,1,0}-{2584,9625,0,1,0}-{2584,9637,0,1,0}-{2589,9644,0,1,0}-{2590,9601,0,1,0}-{2590,9638,0,1,0}-{2591,9601,0,1,0}-{2591,9621,0,1,0}-{2594,9636,0,1,0}-{2594,9644,0,1,0}-{2597,9604,0,1,0}-{2607,9615,0,1,0}-{2608,9628,0,1,0}-{2614,9651,0,1,0}-{2614,9656,0,1,0}-{2615,9647,0,1,0}-{2615,9661,0,1,0}-{2616,9633,0,1,0}-{2618,9630,0,1,0}-{3108,9754,0,1,5}-{3110,9754,0,1,5}-{3108,9750,0,1,5}-{2592,9831,0,1,3}-{2588,9825,0,1,6}-{2583,9829,0,1,4}-{2581,9841,0,1,0}-{2597,9823,0,1,2}-{2579,9805,0,1,3}-{2576,9804,0,1,0}-{2573,9805,0,1,5}-{2571,9808,0,1,3}-{2576,9810,0,1,2}-{2587,9802,0,1,2}-{2592,9800,0,1,4}-{2596,9805,0,1,6}-{2601,9802,0,1,5}-{2585,9801,0,1,7}-{2594,9803,0,1,0}-{2590,9806,0,1,3}-{2612,9808,0,1,6}-{2604,9810,0,1,6}-{2579,9821,0,1,2}-{2576,9812,0,1,6}-{2580,9813,0,1,6}-{2600,9813,0,1,4}-{2599,9809,0,1,4}-{3158,3226,0,1,5}-{3160,3202,0,1,4}-{3192,3203,0,1,0}-{3194,3204,0,1,0}-{3196,3206,0,1,0}-{3197,3204,0,1,0}-{2654,9640,0,1,6}-{2655,9637,0,1,4}-{2656,9639,0,1,7}-{2651,9636,0,1,5}-{2648,9637,0,1,4}-{2651,9642,0,1,1}-{2654,9640,0,1,0}-{2654,9635,0,1,6}-{2655,9635,0,1,3}-{2664,9626,0,1,6}-{2664,9624,0,1,1}-{2661,9623,0,1,1}-{2663,9623,0,1,3}-{2664,9626,0,1,6}-{2930,9699,0,1,0}-{2933,9697,0,1,0}-{2932,9685,0,1,0}-{2930,9693,0,1,0}-{3235,3224,0,1,3}-{3229,3220,0,1,4}-{3211,3211,0,1,3}-{3225,3220,0,1,1}-{3237,3215,0,1,5}-{3211,3210,0,1,7}-{3227,3220,0,1,7}-{3233,3227,0,1,5}-{3227,3210,0,1,6}-{3228,3222,0,1,4}-{3229,3226,0,1,0}-{3236,3217,0,1,4}-{3259,3230,0,1,4}-{3233,3237,0,1,7}-{3205,3204,0,1,0}-{3206,3204,0,1,0}-{3205,3203,0,1,0}-{3206,3202,0,1,0}-{3207,3202,0,1,0}-{3208,3203,0,1,0}-{3001,3202,0,1,5}-{3243,3687,0,1,5}-{3249,3669,0,1,3}-{3252,3675,0,1,4}-{3252,3680,0,1,3}-{3259,3683,0,1,0}-{3475,9840,0,1,6}-{3481,9842,0,1,1}-{3486,9843,0,1,7}-{3483,9824,0,1,4}-{3496,9808,0,0,5}-{3490,9815,0,1,1}-{3478,9834,0,0,3}-{3490,9824,0,1,4}-{3225,9862,0,1,4}-{3222,9861,0,1,6}-{3220,9860,0,1,6}-{3219,9865,0,1,6}-{3237,9862,0,1,4}-{2536,2982,0,1,3}-{2531,2980,0,1,0}-{2522,2981,0,1,4}-{2545,2989,0,1,4}-{2523,2970,0,1,2}-{3026,3174,0,1,5}-{3019,3176,0,1,7}-{2801,3158,0,1,2}-{2514,3193,0,1,6}-{2518,3192,0,1,3}-{2507,3181,0,1,3}-{2508,3178,0,1,6}-{2511,3183,0,1,3}-{2515,3182,0,1,1}-{3021,3205,0,1,6}-{3019,3292,0,1,7}-{3018,3295,0,1,7}-{2531,3325,0,1,3}-{2530,3327,0,1,5}-{2521,3331,0,1,3}-{2526,3328,0,1,3}-{2523,3331,0,1,4}-{2523,3334,0,1,1}-{2531,3329,0,1,5}-{2532,3333,0,1,5}-{3276,9871,0,1,1}-{3277,9871,0,1,3}-" }, { "npc_id": "48", @@ -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", diff --git a/Server/src/main/content/global/handlers/item/EmptyOptionListener.kt b/Server/src/main/content/global/handlers/item/EmptyOptionListener.kt index 71239cc7c..6cca82bfe 100644 --- a/Server/src/main/content/global/handlers/item/EmptyOptionListener.kt +++ b/Server/src/main/content/global/handlers/item/EmptyOptionListener.kt @@ -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() diff --git a/Server/src/main/content/global/handlers/item/withobject/SandSourceListener.kt b/Server/src/main/content/global/handlers/item/withobject/SandSourceListener.kt index bbde6bc21..6d35c62e7 100644 --- a/Server/src/main/content/global/handlers/item/withobject/SandSourceListener.kt +++ b/Server/src/main/content/global/handlers/item/withobject/SandSourceListener.kt @@ -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 ) } diff --git a/Server/src/main/content/global/skill/farming/Patch.kt b/Server/src/main/content/global/skill/farming/Patch.kt index b2235419e..c9504aff0 100644 --- a/Server/src/main/content/global/skill/farming/Patch.kt +++ b/Server/src/main/content/global/skill/farming/Patch.kt @@ -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 } diff --git a/Server/src/main/content/global/skill/farming/Plantable.kt b/Server/src/main/content/global/skill/farming/Plantable.kt index 6d08d9386..3f344a00d 100644 --- a/Server/src/main/content/global/skill/farming/Plantable.kt +++ b/Server/src/main/content/global/skill/farming/Plantable.kt @@ -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) diff --git a/Server/src/main/content/global/skill/farming/UseWithPatchHandler.kt b/Server/src/main/content/global/skill/farming/UseWithPatchHandler.kt index e443ad061..5c5b9c086 100644 --- a/Server/src/main/content/global/skill/farming/UseWithPatchHandler.kt +++ b/Server/src/main/content/global/skill/farming/UseWithPatchHandler.kt @@ -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.") } diff --git a/Server/src/main/content/global/skill/farming/timers/CropGrowth.kt b/Server/src/main/content/global/skill/farming/timers/CropGrowth.kt index 3cc14cd55..a6ec03ac3 100644 --- a/Server/src/main/content/global/skill/farming/timers/CropGrowth.kt +++ b/Server/src/main/content/global/skill/farming/timers/CropGrowth.kt @@ -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) } diff --git a/Server/src/main/content/global/travel/balloon/BalloonAssistantsDialogue.kt b/Server/src/main/content/global/travel/balloon/BalloonAssistantsDialogue.kt new file mode 100644 index 000000000..69cc72da2 --- /dev/null +++ b/Server/src/main/content/global/travel/balloon/BalloonAssistantsDialogue.kt @@ -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.") + ) + } + +} \ No newline at end of file diff --git a/Server/src/main/content/global/travel/balloon/BalloonCrashSites.kt b/Server/src/main/content/global/travel/balloon/BalloonCrashSites.kt new file mode 100644 index 000000000..6415e606e --- /dev/null +++ b/Server/src/main/content/global/travel/balloon/BalloonCrashSites.kt @@ -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 { + return arrayOf(getRegionBorders(BalloonCrashSites.CRASH_SITE_REGION_7244)) + } + + override fun getRestrictions(): Array { + 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.") + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/travel/balloon/BalloonFlightGrid.kt b/Server/src/main/content/global/travel/balloon/BalloonFlightGrid.kt new file mode 100644 index 000000000..56fb98a67 --- /dev/null +++ b/Server/src/main/content/global/travel/balloon/BalloonFlightGrid.kt @@ -0,0 +1,219 @@ +package content.global.travel.balloon + +/** Row and column within the balloon interface grid, starting from the top-left. */ +data class BalloonGridPosition(val row: Int, val column: Int) + +/** + * Defines the interface grid and collision points for the balloon minigame. + * @author Edith + */ +object BalloonFlightGrid { + private const val GRID_ROWS = 13 + private const val GRID_COLUMNS = 20 + private const val TOP_LEFT_GRID_CHILD = 258 + private val FLIGHT_AREA_ROWS = 2..9 + + const val SCREEN_ONE = 1 + const val SCREEN_TWO = 2 + const val SCREEN_THREE = 3 + + private val childGrid = Array(GRID_ROWS) { row -> + IntArray(GRID_COLUMNS) { column -> TOP_LEFT_GRID_CHILD - row * GRID_COLUMNS + column } + }.apply { + // Children 98 and 99 are reversed in interface 470. + this[8][0] = 99 + this[8][1] = 98 + } + + /** Interface model children that cause a collision on each flight screen. */ + private val collisionGrids = mapOf( + BalloonTravelLocation.TAVERLEY to mapOf( + SCREEN_ONE to setOf( + 78, 82, 83, 84, 85, 86, 87, 88, 89, 91, + 92, 102, 103, 104, 105, 106, 107, 108, 109, 123, + 124, 125, 127, 128, 145, 148, 155, 156, 172, 173, + 174, 175, 177, 209, 210, 211, 212, 213, 227, 228, + 229, 230 + ), + SCREEN_TWO to setOf( + 83, 84, 90, 91, 92, 93, 94, 95, 96, 97, + 103, 111, 112, 113, 114, 115, 116, 117, 123, 133, + 134, 135, 136, 137, 154, 155, 156, 157, 158, 161, + 179, 180, 181, 185, 186, 187, 188, 201, 202, 203, + 204, 219 + ), + SCREEN_THREE to setOf( + 78, 79, 80, 81, 82, 83, 84, 85, 86, 92, + 93, 97, 98, 99, 100, 101, 102, 103, 106, 113, + 118, 119, 120, 121, 126, 138, 146, 151, 168, 169, + 170, 171, 174, 175, 176, 191, 192, 207, 208, 212, + 213, 214, 215 + ) + ), + BalloonTravelLocation.CRAFTING_GUILD to mapOf( + SCREEN_ONE to setOf( + 78, 79, 80, 81, 82, 83, 84, 88, 89, 90, + 91, 93, 96, 97, 98, 99, 100, 102, 103, 104, + 108, 109, 110, 111, 113, 116, 117, 118, 119, 122, + 124, 128, 129, 130, 131, 133, 137, 138, 148, 151, + 162, 163, 164, 171, 175, 183, 192, 193, 194, 206, + 220, 221, 233, 234, 235 + ), + SCREEN_TWO to setOf( + 78, 79, 83, 84, 87, 89, 92, 96, 97, 98, + 99, 103, 104, 107, 109, 112, 119, 124, 127, 132, + 161, 168, 169, 170, 171, 187, 188, 193, 194, 195, + 200, 201, 202, 205, 206, 207, 216, 217 + ), + SCREEN_THREE to setOf( + 78, 79, 80, 83, 84, 85, 86, 87, 88, 89, + 90, 97, 100, 103, 104, 105, 107, 108, 110, 120, + 123, 130, 143, 163, 166, 167, 168, 173, 174, 175, + 198, 199, 209, 210, 211, 212, 222, 223, 224, 228, + 229, 230 + ) + ), + BalloonTravelLocation.VARROCK to mapOf( + SCREEN_ONE to setOf( + 78, 79, 80, 83, 86, 87, 88, 89, 90, 91, + 93, 94, 95, 97, 99, 100, 107, 108, 109, 110, + 111, 113, 114, 115, 117, 120, 122, 127, 129, 131, + 135, 137, 144, 145, 146, 157, 161, 163, 170, 174, + 188, 189, 190, 193, 194, 195, 198, 199, 200, 206, + 207, 208, 216, 232 + ), + SCREEN_TWO to setOf( + 79, 81, 82, 83, 85, 86, 87, 88, 89, 93, + 94, 95, 97, 98, 101, 102, 103, 105, 106, 107, + 108, 109, 113, 114, 115, 117, 119, 122, 125, 126, + 127, 128, 129, 134, 135, 137, 139, 145, 146, 147, + 148, 149, 154, 166, 167, 168, 171, 172, 173, 187, + 190, 192, 200, 201, 202, 209, 214, 215, 216, 217, + 218, 224, 225, 226, 236 + ), + SCREEN_THREE to setOf( + 79, 80, 81, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 97, 98, 100, 101, 103, + 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, + 114, 119, 120, 123, 124, 125, 126, 127, 128, 129, + 130, 131, 132, 133, 134, 141, 143, 144, 145, 146, + 147, 148, 149, 150, 151, 152, 153, 154, 163, 164, + 167, 168, 171, 172, 183, 184, 187, 188, 191, 192, + 198, 199, 201, 202, 225, 230 + ) + ), + BalloonTravelLocation.CASTLE_WARS to mapOf( + SCREEN_ONE to setOf( + 78, 80, 83, 84, 85, 92, 93, 97, 100, 105, + 112, 120, 130, 136, 140, 147, 164, 165, 166, 169, + 170, 175, 176, 177, 181, 182, 183, 193, 207, 212, + 213, 214, 227, 228, 229 + ), + SCREEN_TWO to setOf( + 79, 85, 86, 87, 88, 89, 90, 91, 92, 93, + 95, 96, 107, 108, 109, 110, 111, 112, 113, 115, + 116, 118, 129, 130, 131, 132, 133, 135, 136, 140, + 141, 142, 152, 153, 156, 158, 159, 173, 180, 181, + 189, 190, 191, 197, 203, 204, 205, 215, 216, 217, + 228, 229, 230, 231 + ), + SCREEN_THREE to setOf( + 78, 80, 83, 84, 85, 86, 87, 92, 93, 97, + 99, 100, 103, 104, 105, 106, 107, 112, 113, 118, + 120, 123, 124, 125, 126, 127, 133, 138, 140, 147, + 153, 170, 171, 172, 179, 183, 184, 185, 186, 198, + 199, 208, 209, 210, 211, 215, 216, 217, 222, 223 + ) + ), + BalloonTravelLocation.GRAND_TREE to mapOf( + SCREEN_ONE to setOf( + 78, 79, 80, 81, 84, 85, 89, 90, 93, 94, + 97, 98, 99, 100, 104, 110, 118, 119, 130, 138, + 144, 186, 187, 188, 191, 192, 193, 194, 195, 201, + 202, 203, 214, 229, 230, 231, 232, 237 + ), + SCREEN_TWO to setOf( + 79, 80, 81, 82, 83, 84, 87, 88, 89, 90, + 91, 92, 94, 95, 96, 97, 100, 101, 102, 103, + 104, 107, 108, 109, 110, 111, 112, 114, 115, 116, + 117, 120, 122, 124, 127, 128, 129, 130, 132, 134, + 135, 140, 142, 144, 147, 149, 150, 152, 154, 167, + 172, 181, 183, 191, 192, 196, 197, 200, 201, 203, + 204, 205, 208, 209, 214, 215, 216, 217, 219, 220, + 224, 228 + ), + SCREEN_THREE to setOf( + 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 97, 98, 99, 100, 101, + 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 120, 121, 122, 123, 124, 125, 126, 127, 128, + 129, 130, 131, 132, 140, 141, 142, 143, 144, 145, + 146, 147, 148, 149, 150, 151, 161, 162, 163, 164, + 165, 166, 167, 168, 169, 170, 174, 175, 178, 182, + 183, 187, 188, 189, 198, 199, 200, 212, 213, 214, + 225 + ) + ) + ) + + /** Grid columns for crashing over ocean. */ + private val oceanColumns = mapOf( + BalloonTravelLocation.TAVERLEY to mapOf( + SCREEN_ONE to 17..19, + SCREEN_TWO to 0..7 + ), + BalloonTravelLocation.CRAFTING_GUILD to mapOf( + SCREEN_TWO to 4..19, + SCREEN_THREE to 0..2 + ), + BalloonTravelLocation.VARROCK to mapOf( + SCREEN_ONE to 2..6 + ), + BalloonTravelLocation.CASTLE_WARS to mapOf( + SCREEN_ONE to 4..19, + SCREEN_TWO to 0..5 + ), + BalloonTravelLocation.GRAND_TREE to mapOf( + SCREEN_ONE to 4..19 + ) + ) + + /** Checks whether balloon position is above the ocean. */ + fun isOverOcean(destination: BalloonTravelLocation, screen: Int, position: BalloonGridPosition): Boolean { + return oceanColumns[destination]?.get(screen)?.contains(position.column) == true + } + + /** Gets the interface child occupied by a grid position. */ + fun getGridChild(position: BalloonGridPosition): Int? { + if (position.row !in childGrid.indices) { + return null + } + if (position.column !in childGrid[position.row].indices) { + return null + } + return childGrid[position.row][position.column] + } + + /** Checks whether a grid position is inside the flight area. */ + fun isInFlightArea(position: BalloonGridPosition): Boolean { + return position.row in FLIGHT_AREA_ROWS && position.column in 0 until GRID_COLUMNS + } + + /** Checks whether either part of the balloon collides with the flight boundary or a model. */ + fun willBalloonCollide(destination: BalloonTravelLocation, screen: Int, position: BalloonGridPosition): Boolean { + val topPosition = position.copy(row = position.row - 1) + + if (position.column == GRID_COLUMNS) { + return topPosition.row !in FLIGHT_AREA_ROWS || position.row !in FLIGHT_AREA_ROWS + } + + if (!isInFlightArea(topPosition) || !isInFlightArea(position)) { + return true + } + + val collisionChildren = collisionGrids[destination]?.get(screen) ?: return true + val topChild = getGridChild(topPosition) ?: return true + val bottomChild = getGridChild(position) ?: return true + return topChild in collisionChildren || bottomChild in collisionChildren + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/travel/balloon/BalloonFlightInterface.kt b/Server/src/main/content/global/travel/balloon/BalloonFlightInterface.kt new file mode 100644 index 000000000..47ff84faa --- /dev/null +++ b/Server/src/main/content/global/travel/balloon/BalloonFlightInterface.kt @@ -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 + } + } + +} \ No newline at end of file diff --git a/Server/src/main/content/global/travel/balloon/BalloonFlightRoutes.kt b/Server/src/main/content/global/travel/balloon/BalloonFlightRoutes.kt new file mode 100644 index 000000000..7bea83a6a --- /dev/null +++ b/Server/src/main/content/global/travel/balloon/BalloonFlightRoutes.kt @@ -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 { + 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 { + return when (screen) { + 1 -> taverleyRouteFirstScreen + 2 -> taverleyRouteSecondScreen + 3 -> taverleyRouteThirdScreen + else -> emptyList() + } + } + + private fun getCraftingGuildSequence(screen: Int): List { + return when (screen) { + 1 -> craftingGuildRouteFirstScreen + 2 -> craftingGuildRouteSecondScreen + 3 -> craftingGuildRouteThirdScreen + else -> emptyList() + } + } + + private fun getVarrockSequence(screen: Int): List { + return when (screen) { + 1 -> varrockRouteFirstScreen + 2 -> varrockRouteSecondScreen + 3 -> varrockRouteThirdScreen + else -> emptyList() + } + } + + private fun getCastleWarsSequence(screen: Int): List { + return when (screen) { + 1 -> castleWarsRouteFirstScreen + 2 -> castleWarsRouteSecondScreen + 3 -> castleWarsRouteThirdScreen + else -> emptyList() + } + } + + private fun getGrandTreeSequence(screen: Int): List { + return when (screen) { + 1 -> grandTreeRouteFirstScreen + 2 -> grandTreeRouteSecondScreen + 3 -> grandTreeRouteThirdScreen + else -> emptyList() + } + } + +} \ No newline at end of file diff --git a/Server/src/main/content/global/travel/balloon/BalloonFlightScreens.kt b/Server/src/main/content/global/travel/balloon/BalloonFlightScreens.kt new file mode 100644 index 000000000..411857a3c --- /dev/null +++ b/Server/src/main/content/global/travel/balloon/BalloonFlightScreens.kt @@ -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? { + 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) + } + +} \ No newline at end of file diff --git a/Server/src/main/content/global/travel/balloon/BalloonTravelInterface.kt b/Server/src/main/content/global/travel/balloon/BalloonTravelInterface.kt new file mode 100644 index 000000000..d006b3261 --- /dev/null +++ b/Server/src/main/content/global/travel/balloon/BalloonTravelInterface.kt @@ -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.") + } + } + } + } +} diff --git a/Server/src/main/content/global/travel/balloon/OceanCrashSharkPatrol.kt b/Server/src/main/content/global/travel/balloon/OceanCrashSharkPatrol.kt new file mode 100644 index 000000000..0ac049cd3 --- /dev/null +++ b/Server/src/main/content/global/travel/balloon/OceanCrashSharkPatrol.kt @@ -0,0 +1,62 @@ +package content.global.travel.balloon + +import core.api.getAttribute +import core.api.setAttribute +import core.game.node.entity.npc.NPC +import core.game.node.entity.npc.NPCBehavior +import core.game.world.map.Location +import org.rs09.consts.NPCs + +/** + * Makes the sharks in the ocean crash site swim around in a clockwise patrol. + * All three should have their own slightly different route instead of a shared one but honestly I cba. + * @author Edith + */ +class OceanCrashSharkPatrol : NPCBehavior(NPCs.SHARK_5067, NPCs.SHARK_5068, NPCs.SHARK_5069) { + companion object { + private const val ATTRIBUTE_NEXT_WAYPOINT = "balloon_shark_next_waypoint" + + private fun route(vararg coordinates: Pair) = + Array(coordinates.size) { Location.create(coordinates[it].first, coordinates[it].second) } + + private val PATROL_ROUTE = route( + 1799 to 4885, + 1798 to 4886, + 1797 to 4887, + 1797 to 4892, + 1798 to 4893, + 1798 to 4894, + 1799 to 4894, + 1803 to 4894, + 1804 to 4893, + 1805 to 4893, + 1807 to 4893, + 1807 to 4887, + 1806 to 4886, + 1805 to 4885 + ) + } + + override fun onCreation(self: NPC) { + val startingWaypoint = PATROL_ROUTE.indexOf(self.location) + val nextWaypoint = if (startingWaypoint == -1) 0 else (startingWaypoint + 1) % PATROL_ROUTE.size + setAttribute(self, ATTRIBUTE_NEXT_WAYPOINT, nextWaypoint) + } + + override fun tick(self: NPC): Boolean { + if (!self.walkingQueue.hasPath()) { + var nextWaypoint = getAttribute(self, ATTRIBUTE_NEXT_WAYPOINT, 0) + + if (self.location == PATROL_ROUTE[nextWaypoint]) { + nextWaypoint = (nextWaypoint + 1) % PATROL_ROUTE.size + setAttribute(self, ATTRIBUTE_NEXT_WAYPOINT, nextWaypoint) + } + + val destination = PATROL_ROUTE[nextWaypoint] + self.walkingQueue.reset() + self.walkingQueue.addPath(destination.x, destination.y) + } + + return false + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/misc/entrana/quest/enlightenedjourney/cutscene/BalloonConstructionCutscene.kt b/Server/src/main/content/region/misc/entrana/quest/enlightenedjourney/cutscene/BalloonConstructionCutscene.kt new file mode 100644 index 000000000..65f4caf07 --- /dev/null +++ b/Server/src/main/content/region/misc/entrana/quest/enlightenedjourney/cutscene/BalloonConstructionCutscene.kt @@ -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) + } + } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/misc/entrana/quest/enlightenedjourney/cutscene/BalloonExperimentCutscene.kt b/Server/src/main/content/region/misc/entrana/quest/enlightenedjourney/cutscene/BalloonExperimentCutscene.kt new file mode 100644 index 000000000..5aa7276bb --- /dev/null +++ b/Server/src/main/content/region/misc/entrana/quest/enlightenedjourney/cutscene/BalloonExperimentCutscene.kt @@ -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) + } + } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/misc/entrana/quest/enlightenedjourney/cutscene/SecondBalloonExperimentCutscene.kt b/Server/src/main/content/region/misc/entrana/quest/enlightenedjourney/cutscene/SecondBalloonExperimentCutscene.kt new file mode 100644 index 000000000..64955fd67 --- /dev/null +++ b/Server/src/main/content/region/misc/entrana/quest/enlightenedjourney/cutscene/SecondBalloonExperimentCutscene.kt @@ -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) + } + } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/misc/entrana/quest/enlightenedjourney/dialogue/AugusteDialogue.kt b/Server/src/main/content/region/misc/entrana/quest/enlightenedjourney/dialogue/AugusteDialogue.kt new file mode 100644 index 000000000..3ea226325 --- /dev/null +++ b/Server/src/main/content/region/misc/entrana/quest/enlightenedjourney/dialogue/AugusteDialogue.kt @@ -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) + } + + } +} diff --git a/Server/src/main/content/region/misc/entrana/quest/enlightenedjourney/quest/EnlightenedJourney.kt b/Server/src/main/content/region/misc/entrana/quest/enlightenedjourney/quest/EnlightenedJourney.kt new file mode 100644 index 000000000..3b39482de --- /dev/null +++ b/Server/src/main/content/region/misc/entrana/quest/enlightenedjourney/quest/EnlightenedJourney.kt @@ -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 + } +} diff --git a/Server/src/main/content/region/misc/entrana/quest/enlightenedjourney/quest/EnlightenedJourneyListeners.kt b/Server/src/main/content/region/misc/entrana/quest/enlightenedjourney/quest/EnlightenedJourneyListeners.kt new file mode 100644 index 000000000..13f7e2c79 --- /dev/null +++ b/Server/src/main/content/region/misc/entrana/quest/enlightenedjourney/quest/EnlightenedJourneyListeners.kt @@ -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 { + 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() + } + } + } + } +} \ No newline at end of file diff --git a/Server/src/main/core/game/dialogue/FacialExpression.java b/Server/src/main/core/game/dialogue/FacialExpression.java index 8b4f2eb8c..a42df4bb7 100644 --- a/Server/src/main/core/game/dialogue/FacialExpression.java +++ b/Server/src/main/core/game/dialogue/FacialExpression.java @@ -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), diff --git a/Server/src/main/core/game/node/entity/player/link/diary/DiaryType.java b/Server/src/main/core/game/node/entity/player/link/diary/DiaryType.java index dbbb06e81..ce225efe0 100644 --- a/Server/src/main/core/game/node/entity/player/link/diary/DiaryType.java +++ b/Server/src/main/core/game/node/entity/player/link/diary/DiaryType.java @@ -125,7 +125,7 @@ public enum DiaryType { "Get a Slayer task from Vannaka", "Buy twenty mahogany planks from the Sawmill Operator in one

transaction", "Pick a fruit from the White Tree", // TODO Garden of Tranquility - "Use the hot air balloon to travel from Varrock to somewhere

else", // TODO Enlightened Journey + "Use the hot air balloon to travel from Varrock to somewhere

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" diff --git a/Server/src/main/core/game/system/command/sets/AudioCommandSet.kt b/Server/src/main/core/game/system/command/sets/AudioCommandSet.kt index 70e797b0c..e5a4fb522 100644 --- a/Server/src/main/core/game/system/command/sets/AudioCommandSet.kt +++ b/Server/src/main/core/game/system/command/sets/AudioCommandSet.kt @@ -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