diff --git a/Server/data/configs/drop_tables.json b/Server/data/configs/drop_tables.json index 076cf6b80..474c5bbe7 100644 --- a/Server/data/configs/drop_tables.json +++ b/Server/data/configs/drop_tables.json @@ -36458,12 +36458,6 @@ "weight": "100.0", "id": "532", "maxAmount": "1" - }, - { - "minAmount": "3", - "weight": "100.0", - "id": "526", - "maxAmount": "3" } ], "charm": [], diff --git a/Server/data/configs/ground_spawns.json b/Server/data/configs/ground_spawns.json index 632bf2fc7..7ddcf821d 100644 --- a/Server/data/configs/ground_spawns.json +++ b/Server/data/configs/ground_spawns.json @@ -207,6 +207,10 @@ "item_id": "590", "loc_data": "{1,2368,3135,0,140}-{1,2431,3072,0,140}-{1,3112,3369,2,140}-{1,3209,3734,0,100}-" }, + { + "item_id": "600", + "loc_data": "{1,2438,3187,0,200}-" + }, { "item_id": "677", "loc_data": "{1,3369,3378,0,150}-" @@ -671,6 +675,10 @@ "item_id": "11065", "loc_data": "{1,2928,3289,0,90}-" }, + { + "item_id": "11656", + "loc_data": "{1,2438,3185,0,200}-" + }, { "item_id": "12494", "loc_data": "{1,2762,2973,0,60}-" diff --git a/Server/data/configs/item_configs.json b/Server/data/configs/item_configs.json index 951d647f5..bf79f32e1 100644 --- a/Server/data/configs/item_configs.json +++ b/Server/data/configs/item_configs.json @@ -5575,7 +5575,7 @@ "id": "601" }, { - "destroy_message": "You'll have to get another down in the Dungeon", + "destroy_message": "You'll have to get another down in the dungeon.", "examine": "An unusual clay mould in the shape of a disc.", "durability": null, "name": "Lens mould", diff --git a/Server/data/configs/npc_configs.json b/Server/data/configs/npc_configs.json index 87b666074..d4efc299f 100644 --- a/Server/data/configs/npc_configs.json +++ b/Server/data/configs/npc_configs.json @@ -6227,6 +6227,22 @@ "range_level": "1", "attack_level": "1" }, + { + "examine": "A man, learned in the ways of the stars.", + "melee_animation": "0", + "range_animation": "0", + "defence_animation": "0", + "magic_animation": "0", + "death_animation": "0", + "name": "Observatory professor", + "defence_level": "1", + "safespot": null, + "lifepoints": "10", + "strength_level": "1", + "id": "488", + "range_level": "1", + "attack_level": "1" + }, { "examine": "He doesn't look like he'd trust his own mother.", "melee_animation": "6199", @@ -6237,13 +6253,13 @@ "magic_animation": "0", "death_animation": "6190", "name": "Goblin guard", - "defence_level": "20", + "defence_level": "37", "safespot": null, - "lifepoints": "28", - "strength_level": "20", + "lifepoints": "43", + "strength_level": "37", "id": "489", "range_level": "1", - "attack_level": "20" + "attack_level": "32" }, { "examine": "If the mummy is at school", @@ -9414,7 +9430,7 @@ "attack_level": "15" }, { - "examine": "Children are just like real people...just smaller.", + "examine": "A sad looking child.", "melee_animation": "0", "range_animation": "0", "defence_animation": "0", @@ -10518,13 +10534,13 @@ "death_animation": "5329", "name": "Poison spider", "safespot": null, - "defence_level": "52", - "lifepoints": "64", - "strength_level": "65", + "defence_level": "28", + "lifepoints": "25", + "strength_level": "28", "id": "1009", "aggressive": "true", "range_level": "1", - "attack_level": "50" + "attack_level": "28" }, { "examine": "A green skinned croaker", @@ -36003,7 +36019,7 @@ "name": "Tortoise", "defence_level": "36", "safespot": null, - "lifepoints": "51", + "lifepoints": "121", "strength_level": "36", "id": "3808", "range_level": "1", @@ -36077,61 +36093,68 @@ "examine": "A Gnome Arrow-chucker", "combat_style": "1", "melee_animation": "190", - "range_animation": "0", + "range_animation": "190", "respawn_delay": "60", - "defence_animation": "0", + "defence_animation": "193", "weakness": "2", "magic_animation": "0", "death_animation": "196", "name": "Gnome Archer", - "defence_level": "30", + "defence_level": "1", "safespot": null, - "lifepoints": "42", + "lifepoints": "10", "strength_level": "1", "id": "3814", "aggressive": "true", - "range_level": "30", - "attack_level": "1" + "range_level": "5", + "projectile": "10", + "attack_level": "1", + "prj_height": "30" }, { "examine": "Yee haa!", "melee_animation": "3969", "range_animation": "0", "respawn_delay": "60", - "defence_animation": "0", + "defence_animation": "193", "weakness": "9", "magic_animation": "0", "death_animation": "196", "name": "Gnome Driver", - "defence_level": "33", + "defence_level": "1", "safespot": null, - "lifepoints": "47", - "strength_level": "33", + "lifepoints": "10", + "strength_level": "1", "id": "3815", "aggressive": "true", "range_level": "1", - "attack_level": "33" + "attack_level": "5" }, { "examine": "A battle mage of the gnomish variety.", "combat_style": "2", + "start_gfx": "93", + "start_height": "80", "melee_animation": "3968", "range_animation": "0", - "magic_level": "34", + "magic_level": "5", + "spell_id": "", "respawn_delay": "60", - "defence_animation": "0", + "defence_animation": "193", "weakness": "5", - "magic_animation": "0", + "magic_animation": "200", "death_animation": "196", "name": "Gnome Mage", - "defence_level": "34", + "defence_level": "1", "safespot": null, - "lifepoints": "48", + "lifepoints": "10", "strength_level": "1", "id": "3816", "aggressive": "true", "range_level": "1", - "attack_level": "1" + "projectile": "94", + "attack_level": "1", + "prj_height": "30" }, { "examine": "The cruel tortoise trainer. Boo!", @@ -36160,7 +36183,7 @@ "name": "Tortoise", "defence_level": "36", "safespot": null, - "lifepoints": "51", + "lifepoints": "101", "strength_level": "36", "id": "3819", "range_level": "1", @@ -53255,6 +53278,7 @@ "attack_level": "1" }, { + "examine": "An ugly green creature.", "name": "Goblin", "defence_level": "1", "safespot": null, @@ -73515,10 +73539,6 @@ "name": "Kalron", "id": "486" }, - { - "name": "Observatory professor", - "id": "488" - }, { "name": "Hajedy", "id": "510" @@ -74752,6 +74772,7 @@ }, { "name": "Solihib", + "movement_radius": "1", "id": "1433" }, { @@ -82650,6 +82671,7 @@ "id": "6114" }, { + "examine": "A man, learned in the ways of the stars.", "name": "Observatory professor", "id": "6119" }, diff --git a/Server/data/configs/npc_spawns.json b/Server/data/configs/npc_spawns.json index cee858845..46a5a18e7 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}-{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}-{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", @@ -193,7 +193,7 @@ }, { "npc_id": "59", - "loc_data": "{3082,3362,0,1,4}-{2602,9640,0,1,0}-{2603,9635,0,1,0}-{2603,9638,0,1,0}-{2605,9637,0,1,0}-{2605,9639,0,1,0}-{2606,9635,0,1,0}-{2606,9646,0,1,0}-{2607,9637,0,1,0}-{2607,9641,0,1,0}-{2607,9644,0,1,0}-{2607,9648,0,1,0}-{2608,9635,0,1,0}-{2608,9642,0,1,0}-{2609,9639,0,1,0}-{2609,9643,0,1,0}-{3102,9881,0,1,3}-{3095,9883,0,1,2}-{3182,3244,0,1,1}-{3162,3223,0,1,7}-{3164,3242,0,1,0}-{3169,3246,0,1,0}-{3170,3250,0,1,0}-{3166,3247,0,1,0}-{3164,3249,0,1,0}-{3157,3226,0,1,0}-{3163,3227,0,1,0}-{3165,3223,0,1,0}-{3194,3236,0,1,0}-{3146,3347,0,1,3}-{2648,9766,0,1,4}-{2653,9761,0,1,3}-{2483,2877,0,1,3}-{2481,2876,0,1,4}-{2457,2867,0,1,4}-{2481,2847,0,1,0}-{2475,2854,0,1,3}-{2482,2873,0,1,3}-{2485,2876,0,1,2}-{2449,2865,0,1,1}-{2461,2880,0,1,5}-{2489,2935,0,1,7}-{2492,2907,0,1,1}-{2487,2894,0,1,2}-{2487,2888,0,1,7}-{2478,2916,0,1,5}-{2485,2902,0,1,3}-{2487,2902,0,1,2}-{2489,2894,0,1,2}-{2490,2905,0,1,7}-{2490,2917,0,1,5}-{2484,2890,0,1,1}-{2491,2927,0,1,1}-{3250,3239,0,1,1}-{3241,3241,0,1,1}-{3249,3249,0,1,1}-{3218,9890,0,1,3}-{3220,9887,0,1,0}-{3218,9887,0,1,1}-{3218,9889,0,1,5}-{3213,9890,0,1,1}-{2496,2890,0,1,4}-{2503,2889,0,1,1}-{2497,2939,0,1,2}-{2369,3374,0,1,0}-{2372,3379,0,1,0}-{2378,3366,0,1,0}-{2379,3378,0,1,0}-{2387,3370,0,1,0}-{2394,3365,0,1,0}-{2402,3386,0,1,0}-{2405,3381,0,1,0}-{2407,3387,0,1,0}-{2412,3384,0,1,0}-" + "loc_data": "{3082,3362,0,1,4}-{2602,9640,0,1,0}-{2603,9635,0,1,0}-{2603,9638,0,1,0}-{2605,9637,0,1,0}-{2605,9639,0,1,0}-{2606,9635,0,1,0}-{2606,9646,0,1,0}-{2607,9637,0,1,0}-{2607,9641,0,1,0}-{2607,9644,0,1,0}-{2607,9648,0,1,0}-{2608,9635,0,1,0}-{2608,9642,0,1,0}-{2609,9639,0,1,0}-{2609,9643,0,1,0}-{3102,9881,0,1,3}-{3095,9883,0,1,2}-{3182,3244,0,1,1}-{3162,3223,0,1,7}-{3164,3242,0,1,0}-{3169,3246,0,1,0}-{3170,3250,0,1,0}-{3166,3247,0,1,0}-{3164,3249,0,1,0}-{3157,3226,0,1,0}-{3163,3227,0,1,0}-{3165,3223,0,1,0}-{3194,3236,0,1,0}-{3146,3347,0,1,3}-{2369,3374,0,1,0}-{2372,3379,0,1,0}-{2378,3366,0,1,0}-{2379,3378,0,1,0}-{2387,3370,0,1,0}-{2394,3365,0,1,0}-{2402,3386,0,1,0}-{2405,3381,0,1,0}-{2407,3387,0,1,0}-{2412,3384,0,1,0}-{2648,9766,0,1,4}-{2653,9761,0,1,3}-{2483,2877,0,1,3}-{2481,2876,0,1,4}-{2457,2867,0,1,4}-{2481,2847,0,1,0}-{2475,2854,0,1,3}-{2482,2873,0,1,3}-{2485,2876,0,1,2}-{2449,2865,0,1,1}-{2461,2880,0,1,5}-{2489,2935,0,1,7}-{2492,2907,0,1,1}-{2487,2894,0,1,2}-{2487,2888,0,1,7}-{2478,2916,0,1,5}-{2485,2902,0,1,3}-{2487,2902,0,1,2}-{2489,2894,0,1,2}-{2490,2905,0,1,7}-{2490,2917,0,1,5}-{2484,2890,0,1,1}-{2491,2927,0,1,1}-{3250,3239,0,1,1}-{3241,3241,0,1,1}-{3249,3249,0,1,1}-{3218,9890,0,1,3}-{3220,9887,0,1,0}-{3218,9887,0,1,1}-{3218,9889,0,1,5}-{3213,9890,0,1,1}-{2496,2890,0,1,4}-{2503,2889,0,1,1}-{2497,2939,0,1,2}-" }, { "npc_id": "60", @@ -217,15 +217,15 @@ }, { "npc_id": "66", - "loc_data": "{2417,3493,1,1,0}-{2418,3495,1,1,0}-{2383,3452,0,1,0}-{2401,3417,0,1,0}-{2402,3422,0,1,0}-{2402,3441,0,1,0}-{2403,3430,0,1,0}-{2408,3441,0,1,0}-{2409,3430,0,1,0}-{2423,3426,0,1,0}-{2427,3428,0,1,0}-{2427,3440,0,1,0}-{2526,3168,0,1,0}-{2529,3163,0,1,0}-{2530,3172,0,1,0}-{2536,3169,0,1,0}-{2435,3460,0,1,0}-{2440,3470,0,1,0}-{2447,3502,0,1,0}-{2457,3462,0,1,0}-{2478,3502,0,1,0}-{2449,3492,1,1,0}-{2450,3490,1,1,0}-{2474,3490,1,1,0}-{2482,3492,1,1,0}-{2482,3498,1,1,0}-{2399,3356,0,1,0}-" + "loc_data": "{2399,3356,0,1,0}-{2383,3452,0,1,0}-{2401,3417,0,1,0}-{2402,3422,0,1,0}-{2402,3441,0,1,0}-{2403,3430,0,1,0}-{2408,3441,0,1,0}-{2409,3430,0,1,0}-{2423,3426,0,1,0}-{2427,3428,0,1,0}-{2427,3440,0,1,0}-{2417,3493,1,1,0}-{2418,3495,1,1,0}-{2435,3460,0,1,0}-{2440,3470,0,1,0}-{2447,3502,0,1,0}-{2457,3462,0,1,0}-{2478,3502,0,1,0}-{2449,3492,1,1,0}-{2450,3490,1,1,0}-{2474,3490,1,1,0}-{2482,3492,1,1,0}-{2482,3498,1,1,0}-{2526,3168,0,1,0}-{2529,3163,0,1,0}-{2530,3172,0,1,0}-{2536,3169,0,1,0}-" }, { "npc_id": "67", - "loc_data": "{2556,3226,0,1,0}-{2456,3425,0,1,0}-{2459,3421,0,1,0}-{2462,3431,0,1,0}-{2394,3500,1,1,0}-{2417,3483,1,1,0}-{2377,3442,0,1,0}-{2380,3425,0,1,0}-{2383,3433,0,1,0}-{2405,3447,0,1,0}-{2406,3440,0,1,0}-{2393,3451,1,1,0}-{2408,3437,1,1,0}-{2521,3169,0,1,0}-{2521,3171,0,1,0}-{2033,5530,1,1,0}-{2437,3478,0,1,0}-{2442,3465,0,1,0}-{2456,3467,0,1,0}-{2471,3496,0,1,0}-{2474,3508,0,1,0}-{2475,3471,0,1,0}-{2479,3468,0,1,0}-{2486,3470,0,1,0}-{2492,3474,0,1,0}-{2443,3464,1,1,0}-{2443,3502,1,1,0}-{2448,3496,1,1,0}-{2449,3506,1,1,0}-{2457,3498,1,1,0}-{2474,3498,1,1,0}-{2480,3502,1,1,0}-{2481,3482,1,1,0}-{2490,3503,1,1,0}-{2400,3356,0,1,0}-" + "loc_data": "{2400,3356,0,1,0}-{2377,3442,0,1,0}-{2380,3425,0,1,0}-{2383,3433,0,1,0}-{2405,3447,0,1,0}-{2406,3440,0,1,0}-{2393,3451,1,1,0}-{2408,3437,1,1,0}-{2394,3500,1,1,0}-{2417,3483,1,1,0}-{2456,3425,0,1,0}-{2459,3421,0,1,0}-{2462,3431,0,1,0}-{2437,3478,0,1,0}-{2442,3465,0,1,0}-{2456,3467,0,1,0}-{2471,3496,0,1,0}-{2474,3508,0,1,0}-{2475,3471,0,1,0}-{2479,3468,0,1,0}-{2486,3470,0,1,0}-{2492,3474,0,1,0}-{2443,3464,1,1,0}-{2443,3502,1,1,0}-{2448,3496,1,1,0}-{2449,3506,1,1,0}-{2457,3498,1,1,0}-{2474,3498,1,1,0}-{2480,3502,1,1,0}-{2481,3482,1,1,0}-{2490,3503,1,1,0}-{2521,3169,0,1,0}-{2521,3171,0,1,0}-{2556,3226,0,1,0}-{2033,5530,1,1,0}-" }, { "npc_id": "68", - "loc_data": "{2400,3514,1,1,0}-{2418,3485,1,1,0}-{2378,3423,0,1,0}-{2393,3435,0,1,0}-{2394,3425,0,1,0}-{2395,3449,0,1,0}-{2403,3433,0,1,0}-{2420,3429,0,1,0}-{2420,3438,0,1,0}-{2522,3172,0,1,0}-{2433,3474,0,1,0}-{2433,3492,0,1,0}-{2458,3497,0,1,0}-{2464,3505,0,1,0}-{2476,3458,0,1,0}-{2445,3502,1,1,0}-{2453,3488,1,1,0}-{2475,3502,1,1,0}-{2465,3490,2,1,0}-{2401,3357,0,1,0}-" + "loc_data": "{2401,3357,0,1,0}-{2378,3423,0,1,0}-{2393,3435,0,1,0}-{2394,3425,0,1,0}-{2395,3449,0,1,0}-{2403,3433,0,1,0}-{2420,3429,0,1,0}-{2420,3438,0,1,0}-{2400,3514,1,1,0}-{2418,3485,1,1,0}-{2433,3474,0,1,0}-{2433,3492,0,1,0}-{2458,3497,0,1,0}-{2464,3505,0,1,0}-{2476,3458,0,1,0}-{2445,3502,1,1,0}-{2453,3488,1,1,0}-{2475,3502,1,1,0}-{2465,3490,2,1,0}-{2522,3172,0,1,0}-" }, { "npc_id": "73", @@ -325,11 +325,11 @@ }, { "npc_id": "100", - "loc_data": "{2567,3440,0,1,3}-{2564,9653,0,1,0}-{2565,9655,0,1,0}-{2566,9626,0,1,0}-{2567,9653,0,1,0}-{2569,9633,0,1,0}-{2607,9621,0,1,0}-{2551,3408,0,1,6}-{2553,3405,0,1,3}-{2559,3452,0,1,3}-" + "loc_data": "{2567,3440,0,1,3}-{2316,9392,0,1,0}-{2319,9367,0,1,0}-{2321,9402,0,1,0}-{2322,9387,0,1,0}-{2325,9360,0,1,0}-{2333,9346,0,1,0}-{2335,9382,0,1,0}-{2337,9387,0,1,0}-{2343,9360,0,1,0}-{2344,9369,0,1,0}-{2348,9390,0,1,0}-{2349,9380,0,1,0}-{2349,9402,0,1,0}-{2359,9345,0,1,0}-{2359,9359,0,1,0}-{2359,9374,0,1,0}-{2359,9382,0,1,0}-{2363,9403,0,1,0}-{2564,9653,0,1,0}-{2565,9655,0,1,0}-{2566,9626,0,1,0}-{2567,9653,0,1,0}-{2569,9633,0,1,0}-{2607,9621,0,1,0}-{2551,3408,0,1,6}-{2553,3405,0,1,3}-{2559,3452,0,1,3}-" }, { "npc_id": "101", - "loc_data": "{2622,3389,0,1,4}-{2619,3390,0,1,4}-{2573,3437,0,1,3}-{2562,9659,0,1,0}-{2563,9661,0,1,0}-{2599,9626,0,1,0}-{2601,9627,0,1,0}-{2605,9621,0,1,0}-{2610,9620,0,1,0}-{2624,3391,0,1,1}-{2555,3407,0,1,3}-{2553,3457,0,1,3}-" + "loc_data": "{2622,3389,0,1,4}-{2619,3390,0,1,4}-{2573,3437,0,1,3}-{2305,9385,0,1,0}-{2307,9359,0,1,0}-{2307,9403,0,1,0}-{2310,9396,0,1,0}-{2317,9371,0,1,0}-{2317,9383,0,1,0}-{2324,9404,0,1,0}-{2333,9366,0,1,0}-{2335,9393,0,1,0}-{2339,9403,0,1,0}-{2342,9347,0,1,0}-{2351,9359,0,1,0}-{2359,9392,0,1,0}-{2562,9659,0,1,0}-{2563,9661,0,1,0}-{2599,9626,0,1,0}-{2601,9627,0,1,0}-{2605,9621,0,1,0}-{2610,9620,0,1,0}-{2624,3391,0,1,1}-{2555,3407,0,1,3}-{2553,3457,0,1,3}-" }, { "npc_id": "102", @@ -345,7 +345,7 @@ }, { "npc_id": "105", - "loc_data": "{3100,3594,0,1,6}-{3107,3608,0,1,2}-{3099,3602,0,1,0}-{2632,3280,0,1,1}-{2633,3274,0,1,3}-{2696,3329,0,1,4}-{2708,3336,0,1,4}-{3230,3500,0,1,5}-{2988,3671,0,1,0}-{3001,3674,0,1,0}-{2497,3164,0,1,2}-{2387,3376,0,1,0}-{2398,3366,0,1,0}-{2412,3378,0,1,0}-{2419,3372,0,1,0}-" + "loc_data": "{3100,3594,0,1,6}-{3107,3608,0,1,2}-{3099,3602,0,1,0}-{2632,3280,0,1,1}-{2633,3274,0,1,3}-{2387,3376,0,1,0}-{2398,3366,0,1,0}-{2412,3378,0,1,0}-{2419,3372,0,1,0}-{2696,3329,0,1,4}-{2708,3336,0,1,4}-{3230,3500,0,1,5}-{2988,3671,0,1,0}-{3001,3674,0,1,0}-{2497,3164,0,1,2}-" }, { "npc_id": "106", @@ -365,7 +365,7 @@ }, { "npc_id": "110", - "loc_data": "{2564,9887,0,1,4}-{2581,9897,0,1,1}-{2577,9888,0,1,1}-{3234,5497,0,1,1}-{3305,9400,0,1,1}-{3244,9356,0,1,1}-{3252,9370,0,1,1}-{3294,9375,0,1,3}-{3050,10337,0,1,4}-{3047,10340,0,1,4}-{3048,10346,0,1,4}-{3048,10346,0,1,3}-" + "loc_data": "{2564,9887,0,1,4}-{2581,9897,0,1,1}-{2577,9888,0,1,1}-{3234,5497,0,1,1}-{3244,9356,0,1,1}-{3252,9370,0,1,1}-{3305,9400,0,1,1}-{3294,9375,0,1,3}-{3050,10337,0,1,4}-{3047,10340,0,1,4}-{3048,10346,0,1,4}-{3048,10346,0,1,3}-" }, { "npc_id": "111", @@ -389,7 +389,7 @@ }, { "npc_id": "117", - "loc_data": "{2369,3404,0,1,0}-{3118,9845,0,1,4}-{3111,9844,0,1,6}-{3123,9845,0,1,3}-{3114,9833,0,1,3}-{3110,9841,0,1,1}-{3119,9839,0,1,1}-{3097,9832,0,1,0}-{3101,9832,0,1,1}-{3107,9829,0,1,3}-{3115,9831,0,1,4}-{3109,9835,0,1,4}-{2904,9734,0,1,0}-{2548,3146,0,1,1}-{2542,3145,0,1,4}-{2503,3150,0,1,4}-{3300,3649,0,1,0}-{3044,10321,0,1,2}-{3044,10316,0,1,4}-{3045,10308,0,1,4}-{3048,10317,0,1,2}-" + "loc_data": "{3118,9845,0,1,4}-{3111,9844,0,1,6}-{3123,9845,0,1,3}-{3114,9833,0,1,3}-{3110,9841,0,1,1}-{3119,9839,0,1,1}-{3097,9832,0,1,0}-{3101,9832,0,1,1}-{3107,9829,0,1,3}-{3115,9831,0,1,4}-{3109,9835,0,1,4}-{2369,3404,0,1,0}-{2904,9734,0,1,0}-{2548,3146,0,1,1}-{2542,3145,0,1,4}-{2503,3150,0,1,4}-{3300,3649,0,1,0}-{3044,10321,0,1,2}-{3044,10316,0,1,4}-{3045,10308,0,1,4}-{3048,10317,0,1,2}-" }, { "npc_id": "118", @@ -489,23 +489,23 @@ }, { "npc_id": "153", - "loc_data": "{2540,9820,0,1,0}-{2543,9813,0,1,0}-{2546,9817,0,1,0}-{2464,4421,0,1,0}-{2466,4423,0,1,0}-{2486,4464,0,1,0}-{2556,3444,0,1,0}-{3082,3077,0,1,0}-{3092,3082,0,1,0}-{3109,3090,0,1,0}-{3112,3093,0,1,0}-{2249,3260,0,1,0}-{2250,3257,0,1,0}-{2250,3259,0,1,0}-{2252,3261,0,1,0}-{2253,3256,0,1,0}-{2262,3226,0,1,0}-{2264,3225,0,1,0}-{2266,3229,0,1,0}-{2268,3227,0,1,0}-{2269,3222,0,1,0}-{2269,3224,0,1,0}-{2270,3223,0,1,0}-{2270,3226,0,1,0}-{3200,5954,0,1,0}-{3202,5958,0,1,0}-{3204,5955,0,1,0}-{3204,5959,0,1,0}-{3231,5967,0,1,0}-{3236,5974,0,1,0}-{3237,5968,0,1,0}-{3240,5992,0,1,0}-{3240,5994,0,1,0}-{3242,5990,0,1,0}-{3242,5993,0,1,0}-{3244,5994,0,1,0}-{3246,5989,0,1,0}-{2196,3191,0,1,0}-{2197,3189,0,1,0}-{2198,3181,0,1,0}-{2198,3187,0,1,0}-{2199,3183,0,1,0}-{2199,3187,0,1,0}-{2200,3185,0,1,0}-{2201,3185,0,1,0}-{2204,3180,0,1,0}-{2324,4599,0,1,0}-{2325,4597,0,1,0}-{2326,4589,0,1,0}-{2326,4595,0,1,0}-{2327,4591,0,1,0}-{2327,4595,0,1,0}-{2328,4593,0,1,0}-{2329,4593,0,1,0}-{2332,4588,0,1,0}-{2176,3202,0,1,0}-{2178,3206,0,1,0}-{2180,3203,0,1,0}-{2180,3207,0,1,0}-{2207,3215,0,1,0}-{2212,3222,0,1,0}-{2213,3216,0,1,0}-{2216,3240,0,1,0}-{2216,3242,0,1,0}-{2218,3238,0,1,0}-{2218,3241,0,1,0}-{2220,3242,0,1,0}-{2222,3237,0,1,0}-{2437,3425,0,1,0}-{2438,3422,0,1,0}-{2450,3419,0,1,0}-{2415,4466,0,1,0}-{2419,4427,0,1,0}-{2421,4468,0,1,0}-{2426,4431,0,1,0}-{3175,3226,0,1,0}-{3178,3228,0,1,0}-{3179,3226,0,1,0}-{3182,3251,0,1,0}-{3183,3254,0,1,0}-{2697,6204,0,1,0}-{2698,6201,0,1,0}-{2698,6203,0,1,0}-{2700,6205,0,1,0}-{2701,6200,0,1,0}-{2710,6170,0,1,0}-{2712,6169,0,1,0}-{2714,6173,0,1,0}-{2716,6171,0,1,0}-{2717,6166,0,1,0}-{2717,6168,0,1,0}-{2718,6167,0,1,0}-{2718,6170,0,1,0}-{2478,3373,0,1,0}-{2479,3381,0,1,0}-{2481,3384,0,1,0}-{2490,3371,0,1,0}-{2689,6083,0,1,0}-{2689,6088,0,1,0}-{2690,6094,0,1,0}-{2691,6084,0,1,0}-{2692,6081,0,1,0}-{2692,6090,0,1,0}-{2693,6083,0,1,0}-{2693,6085,0,1,0}-{2693,6090,0,1,0}-{2694,6083,0,1,0}-{2694,6086,0,1,0}-{2695,6082,0,1,0}-{2695,6085,0,1,0}-{2695,6088,0,1,0}-{2695,6091,0,1,0}-{2695,6121,0,1,0}-{2698,6121,0,1,0}-{2699,6082,0,1,0}-{2699,6085,0,1,0}-{2700,6084,0,1,0}-{2701,6085,0,1,0}-{2702,6081,0,1,0}-{2702,6083,0,1,0}-{2703,6119,0,1,0}-{2704,6117,0,1,0}-{2705,6121,0,1,0}-{2730,6091,0,1,0}-{2732,6095,0,1,0}-{2734,6090,0,1,0}-{2735,6094,0,1,0}-{2735,6097,0,1,0}-{2738,6098,0,1,0}-{2416,3404,0,1,0}-{2424,3410,0,1,0}-{2430,3405,0,1,0}-{1906,5223,0,1,0}-{1907,5221,0,1,0}-{3273,6012,0,1,0}-{3274,6009,0,1,0}-{3274,6011,0,1,0}-{3276,6013,0,1,0}-{3277,6008,0,1,0}-{3286,5978,0,1,0}-{3288,5977,0,1,0}-{3290,5981,0,1,0}-{3292,5979,0,1,0}-{3293,5974,0,1,0}-{3293,5976,0,1,0}-{3294,5975,0,1,0}-{3294,5978,0,1,0}-{2828,5091,0,1,0}-{2832,5108,0,1,0}-{2835,5079,0,1,0}-{2846,5067,0,1,0}-{2850,5111,0,1,0}-{2853,5086,0,1,0}-{2864,5071,0,1,0}-{2549,3139,0,1,0}-{2241,3139,0,1,0}-{2241,3144,0,1,0}-{2242,3150,0,1,0}-{2243,3140,0,1,0}-{2244,3137,0,1,0}-{2244,3146,0,1,0}-{2245,3139,0,1,0}-{2245,3141,0,1,0}-{2245,3146,0,1,0}-{2246,3139,0,1,0}-{2246,3142,0,1,0}-{2247,3138,0,1,0}-{2247,3141,0,1,0}-{2247,3144,0,1,0}-{2247,3147,0,1,0}-{2247,3177,0,1,0}-{2250,3177,0,1,0}-{2251,3138,0,1,0}-{2251,3141,0,1,0}-{2252,3140,0,1,0}-{2253,3141,0,1,0}-{2254,3137,0,1,0}-{2254,3139,0,1,0}-{2255,3175,0,1,0}-{2256,3173,0,1,0}-{2257,3177,0,1,0}-{2282,3147,0,1,0}-{2284,3151,0,1,0}-{2286,3146,0,1,0}-{2287,3150,0,1,0}-{2287,3153,0,1,0}-{2290,3154,0,1,0}-" + "loc_data": "{3082,3077,0,1,0}-{3092,3082,0,1,0}-{3109,3090,0,1,0}-{3112,3093,0,1,0}-{2324,4599,0,1,0}-{2325,4597,0,1,0}-{2326,4589,0,1,0}-{2326,4595,0,1,0}-{2327,4591,0,1,0}-{2327,4595,0,1,0}-{2328,4593,0,1,0}-{2329,4593,0,1,0}-{2332,4588,0,1,0}-{2828,5091,0,1,0}-{2832,5108,0,1,0}-{2835,5079,0,1,0}-{2846,5067,0,1,0}-{2850,5111,0,1,0}-{2853,5086,0,1,0}-{2864,5071,0,1,0}-{3175,3226,0,1,0}-{3178,3228,0,1,0}-{3179,3226,0,1,0}-{3182,3251,0,1,0}-{3183,3254,0,1,0}-{2416,3404,0,1,0}-{2424,3410,0,1,0}-{2430,3405,0,1,0}-{2415,4466,0,1,0}-{2419,4427,0,1,0}-{2421,4468,0,1,0}-{2426,4431,0,1,0}-{1906,5223,0,1,0}-{1907,5221,0,1,0}-{2196,3191,0,1,0}-{2197,3189,0,1,0}-{2198,3181,0,1,0}-{2198,3187,0,1,0}-{2199,3183,0,1,0}-{2199,3187,0,1,0}-{2200,3185,0,1,0}-{2201,3185,0,1,0}-{2204,3180,0,1,0}-{2176,3202,0,1,0}-{2178,3206,0,1,0}-{2180,3203,0,1,0}-{2180,3207,0,1,0}-{2207,3215,0,1,0}-{2212,3222,0,1,0}-{2213,3216,0,1,0}-{2216,3240,0,1,0}-{2216,3242,0,1,0}-{2218,3238,0,1,0}-{2218,3241,0,1,0}-{2220,3242,0,1,0}-{2222,3237,0,1,0}-{2478,3373,0,1,0}-{2479,3381,0,1,0}-{2481,3384,0,1,0}-{2490,3371,0,1,0}-{2437,3425,0,1,0}-{2438,3422,0,1,0}-{2450,3419,0,1,0}-{2464,4421,0,1,0}-{2466,4423,0,1,0}-{2486,4464,0,1,0}-{3200,5954,0,1,0}-{3202,5958,0,1,0}-{3204,5955,0,1,0}-{3204,5959,0,1,0}-{3231,5967,0,1,0}-{3236,5974,0,1,0}-{3237,5968,0,1,0}-{3240,5992,0,1,0}-{3240,5994,0,1,0}-{3242,5990,0,1,0}-{3242,5993,0,1,0}-{3244,5994,0,1,0}-{3246,5989,0,1,0}-{2689,6083,0,1,0}-{2689,6088,0,1,0}-{2690,6094,0,1,0}-{2691,6084,0,1,0}-{2692,6081,0,1,0}-{2692,6090,0,1,0}-{2693,6083,0,1,0}-{2693,6085,0,1,0}-{2693,6090,0,1,0}-{2694,6083,0,1,0}-{2694,6086,0,1,0}-{2695,6082,0,1,0}-{2695,6085,0,1,0}-{2695,6088,0,1,0}-{2695,6091,0,1,0}-{2695,6121,0,1,0}-{2698,6121,0,1,0}-{2699,6082,0,1,0}-{2699,6085,0,1,0}-{2700,6084,0,1,0}-{2701,6085,0,1,0}-{2702,6081,0,1,0}-{2702,6083,0,1,0}-{2703,6119,0,1,0}-{2704,6117,0,1,0}-{2705,6121,0,1,0}-{2730,6091,0,1,0}-{2732,6095,0,1,0}-{2734,6090,0,1,0}-{2735,6094,0,1,0}-{2735,6097,0,1,0}-{2738,6098,0,1,0}-{2697,6204,0,1,0}-{2698,6201,0,1,0}-{2698,6203,0,1,0}-{2700,6205,0,1,0}-{2701,6200,0,1,0}-{2710,6170,0,1,0}-{2712,6169,0,1,0}-{2714,6173,0,1,0}-{2716,6171,0,1,0}-{2717,6166,0,1,0}-{2717,6168,0,1,0}-{2718,6167,0,1,0}-{2718,6170,0,1,0}-{2549,3139,0,1,0}-{2241,3139,0,1,0}-{2241,3144,0,1,0}-{2242,3150,0,1,0}-{2243,3140,0,1,0}-{2244,3137,0,1,0}-{2244,3146,0,1,0}-{2245,3139,0,1,0}-{2245,3141,0,1,0}-{2245,3146,0,1,0}-{2246,3139,0,1,0}-{2246,3142,0,1,0}-{2247,3138,0,1,0}-{2247,3141,0,1,0}-{2247,3144,0,1,0}-{2247,3147,0,1,0}-{2247,3177,0,1,0}-{2250,3177,0,1,0}-{2251,3138,0,1,0}-{2251,3141,0,1,0}-{2252,3140,0,1,0}-{2253,3141,0,1,0}-{2254,3137,0,1,0}-{2254,3139,0,1,0}-{2255,3175,0,1,0}-{2256,3173,0,1,0}-{2257,3177,0,1,0}-{2282,3147,0,1,0}-{2284,3151,0,1,0}-{2286,3146,0,1,0}-{2287,3150,0,1,0}-{2287,3153,0,1,0}-{2290,3154,0,1,0}-{2249,3260,0,1,0}-{2250,3257,0,1,0}-{2250,3259,0,1,0}-{2252,3261,0,1,0}-{2253,3256,0,1,0}-{2262,3226,0,1,0}-{2264,3225,0,1,0}-{2266,3229,0,1,0}-{2268,3227,0,1,0}-{2269,3222,0,1,0}-{2269,3224,0,1,0}-{2270,3223,0,1,0}-{2270,3226,0,1,0}-{2556,3444,0,1,0}-{3273,6012,0,1,0}-{3274,6009,0,1,0}-{3274,6011,0,1,0}-{3276,6013,0,1,0}-{3277,6008,0,1,0}-{3286,5978,0,1,0}-{3288,5977,0,1,0}-{3290,5981,0,1,0}-{3292,5979,0,1,0}-{3293,5974,0,1,0}-{3293,5976,0,1,0}-{3294,5975,0,1,0}-{3294,5978,0,1,0}-{2540,9820,0,1,0}-{2543,9813,0,1,0}-{2546,9817,0,1,0}-" }, { "npc_id": "154", - "loc_data": "{2475,4459,0,1,0}-{2480,4458,0,1,0}-{2481,4454,0,1,0}-{2488,4465,0,1,0}-{3125,3084,0,1,0}-{2268,3228,0,1,0}-{3202,5955,0,1,0}-{3206,5953,0,1,0}-{3231,5976,0,1,0}-{3233,5974,0,1,0}-{3235,5970,0,1,0}-{3241,5996,0,1,0}-{3242,5988,0,1,0}-{3244,5992,0,1,0}-{2180,3173,0,1,0}-{2193,3187,0,1,0}-{2196,3184,0,1,0}-{2200,3181,0,1,0}-{2200,3188,0,1,0}-{2203,3189,0,1,0}-{2308,4581,0,1,0}-{2321,4595,0,1,0}-{2324,4592,0,1,0}-{2328,4589,0,1,0}-{2328,4596,0,1,0}-{2331,4597,0,1,0}-{2178,3203,0,1,0}-{2182,3201,0,1,0}-{2207,3224,0,1,0}-{2209,3222,0,1,0}-{2211,3218,0,1,0}-{2217,3244,0,1,0}-{2218,3236,0,1,0}-{2220,3240,0,1,0}-{2446,3396,0,1,0}-{2470,3397,0,1,0}-{2394,4453,0,1,0}-{2394,4457,0,1,0}-{2406,4447,0,1,0}-{2422,3489,0,1,0}-{2716,6172,0,1,0}-{2688,6092,0,1,0}-{2690,6087,0,1,0}-{2690,6095,0,1,0}-{2692,6082,0,1,0}-{2692,6088,0,1,0}-{2692,6123,0,1,0}-{2693,6081,0,1,0}-{2694,6125,0,1,0}-{2695,6087,0,1,0}-{2697,6086,0,1,0}-{2697,6087,0,1,0}-{2698,6081,0,1,0}-{2698,6124,0,1,0}-{2700,6083,0,1,0}-{2702,6084,0,1,0}-{2726,6085,0,1,0}-{2727,6081,0,1,0}-{2729,6083,0,1,0}-{2732,6084,0,1,0}-{2378,3418,0,1,0}-{2380,3422,0,1,0}-{2393,3442,0,1,0}-{2397,3440,0,1,0}-{2423,3399,0,1,0}-{2425,3406,0,1,0}-{2427,3402,0,1,0}-{1908,5222,0,1,0}-{3292,5980,0,1,0}-{2831,5070,0,1,0}-{2843,5106,0,1,0}-{2844,5080,0,1,0}-{2860,5079,0,1,0}-{2866,5104,0,1,0}-{2868,5096,0,1,0}-{2240,3148,0,1,0}-{2242,3143,0,1,0}-{2242,3151,0,1,0}-{2244,3138,0,1,0}-{2244,3144,0,1,0}-{2244,3179,0,1,0}-{2245,3137,0,1,0}-{2246,3181,0,1,0}-{2247,3143,0,1,0}-{2249,3142,0,1,0}-{2249,3143,0,1,0}-{2250,3137,0,1,0}-{2250,3180,0,1,0}-{2252,3139,0,1,0}-{2254,3140,0,1,0}-{2278,3141,0,1,0}-{2279,3137,0,1,0}-{2281,3139,0,1,0}-{2284,3140,0,1,0}-" + "loc_data": "{3125,3084,0,1,0}-{2308,4581,0,1,0}-{2321,4595,0,1,0}-{2324,4592,0,1,0}-{2328,4589,0,1,0}-{2328,4596,0,1,0}-{2331,4597,0,1,0}-{2831,5070,0,1,0}-{2843,5106,0,1,0}-{2844,5080,0,1,0}-{2860,5079,0,1,0}-{2866,5104,0,1,0}-{2868,5096,0,1,0}-{2378,3418,0,1,0}-{2380,3422,0,1,0}-{2393,3442,0,1,0}-{2397,3440,0,1,0}-{2423,3399,0,1,0}-{2425,3406,0,1,0}-{2427,3402,0,1,0}-{2422,3489,0,1,0}-{2394,4453,0,1,0}-{2394,4457,0,1,0}-{2406,4447,0,1,0}-{1908,5222,0,1,0}-{2180,3173,0,1,0}-{2193,3187,0,1,0}-{2196,3184,0,1,0}-{2200,3181,0,1,0}-{2200,3188,0,1,0}-{2203,3189,0,1,0}-{2178,3203,0,1,0}-{2182,3201,0,1,0}-{2207,3224,0,1,0}-{2209,3222,0,1,0}-{2211,3218,0,1,0}-{2217,3244,0,1,0}-{2218,3236,0,1,0}-{2220,3240,0,1,0}-{2446,3396,0,1,0}-{2470,3397,0,1,0}-{2475,4459,0,1,0}-{2480,4458,0,1,0}-{2481,4454,0,1,0}-{2488,4465,0,1,0}-{3202,5955,0,1,0}-{3206,5953,0,1,0}-{3231,5976,0,1,0}-{3233,5974,0,1,0}-{3235,5970,0,1,0}-{3241,5996,0,1,0}-{3242,5988,0,1,0}-{3244,5992,0,1,0}-{2688,6092,0,1,0}-{2690,6087,0,1,0}-{2690,6095,0,1,0}-{2692,6082,0,1,0}-{2692,6088,0,1,0}-{2692,6123,0,1,0}-{2693,6081,0,1,0}-{2694,6125,0,1,0}-{2695,6087,0,1,0}-{2697,6086,0,1,0}-{2697,6087,0,1,0}-{2698,6081,0,1,0}-{2698,6124,0,1,0}-{2700,6083,0,1,0}-{2702,6084,0,1,0}-{2726,6085,0,1,0}-{2727,6081,0,1,0}-{2729,6083,0,1,0}-{2732,6084,0,1,0}-{2716,6172,0,1,0}-{2240,3148,0,1,0}-{2242,3143,0,1,0}-{2242,3151,0,1,0}-{2244,3138,0,1,0}-{2244,3144,0,1,0}-{2244,3179,0,1,0}-{2245,3137,0,1,0}-{2246,3181,0,1,0}-{2247,3143,0,1,0}-{2249,3142,0,1,0}-{2249,3143,0,1,0}-{2250,3137,0,1,0}-{2250,3180,0,1,0}-{2252,3139,0,1,0}-{2254,3140,0,1,0}-{2278,3141,0,1,0}-{2279,3137,0,1,0}-{2281,3139,0,1,0}-{2284,3140,0,1,0}-{2268,3228,0,1,0}-{3292,5980,0,1,0}-" }, { "npc_id": "155", - "loc_data": "{3093,3086,0,1,0}-{3097,3085,0,1,0}-{3129,3091,0,1,0}-{3135,3084,0,1,0}-{2190,3180,0,1,0}-{2318,4588,0,1,0}-{2374,3469,0,1,0}-{2376,3466,0,1,0}-{3254,3230,0,1,0}-{1986,5564,0,1,0}-{2434,3516,0,1,0}-{2479,3501,0,1,0}-" + "loc_data": "{3093,3086,0,1,0}-{3097,3085,0,1,0}-{3129,3091,0,1,0}-{3135,3084,0,1,0}-{2318,4588,0,1,0}-{2374,3469,0,1,0}-{2376,3466,0,1,0}-{2190,3180,0,1,0}-{3254,3230,0,1,0}-{2434,3516,0,1,0}-{2479,3501,0,1,0}-{1986,5564,0,1,0}-" }, { "npc_id": "156", - "loc_data": "{2371,3460,0,1,0}-{2372,3456,0,1,0}-{2422,3467,0,1,0}-{2725,6127,0,1,0}-{2444,3491,0,1,0}-{2277,3183,0,1,0}-" + "loc_data": "{2371,3460,0,1,0}-{2372,3456,0,1,0}-{2422,3467,0,1,0}-{2444,3491,0,1,0}-{2725,6127,0,1,0}-{2277,3183,0,1,0}-" }, { "npc_id": "157", - "loc_data": "{2180,2798,0,1,0}-{2209,2812,0,1,0}-{2217,2780,0,1,0}-{1921,5931,0,1,0}-{1947,5908,0,1,0}-{1955,5949,0,1,0}-{1973,5888,0,1,0}-{1975,5918,0,1,0}-{1988,5870,0,1,0}-{2017,5884,0,1,0}-{2025,5852,0,1,0}-{2182,2979,0,1,0}-{2211,2963,0,1,0}-{1931,6038,0,1,0}-{1983,6037,0,1,0}-{2153,2810,0,1,0}-{2172,2806,0,1,0}-{1961,5882,0,1,0}-{1980,5878,0,1,0}-{2242,2788,0,1,0}-{2248,2858,0,1,0}-{2256,2828,0,1,0}-{2284,2874,0,1,0}-{2255,3216,0,1,0}-{2262,3210,0,1,0}-{2271,3204,0,1,0}-{2294,3219,0,1,0}-{2296,3206,0,1,0}-{2298,3201,0,1,0}-{2056,5930,0,1,0}-{2064,5900,0,1,0}-{2092,5946,0,1,0}-{2050,5860,0,1,0}-{3201,5986,0,1,0}-{3254,5972,0,1,0}-{3255,5986,0,1,0}-{3256,6008,0,1,0}-{3257,5973,0,1,0}-{3257,5975,0,1,0}-{3260,5974,0,1,0}-{3261,5977,0,1,0}-{3262,5957,0,1,0}-{2194,3158,0,1,0}-{2216,3188,0,1,0}-{2217,3190,0,1,0}-{2220,3158,0,1,0}-{2222,3141,0,1,0}-{2229,3138,0,1,0}-{2231,3181,0,1,0}-{2236,3155,0,1,0}-{2278,2956,0,1,0}-{2322,4566,0,1,0}-{2344,4596,0,1,0}-{2345,4598,0,1,0}-{2348,4566,0,1,0}-{2350,4549,0,1,0}-{2357,4546,0,1,0}-{2359,4589,0,1,0}-{2364,4563,0,1,0}-{2177,3234,0,1,0}-{2230,3220,0,1,0}-{2231,3234,0,1,0}-{2232,3256,0,1,0}-{2233,3221,0,1,0}-{2233,3223,0,1,0}-{2236,3222,0,1,0}-{2237,3225,0,1,0}-{2238,3205,0,1,0}-{2479,3396,0,1,0}-{2102,2942,0,1,0}-{2108,2919,0,1,0}-{2104,2873,0,1,0}-{2122,6032,0,1,0}-{1912,5945,0,1,0}-{3143,3210,0,1,0}-{3155,3253,0,1,0}-{3163,3261,0,1,0}-{3168,3258,0,1,0}-{2086,6028,0,1,0}-{2703,6160,0,1,0}-{2710,6154,0,1,0}-{2719,6148,0,1,0}-{2742,6163,0,1,0}-{2744,6150,0,1,0}-{2746,6145,0,1,0}-{2314,2960,0,1,0}-{2723,6105,0,1,0}-{2739,6123,0,1,0}-{2744,6108,0,1,0}-{2326,2894,0,1,0}-{2333,2920,0,1,0}-{3279,5968,0,1,0}-{3286,5962,0,1,0}-{3295,5956,0,1,0}-{3318,5971,0,1,0}-{3320,5958,0,1,0}-{3322,5953,0,1,0}-{2134,5966,0,1,0}-{2141,5992,0,1,0}-{2123,2966,0,1,0}-{2175,2965,0,1,0}-{1910,6014,0,1,0}-{1916,5991,0,1,0}-{2540,3167,0,1,0}-{2110,2955,0,1,0}-{1990,6051,0,1,0}-{2019,6035,0,1,0}-{2447,3467,0,1,0}-{2448,3469,0,1,0}-{1918,6027,0,1,0}-{2275,3161,0,1,0}-{2291,3179,0,1,0}-{2296,3164,0,1,0}-{2113,2859,0,1,0}-{2139,2836,0,1,0}-{2147,2877,0,1,0}-{2165,2816,0,1,0}-{2167,2846,0,1,0}-" + "loc_data": "{2104,2873,0,1,0}-{2102,2942,0,1,0}-{2108,2919,0,1,0}-{2326,2894,0,1,0}-{2333,2920,0,1,0}-{2314,2960,0,1,0}-{2110,2955,0,1,0}-{2322,4566,0,1,0}-{2344,4596,0,1,0}-{2345,4598,0,1,0}-{2348,4566,0,1,0}-{2350,4549,0,1,0}-{2357,4546,0,1,0}-{2359,4589,0,1,0}-{2364,4563,0,1,0}-{2050,5860,0,1,0}-{2056,5930,0,1,0}-{2064,5900,0,1,0}-{2092,5946,0,1,0}-{2086,6028,0,1,0}-{2153,2810,0,1,0}-{2172,2806,0,1,0}-{2113,2859,0,1,0}-{2139,2836,0,1,0}-{2147,2877,0,1,0}-{2165,2816,0,1,0}-{2167,2846,0,1,0}-{2123,2966,0,1,0}-{2175,2965,0,1,0}-{3143,3210,0,1,0}-{3155,3253,0,1,0}-{3163,3261,0,1,0}-{3168,3258,0,1,0}-{1912,5945,0,1,0}-{2134,5966,0,1,0}-{2141,5992,0,1,0}-{1910,6014,0,1,0}-{1916,5991,0,1,0}-{2122,6032,0,1,0}-{1918,6027,0,1,0}-{2180,2798,0,1,0}-{2209,2812,0,1,0}-{2217,2780,0,1,0}-{2182,2979,0,1,0}-{2211,2963,0,1,0}-{2194,3158,0,1,0}-{2216,3188,0,1,0}-{2217,3190,0,1,0}-{2220,3158,0,1,0}-{2222,3141,0,1,0}-{2229,3138,0,1,0}-{2231,3181,0,1,0}-{2236,3155,0,1,0}-{2177,3234,0,1,0}-{2230,3220,0,1,0}-{2231,3234,0,1,0}-{2232,3256,0,1,0}-{2233,3221,0,1,0}-{2233,3223,0,1,0}-{2236,3222,0,1,0}-{2237,3225,0,1,0}-{2238,3205,0,1,0}-{2479,3396,0,1,0}-{2447,3467,0,1,0}-{2448,3469,0,1,0}-{1961,5882,0,1,0}-{1980,5878,0,1,0}-{1921,5931,0,1,0}-{1947,5908,0,1,0}-{1955,5949,0,1,0}-{1973,5888,0,1,0}-{1975,5918,0,1,0}-{3201,5986,0,1,0}-{3254,5972,0,1,0}-{3255,5986,0,1,0}-{3256,6008,0,1,0}-{3257,5973,0,1,0}-{3257,5975,0,1,0}-{3260,5974,0,1,0}-{3261,5977,0,1,0}-{3262,5957,0,1,0}-{1931,6038,0,1,0}-{1983,6037,0,1,0}-{2723,6105,0,1,0}-{2739,6123,0,1,0}-{2744,6108,0,1,0}-{2703,6160,0,1,0}-{2710,6154,0,1,0}-{2719,6148,0,1,0}-{2742,6163,0,1,0}-{2744,6150,0,1,0}-{2746,6145,0,1,0}-{2242,2788,0,1,0}-{2248,2858,0,1,0}-{2256,2828,0,1,0}-{2284,2874,0,1,0}-{2278,2956,0,1,0}-{2540,3167,0,1,0}-{2275,3161,0,1,0}-{2291,3179,0,1,0}-{2296,3164,0,1,0}-{2255,3216,0,1,0}-{2262,3210,0,1,0}-{2271,3204,0,1,0}-{2294,3219,0,1,0}-{2296,3206,0,1,0}-{2298,3201,0,1,0}-{1988,5870,0,1,0}-{2017,5884,0,1,0}-{2025,5852,0,1,0}-{3279,5968,0,1,0}-{3286,5962,0,1,0}-{3295,5956,0,1,0}-{3318,5971,0,1,0}-{3320,5958,0,1,0}-{3322,5953,0,1,0}-{1990,6051,0,1,0}-{2019,6035,0,1,0}-" }, { "npc_id": "158", @@ -513,7 +513,7 @@ }, { "npc_id": "159", - "loc_data": "{2392,3475,0,1,0}-{2394,3506,0,1,0}-{2396,3471,0,1,0}-{2405,3499,0,1,0}-{2416,3487,0,1,0}-{2413,3445,1,1,0}-{2415,3435,1,1,0}-{2416,3416,1,1,0}-{2424,3434,1,1,0}-{2483,3500,1,1,0}-" + "loc_data": "{2413,3445,1,1,0}-{2415,3435,1,1,0}-{2416,3416,1,1,0}-{2424,3434,1,1,0}-{2392,3475,0,1,0}-{2394,3506,0,1,0}-{2396,3471,0,1,0}-{2405,3499,0,1,0}-{2416,3487,0,1,0}-{2483,3500,1,1,0}-" }, { "npc_id": "160", @@ -529,11 +529,11 @@ }, { "npc_id": "163", - "loc_data": "{2451,3414,0,1,0}-{2459,3395,0,1,0}-{2459,3438,0,1,0}-{2462,3395,0,1,0}-{2445,3429,1,1,0}-{2409,3470,1,1,0}-{2416,3466,1,1,0}-{2420,3466,1,1,0}-{2461,9895,0,1,0}-{2465,9894,0,1,0}-{2465,9899,0,1,0}-{2459,3385,0,1,0}-{2463,3385,0,1,0}-{2392,3454,0,1,0}-{2394,3436,0,1,0}-{2408,3452,0,1,0}-{2421,3413,0,1,0}-{2442,3489,0,1,0}-{2450,3485,0,1,0}-{2451,3507,0,1,0}-{2453,3490,0,1,0}-{2459,3503,0,1,0}-{2461,3509,0,1,0}-{2464,3465,0,1,0}-{2464,3468,0,1,0}-{2468,3465,0,1,0}-{2468,3468,0,1,0}-{2468,3506,0,1,0}-{2472,3484,0,1,0}-{2475,3490,0,1,0}-{2475,3502,0,1,0}-{2477,3512,0,1,0}-{2481,3485,0,1,0}-{2482,3501,0,1,0}-{2467,3495,1,1,0}-{2473,3495,1,1,0}-{2448,3497,2,1,0}-{2463,3480,2,1,0}-{2465,3497,2,1,0}-{2465,3510,2,1,0}-{2466,3480,2,1,0}-" + "loc_data": "{2392,3454,0,1,0}-{2394,3436,0,1,0}-{2408,3452,0,1,0}-{2421,3413,0,1,0}-{2409,3470,1,1,0}-{2416,3466,1,1,0}-{2420,3466,1,1,0}-{2459,3385,0,1,0}-{2463,3385,0,1,0}-{2451,3414,0,1,0}-{2459,3395,0,1,0}-{2459,3438,0,1,0}-{2462,3395,0,1,0}-{2445,3429,1,1,0}-{2442,3489,0,1,0}-{2450,3485,0,1,0}-{2451,3507,0,1,0}-{2453,3490,0,1,0}-{2459,3503,0,1,0}-{2461,3509,0,1,0}-{2464,3465,0,1,0}-{2464,3468,0,1,0}-{2468,3465,0,1,0}-{2468,3468,0,1,0}-{2468,3506,0,1,0}-{2472,3484,0,1,0}-{2475,3490,0,1,0}-{2475,3502,0,1,0}-{2477,3512,0,1,0}-{2481,3485,0,1,0}-{2482,3501,0,1,0}-{2467,3495,1,1,0}-{2473,3495,1,1,0}-{2448,3497,2,1,0}-{2463,3480,2,1,0}-{2465,3497,2,1,0}-{2465,3510,2,1,0}-{2466,3480,2,1,0}-{2461,9895,0,1,0}-{2465,9894,0,1,0}-{2465,9899,0,1,0}-" }, { "npc_id": "164", - "loc_data": "{2459,3392,0,1,0}-{2461,3422,0,1,0}-{2462,3392,0,1,0}-{2460,3382,0,1,0}-{2462,3382,0,1,0}-{2410,3416,0,1,0}-{2420,3435,0,1,0}-{2420,3447,0,1,0}-{2427,3410,0,1,0}-{2012,5535,2,1,0}-{2023,5544,2,1,0}-{2441,3497,0,1,0}-{2447,3510,0,1,0}-{2461,3487,0,1,0}-{2464,3472,0,1,0}-{2464,3489,0,1,0}-{2467,3489,0,1,0}-{2468,3472,0,1,0}-{2473,3503,0,1,0}-{2478,3497,0,1,0}-{2464,3496,1,1,0}-{2448,3495,2,1,0}-{2460,3487,2,1,0}-{2467,3494,2,1,0}-{2467,3510,2,1,0}-{2471,3496,2,1,0}-{2483,3495,2,1,0}-{2483,3497,2,1,0}-" + "loc_data": "{2410,3416,0,1,0}-{2420,3435,0,1,0}-{2420,3447,0,1,0}-{2427,3410,0,1,0}-{2460,3382,0,1,0}-{2462,3382,0,1,0}-{2459,3392,0,1,0}-{2461,3422,0,1,0}-{2462,3392,0,1,0}-{2441,3497,0,1,0}-{2447,3510,0,1,0}-{2461,3487,0,1,0}-{2464,3472,0,1,0}-{2464,3489,0,1,0}-{2467,3489,0,1,0}-{2468,3472,0,1,0}-{2473,3503,0,1,0}-{2478,3497,0,1,0}-{2464,3496,1,1,0}-{2448,3495,2,1,0}-{2460,3487,2,1,0}-{2467,3494,2,1,0}-{2467,3510,2,1,0}-{2471,3496,2,1,0}-{2483,3495,2,1,0}-{2483,3497,2,1,0}-{2012,5535,2,1,0}-{2023,5544,2,1,0}-" }, { "npc_id": "166", @@ -541,11 +541,11 @@ }, { "npc_id": "168", - "loc_data": "{2434,3436,0,1,0}-{2437,3451,0,1,0}-{2438,3427,0,1,0}-{2441,3411,0,1,0}-{2466,3449,0,1,0}-{2470,3399,0,1,0}-{2472,3400,0,1,0}-{2473,3412,0,1,0}-{2476,3454,0,1,0}-{2482,3397,0,1,0}-{2489,3401,0,1,0}-{2479,3407,1,1,0}-{2379,3482,0,1,0}-{2381,3496,0,1,0}-{2384,3497,0,1,0}-{2391,3476,0,1,0}-{2410,3496,0,1,0}-{2421,3481,0,1,0}-{2397,3514,1,1,0}-{2398,3451,1,1,0}-{2414,3447,1,1,0}-{2438,3465,0,1,0}-{2442,3505,0,1,0}-{2448,3486,0,1,0}-{2449,3487,0,1,0}-{2450,3489,0,1,0}-{2450,3505,0,1,0}-{2454,3465,0,1,0}-{2449,3486,1,1,0}-{2457,3488,1,1,0}-{2476,3488,1,1,0}-{2450,3496,2,1,0}-{2467,3488,2,1,0}-{2470,3503,2,1,0}-{2481,3498,2,1,0}-" + "loc_data": "{2398,3451,1,1,0}-{2414,3447,1,1,0}-{2379,3482,0,1,0}-{2381,3496,0,1,0}-{2384,3497,0,1,0}-{2391,3476,0,1,0}-{2410,3496,0,1,0}-{2421,3481,0,1,0}-{2397,3514,1,1,0}-{2434,3436,0,1,0}-{2437,3451,0,1,0}-{2438,3427,0,1,0}-{2441,3411,0,1,0}-{2466,3449,0,1,0}-{2470,3399,0,1,0}-{2472,3400,0,1,0}-{2473,3412,0,1,0}-{2476,3454,0,1,0}-{2482,3397,0,1,0}-{2489,3401,0,1,0}-{2479,3407,1,1,0}-{2438,3465,0,1,0}-{2442,3505,0,1,0}-{2448,3486,0,1,0}-{2449,3487,0,1,0}-{2450,3489,0,1,0}-{2450,3505,0,1,0}-{2454,3465,0,1,0}-{2449,3486,1,1,0}-{2457,3488,1,1,0}-{2476,3488,1,1,0}-{2450,3496,2,1,0}-{2467,3488,2,1,0}-{2470,3503,2,1,0}-{2481,3498,2,1,0}-" }, { "npc_id": "169", - "loc_data": "{2439,3433,0,1,0}-{2441,3449,0,1,0}-{2442,3428,0,1,0}-{2446,3403,0,1,0}-{2450,3416,0,1,0}-{2468,3441,0,1,0}-{2480,3408,0,1,0}-{2480,3406,1,1,0}-{2486,3400,1,1,0}-{2378,3482,0,1,0}-{2383,3496,0,1,0}-{2402,3507,0,1,0}-{2406,3476,0,1,0}-{2422,3485,0,1,0}-{2382,3506,1,1,0}-{2418,3472,1,1,0}-{2392,3450,1,1,0}-{2415,3415,1,1,0}-{2416,3434,1,1,0}-{2423,3425,1,1,0}-{2424,3442,1,1,0}-{2448,3489,0,1,0}-{2450,3488,0,1,0}-{2463,3508,0,1,0}-{2473,3489,0,1,0}-{2474,3457,0,1,0}-{2479,3503,0,1,0}-{2486,3467,0,1,0}-{2437,3463,1,1,0}-{2448,3489,1,1,0}-{2482,3508,1,1,0}-" + "loc_data": "{2392,3450,1,1,0}-{2415,3415,1,1,0}-{2416,3434,1,1,0}-{2423,3425,1,1,0}-{2424,3442,1,1,0}-{2378,3482,0,1,0}-{2383,3496,0,1,0}-{2402,3507,0,1,0}-{2406,3476,0,1,0}-{2422,3485,0,1,0}-{2382,3506,1,1,0}-{2418,3472,1,1,0}-{2439,3433,0,1,0}-{2441,3449,0,1,0}-{2442,3428,0,1,0}-{2446,3403,0,1,0}-{2450,3416,0,1,0}-{2468,3441,0,1,0}-{2480,3408,0,1,0}-{2480,3406,1,1,0}-{2486,3400,1,1,0}-{2448,3489,0,1,0}-{2450,3488,0,1,0}-{2463,3508,0,1,0}-{2473,3489,0,1,0}-{2474,3457,0,1,0}-{2479,3503,0,1,0}-{2486,3467,0,1,0}-{2437,3463,1,1,0}-{2448,3489,1,1,0}-{2482,3508,1,1,0}-" }, { "npc_id": "170", @@ -1357,11 +1357,11 @@ }, { "npc_id": "479", - "loc_data": "{1970,5522,3,1,0}-{2384,3481,0,1,0}-{2388,3473,0,1,0}-{2418,3474,3,1,0}-{2415,3433,3,1,0}-{2466,3500,2,1,0}-" + "loc_data": "{2415,3433,3,1,0}-{2384,3481,0,1,0}-{2388,3473,0,1,0}-{2418,3474,3,1,0}-{2466,3500,2,1,0}-{1970,5522,3,1,0}-" }, { "npc_id": "480", - "loc_data": "{1964,5522,3,1,0}-{2458,3417,1,1,0}-{2460,3417,1,1,0}-{2409,3507,0,1,0}-{2412,3474,3,1,0}-{2463,3504,2,1,0}-" + "loc_data": "{2409,3507,0,1,0}-{2412,3474,3,1,0}-{2458,3417,1,1,0}-{2460,3417,1,1,0}-{2463,3504,2,1,0}-{1964,5522,3,1,0}-" }, { "npc_id": "481", @@ -1387,6 +1387,10 @@ "npc_id": "490", "loc_data": "{1887,5026,0,0,6}-" }, + { + "npc_id": "492", + "loc_data": "{2464,3227,0,1,0}-" + }, { "npc_id": "494", "loc_data": "{2615,3094,0,0,3}-{2615,3092,0,0,3}-{2615,3092,0,0,3}-{2615,3094,0,0,3}-{3122,3125,0,0,6}-{3120,3125,0,0,6}-{3090,3242,0,0,4}-{3090,3245,0,0,4}-{3090,3243,0,0,4}-{2618,3330,0,0,0}-{2619,3330,0,0,0}-{2584,3422,0,0,4}-{2584,3419,0,0,4}-{2584,3418,0,0,4}-{2657,3283,0,0,3}-{2657,3286,0,0,3}-{2807,3443,0,0,6}-{2810,3443,0,0,6}-" @@ -2433,11 +2437,11 @@ }, { "npc_id": "839", - "loc_data": "{3265,3066,0,1,0}-{3267,3066,0,1,0}-{3310,3068,0,1,0}-{3322,3011,0,1,0}-{3322,3052,0,1,0}-{3324,3030,0,1,0}-{3150,3044,0,1,0}-{3172,3009,0,1,0}-{3197,3012,0,1,0}-{3198,3040,0,1,0}-{3217,3092,0,1,0}-{3235,3074,0,1,0}-{3252,3125,0,1,0}-{3258,3078,0,1,0}-{3237,2968,0,1,0}-{3224,3013,0,1,0}-{3225,3034,0,1,0}-{3226,3060,0,1,0}-{3250,3057,0,1,0}-{3283,3108,0,1,0}-{3323,3094,0,1,0}-" + "loc_data": "{3150,3044,0,1,0}-{3172,3009,0,1,0}-{3197,3012,0,1,0}-{3198,3040,0,1,0}-{3237,2968,0,1,0}-{3224,3013,0,1,0}-{3225,3034,0,1,0}-{3226,3060,0,1,0}-{3250,3057,0,1,0}-{3217,3092,0,1,0}-{3235,3074,0,1,0}-{3252,3125,0,1,0}-{3258,3078,0,1,0}-{3265,3066,0,1,0}-{3267,3066,0,1,0}-{3310,3068,0,1,0}-{3322,3011,0,1,0}-{3322,3052,0,1,0}-{3324,3030,0,1,0}-{3283,3108,0,1,0}-{3323,3094,0,1,0}-" }, { "npc_id": "840", - "loc_data": "{3268,3052,0,1,0}-{3281,3056,0,1,0}-{3307,3055,0,1,0}-{3318,3020,0,1,0}-{3318,3040,0,1,0}-{3190,3054,0,1,0}-{3192,3016,0,1,0}-{3217,3111,0,1,0}-{3222,3086,0,1,0}-{3238,3101,0,1,0}-{3244,3080,0,1,0}-{3253,3116,0,1,0}-{3255,3095,0,1,0}-{3237,3000,0,1,0}-{3245,2960,0,1,0}-{3208,3032,0,1,0}-{3217,3064,0,1,0}-{3238,3015,0,1,0}-{3258,3063,0,1,0}-{3267,3077,0,1,0}-{3269,3110,0,1,0}-{3275,3094,0,1,0}-{3291,3078,0,1,0}-{3291,3100,0,1,0}-{3305,3089,0,1,0}-{3318,3078,0,1,0}-{3321,3104,0,1,0}-" + "loc_data": "{3190,3054,0,1,0}-{3192,3016,0,1,0}-{3237,3000,0,1,0}-{3245,2960,0,1,0}-{3208,3032,0,1,0}-{3217,3064,0,1,0}-{3238,3015,0,1,0}-{3258,3063,0,1,0}-{3217,3111,0,1,0}-{3222,3086,0,1,0}-{3238,3101,0,1,0}-{3244,3080,0,1,0}-{3253,3116,0,1,0}-{3255,3095,0,1,0}-{3268,3052,0,1,0}-{3281,3056,0,1,0}-{3307,3055,0,1,0}-{3318,3020,0,1,0}-{3318,3040,0,1,0}-{3267,3077,0,1,0}-{3269,3110,0,1,0}-{3275,3094,0,1,0}-{3291,3078,0,1,0}-{3291,3100,0,1,0}-{3305,3089,0,1,0}-{3318,3078,0,1,0}-{3321,3104,0,1,0}-" }, { "npc_id": "841", @@ -2781,7 +2785,7 @@ }, { "npc_id": "1019", - "loc_data": "{3187,5555,0,1,4}-{3190,5563,0,1,1}-{3193,5555,0,1,3} -{3213,9377,0,1,3}-{3209,9397,0,1,4}-{3245,9401,0,1,6}- {3237,9402,0,1,2}-{3207,9349,0,1,3}-{3220,9347,0,1,6} -{3233,9359,0,1,4}-{3235,9354,0,0,6}-{3259,9370,0,1,1}- {3258,9387,0,1,6}-{2707,9880,0,1,0}-{2711,9876,0,1,0}- {2712,9871,0,1,0}-{2715,9874,0,1,0}-{2720,9871,0,1,0} -{2717,9880,0,1,0}-{2722,9879,0,1,0}-{2723,9875,0,1,0}- {3278,9368,0,1,5}-{3271,9359,0,1,3}-{3287,9359,0,1,5}- {3301,9394,0,1,5}-{3318,9352,0,1,5}-" + "loc_data": "{3187,5555,0,1,4}-{3190,5563,0,1,1}-{3193,5555,0,1,3}-{3213,9377,0,1,3}-{3209,9397,0,1,4}-{3245,9401,0,1,6}-{3237,9402,0,1,2}-{3207,9349,0,1,3}-{3220,9347,0,1,6}-{3233,9359,0,1,4}-{3235,9354,0,0,6}-{3259,9370,0,1,1}-{3258,9387,0,1,6}-{2707,9880,0,1,0}-{2711,9876,0,1,0}-{2712,9871,0,1,0}-{2715,9874,0,1,0}-{2720,9871,0,1,0}-{2717,9880,0,1,0}-{2722,9879,0,1,0}-{2723,9875,0,1,0}-{3278,9368,0,1,5}-{3271,9359,0,1,3}-{3287,9359,0,1,5}-{3301,9394,0,1,5}-{3318,9352,0,1,5}-" }, { "npc_id": "1020", @@ -2829,7 +2833,7 @@ }, { "npc_id": "1043", - "loc_data": "{3726,3381,0,0,0}-{3745,3359,0,0,0}-{3749,3365,0,0,0}-{3750,3354,0,0,0}-{3757,3363,0,0,0}-{3763,3337,0,0,0}-{3459,3457,0,0,0}-{3460,3462,0,0,0}-{3462,3459,0,0,0}-{3463,3490,0,0,0}-{3464,3511,0,0,0}-{3465,3466,0,0,0}-{3466,3495,0,0,0}-{3466,3497,0,0,0}-{3467,3485,0,0,0}-{3467,3497,0,0,0}-{3467,3509,0,0,0}-{3469,3470,0,0,0}-{3470,3469,0,0,0}-{3471,3477,0,0,0}-{3474,3505,0,0,0}-{3476,3507,0,0,0}-{3479,3511,0,0,0}-{3480,3468,0,0,0}-{3480,3470,0,0,0}-{3482,3469,0,0,0}-{3483,3511,0,0,0}-{3484,3464,0,0,0}-{3485,3509,0,0,0}-{3490,3461,0,0,0}-{3491,3460,0,0,0}-{3494,3461,0,0,0}-{3494,3512,0,0,0}-{3498,3461,0,0,0}-{3499,3513,0,0,0}-{3501,3513,0,0,0}-{3502,3464,0,0,0}-{3504,3463,0,0,0}-{3506,3512,0,0,0}-{3506,3515,0,0,0}-{3507,3515,0,0,0}-{3509,3512,0,0,0}-{3511,3488,0,0,0}-{3513,3467,0,0,0}-{3513,3487,0,0,0}-{3513,3489,0,0,0}-{3513,3503,0,0,0}-{3514,3490,0,0,0}-{3515,3495,0,0,0}-{3515,3499,0,0,0}-{3516,3469,0,0,0}-{3409,3369,0,0,0}-{3420,3349,0,0,0}-{3430,3340,0,0,0}-{3435,3330,0,0,0}-{3435,3359,0,0,0}-{3437,3374,0,0,0}-{3446,3385,0,0,0}-{3447,3388,0,0,0}-{3450,3370,0,0,0}-{3450,3387,0,0,0}-{3451,3336,0,0,0}-{3452,3355,0,0,0}-{3726,3289,0,0,0}-{3732,3291,0,0,0}-{3737,3280,0,0,0}-{3745,3285,0,0,0}-{3418,3420,0,0,0}-{3424,3452,0,0,0}-{3428,3409,0,0,0}-{3433,3416,0,0,0}-{3434,3394,0,0,0}-{3436,3407,0,0,0}-{3438,3414,0,0,0}-{3439,3449,0,0,0}-{3446,3439,0,0,0}-{3454,3410,0,0,0}-{3454,3423,0,0,0}-{2852,4561,0,0,0}-{2853,4566,0,0,0}-{2856,4568,0,0,0}-{2864,4561,0,0,0}-{2864,4563,0,0,0}-" + "loc_data": "{2852,4561,0,0,0}-{2853,4566,0,0,0}-{2856,4568,0,0,0}-{2864,4561,0,0,0}-{2864,4563,0,0,0}-{3409,3369,0,0,0}-{3420,3349,0,0,0}-{3430,3340,0,0,0}-{3435,3330,0,0,0}-{3435,3359,0,0,0}-{3437,3374,0,0,0}-{3446,3385,0,0,0}-{3447,3388,0,0,0}-{3450,3370,0,0,0}-{3450,3387,0,0,0}-{3451,3336,0,0,0}-{3452,3355,0,0,0}-{3418,3420,0,0,0}-{3424,3452,0,0,0}-{3428,3409,0,0,0}-{3433,3416,0,0,0}-{3434,3394,0,0,0}-{3436,3407,0,0,0}-{3438,3414,0,0,0}-{3439,3449,0,0,0}-{3446,3439,0,0,0}-{3454,3410,0,0,0}-{3454,3423,0,0,0}-{3726,3289,0,0,0}-{3732,3291,0,0,0}-{3737,3280,0,0,0}-{3745,3285,0,0,0}-{3726,3381,0,0,0}-{3745,3359,0,0,0}-{3749,3365,0,0,0}-{3750,3354,0,0,0}-{3757,3363,0,0,0}-{3763,3337,0,0,0}-{3459,3457,0,0,0}-{3460,3462,0,0,0}-{3462,3459,0,0,0}-{3463,3490,0,0,0}-{3464,3511,0,0,0}-{3465,3466,0,0,0}-{3466,3495,0,0,0}-{3466,3497,0,0,0}-{3467,3485,0,0,0}-{3467,3497,0,0,0}-{3467,3509,0,0,0}-{3469,3470,0,0,0}-{3470,3469,0,0,0}-{3471,3477,0,0,0}-{3474,3505,0,0,0}-{3476,3507,0,0,0}-{3479,3511,0,0,0}-{3480,3468,0,0,0}-{3480,3470,0,0,0}-{3482,3469,0,0,0}-{3483,3511,0,0,0}-{3484,3464,0,0,0}-{3485,3509,0,0,0}-{3490,3461,0,0,0}-{3491,3460,0,0,0}-{3494,3461,0,0,0}-{3494,3512,0,0,0}-{3498,3461,0,0,0}-{3499,3513,0,0,0}-{3501,3513,0,0,0}-{3502,3464,0,0,0}-{3504,3463,0,0,0}-{3506,3512,0,0,0}-{3506,3515,0,0,0}-{3507,3515,0,0,0}-{3509,3512,0,0,0}-{3511,3488,0,0,0}-{3513,3467,0,0,0}-{3513,3487,0,0,0}-{3513,3489,0,0,0}-{3513,3503,0,0,0}-{3514,3490,0,0,0}-{3515,3495,0,0,0}-{3515,3499,0,0,0}-{3516,3469,0,0,0}-" }, { "npc_id": "1044", @@ -3345,11 +3349,11 @@ }, { "npc_id": "1212", - "loc_data": "{2247,3226,0,0,0}-{2249,3227,0,0,0}-{2249,3258,0,0,0}-{2251,3260,0,0,0}-{2259,3211,0,0,0}-{2267,3223,0,0,0}-{2267,3225,0,0,0}-{2298,3262,0,0,0}-{3202,5990,0,0,0}-{3232,5977,0,0,0}-{3234,5969,0,0,0}-{3237,5995,0,0,0}-{3242,5997,0,0,0}-{3244,5995,0,0,0}-{3245,5995,0,0,0}-{2194,3189,0,0,0}-{2196,3187,0,0,0}-{2196,3189,0,0,0}-{2206,3164,0,0,0}-{2209,3164,0,0,0}-{2216,3142,0,0,0}-{2218,3139,0,0,0}-{2178,3238,0,0,0}-{2208,3225,0,0,0}-{2210,3217,0,0,0}-{2213,3243,0,0,0}-{2218,3245,0,0,0}-{2220,3243,0,0,0}-{2221,3243,0,0,0}-{2695,6170,0,0,0}-{2697,6171,0,0,0}-{2697,6202,0,0,0}-{2699,6204,0,0,0}-{2707,6155,0,0,0}-{2715,6167,0,0,0}-{2715,6169,0,0,0}-{2746,6206,0,0,0}-{2729,6086,0,0,0}-{2731,6080,0,0,0}-{2731,6089,0,0,0}-{2744,6139,0,0,0}-{3271,5978,0,0,0}-{3273,5979,0,0,0}-{3273,6010,0,0,0}-{3275,6012,0,0,0}-{3283,5963,0,0,0}-{3291,5975,0,0,0}-{3291,5977,0,0,0}-{3322,6014,0,0,0}-{2281,3142,0,0,0}-{2283,3136,0,0,0}-{2283,3145,0,0,0}-{2296,3195,0,0,0}-" + "loc_data": "{2194,3189,0,0,0}-{2196,3187,0,0,0}-{2196,3189,0,0,0}-{2206,3164,0,0,0}-{2209,3164,0,0,0}-{2216,3142,0,0,0}-{2218,3139,0,0,0}-{2178,3238,0,0,0}-{2208,3225,0,0,0}-{2210,3217,0,0,0}-{2213,3243,0,0,0}-{2218,3245,0,0,0}-{2220,3243,0,0,0}-{2221,3243,0,0,0}-{3202,5990,0,0,0}-{3232,5977,0,0,0}-{3234,5969,0,0,0}-{3237,5995,0,0,0}-{3242,5997,0,0,0}-{3244,5995,0,0,0}-{3245,5995,0,0,0}-{2729,6086,0,0,0}-{2731,6080,0,0,0}-{2731,6089,0,0,0}-{2744,6139,0,0,0}-{2695,6170,0,0,0}-{2697,6171,0,0,0}-{2697,6202,0,0,0}-{2699,6204,0,0,0}-{2707,6155,0,0,0}-{2715,6167,0,0,0}-{2715,6169,0,0,0}-{2746,6206,0,0,0}-{2281,3142,0,0,0}-{2283,3136,0,0,0}-{2283,3145,0,0,0}-{2296,3195,0,0,0}-{2247,3226,0,0,0}-{2249,3227,0,0,0}-{2249,3258,0,0,0}-{2251,3260,0,0,0}-{2259,3211,0,0,0}-{2267,3223,0,0,0}-{2267,3225,0,0,0}-{2298,3262,0,0,0}-{3271,5978,0,0,0}-{3273,5979,0,0,0}-{3273,6010,0,0,0}-{3275,6012,0,0,0}-{3283,5963,0,0,0}-{3291,5975,0,0,0}-{3291,5977,0,0,0}-{3322,6014,0,0,0}-" }, { "npc_id": "1213", - "loc_data": "{2248,3225,0,0,0}-{2248,3227,0,0,0}-{2251,3257,0,0,0}-{2253,3259,0,0,0}-{2268,3224,0,0,0}-{2291,3262,0,0,0}-{3204,5988,0,0,0}-{3232,5979,0,0,0}-{3234,5967,0,0,0}-{3234,5976,0,0,0}-{3238,5996,0,0,0}-{3239,5997,0,0,0}-{3243,5996,0,0,0}-{2192,3188,0,0,0}-{2192,3191,0,0,0}-{2194,3190,0,0,0}-{2208,3163,0,0,0}-{2209,3163,0,0,0}-{2216,3136,0,0,0}-{2216,3140,0,0,0}-{2180,3236,0,0,0}-{2208,3227,0,0,0}-{2210,3215,0,0,0}-{2210,3224,0,0,0}-{2214,3244,0,0,0}-{2215,3245,0,0,0}-{2219,3244,0,0,0}-{2696,6169,0,0,0}-{2696,6171,0,0,0}-{2699,6201,0,0,0}-{2701,6203,0,0,0}-{2716,6168,0,0,0}-{2739,6206,0,0,0}-{2730,6084,0,0,0}-{3272,5977,0,0,0}-{3272,5979,0,0,0}-{3275,6009,0,0,0}-{3277,6011,0,0,0}-{3292,5976,0,0,0}-{3315,6014,0,0,0}-{2282,3140,0,0,0}-" + "loc_data": "{2192,3188,0,0,0}-{2192,3191,0,0,0}-{2194,3190,0,0,0}-{2208,3163,0,0,0}-{2209,3163,0,0,0}-{2216,3136,0,0,0}-{2216,3140,0,0,0}-{2180,3236,0,0,0}-{2208,3227,0,0,0}-{2210,3215,0,0,0}-{2210,3224,0,0,0}-{2214,3244,0,0,0}-{2215,3245,0,0,0}-{2219,3244,0,0,0}-{3204,5988,0,0,0}-{3232,5979,0,0,0}-{3234,5967,0,0,0}-{3234,5976,0,0,0}-{3238,5996,0,0,0}-{3239,5997,0,0,0}-{3243,5996,0,0,0}-{2730,6084,0,0,0}-{2696,6169,0,0,0}-{2696,6171,0,0,0}-{2699,6201,0,0,0}-{2701,6203,0,0,0}-{2716,6168,0,0,0}-{2739,6206,0,0,0}-{2282,3140,0,0,0}-{2248,3225,0,0,0}-{2248,3227,0,0,0}-{2251,3257,0,0,0}-{2253,3259,0,0,0}-{2268,3224,0,0,0}-{2291,3262,0,0,0}-{3272,5977,0,0,0}-{3272,5979,0,0,0}-{3275,6009,0,0,0}-{3277,6011,0,0,0}-{3292,5976,0,0,0}-{3315,6014,0,0,0}-" }, { "npc_id": "1214", @@ -3771,6 +3775,10 @@ "npc_id": "1427", "loc_data": "{2957,3025,0,0,0}-" }, + { + "npc_id": "1433", + "loc_data": "{2770,2789,0,1,6}-" + }, { "npc_id": "1434", "loc_data": "{2753,2770,0,1,6}-" @@ -4057,7 +4065,7 @@ }, { "npc_id": "1633", - "loc_data": "{1837,3244,0,1,0}-{1836,3250,0,1,0}-{1845,3251,0,1,0}-{1845,3247,0,1,0}-{1849,3241,0,1,0}-{1853,3247,0,1,0}-{1848,3254,0,1,0}-{1858,3251,0,1,0}-{1935,3217,0,1,0}-{1933,3209,0,1,0}-{1930,3208,0,1,0}-{1929,3217,0,1,0}-{1927,3219,0,1,0}-{1924,3211,0,1,0}-{1920,3216,0,1,0}-{1925,3213,0,1,0}-{3263,9399,0,1,3}-{3274,9397,0,1,1}-{3275,9393,0,1,6}-{3271,9384,0,1,0}-{3270,9380,0,1,2}-{3283,9378,0,1,6}-{3285,9386,0,1,5}-{3284,9401,0,1,4}-{3278,9353,0,1,1}-{3299,9380,0,1,1}-{3319,9402,0,1,1}-{3305,9350,0,1,4}-{3248,9374,0,1,3}-{3253,9363,0,1,3}-{3249,9355,0,1,3}-{2761,10007,0,1,0}-{2757,10010,0,1,3}-{2763,10000,0,1,5}-{2760,10011,0,1,0}-{2761,9997,0,1,4}-" + "loc_data": "{1837,3244,0,1,0}-{1836,3250,0,1,0}-{1845,3251,0,1,0}-{1845,3247,0,1,0}-{1849,3241,0,1,0}-{1853,3247,0,1,0}-{1848,3254,0,1,0}-{1858,3251,0,1,0}-{1935,3217,0,1,0}-{1933,3209,0,1,0}-{1930,3208,0,1,0}-{1929,3217,0,1,0}-{1927,3219,0,1,0}-{1924,3211,0,1,0}-{1920,3216,0,1,0}-{1925,3213,0,1,0}-{3263,9399,0,1,3}-{3248,9374,0,1,3}-{3253,9363,0,1,3}-{3249,9355,0,1,3}-{3274,9397,0,1,1}-{3275,9393,0,1,6}-{3271,9384,0,1,0}-{3270,9380,0,1,2}-{3283,9378,0,1,6}-{3285,9386,0,1,5}-{3284,9401,0,1,4}-{3278,9353,0,1,1}-{3299,9380,0,1,1}-{3319,9402,0,1,1}-{3305,9350,0,1,4}-{2761,10007,0,1,0}-{2757,10010,0,1,3}-{2763,10000,0,1,5}-{2760,10011,0,1,0}-{2761,9997,0,1,4}-" }, { "npc_id": "1634", @@ -4257,7 +4265,7 @@ }, { "npc_id": "1752", - "loc_data": "{2437,3442,0,1,0}-{2449,3421,0,1,0}-{2457,3394,0,1,0}-{2464,3394,0,1,0}-{2480,3406,0,1,0}-{2432,3387,0,1,0}-{2442,3388,0,1,0}-{2453,3363,0,1,0}-{2463,3375,0,1,0}-{2381,3428,0,1,0}-{2392,3405,0,1,0}-{2413,3397,0,1,0}-{2415,3408,0,1,0}-{2463,3456,0,1,0}-{2468,3456,0,1,0}-" + "loc_data": "{2381,3428,0,1,0}-{2392,3405,0,1,0}-{2413,3397,0,1,0}-{2415,3408,0,1,0}-{2432,3387,0,1,0}-{2442,3388,0,1,0}-{2453,3363,0,1,0}-{2463,3375,0,1,0}-{2437,3442,0,1,0}-{2449,3421,0,1,0}-{2457,3394,0,1,0}-{2464,3394,0,1,0}-{2480,3406,0,1,0}-{2463,3456,0,1,0}-{2468,3456,0,1,0}-" }, { "npc_id": "1754", @@ -4541,7 +4549,7 @@ }, { "npc_id": "1874", - "loc_data": "{3371,9301,0,1,0}-{3371,9303,0,1,0}-{3371,9305,0,1,0}-{3371,9307,0,1,0}-{3371,9309,0,1,0}-{3373,9301,0,1,0}-{3373,9303,0,1,0}-{3373,9306,0,1,0}-{3373,9308,0,1,0}-{3375,9301,0,1,0}-{3375,9303,0,1,0}-{3375,9305,0,1,0}-{3375,9307,0,1,0}-{3375,9309,0,1,0}-{3403,2963,0,1,0}-{3429,2976,0,1,0}-{3328,2952,0,1,0}-{3328,2957,0,1,0}-{3331,2955,0,1,0}-{3331,2961,0,1,0}-{3266,2955,0,1,0}-{3271,2967,0,1,0}-{3279,2957,0,1,0}-{3280,2975,0,1,0}-{3284,2959,0,1,0}-{3294,2964,0,1,0}-{3295,2978,0,1,0}-{3305,2966,0,1,0}-{3307,2959,0,1,0}-{3309,2973,0,1,0}-{3396,3029,0,1,0}-{3397,3044,0,1,0}-{3398,3038,0,1,0}-" + "loc_data": "{3328,2952,0,1,0}-{3328,2957,0,1,0}-{3331,2955,0,1,0}-{3331,2961,0,1,0}-{3371,9301,0,1,0}-{3371,9303,0,1,0}-{3371,9305,0,1,0}-{3371,9307,0,1,0}-{3371,9309,0,1,0}-{3373,9301,0,1,0}-{3373,9303,0,1,0}-{3373,9306,0,1,0}-{3373,9308,0,1,0}-{3375,9301,0,1,0}-{3375,9303,0,1,0}-{3375,9305,0,1,0}-{3375,9307,0,1,0}-{3375,9309,0,1,0}-{3403,2963,0,1,0}-{3429,2976,0,1,0}-{3396,3029,0,1,0}-{3397,3044,0,1,0}-{3398,3038,0,1,0}-{3266,2955,0,1,0}-{3271,2967,0,1,0}-{3279,2957,0,1,0}-{3280,2975,0,1,0}-{3284,2959,0,1,0}-{3294,2964,0,1,0}-{3295,2978,0,1,0}-{3305,2966,0,1,0}-{3307,2959,0,1,0}-{3309,2973,0,1,0}-" }, { "npc_id": "1875", @@ -4701,19 +4709,19 @@ }, { "npc_id": "1961", - "loc_data": "{3156,5477,0,1,5}-{3162,5479,0,1,4}-{3159,5477,0,1,6}-{3150,5474,0,1,3}-{3147,5480,0,1,1}-{3159,5484,0,1,3}-{2764,4944,1,1,0}-{2796,4976,1,1,0}-{2798,4950,1,1,0}-{2806,4939,1,1,0}-{2832,4959,2,1,0}-{3201,9293,0,1,0}-{3205,9303,0,1,0}-{3206,9328,0,1,0}-{3207,9310,0,1,0}-{3211,9284,0,1,0}-{3220,9289,0,1,0}-{3224,9334,0,1,0}-{3249,9286,0,1,0}-{3251,9303,0,1,0}-{3252,9327,0,1,0}-{3252,9332,0,1,0}-{3255,9315,0,1,0}-{3261,9296,0,1,0}-{3261,9306,0,1,0}-" + "loc_data": "{2832,4959,2,1,0}-{3156,5477,0,1,5}-{3162,5479,0,1,4}-{3159,5477,0,1,6}-{3150,5474,0,1,3}-{3147,5480,0,1,1}-{3159,5484,0,1,3}-{3201,9293,0,1,0}-{3205,9303,0,1,0}-{3206,9328,0,1,0}-{3207,9310,0,1,0}-{3211,9284,0,1,0}-{3220,9289,0,1,0}-{3224,9334,0,1,0}-{3249,9286,0,1,0}-{3251,9303,0,1,0}-{3252,9327,0,1,0}-{3252,9332,0,1,0}-{3255,9315,0,1,0}-{3261,9296,0,1,0}-{3261,9306,0,1,0}-{2764,4944,1,1,0}-{2796,4976,1,1,0}-{2798,4950,1,1,0}-{2806,4939,1,1,0}-" }, { "npc_id": "1962", - "loc_data": "{3168,5458,0,1,2}-{2900,4948,3,1,0}-{2762,4962,1,1,0}-{2763,4973,1,1,0}-{2780,4977,1,1,0}-{2787,4967,1,1,0}-{2796,4959,1,1,0}-{2838,4948,2,1,0}-{3205,9329,0,1,0}-{3210,9292,0,1,0}-{3221,9310,0,1,0}-{3225,9323,0,1,0}-{3243,9310,0,1,0}-{3246,9290,0,1,0}-{3255,9301,0,1,0}-{3255,9321,0,1,0}-{3261,9331,0,1,0}-" + "loc_data": "{2838,4948,2,1,0}-{2900,4948,3,1,0}-{3168,5458,0,1,2}-{3205,9329,0,1,0}-{3210,9292,0,1,0}-{3221,9310,0,1,0}-{3225,9323,0,1,0}-{3243,9310,0,1,0}-{3246,9290,0,1,0}-{3255,9301,0,1,0}-{3255,9321,0,1,0}-{3261,9331,0,1,0}-{2762,4962,1,1,0}-{2763,4973,1,1,0}-{2780,4977,1,1,0}-{2787,4967,1,1,0}-{2796,4959,1,1,0}-" }, { "npc_id": "1963", - "loc_data": "{3166,5465,0,1,7}-{3168,5462,0,1,0}-{3167,5467,0,1,1}-{3167,5467,0,1,7}-{2926,4965,3,1,0}-{2771,4947,1,1,0}-{2775,4963,1,1,0}-{2781,4948,1,1,0}-{2792,4942,1,1,0}-{2798,4954,1,1,0}-{2858,4964,2,1,0}-{3204,9309,0,1,0}-{3214,9333,0,1,0}-{3219,9297,0,1,0}-{3239,9300,0,1,0}-{3240,9286,0,1,0}-{3240,9330,0,1,0}-{3245,9306,0,1,0}-{3250,9316,0,1,0}-{3250,9317,0,1,0}-{3257,9290,0,1,0}-" + "loc_data": "{2858,4964,2,1,0}-{2926,4965,3,1,0}-{3166,5465,0,1,7}-{3168,5462,0,1,0}-{3167,5467,0,1,1}-{3167,5467,0,1,7}-{3204,9309,0,1,0}-{3214,9333,0,1,0}-{3219,9297,0,1,0}-{3239,9300,0,1,0}-{3240,9286,0,1,0}-{3240,9330,0,1,0}-{3245,9306,0,1,0}-{3250,9316,0,1,0}-{3250,9317,0,1,0}-{3257,9290,0,1,0}-{2771,4947,1,1,0}-{2775,4963,1,1,0}-{2781,4948,1,1,0}-{2792,4942,1,1,0}-{2798,4954,1,1,0}-" }, { "npc_id": "1964", - "loc_data": "{2761,4950,1,1,0}-{2765,4938,1,1,0}-{2770,4955,1,1,0}-{2772,4940,1,1,0}-{2777,4943,1,1,0}-{2782,4967,1,1,0}-{2797,4965,1,1,0}-{2799,4937,1,1,0}-{2799,4941,1,1,0}-{2807,4968,1,1,0}-{2808,4975,1,1,0}-{2809,4953,1,1,0}-{2864,4946,2,1,0}-{3202,9283,0,1,0}-{3206,9333,0,1,0}-{3209,9299,0,1,0}-{3219,9301,0,1,0}-{3226,9285,0,1,0}-{3227,9303,0,1,0}-{3228,9293,0,1,0}-{3229,9333,0,1,0}-{3237,9293,0,1,0}-{3246,9321,0,1,0}-{3256,9296,0,1,0}-{3260,9285,0,1,0}-{3262,9317,0,1,0}-" + "loc_data": "{2864,4946,2,1,0}-{3202,9283,0,1,0}-{3206,9333,0,1,0}-{3209,9299,0,1,0}-{3219,9301,0,1,0}-{3226,9285,0,1,0}-{3227,9303,0,1,0}-{3228,9293,0,1,0}-{3229,9333,0,1,0}-{3237,9293,0,1,0}-{3246,9321,0,1,0}-{3256,9296,0,1,0}-{3260,9285,0,1,0}-{3262,9317,0,1,0}-{2761,4950,1,1,0}-{2765,4938,1,1,0}-{2770,4955,1,1,0}-{2772,4940,1,1,0}-{2777,4943,1,1,0}-{2782,4967,1,1,0}-{2797,4965,1,1,0}-{2799,4937,1,1,0}-{2799,4941,1,1,0}-{2807,4968,1,1,0}-{2808,4975,1,1,0}-{2809,4953,1,1,0}-" }, { "npc_id": "1970", @@ -4725,7 +4733,7 @@ }, { "npc_id": "1973", - "loc_data": "{2693,5075,0,1,0}-{2695,5089,0,1,0}-{2697,5096,0,1,0}-{2703,5064,0,1,0}-{2710,5105,0,1,0}-{2719,5078,0,1,0}-{2719,5112,0,1,0}-{2720,5096,0,1,0}-{2726,5086,0,1,0}-{2729,5096,0,1,0}-{2735,5061,0,1,0}-{2740,5069,0,1,0}-{2740,5085,0,1,0}-{2746,5092,0,1,0}-{2746,5114,0,1,0}-{2626,5065,0,1,0}-{2637,5099,0,1,0}-{2638,5058,0,1,0}-{2644,5090,0,1,0}-{2658,5082,0,1,0}-{2666,5097,0,1,0}-{2680,5074,0,1,0}-" + "loc_data": "{2626,5065,0,1,0}-{2637,5099,0,1,0}-{2638,5058,0,1,0}-{2644,5090,0,1,0}-{2658,5082,0,1,0}-{2666,5097,0,1,0}-{2680,5074,0,1,0}-{2693,5075,0,1,0}-{2695,5089,0,1,0}-{2697,5096,0,1,0}-{2703,5064,0,1,0}-{2710,5105,0,1,0}-{2719,5078,0,1,0}-{2719,5112,0,1,0}-{2720,5096,0,1,0}-{2726,5086,0,1,0}-{2729,5096,0,1,0}-{2735,5061,0,1,0}-{2740,5069,0,1,0}-{2740,5085,0,1,0}-{2746,5092,0,1,0}-{2746,5114,0,1,0}-" }, { "npc_id": "1976", @@ -4737,11 +4745,11 @@ }, { "npc_id": "1993", - "loc_data": "{3264,2886,0,1,0}-{3267,2891,0,1,0}-{3268,2881,0,1,0}-{3269,2885,0,1,0}-{3296,2912,0,1,0}-{3297,2919,0,1,0}-{3301,2920,0,1,0}-{3308,2916,0,1,0}-{3258,2868,0,1,0}-{3259,2823,0,1,0}-{3259,2845,0,1,0}-{3260,2830,0,1,0}-{3260,2859,0,1,0}-{3261,2819,0,1,0}-{3261,2851,0,1,0}-{3337,2922,0,1,0}-{3343,2931,0,1,0}-{3349,2922,0,1,0}-{3351,2927,0,1,0}-{3357,2922,0,1,0}-{3359,2927,0,1,0}-{3364,2937,0,1,0}-{3268,2854,0,1,0}-{3269,2827,0,1,0}-{3269,2866,0,1,0}-{3269,2875,0,1,0}-{3270,2818,0,1,0}-{3272,2841,0,1,0}-{3276,2842,0,1,0}-{3282,2839,0,1,0}-{3290,2847,0,1,0}-" + "loc_data": "{3337,2922,0,1,0}-{3343,2931,0,1,0}-{3349,2922,0,1,0}-{3351,2927,0,1,0}-{3357,2922,0,1,0}-{3359,2927,0,1,0}-{3364,2937,0,1,0}-{3258,2868,0,1,0}-{3259,2823,0,1,0}-{3259,2845,0,1,0}-{3260,2830,0,1,0}-{3260,2859,0,1,0}-{3261,2819,0,1,0}-{3261,2851,0,1,0}-{3268,2854,0,1,0}-{3269,2827,0,1,0}-{3269,2866,0,1,0}-{3269,2875,0,1,0}-{3270,2818,0,1,0}-{3272,2841,0,1,0}-{3276,2842,0,1,0}-{3282,2839,0,1,0}-{3290,2847,0,1,0}-{3264,2886,0,1,0}-{3267,2891,0,1,0}-{3268,2881,0,1,0}-{3269,2885,0,1,0}-{3296,2912,0,1,0}-{3297,2919,0,1,0}-{3301,2920,0,1,0}-{3308,2916,0,1,0}-" }, { "npc_id": "1994", - "loc_data": "{3265,2931,0,1,0}-{3265,2935,0,1,0}-{3268,2932,0,1,0}-{3268,2935,0,1,0}-{3316,2900,0,1,0}-{3319,2897,0,1,0}-{3320,2901,0,1,0}-{3321,2899,0,1,0}-{3212,2863,0,1,0}-{3213,2866,0,1,0}-{3215,2863,0,1,0}-{3216,2865,0,1,0}-{3216,2868,0,1,0}-{3218,2831,0,1,0}-{3220,2830,0,1,0}-{3220,2833,0,1,0}-{3221,2831,0,1,0}-{3238,2845,0,1,0}-{3240,2845,0,1,0}-{3241,2843,0,1,0}-{3241,2847,0,1,0}-{3400,2997,0,1,0}-{3402,2999,0,1,0}-{3403,2997,0,1,0}-{3445,2993,0,1,0}-{3446,2992,0,1,0}-{3448,2991,0,1,0}-{3448,2994,0,1,0}-{3329,2933,0,1,0}-{3330,2931,0,1,0}-{3331,2933,0,1,0}-{3343,2895,0,1,0}-{3345,2896,0,1,0}-{3346,2894,0,1,0}-{3348,2894,0,1,0}-{3376,2934,0,1,0}-{3378,2933,0,1,0}-{3378,2935,0,1,0}-{3381,2907,0,1,0}-{3383,2905,0,1,0}-{3383,2908,0,1,0}-{3384,2907,0,1,0}-{3306,2817,0,1,0}-{3308,2818,0,1,0}-{3309,2816,0,1,0}-{3312,2817,0,1,0}-{3314,2861,0,1,0}-{3319,2873,0,1,0}-{3321,2871,0,1,0}-{3324,2870,0,1,0}-{3327,2858,0,1,0}-{3406,3014,0,1,0}-{3408,3013,0,1,0}-{3408,3016,0,1,0}-{3410,3015,0,1,0}-" + "loc_data": "{3329,2933,0,1,0}-{3330,2931,0,1,0}-{3331,2933,0,1,0}-{3343,2895,0,1,0}-{3345,2896,0,1,0}-{3346,2894,0,1,0}-{3348,2894,0,1,0}-{3376,2934,0,1,0}-{3378,2933,0,1,0}-{3378,2935,0,1,0}-{3381,2907,0,1,0}-{3383,2905,0,1,0}-{3383,2908,0,1,0}-{3384,2907,0,1,0}-{3400,2997,0,1,0}-{3402,2999,0,1,0}-{3403,2997,0,1,0}-{3445,2993,0,1,0}-{3446,2992,0,1,0}-{3448,2991,0,1,0}-{3448,2994,0,1,0}-{3406,3014,0,1,0}-{3408,3013,0,1,0}-{3408,3016,0,1,0}-{3410,3015,0,1,0}-{3212,2863,0,1,0}-{3213,2866,0,1,0}-{3215,2863,0,1,0}-{3216,2865,0,1,0}-{3216,2868,0,1,0}-{3218,2831,0,1,0}-{3220,2830,0,1,0}-{3220,2833,0,1,0}-{3221,2831,0,1,0}-{3238,2845,0,1,0}-{3240,2845,0,1,0}-{3241,2843,0,1,0}-{3241,2847,0,1,0}-{3306,2817,0,1,0}-{3308,2818,0,1,0}-{3309,2816,0,1,0}-{3312,2817,0,1,0}-{3314,2861,0,1,0}-{3319,2873,0,1,0}-{3321,2871,0,1,0}-{3324,2870,0,1,0}-{3327,2858,0,1,0}-{3265,2931,0,1,0}-{3265,2935,0,1,0}-{3268,2932,0,1,0}-{3268,2935,0,1,0}-{3316,2900,0,1,0}-{3319,2897,0,1,0}-{3320,2901,0,1,0}-{3321,2899,0,1,0}-" }, { "npc_id": "1995", @@ -4893,7 +4901,7 @@ }, { "npc_id": "2057", - "loc_data": "{2442,9436,2,1,0}-{2458,9413,2,1,0}-{2470,9435,2,1,0}-{2472,9460,2,1,0}-{2483,9413,2,1,0}-{2485,9447,2,1,0}-{2480,3046,0,1,0}-{2453,9393,2,1,0}-{2461,9403,2,1,0}-{2464,9380,2,1,0}-" + "loc_data": "{2480,3046,0,1,0}-{2453,9393,2,1,0}-{2461,9403,2,1,0}-{2464,9380,2,1,0}-{2442,9436,2,1,0}-{2458,9413,2,1,0}-{2470,9435,2,1,0}-{2472,9460,2,1,0}-{2483,9413,2,1,0}-{2485,9447,2,1,0}-" }, { "npc_id": "2058", @@ -6313,7 +6321,7 @@ }, { "npc_id": "2803", - "loc_data": "{3387,3068,0,1,0}-{3387,3017,0,1,0}-{3404,3062,0,1,0}-{3441,3061,0,1,0}-{3445,3034,0,1,0}-{3338,2804,0,1,0}-{3339,2815,0,1,0}-{3342,2809,0,1,0}-{3344,2800,0,1,0}-{3348,2809,0,1,0}-{3349,2814,0,1,0}-{3357,2813,0,1,0}-{3357,2806,0,1,0}-{3365,2815,0,1,0}-" + "loc_data": "{3338,2804,0,1,0}-{3339,2815,0,1,0}-{3342,2809,0,1,0}-{3344,2800,0,1,0}-{3348,2809,0,1,0}-{3349,2814,0,1,0}-{3357,2813,0,1,0}-{3357,2806,0,1,0}-{3365,2815,0,1,0}-{3387,3068,0,1,0}-{3387,3017,0,1,0}-{3404,3062,0,1,0}-{3441,3061,0,1,0}-{3445,3034,0,1,0}-" }, { "npc_id": "2804", @@ -7233,7 +7241,7 @@ }, { "npc_id": "3675", - "loc_data": "{3333,2864,0,1,1}-{3335,2860,0,1,2}-{3338,2864,0,1,4}-{3215,2841,0,1,0}-{3217,2845,0,1,0}-{3220,2841,0,1,0}-{3295,2866,0,1,0}-{3297,2863,0,1,0}-{3301,2864,0,1,0}-{3336,2778,0,1,0}-{3343,2793,0,1,0}-{3367,2791,0,1,0}-" + "loc_data": "{3336,2778,0,1,0}-{3343,2793,0,1,0}-{3367,2791,0,1,0}-{3333,2864,0,1,1}-{3335,2860,0,1,2}-{3338,2864,0,1,4}-{3215,2841,0,1,0}-{3217,2845,0,1,0}-{3220,2841,0,1,0}-{3295,2866,0,1,0}-{3297,2863,0,1,0}-{3301,2864,0,1,0}-" }, { "npc_id": "3677", @@ -8241,19 +8249,19 @@ }, { "npc_id": "4690", - "loc_data": "{2372,3401,0,1,0}-{3104,3875,0,1,2}-{2912,9731,0,1,0}-{3256,3624,0,1,5}-{3308,3661,0,1,2}-" + "loc_data": "{3104,3875,0,1,2}-{2372,3401,0,1,0}-{2912,9731,0,1,0}-{3256,3624,0,1,5}-{3308,3661,0,1,2}-" }, { "npc_id": "4691", - "loc_data": "{2371,3398,0,1,0}-{3110,3854,0,1,0}-" + "loc_data": "{3110,3854,0,1,0}-{2371,3398,0,1,0}-" }, { "npc_id": "4692", - "loc_data": "{2372,3395,0,1,0}-{3116,3858,0,1,0}-" + "loc_data": "{3116,3858,0,1,0}-{2372,3395,0,1,0}-" }, { "npc_id": "4693", - "loc_data": "{2369,3394,0,1,0}-{3094,3849,0,1,0}-{2912,9741,0,1,0}-{2906,9736,0,1,0}-" + "loc_data": "{3094,3849,0,1,0}-{2369,3394,0,1,0}-{2912,9741,0,1,0}-{2906,9736,0,1,0}-" }, { "npc_id": "4694", @@ -9005,7 +9013,7 @@ }, { "npc_id": "5359", - "loc_data": "{2693,5065,0,1,0}-{2693,5089,0,1,0}-{2704,5091,0,1,0}-{2710,5095,0,1,0}-{2712,5076,0,1,0}-{2714,5108,0,1,0}-{2716,5092,0,1,0}-{2719,5071,0,1,0}-{2722,5061,0,1,0}-{2732,5087,0,1,0}-{2739,5078,0,1,0}-{2740,5104,0,1,0}-{2747,5083,0,1,0}-{2627,5093,0,1,0}-{2629,5061,0,1,0}-{2638,5090,0,1,0}-{2651,5105,0,1,0}-{2654,5079,0,1,0}-{2663,5116,0,1,0}-{2683,5059,0,1,0}-{2685,5111,0,1,0}-" + "loc_data": "{2627,5093,0,1,0}-{2629,5061,0,1,0}-{2638,5090,0,1,0}-{2651,5105,0,1,0}-{2654,5079,0,1,0}-{2663,5116,0,1,0}-{2683,5059,0,1,0}-{2685,5111,0,1,0}-{2693,5065,0,1,0}-{2693,5089,0,1,0}-{2704,5091,0,1,0}-{2710,5095,0,1,0}-{2712,5076,0,1,0}-{2714,5108,0,1,0}-{2716,5092,0,1,0}-{2719,5071,0,1,0}-{2722,5061,0,1,0}-{2732,5087,0,1,0}-{2739,5078,0,1,0}-{2740,5104,0,1,0}-{2747,5083,0,1,0}-" }, { "npc_id": "5361", @@ -10289,11 +10297,11 @@ }, { "npc_id": "6050", - "loc_data": "{3264,3008,0,1,0}-{3266,3008,0,1,0}-{3285,3066,0,1,0}-{3322,3032,0,1,0}-{3323,3055,0,1,0}-{3325,3010,0,1,0}-{3174,3010,0,1,0}-{3198,3061,0,1,0}-{3199,3016,0,1,0}-{3219,3125,0,1,0}-{3233,3075,0,1,0}-{3250,3126,0,1,0}-{3223,3011,0,1,0}-{3228,3060,0,1,0}-{3251,3059,0,1,0}-{3310,3076,0,1,0}-" + "loc_data": "{3174,3010,0,1,0}-{3198,3061,0,1,0}-{3199,3016,0,1,0}-{3223,3011,0,1,0}-{3228,3060,0,1,0}-{3251,3059,0,1,0}-{3219,3125,0,1,0}-{3233,3075,0,1,0}-{3250,3126,0,1,0}-{3264,3008,0,1,0}-{3266,3008,0,1,0}-{3285,3066,0,1,0}-{3322,3032,0,1,0}-{3323,3055,0,1,0}-{3325,3010,0,1,0}-{3310,3076,0,1,0}-" }, { "npc_id": "6051", - "loc_data": "{3266,3010,0,1,0}-{3267,3068,0,1,0}-{3287,3065,0,1,0}-{3287,3067,0,1,0}-{3310,3070,0,1,0}-{3312,3068,0,1,0}-{3320,3054,0,1,0}-{3324,3013,0,1,0}-{3325,3033,0,1,0}-{3153,3047,0,1,0}-{3196,3063,0,1,0}-{3199,3036,0,1,0}-{3215,3092,0,1,0}-{3217,3124,0,1,0}-{3260,3077,0,1,0}-{3223,3035,0,1,0}-{3225,3011,0,1,0}-{3283,3084,0,1,0}-" + "loc_data": "{3153,3047,0,1,0}-{3196,3063,0,1,0}-{3199,3036,0,1,0}-{3223,3035,0,1,0}-{3225,3011,0,1,0}-{3215,3092,0,1,0}-{3217,3124,0,1,0}-{3260,3077,0,1,0}-{3266,3010,0,1,0}-{3267,3068,0,1,0}-{3287,3065,0,1,0}-{3287,3067,0,1,0}-{3310,3070,0,1,0}-{3312,3068,0,1,0}-{3320,3054,0,1,0}-{3324,3013,0,1,0}-{3325,3033,0,1,0}-{3283,3084,0,1,0}-" }, { "npc_id": "6052", @@ -10419,6 +10427,38 @@ "npc_id": "6117", "loc_data": "{1884,5020,0,0,6}-" }, + { + "npc_id": "6118", + "loc_data": "{2443,3190,0,0,6}-" + }, + { + "npc_id": "6119", + "loc_data": "{2439,3186,0,1,4}-" + }, + { + "npc_id": "6120", + "loc_data": "{2437,3160,1,1,4}-" + }, + { + "npc_id": "6122", + "loc_data": "{2327,9394,0,0,1}-" + }, + { + "npc_id": "6123", + "loc_data": "{2364,9399,0,0,1}-" + }, + { + "npc_id": "6124", + "loc_data": "{2364,9398,0,0,1}-" + }, + { + "npc_id": "6125", + "loc_data": "{2323,9377,0,1,0}-" + }, + { + "npc_id": "6126", + "loc_data": "{2351,9358,0,1,0}-" + }, { "npc_id": "6127", "loc_data": "{2467,3183,0,0,3}-" @@ -12135,10 +12175,6 @@ "npc_id": "7804", "loc_data": "{3279,4350,0,1,0}-{3294,4353,0,1,0}-{3294,4366,0,1,0}-" }, - { - "npc_id": "7823", - "loc_data": "" - }, { "npc_id": "7891", "loc_data": "{3207,3250,0,0,0}-{3208,3250,0,0,0}-{3209,3250,0,0,0}-" @@ -12301,7 +12337,7 @@ }, { "npc_id": "8324", - "loc_data": "{2911,3811,0,1,0}-{2911,3812,0,1,0}-{2911,3813,0,1,0}-{2925,3821,0,1,0}-{2925,3822,0,1,0}-{2925,3823,0,1,0}-{2929,3798,0,1,0}-{2936,3790,0,1,0}-{2936,3810,0,1,0}-{2939,3823,0,1,0}-{2949,3819,1,1,0}-{2949,3822,1,1,0}-{2955,3822,1,1,0}-{2957,3822,1,1,0}-{3016,9977,1,1,0}-{3021,9939,1,1,0}-{3024,9954,1,1,0}-{3025,9943,1,1,0}-{3025,9954,1,1,0}-{3026,9966,1,1,0}-{3032,9952,1,1,0}-{3039,9954,1,1,0}-{3044,9967,1,1,0}-{3044,9971,1,1,0}-{3057,9936,1,1,0}-{3059,9953,1,1,0}-{3041,9975,2,1,0}-{3043,9975,2,1,0}-{3045,9975,2,1,0}-{3016,10022,2,1,0}-{3017,10039,2,1,0}-{3027,10028,2,1,0}-{3029,10013,2,1,0}-{3036,10038,2,1,0}-{3037,10006,2,1,0}-{3041,10024,2,1,0}-{3043,10032,2,1,0}-{3057,10002,2,1,0}-{3058,10021,2,1,0}-{3065,10006,2,1,0}-{3027,10092,1,1,0}-{3035,10097,1,1,0}-{3051,10099,1,1,0}-{3427,5102,0,1,0}-{3428,5099,0,1,0}-{3428,5102,0,1,0}-{3430,5099,0,1,0}-" + "loc_data": "{2911,3811,0,1,0}-{2911,3812,0,1,0}-{2911,3813,0,1,0}-{2925,3821,0,1,0}-{2925,3822,0,1,0}-{2925,3823,0,1,0}-{2929,3798,0,1,0}-{2936,3790,0,1,0}-{2936,3810,0,1,0}-{2939,3823,0,1,0}-{3427,5102,0,1,0}-{3428,5099,0,1,0}-{3428,5102,0,1,0}-{3430,5099,0,1,0}-{2949,3819,1,1,0}-{2949,3822,1,1,0}-{2955,3822,1,1,0}-{2957,3822,1,1,0}-{3016,9977,1,1,0}-{3021,9939,1,1,0}-{3024,9954,1,1,0}-{3025,9943,1,1,0}-{3025,9954,1,1,0}-{3026,9966,1,1,0}-{3032,9952,1,1,0}-{3039,9954,1,1,0}-{3044,9967,1,1,0}-{3044,9971,1,1,0}-{3057,9936,1,1,0}-{3059,9953,1,1,0}-{3041,9975,2,1,0}-{3043,9975,2,1,0}-{3045,9975,2,1,0}-{3016,10022,2,1,0}-{3017,10039,2,1,0}-{3027,10028,2,1,0}-{3029,10013,2,1,0}-{3036,10038,2,1,0}-{3037,10006,2,1,0}-{3041,10024,2,1,0}-{3043,10032,2,1,0}-{3057,10002,2,1,0}-{3058,10021,2,1,0}-{3065,10006,2,1,0}-{3027,10092,1,1,0}-{3035,10097,1,1,0}-{3051,10099,1,1,0}-" }, { "npc_id": "8328", diff --git a/Server/data/configs/object_configs.json b/Server/data/configs/object_configs.json index 54f50f614..ae364fd37 100644 --- a/Server/data/configs/object_configs.json +++ b/Server/data/configs/object_configs.json @@ -20055,6 +20055,10 @@ "examine": "Home sweet home?", "ids": "15480" }, + { + "examine": "Baby bread.", + "ids": "15506,15507" + }, { "examine": "Home sweet home?", "ids": "15748" @@ -20183,6 +20187,18 @@ "examine": "A short longboat!", "ids": "21834" }, + { + "examine": "A place to sit and watch furniture grow.", + "ids": "28627" + }, + { + "examine": "It's not rolling, but it seems to have gathered some moss.", + "ids": "28635" + }, + { + "examine": "A thick metal gate.", + "ids": "28690,28691,28692,28693" + }, { "examine": "Contains traces of summoning energy.", "ids": "29939,29943,29944,29945,29947,29951,29952,29953,29954" diff --git a/Server/src/main/content/data/EnchantedJewellery.kt b/Server/src/main/content/data/EnchantedJewellery.kt index c34a0ffb6..cb94545e5 100644 --- a/Server/src/main/content/data/EnchantedJewellery.kt +++ b/Server/src/main/content/data/EnchantedJewellery.kt @@ -20,7 +20,7 @@ import org.rs09.consts.Sounds import java.util.* /** - * Represents an enchanted jewellery. + * Represents a piece of enchanted jewellery. * @author Vexia, downthecrop, Player Name */ enum class EnchantedJewellery( diff --git a/Server/src/main/content/data/RepairItem.java b/Server/src/main/content/data/RepairItem.java deleted file mode 100644 index 3ab25a4d5..000000000 --- a/Server/src/main/content/data/RepairItem.java +++ /dev/null @@ -1,87 +0,0 @@ -package content.data; - -import core.game.node.item.Item; - -/** - * Represents the repair item type. - * @author Vexia - */ -public enum RepairItem { - BRONZE_HATCHET(new Item(494, 1), new Item(1351, 1), 0), - BRONZE_PICKAXE(new Item(468, 1), new Item(1265, 1), 0), - IRON_HATCHET(new Item(496, 1), new Item(1349, 1), 0), - IRON_PICKAXE(new Item(470, 1), new Item(1267, 1), 0), - STEEL_HATCHET(new Item(498, 1), new Item(1353, 1), 0), - STEEL_PICKAXE(new Item(472, 1), new Item(1269, 1), 14), - BLACK_HATCHET(new Item(500, 1), new Item(1361, 1), 10), - MITHRIL_HATCHET(new Item(502, 1), new Item(1355, 1), 18), - MITHRIL_PICKAXE(new Item(474, 1), new Item(1273, 1), 43), - ADAMANT_HATCHET(new Item(504, 1), new Item(1357, 1), 43), - ADAMANT_PICKAXE(new Item(476, 1), new Item(1271, 1), 107), - RUNE_HATCHET(new Item(506, 1), new Item(1359, 1), 427), - RUNE_PICKAXE(new Item(478, 1), new Item(1275, 1), 1100), - DRAGON_HATCHET(new Item(6741, 1), new Item(6739, 1), 1800); - - /** - * The item id. - */ - private final Item item; - - /** - * The product item. - */ - private final Item product; - - /** - * The cost of the money to repair. - */ - private final int cost; - - /** - * Constructs a new {@code BobRepairItem} {@code Object}. - * @param item the item. - * @param product the product. - * @param cost the cost. - */ - RepairItem(Item item, Item product, int cost) { - this.item = item; - this.product = product; - this.cost = cost; - } - - /** - * Gets the item. - * @return The item. - */ - public Item getItem() { - return item; - } - - /** - * Gets the product. - * @return The product. - */ - public Item getProduct() { - return product; - } - - /** - * Gets the cost. - * @return The cost. - */ - public int getCost() { - return cost; - } - - /** - * Gets the reapir item by the id. - * @param id the id. - * @return the repair item. - */ - public static RepairItem forId(int id) { - for (RepairItem item : RepairItem.values()) - if (item.item.getId() == id) - return item; - return null; - } -} \ No newline at end of file diff --git a/Server/src/main/content/data/RepairItem.kt b/Server/src/main/content/data/RepairItem.kt new file mode 100644 index 000000000..4d89a7194 --- /dev/null +++ b/Server/src/main/content/data/RepairItem.kt @@ -0,0 +1,43 @@ +package content.data + +import core.game.node.item.Item + +/** + * Represents the repair item type. + * @author Vexia + * @author Damighty - Kotlin conversion + */ +enum class RepairItem( + val item: Item, + val product: Item, + val cost: Int +) { + BRONZE_HATCHET(Item(494, 1), Item(1351, 1), 0), + BRONZE_PICKAXE(Item(468, 1), Item(1265, 1), 0), + IRON_HATCHET(Item(496, 1), Item(1349, 1), 0), + IRON_PICKAXE(Item(470, 1), Item(1267, 1), 0), + STEEL_HATCHET(Item(498, 1), Item(1353, 1), 0), + STEEL_PICKAXE(Item(472, 1), Item(1269, 1), 14), + BLACK_HATCHET(Item(500, 1), Item(1361, 1), 10), + MITHRIL_HATCHET(Item(502, 1), Item(1355, 1), 18), + MITHRIL_PICKAXE(Item(474, 1), Item(1273, 1), 43), + ADAMANT_HATCHET(Item(504, 1), Item(1357, 1), 43), + ADAMANT_PICKAXE(Item(476, 1), Item(1271, 1), 107), + RUNE_HATCHET(Item(506, 1), Item(1359, 1), 427), + RUNE_PICKAXE(Item(478, 1), Item(1275, 1), 1100), + DRAGON_HATCHET(Item(6741, 1), Item(6739, 1), 1800); + + companion object { + /** + * List of all repairable item IDs. + */ + @JvmStatic + val repairableItemIds: List = values().map { it.item.id } + + /** + * Gets the repair item by the broken items ID. + */ + @JvmStatic + fun forId(id: Int): RepairItem? = values().firstOrNull { it.item.id == id } + } +} diff --git a/Server/src/main/content/global/activity/cchallange/ChampionChallengeListener.kt b/Server/src/main/content/global/activity/cchallange/ChampionChallengeListener.kt index e1e2cd6c8..1b9147172 100644 --- a/Server/src/main/content/global/activity/cchallange/ChampionChallengeListener.kt +++ b/Server/src/main/content/global/activity/cchallange/ChampionChallengeListener.kt @@ -45,35 +45,35 @@ class ChampionChallengeListener : InteractionListener, MapArea { private val IMP_SCROLL_TEXT = arrayOf( "How about picking on someone your own size? I'll", - "see you at the Champion's Guild.", + "see you at the Champions' Guild.", "", "Champion of Imps" ) private val GOBLIN_SCROLL_TEXT = arrayOf( "Fight me if you think you can human, I'll wait", - "for you in the Champion's Guild.", + "for you in the Champions' Guild.", "", "Champion of Goblins" ) private val SKELETON_SCROLL_TEXT = arrayOf( - "I'll be waiting at the Champions' Guild to", - "collect your bones.", + "I'll be waiting at the Champions' Guild to collect", + "your bones.", "", "Champion of Skeletons" ) private val ZOMBIE_SCROLL_TEXT = arrayOf( - "You come to Champions' Guild, you fight me,", - "I squish you, I get brains!", + "You come to Champions' Guild, you fight me, I", + "squish you, I get brains!", "", "Champion of Zombies" ) private val GIANT_SCROLL_TEXT = arrayOf( - "Get yourself to the Champions' Guild, if you", - "dare to face me puny human.", + "Get yourself to the Champions' Guild, if you dare", + "to face me puny human.", "", "Champion of Giants" ) @@ -93,21 +93,22 @@ class ChampionChallengeListener : InteractionListener, MapArea { ) private val EARTH_WARRIOR_TEXT = arrayOf( - "I challenge you to a duel, come to the arena beneath", - "the Champion's Guild and fight me if you dare.", + "I challenge you to a duel, come to the arena", + "beneath the Champions' Guild and fight me if you", + "dare.", "", "Champion of Earth Warriors" ) private val JOGRE_SCROLL_TEXT = arrayOf( "You think you can defeat me? Come to the", - "Champion's Guild and prove it!", + "Champions' Guild and prove it!", "", "Champion of Jogres" ) private val LESSER_DEMON_SCROLL_TEXT = arrayOf( - "Come to the Champion's Guild so I can banish", + "Come to the Champions' Guild so I can banish", "you mortal!", "", "Champion of Lesser Demons" diff --git a/Server/src/main/content/global/activity/cchallange/ChampionScrollsDropHandler.kt b/Server/src/main/content/global/activity/cchallange/ChampionScrollsDropHandler.kt index 02b3b2b0e..bfbe78a76 100644 --- a/Server/src/main/content/global/activity/cchallange/ChampionScrollsDropHandler.kt +++ b/Server/src/main/content/global/activity/cchallange/ChampionScrollsDropHandler.kt @@ -70,7 +70,7 @@ class ChampionScrollsDropHandler : ChampionScrollsEventHookBase() { NPCs.CAVE_GOBLIN_GUARD_2073, NPCs.CAVE_GOBLIN_GUARD_2074, - NPCs.GOBLIN_GUARD_489, NPCs.GOBLIN_GUARD_6496, NPCs.GOBLIN_GUARD_6497, + NPCs.GOBLIN_GUARD_6496, NPCs.GOBLIN_GUARD_6497, NPCs.SERGEANT_GRIMSPIKE_6265, NPCs.SERGEANT_STEELWILL_6263, NPCs.SERGEANT_STRONGSTACK_6261 diff --git a/Server/src/main/content/global/activity/cchallange/LarxusDialogue.kt b/Server/src/main/content/global/activity/cchallange/LarxusDialogue.kt index 6ec919bd7..3157b6b50 100644 --- a/Server/src/main/content/global/activity/cchallange/LarxusDialogue.kt +++ b/Server/src/main/content/global/activity/cchallange/LarxusDialogue.kt @@ -27,24 +27,24 @@ class LarxusDialogue(val ChallengeStart: Boolean = false) : DialogueFile() { 0 -> { face(findNPC(NPCs.LARXUS_3050)!!, player!!, 1) for (i in scrolls)when{ - inInventory(player!!,Items.CHAMPION_SCROLL_6798) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're not allowed to use prayer's. Do you still want to proceed?").also { stage = 1 } - inInventory(player!!,Items.CHAMPION_SCROLL_6799) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're allowed to use only weapons. Do you still want to proceed?").also { stage = 1 } - inInventory(player!!,Items.CHAMPION_SCROLL_6800) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're allowed to use only melee combat skill. Do you still want to proceed?").also { stage = 1 } - inInventory(player!!,Items.CHAMPION_SCROLL_6801) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're allowed to use only magic skill. Do you still want to proceed?").also { stage = 1 } - inInventory(player!!,Items.CHAMPION_SCROLL_6802) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're not allowed to use melee combat skills. Do you still want to proceed?").also { stage = 1 } - inInventory(player!!,Items.CHAMPION_SCROLL_6803) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're not allowed to use weapons with special attack. Do you still want to proceed?").also { stage = 1 } - inInventory(player!!,Items.CHAMPION_SCROLL_6804) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're not allowed to use ranged skill. Do you still want to proceed?").also { stage = 1 } - inInventory(player!!,Items.CHAMPION_SCROLL_6805) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're allowed to use only equipment. Do you still want to proceed?").also { stage = 1 } - inInventory(player!!,Items.CHAMPION_SCROLL_6806) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're allowed to use only ranged skill. Do you still want to proceed?").also { stage = 1 } - inInventory(player!!,Items.CHAMPION_SCROLL_6807) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're not allowed to use magic skill. Do you still want to proceed?").also { stage = 1 } + inInventory(player!!,Items.CHAMPION_SCROLL_6798) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're not allowed to use any Prayers. Do you still want to proceed?").also { stage = 1 } + inInventory(player!!,Items.CHAMPION_SCROLL_6799) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're only allowed to take Weapons, no other items are allowed. Do you still want to proceed?").also { stage = 1 } + inInventory(player!!,Items.CHAMPION_SCROLL_6800) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're only allowed to use Melee attacks, no Ranged or Magic. Do you still want to proceed?").also { stage = 1 } + inInventory(player!!,Items.CHAMPION_SCROLL_6801) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're only allowed to use Magic attacks, no Melee or Ranged. Do you still want to proceed?").also { stage = 1 } + inInventory(player!!,Items.CHAMPION_SCROLL_6802) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're not allowed to use any Melee attacks. Do you still want to proceed?").also { stage = 1 } + inInventory(player!!,Items.CHAMPION_SCROLL_6803) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're not allowed to use any Special Attacks. Do you still want to proceed?").also { stage = 1 } + inInventory(player!!,Items.CHAMPION_SCROLL_6804) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're not allowed to use any Ranged attacks. Do you still want to proceed?").also { stage = 1 } + inInventory(player!!,Items.CHAMPION_SCROLL_6805) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're not allowed any Weapons or Armour. Do you still want to proceed?").also { stage = 1 } + inInventory(player!!,Items.CHAMPION_SCROLL_6806) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're only allowed to use Ranged attacks, no Melee or Magic. Do you still want to proceed?").also { stage = 1 } + inInventory(player!!,Items.CHAMPION_SCROLL_6807) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're not allowed to use any Magic attacks. Do you still want to proceed?").also { stage = 1 } else -> { sendMessage(player!!, "Nothing interesting happens.").also { stage = END_DIALOGUE } } } } - 1 -> options("Yes, let me at him!", "No, thanks I'll pass.").also { stage = 2 } + 1 -> options("Yes, let me at him!", "No thanks, I'll pass.").also { stage = 2 } 2 -> when (buttonID) { 1 -> playerl("Yes, let me at him!").also { stage = 3 } - 2 -> playerl("No, thanks I'll pass.").also { stage = END_DIALOGUE } + 2 -> playerl("No thanks, I'll pass.").also { stage = END_DIALOGUE } } 3 -> npcl("Your challenger is ready, please go down through the trapdoor when you're ready.").also { stage = 4 } 4 -> { @@ -65,7 +65,7 @@ class LarxusDialogue(val ChallengeStart: Boolean = false) : DialogueFile() { 3 -> playerl("Nothing thanks.").also { stage = END_DIALOGUE } } 3 -> npcl("Well pass it here and we'll get you started.").also { stage = END_DIALOGUE } - 4 -> npcl("This is the champions' arena, the champions of various, races use it to duel those they deem worthy of the, honour.").also { stage = END_DIALOGUE } + 4 -> npcl("This is the champions' arena. The champions of various races use it to duel those they deem worthy of the honour. If you find a challenge scroll in your travels, bring it here to face your challenger.").also { stage = END_DIALOGUE } } } } diff --git a/Server/src/main/content/global/activity/cchallange/npc/ImpChampionNPC.kt b/Server/src/main/content/global/activity/cchallange/npc/ImpChampionNPC.kt index 4a81fc066..1c131efc9 100644 --- a/Server/src/main/content/global/activity/cchallange/npc/ImpChampionNPC.kt +++ b/Server/src/main/content/global/activity/cchallange/npc/ImpChampionNPC.kt @@ -3,8 +3,6 @@ package content.global.activity.cchallange.npc import core.api.* import core.game.node.entity.Entity import core.game.node.entity.combat.BattleState -import core.game.node.entity.combat.CombatStyle -import core.game.node.entity.combat.equipment.WeaponInterface import core.game.node.entity.npc.AbstractNPC import core.game.node.entity.player.Player import core.game.node.entity.skill.Skills @@ -63,21 +61,11 @@ class ImpChampionNPC(id: Int = 0, location: Location? = null) : AbstractNPC(id, super.checkImpact(state) val player = state.attacker if (player is Player) { - val w = player.getExtension(WeaponInterface::class.java) - if (state.style == CombatStyle.MELEE || state.style == CombatStyle.MAGIC || state.style == CombatStyle.RANGE) { + + //somehow the maximumHit is determined to be zero by this point if you're using a melee special attack. + if (state.maximumHit == 0) { state.neutralizeHits() - state.estimatedHit = state.maximumHit - } - if (w.weaponInterface.interfaceId == 10) { - sendMessage(player, "You cannot use special attack in this challenge.") - if (state.estimatedHit > -1) { - state.estimatedHit = 0 - return - } - if (state.secondaryHit > -1) { - state.secondaryHit = 0 - return - } + sendMessage(player, "Larxus said you couldn't use special attacks in this duel.") } } } diff --git a/Server/src/main/content/global/activity/shootingstar/ShootingStarMiningPulse.kt b/Server/src/main/content/global/activity/shootingstar/ShootingStarMiningPulse.kt index da17fe79b..5c5ecf033 100644 --- a/Server/src/main/content/global/activity/shootingstar/ShootingStarMiningPulse.kt +++ b/Server/src/main/content/global/activity/shootingstar/ShootingStarMiningPulse.kt @@ -6,6 +6,7 @@ import core.game.node.entity.player.Player import core.game.node.entity.skill.SkillPulse import core.game.node.entity.skill.Skills import content.data.skill.SkillingTool +import core.ServerConstants import core.game.node.item.Item import core.tools.RandomFunction import org.rs09.consts.Items @@ -13,6 +14,9 @@ import core.game.world.GameWorld import core.game.world.repository.Repository import core.tools.colorize +// TODO: Shooting stars should roll for bonus gems while mining +// See: https://youtu.be/6OqZ2TGc6fM?si=U8nB5IDQREhWXApD + /** * The pulse used to handle mining shooting stars. */ @@ -53,7 +57,7 @@ class ShootingStarMiningPulse(player: Player?, node: Scenery?, val star: Shootin ShootingStarPlugin.getStoreFile()["isDiscovered"] = star.isDiscovered return player.skills.getLevel(Skills.MINING) >= star.miningLevel } - + if (player.skills.getLevel(Skills.MINING) < star.miningLevel) { player.dialogueInterpreter.sendDialogue("You need a Mining level of at least " + star.miningLevel + " in order to mine this layer.") return false @@ -100,7 +104,7 @@ class ShootingStarMiningPulse(player: Player?, node: Scenery?, val star: Shootin if (ShootingStarPlugin.getStarDust(player) < 200) { player.inventory.add(Item(ShootingStarPlugin.STAR_DUST, 1)) } - if(!inInventory(player, Items.ANCIENT_BLUEPRINT_14651) && !inBank(player, Items.ANCIENT_BLUEPRINT_14651)){ + if (ServerConstants.SHOOTING_STAR_RING && hasAnItem(player, Items.ANCIENT_BLUEPRINT_14651).container == null) { rollBlueprint(player) } @@ -130,7 +134,7 @@ class ShootingStarMiningPulse(player: Player?, node: Scenery?, val star: Shootin override fun message(type: Int) { when (type) { - 0 -> player.packetDispatch.sendMessage("You swing your pickaxe at the rock...") + 0 -> player.packetDispatch.sendMessage("You swing your pickaxe at the rock.") } } diff --git a/Server/src/main/content/global/ame/KidnapHelper.kt b/Server/src/main/content/global/ame/KidnapHelper.kt index 6fa40c6d1..716fb6277 100644 --- a/Server/src/main/content/global/ame/KidnapHelper.kt +++ b/Server/src/main/content/global/ame/KidnapHelper.kt @@ -2,22 +2,57 @@ package content.global.ame import core.ServerConstants import core.api.* +import core.game.interaction.QueueStrength +import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.node.entity.player.link.TeleportManager.TeleportType import core.game.world.map.Location +import core.game.world.update.flag.context.Graphics +import org.rs09.consts.Sounds -fun kidnapPlayer(player: Player, loc: Location, type: TeleportType) { - setAttribute(player, "kidnapped-by-random", true) //only used in POH code when you leave the hut, so does not need /save. Do not rely on this outside of its intended POH use case. - if (getAttribute(player, "/save:original-loc", null) == null) { - setAttribute(player, "/save:original-loc", player.location) +fun kidnapPlayer(npc: NPC, player: Player, dest: Location, playerLine: String? = null, callback: (player: Player, npc: NPC) -> Unit) { + val lockDuration = if (playerLine != null) 4 else 6 + lock(player, lockDuration) + queueScript(player, 1, QueueStrength.SOFT) { stage: Int -> + when (stage) { + 0 -> { + if (playerLine != null) { + sendChat(player, playerLine) + return@queueScript delayScript(player, 2) + } + return@queueScript delayScript(player, 0) + } + 1 -> { + sendGraphics(Graphics(1576, 0, 0), player.location) + animate(player,8939) + playAudio(player, Sounds.TELEPORT_ALL_200) + return@queueScript delayScript(player, 3) + } + 2 -> { + setAttribute(player, "kidnapped-by-random", true) + if (getAttribute(player, "/save:original-loc", null) == null) { + setAttribute(player, "/save:original-loc", player.location) + } + teleport(player, dest, TeleportType.INSTANT) + sendGraphics(Graphics(1577, 0, 0), player.location) + animate(player, 8941) + resetAnimator(player) + callback(player, npc) + return@queueScript delayScript(player, 2) + } + 3 -> { + removeAttribute(player, "kidnapped-by-random") //this is not needed at this point anymore and will reenable the original-loc sanity check tick action + return@queueScript stopExecuting(player) + } + else -> return@queueScript stopExecuting(player) + } } - teleport(player, loc, type) } fun returnPlayer(player: Player) { player.locks.unlockTeleport() - val destination = getAttribute(player, "/save:original-loc", ServerConstants.HOME_LOCATION ?: Location.create(3222, 3218, 0)) - teleport(player, destination) + val destination = getAttribute(player, "/save:original-loc", ServerConstants.HOME_LOCATION) + teleport(player, destination!!) unlock(player) removeAttributes(player, "/save:original-loc", "kidnapped-by-random") } diff --git a/Server/src/main/content/global/ame/RandomEventNPC.kt b/Server/src/main/content/global/ame/RandomEventNPC.kt index b03a075f9..b4da55bb3 100644 --- a/Server/src/main/content/global/ame/RandomEventNPC.kt +++ b/Server/src/main/content/global/ame/RandomEventNPC.kt @@ -4,6 +4,7 @@ import content.global.ame.events.surpriseexam.MysteriousOldManNPC import core.api.playGlobalAudio import core.api.poofClear import core.api.sendMessage +import core.api.setAttribute import core.api.utils.WeightBasedTable import core.game.interaction.MovementPulse import core.game.node.entity.Entity @@ -19,8 +20,10 @@ import core.game.world.map.RegionManager import core.game.world.map.path.Pathfinder import core.game.world.update.flag.context.Graphics import core.integrations.discord.Discord +import core.tools.RandomFunction import core.tools.secondsToTicks import core.tools.ticksToCycles +import org.rs09.consts.NPCs import org.rs09.consts.Sounds import kotlin.math.ceil import kotlin.math.min @@ -43,6 +46,7 @@ abstract class RandomEventNPC(id: Int) : NPC(id) { event.loot = loot event.player = player event.spawnLocation = RegionManager.getSpawnLocation(player, this) + setAttribute(event, "spawned-by-ame", true) return event } @@ -101,7 +105,6 @@ abstract class RandomEventNPC(id: Int) : NPC(id) { } open fun onTimeUp() { - noteAndTeleport() terminate() } @@ -144,4 +147,23 @@ abstract class RandomEventNPC(id: Int) : NPC(id) { val index = min(ids.size, ceil(player.properties.currentCombatLevel / 20.0).toInt()) - 1 return ids[index] } + + fun sayLine(npc: NPC, phrases: Array, hasOpeningPhrase: Boolean, hasOverTimePhrase: Boolean) { + if (!timerPaused && (ticksLeft % 20 == 0 || ticksLeft <= 2)) { //unless the Certer interface is up, speak every 20 ticks, or in the 2nd-to-last tick before attack/note-&-teleport + var playDwarfWhistle = true + if (ticksLeft == secondsToTicks(180) && hasOpeningPhrase) { + sendChat(phrases[0]) + } else if (ticksLeft <= 2 && hasOverTimePhrase) { + sendChat(phrases[phrases.size - 1]) + playDwarfWhistle = false + } else { + val start = if (hasOpeningPhrase) 0 else 1 + val end = if (hasOverTimePhrase) phrases.size - 2 else phrases.size - 1 + sendChat(phrases[RandomFunction.random(start, end + 1)]) + } + if (npc.id == NPCs.DRUNKEN_DWARF_956 && playDwarfWhistle) { + playGlobalAudio(this.location, Sounds.DWARF_WHISTLE_2297) + } + } + } } diff --git a/Server/src/main/content/global/ame/RandomEvents.kt b/Server/src/main/content/global/ame/RandomEvents.kt index 88522b4a0..db5ad9b2e 100644 --- a/Server/src/main/content/global/ame/RandomEvents.kt +++ b/Server/src/main/content/global/ame/RandomEvents.kt @@ -97,5 +97,4 @@ enum class RandomEvents(val npc: RandomEventNPC, val loot: WeightBasedTable? = n } } } - } diff --git a/Server/src/main/content/global/ame/events/HostileRandomEventBehavior.kt b/Server/src/main/content/global/ame/events/HostileRandomEventBehavior.kt index 37083b045..952bd0289 100644 --- a/Server/src/main/content/global/ame/events/HostileRandomEventBehavior.kt +++ b/Server/src/main/content/global/ame/events/HostileRandomEventBehavior.kt @@ -1,5 +1,6 @@ package content.global.ame.events +import core.api.getAttribute import core.game.node.entity.Entity import core.game.node.entity.npc.NPC import core.game.node.entity.npc.NPCBehavior @@ -8,12 +9,12 @@ import org.rs09.consts.NPCs class HostileRandomEventBehavior : NPCBehavior( NPCs.EVIL_CHICKEN_2463, NPCs.EVIL_CHICKEN_2464, NPCs.EVIL_CHICKEN_2465, NPCs.EVIL_CHICKEN_2466, NPCs.EVIL_CHICKEN_2467, NPCs.EVIL_CHICKEN_2468, NPCs.RIVER_TROLL_391, NPCs.RIVER_TROLL_392, NPCs.RIVER_TROLL_393, NPCs.RIVER_TROLL_394, NPCs.RIVER_TROLL_395, NPCs.RIVER_TROLL_396, - NPCs.ROCK_GOLEM_413, NPCs.ROCK_GOLEM_414, NPCs.ROCK_GOLEM_415, NPCs.ROCK_GOLEM_416, NPCs.ROCK_GOLEM_417, NPCs.ROCK_GOLEM_418, NPCs.SHADE_425, NPCs.SHADE_426, NPCs.SHADE_427, NPCs.SHADE_428, NPCs.SHADE_429, NPCs.SHADE_430, NPCs.SHADE_431, NPCs.TREE_SPIRIT_438, NPCs.TREE_SPIRIT_439, NPCs.TREE_SPIRIT_440, NPCs.TREE_SPIRIT_441, NPCs.TREE_SPIRIT_442, NPCs.TREE_SPIRIT_443, NPCs.ZOMBIE_419, NPCs.ZOMBIE_420, NPCs.ZOMBIE_421, NPCs.ZOMBIE_422, NPCs.ZOMBIE_423, NPCs.ZOMBIE_424 ) { override fun getXpMultiplier(self: NPC, attacker: Entity): Double { - return super.getXpMultiplier(self, attacker) / 16.0 + val xprate = super.getXpMultiplier(self, attacker) + return if (getAttribute(self, "spawned-by-ame", false)) xprate / 16.0 else xprate } -} +} \ No newline at end of file diff --git a/Server/src/main/content/global/ame/events/candlelight/PiousPeteNPC.kt b/Server/src/main/content/global/ame/events/candlelight/PiousPeteNPC.kt index 70cf1e11c..5231b6f9c 100644 --- a/Server/src/main/content/global/ame/events/candlelight/PiousPeteNPC.kt +++ b/Server/src/main/content/global/ame/events/candlelight/PiousPeteNPC.kt @@ -4,42 +4,19 @@ import content.global.ame.RandomEventNPC import content.global.ame.kidnapPlayer import core.api.* import core.api.utils.WeightBasedTable -import core.game.interaction.QueueStrength import core.game.node.entity.npc.NPC -import core.game.node.entity.player.link.TeleportManager import core.game.world.map.Location -import core.game.world.update.flag.context.Graphics import org.rs09.consts.NPCs -import org.rs09.consts.Sounds -/** "::revent -p player_name -e candlelight" **/ class PiousPeteNPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.PRIEST_3206) { - override fun init() { super.init() // Supposed to be "I'm sorry to drag you away from your tasks, but I need a little help with something." but it's too goddamn long. - sendChat("${player.username.capitalize()}! I need a little help with something.") + sendChat("${player.username}! I need a little help with something.") face(player) - queueScript(player, 4, QueueStrength.SOFT) { stage: Int -> - when (stage) { - 0 -> { - lock(player, 6) - sendGraphics(Graphics(1576, 0, 0), player.location) - animate(player,8939) - playAudio(player, Sounds.TELEPORT_ALL_200) - return@queueScript delayScript(player, 3) - } - 1 -> { - CandlelightInterface.initCandlelight(player) - kidnapPlayer(player, Location(1972, 5002, 0), TeleportManager.TeleportType.INSTANT) - // AntiMacro.terminateEventNpc(player) - sendGraphics(Graphics(1577, 0, 0), player.location) - animate(player,8941) - openDialogue(player, PiousPeteStartingDialogueFile(), NPC(NPCs.PIOUS_PETE_3207)) - return@queueScript stopExecuting(player) - } - else -> return@queueScript stopExecuting(player) - } + kidnapPlayer(this, player, Location(1972, 5002, 0)) { player, _ -> + CandlelightInterface.initCandlelight(player) + openDialogue(player, PiousPeteStartingDialogueFile(), NPC(NPCs.PIOUS_PETE_3207)) } } diff --git a/Server/src/main/content/global/ame/events/certer/CerterNPC.kt b/Server/src/main/content/global/ame/events/certer/CerterNPC.kt index daf3bc822..20b172404 100644 --- a/Server/src/main/content/global/ame/events/certer/CerterNPC.kt +++ b/Server/src/main/content/global/ame/events/certer/CerterNPC.kt @@ -2,29 +2,19 @@ package content.global.ame.events.certer import core.game.node.entity.npc.NPC import core.game.node.entity.player.link.emote.Emotes -import core.tools.RandomFunction import org.rs09.consts.NPCs import content.global.ame.RandomEventNPC import core.api.animate +import core.api.lock import core.api.utils.WeightBasedTable class CerterNPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.GILES_2538) { - lateinit var pName: String lateinit var phrases: Array override fun tick() { - // Don't speak if we have the interface opened - if (!timerPaused) { - // Over allotted time phrase - if (ticksLeft <= 2) { - player.lock(2) - sendChat(phrases[4]) - - // Say a phrase every 20 ticks starting at 280 ticks - // as to not interfere with the init chat phrase - } else if (ticksLeft <= 280 && ticksLeft % 20 == 0) { - sendChat(phrases[RandomFunction.random(1, 3)]) - } + sayLine(this, phrases, true, true) + if (ticksLeft == 2) { + lock(player, 2) } super.tick() } @@ -36,15 +26,20 @@ class CerterNPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(NP override fun init() { super.init() - pName = player.username.capitalize() - phrases = arrayOf("Greetings $pName, I need your help.", - "ehem... Hello $pName, please talk to me!", - "Hello, are you there $pName?", - "It's really rude to ignore someone, $pName!", - "No-one ignores me!") + phrases = arrayOf( + "Greetings ${player.username}, I need your help.", + "ehem... Hello ${player.username}, please talk to me!", + "Hello, are you there ${player.username}?", + "It's really rude to ignore someone, ${player.username}!", + "No-one ignores me!" + ) player.setAttribute("random:pause", false) player.setAttribute("certer:reward", false) - sendChat(phrases[0]) animate(this, Emotes.BOW.animation, true) } + + override fun onTimeUp() { + noteAndTeleport() + terminate() + } } \ No newline at end of file diff --git a/Server/src/main/content/global/ame/events/drilldemon/DrillDemonUtils.kt b/Server/src/main/content/global/ame/events/drilldemon/DrillDemonUtils.kt index a6638fecb..8c90bd77f 100644 --- a/Server/src/main/content/global/ame/events/drilldemon/DrillDemonUtils.kt +++ b/Server/src/main/content/global/ame/events/drilldemon/DrillDemonUtils.kt @@ -1,12 +1,9 @@ package content.global.ame.events.drilldemon -import content.global.ame.kidnapPlayer import content.global.ame.returnPlayer import core.api.* import core.game.interaction.QueueStrength import core.game.node.entity.player.Player -import core.game.node.entity.player.link.TeleportManager -import core.game.world.map.Location import core.game.world.map.zone.ZoneBorders import core.game.world.update.flag.context.Animation import org.rs09.consts.Items @@ -24,13 +21,6 @@ object DrillDemonUtils { val DD_AREA = ZoneBorders(3158, 4817, 3168, 4823) val DD_NPC = NPCs.SERGEANT_DAMIEN_2790 - fun teleport(player: Player) { - kidnapPlayer(player, Location.create(3163, 4819, 0), TeleportManager.TeleportType.INSTANT) - player.interfaceManager.closeDefaultTabs() - setComponentVisibility(player, 548, 69, true) - setComponentVisibility(player, 746, 12, true) - } - fun changeSignsAndAssignTask(player: Player) { setVarp(player, DD_SIGN_VARP, 0) val tempList = arrayListOf(DD_SIGN_JOG, DD_SIGN_JUMP, DD_SIGN_PUSHUP, DD_SIGN_SITUP).shuffled().toMutableList() @@ -94,6 +84,5 @@ object DrillDemonUtils { } return@queueScript stopExecuting(player) } - } } diff --git a/Server/src/main/content/global/ame/events/drilldemon/SergeantDamienNPC.kt b/Server/src/main/content/global/ame/events/drilldemon/SergeantDamienNPC.kt index af4e8fe3c..2a3ec7f51 100644 --- a/Server/src/main/content/global/ame/events/drilldemon/SergeantDamienNPC.kt +++ b/Server/src/main/content/global/ame/events/drilldemon/SergeantDamienNPC.kt @@ -3,32 +3,22 @@ package content.global.ame.events.drilldemon import core.game.node.entity.npc.NPC import org.rs09.consts.NPCs import content.global.ame.RandomEventNPC +import content.global.ame.kidnapPlayer import core.api.* import core.api.utils.WeightBasedTable -import core.game.interaction.QueueStrength -import core.game.system.timer.impl.AntiMacro -import core.tools.secondsToTicks +import core.game.world.map.Location class SergeantDamienNPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.SERGEANT_DAMIEN_2790) { - override fun init() { super.init() - sendChat(player.username+ "! Drop and give me 20!") - queueScript(player, 4, QueueStrength.SOFT) { stage: Int -> - when (stage) { - 0 -> { - lock(player, secondsToTicks(30)) - DrillDemonUtils.teleport(player) - AntiMacro.terminateEventNpc(player) - return@queueScript delayScript(player, 2) - } - 1 -> { - openDialogue(player, SeargentDamienDialogue(isCorrect = true, eventStart = true), NPCs.SERGEANT_DAMIEN_2790) - return@queueScript stopExecuting(player) - } - else -> return@queueScript stopExecuting(player) - } + sendChat("${player.username}! Drop and give me 20!") + face(player) + kidnapPlayer(this, player, Location(3163, 4819, 0)) { player, _ -> + player.interfaceManager.closeDefaultTabs() + setComponentVisibility(player, 548, 69, true) + setComponentVisibility(player, 746, 12, true) + openDialogue(player, SeargentDamienDialogue(isCorrect = true, eventStart = true), NPCs.SERGEANT_DAMIEN_2790) } } diff --git a/Server/src/main/content/global/ame/events/drunkendwarf/DrunkenDwarfNPC.kt b/Server/src/main/content/global/ame/events/drunkendwarf/DrunkenDwarfNPC.kt index 1485dd8c4..f4a6842ff 100644 --- a/Server/src/main/content/global/ame/events/drunkendwarf/DrunkenDwarfNPC.kt +++ b/Server/src/main/content/global/ame/events/drunkendwarf/DrunkenDwarfNPC.kt @@ -9,35 +9,27 @@ import org.rs09.consts.NPCs import org.rs09.consts.Sounds class DrunkenDwarfNPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.DRUNKEN_DWARF_956) { - private val phrases = arrayOf("Oi, are you der @name!","Dunt ignore your matey!","Aww comeon, talk to ikle me @name!") - private var attackPhrase = false + lateinit var phrases: Array private var attackDelay = 0 - private var lastPhraseTime = 0 - - private fun sendPhrases() { - if (getWorldTicks() > lastPhraseTime + 5) { - playGlobalAudio(this.location, Sounds.DWARF_WHISTLE_2297) - sendChat(this, phrases.random().replace("@name",player.username.capitalize())) - this.face(player) - lastPhraseTime = getWorldTicks() - } - } override fun init() { super.init() - playGlobalAudio(this.location, Sounds.DWARF_WHISTLE_2297) - sendChat(this, "'Ello der ${player.username.capitalize()}! *hic*") + phrases = arrayOf( + "'Ello der ${player.username}! *hic*", + "Oi, are you der ${player.username}!", + "Dunt ignore your matey!", + "Aww comeon, talk to ikle me ${player.username}!", + "I hates you, ${player.username}!" + ) } override fun tick() { - if (RandomFunction.roll(20) && !attackPhrase) - sendPhrases() + sayLine(this, phrases, true, true) if (ticksLeft <= 10) { ticksLeft = 10 - if (!attackPhrase) - sendChat("I hates you, ${player.username.capitalize()}!").also { attackPhrase = true } - if (attackDelay <= getWorldTicks()) + if (attackDelay <= getWorldTicks()) { this.attack(player) + } } super.tick() } @@ -47,4 +39,10 @@ class DrunkenDwarfNPC(override var loot: WeightBasedTable? = null) : RandomEvent this.pulseManager.clear() openDialogue(player, DrunkenDwarfDialogue(), this.asNpc()) } + + override fun onTimeUp() { + if (attackDelay <= getWorldTicks()) { + this.attack(player) + } + } } \ No newline at end of file diff --git a/Server/src/main/content/global/ame/events/evilbob/EvilBobNPC.kt b/Server/src/main/content/global/ame/events/evilbob/EvilBobNPC.kt index fc0fe2826..4995b6b41 100644 --- a/Server/src/main/content/global/ame/events/evilbob/EvilBobNPC.kt +++ b/Server/src/main/content/global/ame/events/evilbob/EvilBobNPC.kt @@ -1,40 +1,22 @@ package content.global.ame.events.evilbob import content.global.ame.RandomEventNPC +import content.global.ame.kidnapPlayer import core.api.* import core.api.utils.WeightBasedTable -import core.game.interaction.QueueStrength import core.game.node.entity.npc.NPC -import core.game.system.timer.impl.AntiMacro +import core.game.world.map.Location import org.rs09.consts.NPCs -import org.rs09.consts.Sounds class EvilBobNPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.EVIL_BOB_2478) { - override fun init() { super.init() sendChat("meow") - queueScript(player, 4, QueueStrength.SOFT) { stage: Int -> - when (stage) { - 0 -> { - lock(player, 6) - sendChat(player, "No... what? Nooooooooooooo!") - animate(player, EvilBobUtils.teleAnim) - player.graphics(EvilBobUtils.telegfx) - playAudio(player, Sounds.TELEPORT_ALL_200) - EvilBobUtils.giveEventFishingSpot(player) - return@queueScript delayScript(player, 3) - } - 1 -> { - sendMessage(player, "Welcome to Scape2009.") - EvilBobUtils.teleport(player) - resetAnimator(player) - openDialogue(player, EvilBobDialogue(), NPCs.EVIL_BOB_2479) - AntiMacro.terminateEventNpc(player) - return@queueScript stopExecuting(player) - } - else -> return@queueScript stopExecuting(player) - } + face(player) + kidnapPlayer(this, player, Location(3419, 4776, 0), "No... what? Nooooooooooooo!") { player, _ -> + EvilBobUtils.giveEventFishingSpot(player) + sendMessage(player, "Welcome to Scape2009.") + openDialogue(player, EvilBobDialogue(), NPCs.EVIL_BOB_2479) } } diff --git a/Server/src/main/content/global/ame/events/evilbob/EvilBobUtils.kt b/Server/src/main/content/global/ame/events/evilbob/EvilBobUtils.kt index d70eab940..58f8e5aee 100644 --- a/Server/src/main/content/global/ame/events/evilbob/EvilBobUtils.kt +++ b/Server/src/main/content/global/ame/events/evilbob/EvilBobUtils.kt @@ -1,12 +1,8 @@ package content.global.ame.events.evilbob -import content.global.ame.kidnapPlayer -import content.global.ame.returnPlayer import core.api.* import core.game.node.entity.player.Player -import core.game.node.entity.player.link.TeleportManager import core.game.node.entity.skill.Skills -import core.game.world.map.Location import core.game.world.map.zone.ZoneBorders import core.game.world.update.flag.context.Animation import core.game.world.update.flag.context.Graphics @@ -53,10 +49,6 @@ object EvilBobUtils { } } - fun teleport(player: Player) { - kidnapPlayer(player, Location.create(3419, 4776, 0), TeleportManager.TeleportType.INSTANT) - } - fun cleanup(player: Player) { removeAttributes(player, assignedFishingZone, eventComplete, attentive, servantHelpDialogueSeen, attentiveNewSpot, startingDialogueSeen) removeAll(player, Items.FISHLIKE_THING_6202) diff --git a/Server/src/main/content/global/ame/events/evilbob/ServantCutscene.kt b/Server/src/main/content/global/ame/events/evilbob/ServantCutscene.kt index db54e0db9..4dfc72698 100644 --- a/Server/src/main/content/global/ame/events/evilbob/ServantCutscene.kt +++ b/Server/src/main/content/global/ame/events/evilbob/ServantCutscene.kt @@ -3,38 +3,47 @@ package content.global.ame.events.evilbob import core.api.* import core.game.activity.Cutscene import core.game.node.entity.player.Player +import core.game.world.map.Direction class ServantCutsceneN(player: Player) : Cutscene(player) { override fun setup() { setExit(player.location.transform(0, 0, 0)) loadRegion(13642) + addNPC(2479, 28, 41, Direction.SOUTH) // Evil Bob + addNPC(2481, 31, 41, Direction.SOUTH_WEST) // Servant } override fun runStage(stage: Int) { when (stage) { 0 -> { - fadeToBlack() - timedUpdate(4) + teleport(player, 30, 41) + moveCamera(30, 37, 400, 255) + rotateCamera(30, 50, 400, 255) + openInterface(player, 186) + timedUpdate(0) } 1 -> { - teleport(player, 29, 41) - moveCamera(30, 43) // +7 from statue - openInterface(player, 186) + timedUpdate(0) + } + 2 -> { // Slow start + moveCamera(30, 49, 400, 2) timedUpdate(2) } - 2 -> { + 3 -> { // Fast middle + moveCamera(30, 44, 380, 4) + timedUpdate(6) + } + 4 -> { // Slow end + moveCamera(30, 45, 340, 2) timedUpdate(2) - rotateCamera(30, 51, 300, 100) // the statue loc - fadeFromBlack() } - 3 -> { - timedUpdate(9) - moveCamera(30, 46, 300, 2) // +4 from statue + 5 -> { + timedUpdate(2) } - 4 -> { - end{ player.locks.lockTeleport(1000000) } + 6 -> { closeInterface(player) + end(fade = false) { player.locks.lockTeleport(1000000) } } } } @@ -45,32 +54,40 @@ class ServantCutsceneS(player: Player) : Cutscene(player) { override fun setup() { setExit(player.location.transform(0, 0, 0)) loadRegion(13642) + addNPC(2479, 28, 41, Direction.SOUTH) // Evil Bob + addNPC(2481, 31, 41, Direction.SOUTH_WEST) // Servant } override fun runStage(stage: Int) { when (stage) { 0 -> { - fadeToBlack() - timedUpdate(4) + teleport(player, 30, 41) + moveCamera(31, 46, 365, 255) + rotateCamera(29, 30, 365, 255) + openInterface(player, 186) + timedUpdate(0) } 1 -> { - teleport(player, 29, 41) - moveCamera(29, 38) // +7 from statue - openInterface(player, 186) - timedUpdate(2) + timedUpdate(0) } 2 -> { + moveCamera(31, 43, 480, 2) timedUpdate(2) - rotateCamera(29, 30, 300, 100) // the statue loc - fadeFromBlack() } 3 -> { - timedUpdate(9) - moveCamera(29, 35, 300, 2) // +4 from statue + moveCamera(31, 37, 455, 4) + timedUpdate(8) } 4 -> { - end{ player.locks.lockTeleport(1000000) } + moveCamera(31, 36, 395, 2) + timedUpdate(2) + } + 5 -> { + timedUpdate(3) + } + 6 -> { closeInterface(player) + end(fade = false) { player.locks.lockTeleport(1000000) } } } } @@ -81,32 +98,40 @@ class ServantCutsceneE(player: Player) : Cutscene(player) { override fun setup() { setExit(player.location.transform(0, 0, 0)) loadRegion(13642) + addNPC(2479, 28, 41, Direction.SOUTH) // Evil Bob + addNPC(2481, 31, 41, Direction.SOUTH_WEST) // Servant } override fun runStage(stage: Int) { when (stage) { 0 -> { - fadeToBlack() - timedUpdate(4) + teleport(player, 30, 41) + moveCamera(25, 41, 440, 255) + rotateCamera(42, 41, 440, 255) + openInterface(player, 186) + timedUpdate(0) } 1 -> { - teleport(player, 29, 41) - moveCamera(35, 41) // +7 from statue - openInterface(player, 186) + timedUpdate(0) + } + 2 -> { // Slow start + moveCamera(28, 41, 500, 3) timedUpdate(2) } - 2 -> { + 3 -> { // Fast middle + moveCamera(34, 41, 390, 5) + timedUpdate(6) + } + 4 -> { // Slow end + moveCamera(36, 41, 340, 2) timedUpdate(2) - rotateCamera(43, 41, 300, 100) // the statue loc - fadeFromBlack() } - 3 -> { - timedUpdate(9) - moveCamera(38, 41, 300, 2) // +4 from statue + 5 -> { + timedUpdate(4) } - 4 -> { - end{ player.locks.lockTeleport(1000000) } + 6 -> { closeInterface(player) + end(fade = false) { player.locks.lockTeleport(1000000) } } } } @@ -117,34 +142,41 @@ class ServantCutsceneW(player: Player) : Cutscene(player) { override fun setup() { setExit(player.location.transform(0, 0, 0)) loadRegion(13642) + addNPC(2479, 28, 41, Direction.SOUTH) // Evil Bob + addNPC(2481, 31, 41, Direction.SOUTH_WEST) // Servant } override fun runStage(stage: Int) { when (stage) { 0 -> { - fadeToBlack() - timedUpdate(4) + teleport(player, 30, 41) + moveCamera(34, 41, 325, 255) + rotateCamera(16, 40, 300, 255) + openInterface(player, 186) + timedUpdate(0) } 1 -> { - teleport(player, 29, 41) - moveCamera(25, 40) // +7 from statue - openInterface(player, 186) + timedUpdate(0) + } + 2 -> { // Slow start + moveCamera(31, 41, 440, 3) timedUpdate(2) } - 2 -> { + 3 -> { // Fast middle + moveCamera(24, 41, 330, 5) + timedUpdate(7) + } + 4 -> { // Slow end + moveCamera(23, 41, 300, 2) timedUpdate(2) - rotateCamera(18, 40, 300, 100) // the statue loc - fadeFromBlack() } - 3 -> { - timedUpdate(9) - moveCamera(22, 40, 300, 2) // +4 from statue + 5 -> { + timedUpdate(3) } - 4 -> { - end{ player.locks.lockTeleport(1000000) } + 6 -> { closeInterface(player) + end(fade = false) { player.locks.lockTeleport(1000000) } } - } } } \ No newline at end of file diff --git a/Server/src/main/content/global/ame/events/freakyforester/FreakUtils.kt b/Server/src/main/content/global/ame/events/freakyforester/FreakUtils.kt index e247bfeb1..84e491f89 100644 --- a/Server/src/main/content/global/ame/events/freakyforester/FreakUtils.kt +++ b/Server/src/main/content/global/ame/events/freakyforester/FreakUtils.kt @@ -1,13 +1,10 @@ package content.global.ame.events.freakyforester -import content.global.ame.kidnapPlayer import content.global.ame.returnPlayer import core.api.* import org.rs09.consts.Items import org.rs09.consts.NPCs import core.game.node.entity.player.Player -import core.game.node.entity.player.link.TeleportManager -import core.game.world.map.Location import core.game.world.map.zone.ZoneBorders import core.tools.RandomFunction @@ -28,10 +25,6 @@ object FreakUtils{ player.dialogueInterpreter.open(FreakyForesterDialogue(), freakNpc) } - fun teleport(player: Player) { - kidnapPlayer(player, Location.create(2599, 4777 ,0), TeleportManager.TeleportType.INSTANT) - } - fun cleanup(player: Player) { returnPlayer(player) removeAttributes(player, freakTask, freakComplete, pheasantKilled) diff --git a/Server/src/main/content/global/ame/events/freakyforester/FreakyForesterNPC.kt b/Server/src/main/content/global/ame/events/freakyforester/FreakyForesterNPC.kt index 4b0366263..1ef52dde6 100644 --- a/Server/src/main/content/global/ame/events/freakyforester/FreakyForesterNPC.kt +++ b/Server/src/main/content/global/ame/events/freakyforester/FreakyForesterNPC.kt @@ -1,39 +1,21 @@ package content.global.ame.events.freakyforester import content.global.ame.RandomEventNPC +import content.global.ame.kidnapPlayer import core.api.* import org.rs09.consts.NPCs import core.api.utils.WeightBasedTable -import core.game.interaction.QueueStrength import core.game.node.entity.npc.NPC -import core.game.system.timer.impl.AntiMacro -import core.game.world.update.flag.context.Graphics -import org.rs09.consts.Sounds +import core.game.world.map.Location class FreakyForesterNPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.FREAKY_FORESTER_2458) { - override fun init() { super.init() sendChat("Ah, ${player.username}, just the person I need!") - queueScript(player, 4, QueueStrength.SOFT) { stage: Int -> - when (stage) { - 0 -> { - lock(player, 6) - sendGraphics(Graphics(308, 100, 50), player.location) - animate(player,714) - playAudio(player, Sounds.TELEPORT_ALL_200) - return@queueScript delayScript(player, 3) - } - 1 -> { - FreakUtils.teleport(player) - FreakUtils.giveFreakTask(player) - AntiMacro.terminateEventNpc(player) - openDialogue(player, FreakyForesterDialogue(), FreakUtils.freakNpc) - resetAnimator(player) - return@queueScript stopExecuting(player) - } - else -> return@queueScript stopExecuting(player) - } + face(player) + kidnapPlayer(this, player, Location(2599, 4777, 0)) { player, _ -> + FreakUtils.giveFreakTask(player) + openDialogue(player, FreakyForesterDialogue(), FreakUtils.freakNpc) } } diff --git a/Server/src/main/content/global/ame/events/genie/GenieNPC.kt b/Server/src/main/content/global/ame/events/genie/GenieNPC.kt index 6f3cf39a1..b4700a9a5 100644 --- a/Server/src/main/content/global/ame/events/genie/GenieNPC.kt +++ b/Server/src/main/content/global/ame/events/genie/GenieNPC.kt @@ -4,27 +4,40 @@ import core.game.node.entity.npc.NPC import core.tools.RandomFunction import org.rs09.consts.NPCs import content.global.ame.RandomEventNPC +import core.api.lock import core.api.playAudio import core.api.utils.WeightBasedTable import org.rs09.consts.Sounds class GenieNPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.GENIE_409) { - val phrases = arrayOf("Greetings, @name!","Ehem... Master @name?","Are you there, Master @name?","No one ignores me!") + lateinit var phrases: Array override fun tick() { - if(RandomFunction.random(1,15) == 5){ - sendChat(phrases.random().replace("@name",player.username.capitalize())) + sayLine(this, phrases, true, true) + if (ticksLeft == 2) { + lock(player, 2) } super.tick() } override fun init() { super.init() + val honorific = if (player.isMale) "Master" else "Mistress" + phrases = arrayOf( + "Greetings, ${player.username}!", + "Ehem... $honorific ${player.username}?", + "Are you there, $honorific ${player.username}?", + "No one ignores me!" + ) playAudio(player, Sounds.GENIE_APPEAR_2301) - sendChat(phrases.random().replace("@name",player.username.capitalize())) } override fun talkTo(npc: NPC) { player.dialogueInterpreter.open(GenieDialogue(),npc) } + + override fun onTimeUp() { + noteAndTeleport() + terminate() + } } diff --git a/Server/src/main/content/global/ame/events/maze/MazeNPC.kt b/Server/src/main/content/global/ame/events/maze/MazeNPC.kt index e4befe00e..a51f0b044 100644 --- a/Server/src/main/content/global/ame/events/maze/MazeNPC.kt +++ b/Server/src/main/content/global/ame/events/maze/MazeNPC.kt @@ -4,54 +4,29 @@ import content.global.ame.RandomEventNPC import content.global.ame.kidnapPlayer import core.api.* import core.api.utils.WeightBasedTable -import core.game.interaction.QueueStrength import core.game.node.entity.npc.NPC -import core.game.node.entity.player.link.TeleportManager -import core.game.system.timer.impl.AntiMacro -import core.game.world.map.Location -import core.game.world.map.build.DynamicRegion -import core.game.world.update.flag.context.Graphics import org.rs09.consts.NPCs -import org.rs09.consts.Sounds class MazeNPC(var type: String = "", override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.MYSTERIOUS_OLD_MAN_410) { - override fun init() { super.init() sendChat("Aha, you'll do ${player.username}!") face(player) - queueScript(player, 4, QueueStrength.SOFT) { stage: Int -> - when (stage) { - 0 -> { - lock(player, 6) - sendGraphics(Graphics(1576, 0, 0), player.location) - animate(player,8939) - playAudio(player, Sounds.TELEPORT_ALL_200) - return@queueScript delayScript(player, 3) - } - 1 -> { - MazeInterface.initMaze(player) - // Note: This event is NOT instanced: - // Sources: - // https://youtu.be/2gpzn9oNdy0 (2007) - // https://youtu.be/Tni1HURgnxg (2008) - // https://youtu.be/igdwDZOv9LU (2008) - // https://youtu.be/0oBCkLArUmc (2011 - even with personal Mysterious Old Man) - "Sorry, this is not the old man you are looking for." - // https://youtu.be/FMuKZm-Ikgs (2011) - // val region = DynamicRegion.create(11591) - kidnapPlayer(player, MazeInterface.STARTING_POINTS.random(), TeleportManager.TeleportType.INSTANT) // 10 random spots - AntiMacro.terminateEventNpc(player) - sendGraphics(Graphics(1577, 0, 0), player.location) - animate(player,8941) - removeAttribute(player, MazeInterface.MAZE_ATTRIBUTE_CHESTS_OPEN) - return@queueScript stopExecuting(player) - } - else -> return@queueScript stopExecuting(player) - } + // Note: This event is NOT instanced: + // Sources: + // https://youtu.be/2gpzn9oNdy0 (2007) + // https://youtu.be/Tni1HURgnxg (2008) + // https://youtu.be/igdwDZOv9LU (2008) + // https://youtu.be/0oBCkLArUmc (2011 - even with personal Mysterious Old Man) - "Sorry, this is not the old man you are looking for." + // https://youtu.be/FMuKZm-Ikgs (2011) + // val region = DynamicRegion.create(11591) + kidnapPlayer(this, player, MazeInterface.STARTING_POINTS.random()) { player, _ -> + MazeInterface.initMaze(player) + removeAttribute(player, MazeInterface.MAZE_ATTRIBUTE_CHESTS_OPEN) } } override fun talkTo(npc: NPC) { - // Do nothing. + sendMessage(player, "He isn't interested in talking to you.") } } \ No newline at end of file diff --git a/Server/src/main/content/global/ame/events/pillory/PilloryNPC.kt b/Server/src/main/content/global/ame/events/pillory/PilloryNPC.kt index f833d9ce2..3917b035c 100644 --- a/Server/src/main/content/global/ame/events/pillory/PilloryNPC.kt +++ b/Server/src/main/content/global/ame/events/pillory/PilloryNPC.kt @@ -4,47 +4,22 @@ import content.global.ame.RandomEventNPC import content.global.ame.kidnapPlayer import core.api.* import core.api.utils.WeightBasedTable -import core.game.interaction.QueueStrength +import core.game.component.Component.setUnclosable import core.game.node.entity.npc.NPC -import core.game.node.entity.player.link.TeleportManager -import core.game.system.timer.impl.AntiMacro -import core.game.world.map.Location -import core.game.world.update.flag.context.Graphics import org.rs09.consts.NPCs -import org.rs09.consts.Sounds -// "::revent [-p] player name [-e event name]" class PilloryNPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.PILLORY_GUARD_2791) { - override fun init() { super.init() sendChat("${player.username}, you're under arrest!") face(player) - player.dialogueInterpreter.sendPlainMessage(true, "", "Solve the pillory puzzle to be returned to where you came from.") - queueScript(player, 4, QueueStrength.SOFT) { stage: Int -> - when (stage) { - 0 -> { - lock(player, 6) - sendGraphics(Graphics(1576, 0, 0), player.location) - animate(player,8939) - playAudio(player, Sounds.TELEPORT_ALL_200) - return@queueScript delayScript(player, 3) - } - 1 -> { - PilloryInterface.initPillory(player) - val dest = PilloryInterface.LOCATIONS.random() //9 random spots! - kidnapPlayer(player, dest, TeleportManager.TeleportType.INSTANT) - AntiMacro.terminateEventNpc(player) - sendGraphics(Graphics(1577, 0, 0), player.location) - animate(player,8941) - return@queueScript stopExecuting(player) - } - else -> return@queueScript stopExecuting(player) - } + kidnapPlayer(this, player, PilloryInterface.LOCATIONS.random()) { player, _ -> + PilloryInterface.initPillory(player) + setUnclosable(player, player.dialogueInterpreter.sendPlainMessage(true, "", "Solve the pillory puzzle to be returned to where you came from.")) } } override fun talkTo(npc: NPC) { - //player.dialogueInterpreter.open(FreakyForesterDialogue(),npc) + sendMessage(player, "He isn't interested in talking to you.") } } \ No newline at end of file diff --git a/Server/src/main/content/global/ame/events/quizmaster/QuizMasterDialogueFile.kt b/Server/src/main/content/global/ame/events/quizmaster/QuizMasterDialogueFile.kt index 4790a8550..4bb2ff79d 100644 --- a/Server/src/main/content/global/ame/events/quizmaster/QuizMasterDialogueFile.kt +++ b/Server/src/main/content/global/ame/events/quizmaster/QuizMasterDialogueFile.kt @@ -5,15 +5,17 @@ import core.ServerConstants import core.api.* import core.api.utils.WeightBasedTable import core.api.utils.WeightedItem -import core.game.dialogue.DialogueFile +import core.game.component.Component +import core.game.dialogue.DialogueLabeller +import core.game.dialogue.DialogueOption import core.game.dialogue.FacialExpression import core.game.interaction.QueueStrength import core.game.node.entity.player.Player -import core.tools.END_DIALOGUE import org.rs09.consts.Components import org.rs09.consts.Items +import org.rs09.consts.NPCs -class QuizMasterDialogueFile : DialogueFile() { +class QuizMasterDialogueFile : DialogueLabeller() { companion object { const val QUIZMASTER_INTERFACE = Components.MACRO_QUIZSHOW_191 const val QUIZMASTER_ATTRIBUTE_QUESTIONS_CORRECT = "/save:quizmaster:questions-correct" @@ -74,56 +76,77 @@ class QuizMasterDialogueFile : DialogueFile() { } - override fun handle(componentID: Int, buttonID: Int) { - when (stage) { - 0 -> npc(FacialExpression.FRIENDLY,"WELCOME to the GREATEST QUIZ SHOW in the", "whole of ${ServerConstants.SERVER_NAME}:", "O D D O N E O U T").also { stage++ } - 1 -> player(FacialExpression.THINKING, "I'm sure I didn't ask to take part in a quiz show...").also { stage++ } - 2 -> npc(FacialExpression.FRIENDLY,"Please welcome our newest contestant:", "${player?.username}!", "Just pick the O D D O N E O U T.", "Four questions right, and then you win!").also { stage++ } - 3 -> { - setAttribute(player!!, QUIZMASTER_ATTRIBUTE_RANDOM_ANSWER, randomQuestion(player!!)) - player!!.interfaceManager.openChatbox(QUIZMASTER_INTERFACE) - stage++ - } - 4-> { - if (buttonID == getAttribute(player!!, QUIZMASTER_ATTRIBUTE_RANDOM_ANSWER, 0)) { - // Correct Answer - setAttribute(player!!, QUIZMASTER_ATTRIBUTE_QUESTIONS_CORRECT, getAttribute(player!!, QUIZMASTER_ATTRIBUTE_QUESTIONS_CORRECT, 0) + 1) - if (getAttribute(player!!, QUIZMASTER_ATTRIBUTE_QUESTIONS_CORRECT, 0) >= 4) { - npc(FacialExpression.FRIENDLY,"CONGRATULATIONS!", "You are a WINNER!", "Please choose your PRIZE!") - stage = 5 - } else { - npc(FacialExpression.FRIENDLY,"Wow, you're a smart one!", "You're absolutely RIGHT!", "Okay, next question!") - stage = 3 - } + override fun addConversation() { + assignToIds(NPCs.QUIZ_MASTER_2477) + afterClose { player -> + loadLabel(player, "question") + } + + npc(FacialExpression.FRIENDLY,"WELCOME to the GREATEST QUIZ SHOW in the", "whole of ${ServerConstants.SERVER_NAME}:", "O D D O N E O U T", unclosable = true) + player(FacialExpression.THINKING, "I'm sure I didn't ask to take part in a quiz show...", unclosable = true) + npc(FacialExpression.FRIENDLY,"Please welcome our newest contestant:", "${player?.username}!", "Just pick the O D D O N E O U T.", "Four questions right, and then you win!", unclosable = true) + goto("question") + + label("question") + manual(unclosable = true) { player, _ -> + setAttribute(player, QUIZMASTER_ATTRIBUTE_RANDOM_ANSWER, randomQuestion(player)) + val comp = Component(QUIZMASTER_INTERFACE) + player.interfaceManager.openChatbox(comp) + return@manual comp + } + exec { player, _ -> + if (buttonID == getAttribute(player, QUIZMASTER_ATTRIBUTE_RANDOM_ANSWER, 0)) { + // Correct Answer + setAttribute(player, QUIZMASTER_ATTRIBUTE_QUESTIONS_CORRECT, getAttribute(player, QUIZMASTER_ATTRIBUTE_QUESTIONS_CORRECT, 0) + 1) + if (getAttribute(player, QUIZMASTER_ATTRIBUTE_QUESTIONS_CORRECT, 0) >= 4) { + goto("winner") } else { - // Wrong Answer - npc(FacialExpression.FRIENDLY,"WRONG!", "That's just WRONG!", "Okay, next question!") - stage = 3 + goto("right") } - } - // Random Item should be "Mystery Box", but the current MYSTERY_BOX_6199 is already inauthentically used by Giftmas. - 5 -> options("1000 Coins", "Random Item").also { stage++ } - 6 -> { - resetAnimator(player!!) - returnPlayer(player!!) - when (buttonID) { - 1 -> { - queueScript(player!!, 0, QueueStrength.SOFT) { stage: Int -> - addItemOrDrop(player!!, Items.COINS_995, 1000) - return@queueScript stopExecuting(player!!) - } - } - 2 -> { - queueScript(player!!, 0, QueueStrength.SOFT) { stage: Int -> - addItemOrDrop(player!!, tableRoll.roll()[0].id) - return@queueScript stopExecuting(player!!) - } - } - } - removeAttributes(player!!, QUIZMASTER_ATTRIBUTE_QUESTIONS_CORRECT, QUIZMASTER_ATTRIBUTE_RANDOM_ANSWER) - stage = END_DIALOGUE - end() + } else { + goto("wrong") } } + + label("right") + npc(FacialExpression.FRIENDLY,"Wow, you're a smart one!", "You're absolutely RIGHT!", "Okay, next question!", unclosable = true) + goto("question") + + label("wrong") + npc(FacialExpression.FRIENDLY,"WRONG!", "That's just WRONG!", "Okay, next question!", unclosable = true) + goto("question") + + label("winner") + npc(FacialExpression.FRIENDLY,"CONGRATULATIONS!", "You are a WINNER!", "Please choose your PRIZE!", unclosable = true) + options( + DialogueOption("money", "1000 Coins", skipPlayer = true), + DialogueOption("item", "Random Item", skipPlayer = true) + ) + + label("money") + exec { player, _ -> + queueScript(player, 0, QueueStrength.SOFT) { _ -> + addItemOrDrop(player, Items.COINS_995, 1000) + return@queueScript stopExecuting(player) + } + } + goto("cleanup") + + label("item") + exec { player, _ -> + queueScript(player, 0, QueueStrength.SOFT) { _ -> + addItemOrDrop(player, tableRoll.roll()[0].id) + return@queueScript stopExecuting(player) + } + } + goto("cleanup") + + label("cleanup") + exec { player, _ -> + resetAnimator(player) + returnPlayer(player) + removeAttributes(player, QUIZMASTER_ATTRIBUTE_QUESTIONS_CORRECT, QUIZMASTER_ATTRIBUTE_RANDOM_ANSWER) + } + goto("nowhere") } -} \ No newline at end of file +} diff --git a/Server/src/main/content/global/ame/events/quizmaster/QuizMasterNPC.kt b/Server/src/main/content/global/ame/events/quizmaster/QuizMasterNPC.kt index 26407a1dc..444fc1f1b 100644 --- a/Server/src/main/content/global/ame/events/quizmaster/QuizMasterNPC.kt +++ b/Server/src/main/content/global/ame/events/quizmaster/QuizMasterNPC.kt @@ -4,14 +4,9 @@ import content.global.ame.RandomEventNPC import content.global.ame.kidnapPlayer import core.api.* import core.api.utils.WeightBasedTable -import core.game.interaction.QueueStrength import core.game.node.entity.npc.NPC -import core.game.node.entity.player.link.TeleportManager -import core.game.system.timer.impl.AntiMacro import core.game.world.map.Location -import core.game.world.update.flag.context.Graphics import org.rs09.consts.NPCs -import org.rs09.consts.Sounds /** * Quiz Master NPC: @@ -27,35 +22,14 @@ class QuizMasterNPC(var type: String = "", override var loot: WeightBasedTable? override fun init() { super.init() sendChat("Hey ${player.username}! It's your lucky day!") - queueScript(player, 4, QueueStrength.SOFT) { stage: Int -> - when (stage) { - 0 -> { - lock(player, 6) - sendGraphics(Graphics(1576, 0, 0), player.location) - animate(player,8939) - playAudio(player, Sounds.TELEPORT_ALL_200) - return@queueScript delayScript(player, 3) - } - 1 -> { - kidnapPlayer(player, Location(1952, 4764, 1), TeleportManager.TeleportType.INSTANT) - setAttribute(player, QuizMasterDialogueFile.QUIZMASTER_ATTRIBUTE_QUESTIONS_CORRECT, 0) - AntiMacro.terminateEventNpc(player) - sendGraphics(Graphics(1577, 0, 0), player.location) - animate(player,8941) - sendMessage(player, "Answer four questions correctly in a row to be teleported back where you came from.") - sendMessage(player, "You will need to relog in if you lose the quiz dialog.") // Inauthentic, but there to notify the player in case. - return@queueScript delayScript(player, 6) - } - 2 -> { - face(player, Location(1952, 4768, 1)) - animate(player,2378) - // This is not needed as when you enter the QuizMasterBorders, it should fire off the dialogue - // openDialogue(player, QuizMasterDialogueFile(), this.asNpc()) - return@queueScript stopExecuting(player) - } - else -> return@queueScript stopExecuting(player) - } - + face(player) + kidnapPlayer(this, player, Location(1952, 4764, 0)) { player, _ -> + setAttribute(player, QuizMasterDialogueFile.QUIZMASTER_ATTRIBUTE_QUESTIONS_CORRECT, 0) + sendMessage(player, "Answer four questions correctly in a row to be teleported back where you came from.") + sendMessage(player, "You will need to relog in if you lose the quiz dialog.") // Inauthentic, but there to notify the player in case. + face(player, Location(1952, 4768, 1)) + animate(player,2378) + // Quiz dialogue gets opened automatically on zone entry. } } diff --git a/Server/src/main/content/global/ame/events/rockgolem/RockGolemBehavior.kt b/Server/src/main/content/global/ame/events/rockgolem/RockGolemBehavior.kt new file mode 100644 index 000000000..bd397a5b9 --- /dev/null +++ b/Server/src/main/content/global/ame/events/rockgolem/RockGolemBehavior.kt @@ -0,0 +1,25 @@ +package content.global.ame.events.rockgolem + +import core.game.node.entity.Entity +import core.game.node.entity.combat.CombatStyle +import core.game.node.entity.combat.CombatSwingHandler +import core.game.node.entity.combat.MultiSwingHandler +import core.game.node.entity.combat.equipment.SwitchAttack +import core.game.node.entity.npc.NPC +import core.game.node.entity.npc.NPCBehavior +import org.rs09.consts.NPCs + +class RockGolemBehavior() : NPCBehavior( + NPCs.ROCK_GOLEM_413, NPCs.ROCK_GOLEM_414, NPCs.ROCK_GOLEM_415, NPCs.ROCK_GOLEM_416, NPCs.ROCK_GOLEM_417, NPCs.ROCK_GOLEM_418 +) { + val rangeHandler = SwitchAttack(CombatStyle.RANGE) + val meleeHandler = SwitchAttack(CombatStyle.MELEE) + val combatHandler = MultiSwingHandler(rangeHandler, meleeHandler) + override fun getSwingHandlerOverride(self: NPC, original: CombatSwingHandler): CombatSwingHandler { + return combatHandler + } + + override fun getXpMultiplier(self: NPC, attacker: Entity): Double { + return super.getXpMultiplier(self, attacker) / 16.0 + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/ame/events/sandwichlady/SandwichLadyRENPC.kt b/Server/src/main/content/global/ame/events/sandwichlady/SandwichLadyRENPC.kt index 1c5a14956..99e0a0e68 100644 --- a/Server/src/main/content/global/ame/events/sandwichlady/SandwichLadyRENPC.kt +++ b/Server/src/main/content/global/ame/events/sandwichlady/SandwichLadyRENPC.kt @@ -5,32 +5,49 @@ import core.tools.RandomFunction import org.rs09.consts.Items import org.rs09.consts.NPCs import content.global.ame.RandomEventNPC +import core.api.lock import core.api.utils.WeightBasedTable class SandwichLadyRENPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.SANDWICH_LADY_3117) { - val phrases = arrayOf("Hello, @name, can you hear me?","Sandwiches, @name!","Are you ignoring me, @name??","Yoohoo! Sandwiches, @name!","Hello, @name?", "Come get your sandwiches, @name!", "How could you ignore me like this, @name?!", "Do you even want your sandwiches, @name?") + lateinit var phrases: Array var assigned_item = 0 - val items = arrayOf(Items.BAGUETTE_6961,Items.TRIANGLE_SANDWICH_6962,Items.SQUARE_SANDWICH_6965,Items.ROLL_6963,Items.MEAT_PIE_2327,Items.KEBAB_1971,Items.CHOCOLATE_BAR_1973) + val items = arrayOf(Items.BAGUETTE_6961, Items.TRIANGLE_SANDWICH_6962, Items.SQUARE_SANDWICH_6965, Items.ROLL_6963, Items.MEAT_PIE_2327, Items.KEBAB_1971, Items.CHOCOLATE_BAR_1973) override fun tick() { - if(RandomFunction.random(1,15) == 5){ - sendChat(phrases.random().replace("@name",player.username.capitalize())) + sayLine(this, phrases, true, true) + if (ticksLeft == 2) { + lock(player, 2) } super.tick() } override fun init() { super.init() + phrases = arrayOf( + // https://www.youtube.com/watch?v=ek8r3ZS929E + // She always starts with "Sandwiches, ${player.username}!" but she ALSO picks that at random, hence duplicate it with hasOpeningPhrase = true + "Sandwiches, ${player.username}!", + "Sandwiches, ${player.username}!", + "Come on ${player.username}, I made these specially!!", + "All types of sandwiches, ${player.username}.", + "Did you hear me ${player.username}?", + "You think I made these just for fun?!!?", + "How could you ignore me like this, ${player.username}?!" //unknown if authentic but it was already here + ) assignItem() - sendChat(phrases.random().replace("@name",player.username.capitalize())) + } + + override fun onTimeUp() { + noteAndTeleport() + terminate() } fun assignItem(){ assigned_item = items.random() - player.setAttribute("sandwich-lady:item",assigned_item) + player.setAttribute("sandwich-lady:item", assigned_item) } override fun talkTo(npc: NPC) { - player.dialogueInterpreter.open(SandwichLadyDialogue(false),npc) + player.dialogueInterpreter.open(SandwichLadyDialogue(false), npc) } } diff --git a/Server/src/main/content/global/ame/events/surpriseexam/MysteriousOldManNPC.kt b/Server/src/main/content/global/ame/events/surpriseexam/MysteriousOldManNPC.kt index 04818dda8..2a3896836 100644 --- a/Server/src/main/content/global/ame/events/surpriseexam/MysteriousOldManNPC.kt +++ b/Server/src/main/content/global/ame/events/surpriseexam/MysteriousOldManNPC.kt @@ -5,32 +5,16 @@ import content.global.ame.kidnapPlayer import core.api.* import org.rs09.consts.NPCs import core.api.utils.WeightBasedTable -import core.game.interaction.QueueStrength import core.game.node.entity.npc.NPC -import core.game.node.entity.player.link.TeleportManager import core.game.world.map.Location -import core.game.world.update.flag.context.Graphics -import org.rs09.consts.Sounds class MysteriousOldManNPC(var type: String = "", override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.MYSTERIOUS_OLD_MAN_410) { override fun init() { super.init() sendChat("Surprise exam, ${player.username}!") - queueScript(player, 4, QueueStrength.SOFT) { stage: Int -> - when (stage) { - 0 -> { - lock(player, 6) - sendGraphics(Graphics(1576, 0, 0), player.location) - animate(player,8939) - playAudio(player, Sounds.TELEPORT_ALL_200) - return@queueScript delayScript(player, 3) - } - 1 -> { - kidnapPlayer(player, Location(1886, 5025, 0), TeleportManager.TeleportType.INSTANT) - return@queueScript stopExecuting(player) - } - else -> return@queueScript stopExecuting(player) - } + face(player) + kidnapPlayer(this, player, Location(1886, 5025, 0)) { _, _ -> + /* nothing needed */ } } diff --git a/Server/src/main/content/global/ame/events/surpriseexam/SupriseExamListeners.kt b/Server/src/main/content/global/ame/events/surpriseexam/SupriseExamListeners.kt index 6d8ad6b4f..ad19ffbda 100644 --- a/Server/src/main/content/global/ame/events/surpriseexam/SupriseExamListeners.kt +++ b/Server/src/main/content/global/ame/events/surpriseexam/SupriseExamListeners.kt @@ -2,7 +2,6 @@ package content.global.ame.events.surpriseexam import core.game.component.Component import core.game.node.entity.player.Player -import core.game.node.item.Item import core.game.world.map.Location import org.rs09.consts.Items import org.rs09.consts.NPCs @@ -66,6 +65,6 @@ class SupriseExamListeners : InteractionListener, MapArea { } override fun getRestrictions(): Array { - return arrayOf(ZoneRestriction.RANDOM_EVENTS, ZoneRestriction.CANNON, ZoneRestriction.FOLLOWERS, ZoneRestriction.OFF_MAP) + return arrayOf(ZoneRestriction.RANDOM_EVENTS, ZoneRestriction.CANNON, ZoneRestriction.FOLLOWERS, ZoneRestriction.TELEPORT, ZoneRestriction.OFF_MAP) } } diff --git a/Server/src/main/content/global/ame/events/surpriseexam/SurpriseExamUtils.kt b/Server/src/main/content/global/ame/events/surpriseexam/SurpriseExamUtils.kt index 4f1530eeb..e1cbdd5a8 100644 --- a/Server/src/main/content/global/ame/events/surpriseexam/SurpriseExamUtils.kt +++ b/Server/src/main/content/global/ame/events/surpriseexam/SurpriseExamUtils.kt @@ -1,15 +1,12 @@ package content.global.ame.events.surpriseexam -import content.global.ame.kidnapPlayer import content.global.ame.returnPlayer import core.api.* import core.game.node.entity.impl.PulseType import core.game.node.entity.player.Player import core.game.system.task.Pulse -import core.game.world.map.Location import org.rs09.consts.Components import org.rs09.consts.Items -import core.game.node.entity.player.link.TeleportManager object SurpriseExamUtils { val SE_KEY_INDEX = "supexam:index" diff --git a/Server/src/main/content/global/bots/FletchingBankstander.kt b/Server/src/main/content/global/bots/FletchingBankstander.kt index 201dd6f00..dd60a4e3c 100644 --- a/Server/src/main/content/global/bots/FletchingBankstander.kt +++ b/Server/src/main/content/global/bots/FletchingBankstander.kt @@ -1,12 +1,12 @@ package content.global.bots +import content.global.skill.fletching.log.LogCraftInfo +import content.global.skill.fletching.log.CraftItemWithLogScript +import core.game.bots.Script +import core.game.bots.SkillingBotAssembler import core.game.node.entity.skill.Skills -import content.global.skill.fletching.Fletching -import content.global.skill.fletching.FletchingPulse import core.game.node.item.Item import org.rs09.consts.Items -import core.game.bots.SkillingBotAssembler -import core.game.bots.Script class FletchingBankstander : Script(){ var state = State.FLETCHING @@ -17,7 +17,7 @@ class FletchingBankstander : Script(){ State.FLETCHING -> { bot.inventory.add(Item(Items.KNIFE_946)) bot.inventory.add(Item(Items.LOGS_1511,27)) - bot.pulseManager.run(FletchingPulse(bot, Item(Items.LOGS_1511),27, Fletching.FletchingItems.ARROW_SHAFT)) + CraftItemWithLogScript(bot, LogCraftInfo.ARROW_SHAFT, 27).invoke() State.BANKING } diff --git a/Server/src/main/content/global/dialogue/BankerDialogue.kt b/Server/src/main/content/global/dialogue/BankerDialogue.kt deleted file mode 100644 index e913ce4f0..000000000 --- a/Server/src/main/content/global/dialogue/BankerDialogue.kt +++ /dev/null @@ -1,181 +0,0 @@ -package content.global.dialogue - -import core.api.* -import core.game.node.entity.player.Player -import core.game.node.entity.player.link.IronmanMode -import core.plugin.Initializable -import core.game.dialogue.IfTopic -import core.game.dialogue.Topic -import content.global.handlers.npc.BankerNPC -import core.tools.END_DIALOGUE -import core.tools.START_DIALOGUE - -@Initializable -class BankerDialogue(player: Player? = null) : core.game.dialogue.DialoguePlugin(player) { - override fun handle(interfaceId: Int, buttonId: Int): Boolean { - when (stage) { - START_DIALOGUE -> when { - hasIronmanRestriction(player, IronmanMode.ULTIMATE) -> { - npcl( - core.game.dialogue.FacialExpression.ANNOYED, - "My apologies, dear ${if (player.isMale) "sir" else "madam"}, " + - "our services are not available for Ultimate ${if (player.isMale) "Ironmen" else "Ironwomen"}" - ).also { stage = END_DIALOGUE } - } - - else -> { - npcl( - core.game.dialogue.FacialExpression.FRIENDLY, - "Good day, how may I help you?" - ).also { - if (hasAwaitingGrandExchangeCollections(player)) { - stage++ - } else { - stage += 2 - } - } - } - } - - 1 -> npcl( - core.game.dialogue.FacialExpression.FRIENDLY, - "Before we go any further, I should inform you that you " + - "have items ready for collection from the Grand Exchange." - ).also { stage++ } - - 2 -> showTopics( - Topic(core.game.dialogue.FacialExpression.FRIENDLY, "I'd like to access my bank account, please.", 10), - IfTopic( - core.game.dialogue.FacialExpression.FRIENDLY, - "I'd like to switch to my ${getBankAccountName(player, true)} bank account.", - 13, - hasActivatedSecondaryBankAccount(player) - ), - IfTopic( - core.game.dialogue.FacialExpression.FRIENDLY, - "I'd like to open a secondary bank account.", - 20, - !hasActivatedSecondaryBankAccount(player) - ), - Topic(core.game.dialogue.FacialExpression.FRIENDLY, "I'd like to check my PIN settings.", 11), - Topic(core.game.dialogue.FacialExpression.FRIENDLY, "I'd like to collect items.", 12), - Topic(core.game.dialogue.FacialExpression.ASKING, "What is this place?", 3), - ) - - 3 -> npcl( - core.game.dialogue.FacialExpression.FRIENDLY, - "This is a branch of the Bank of Gielinor. We have branches in many towns." - ).also { stage++ } - - 4 -> playerl( - core.game.dialogue.FacialExpression.ASKING, - "And what do you do?" - ).also { stage++ } - - 5 -> npcl( - core.game.dialogue.FacialExpression.FRIENDLY, - "We will look after your items and money for you. " + - "Leave your valuables with us if you want to keep them safe." - ).also { stage = END_DIALOGUE } - - 10 -> { - openBankAccount(player) - end() - } - - 11 -> { - openBankPinSettings(player) - end() - } - - 12 -> { - openGrandExchangeCollectionBox(player) - end() - } - - 13 -> { - toggleBankAccount(player) - - npcl( - core.game.dialogue.FacialExpression.FRIENDLY, - "Your active bank account has been switched. " + - "You can now access your ${getBankAccountName(player)} account." - ).also { stage = 2 } - } - - 20 -> npcl( - core.game.dialogue.FacialExpression.FRIENDLY, - "Certainly. We offer secondary accounts to all our customers." - ).also { stage++ } - - 21 -> npcl( - core.game.dialogue.FacialExpression.FRIENDLY, - "The secondary account comes with a standard fee of 5,000,000 coins. The fee is non-refundable " + - "and account activation is permanent." - ).also { stage++ } - - 22 -> npcl( - core.game.dialogue.FacialExpression.FRIENDLY, - "If your inventory does not contain enough money to cover the costs, we will complement " + - "the amount with the money inside your primary bank account." - ).also { stage++ } - - 23 -> npcl( - core.game.dialogue.FacialExpression.ASKING, - "Knowing all this, would you like to proceed with opening your secondary bank account?" - ).also { stage++ } - - 24 -> showTopics( - Topic(core.game.dialogue.FacialExpression.HAPPY, "Yes, I am still interested.", 25), - Topic(core.game.dialogue.FacialExpression.ANNOYED, "Actually, I've changed my mind.", 26) - ) - - 25 -> { - when (activateSecondaryBankAccount(player)) { - SecondaryBankAccountActivationResult.ALREADY_ACTIVE -> { - npcl( - core.game.dialogue.FacialExpression.FRIENDLY, - "Your bank account was already activated, there is no need to pay twice." - ).also { stage = END_DIALOGUE } - } - - SecondaryBankAccountActivationResult.INTERNAL_FAILURE -> { - npcl( - core.game.dialogue.FacialExpression.ANNOYED, - "I must apologize, the transaction was not successful. Please check your " + - "primary bank account and your inventory - if there's money missing, please " + - "screenshot your chat box and contact the game developers." - ).also { stage = END_DIALOGUE } - } - - SecondaryBankAccountActivationResult.NOT_ENOUGH_MONEY -> { - npcl( - core.game.dialogue.FacialExpression.ANNOYED, - "It appears that you do not have the money necessary to cover the costs " + - "associated with opening a secondary bank account. I will be waiting here " + - "until you do." - ).also { stage = END_DIALOGUE } - } - - SecondaryBankAccountActivationResult.SUCCESS -> { - npcl( - core.game.dialogue.FacialExpression.FRIENDLY, - "Your secondary bank account has been opened and can be accessed through any " + - "of the Bank of Gielinor's employees. Thank you for choosing our services." - ).also { stage = END_DIALOGUE } - } - } - } - - 26 -> npcl( - core.game.dialogue.FacialExpression.FRIENDLY, - "Very well. Should you decide a secondary bank account is needed, do not hesitate to " + - "contact any of the Bank of Gielinor's stationary employees. We will be happy to help." - ).also { stage = END_DIALOGUE } - } - - return true - } - - override fun getIds(): IntArray = BankerNPC.NPC_IDS -} diff --git a/Server/src/main/content/global/handlers/iface/BookInterface.kt b/Server/src/main/content/global/handlers/iface/BookInterface.kt index 070b83ebd..508b8b16d 100644 --- a/Server/src/main/content/global/handlers/iface/BookInterface.kt +++ b/Server/src/main/content/global/handlers/iface/BookInterface.kt @@ -1,5 +1,6 @@ package content.global.handlers.iface +import content.global.handlers.iface.BookInterface.Companion.FANCY_BOOK_2_27 import core.api.closeInterface import core.api.getAttribute import core.api.openInterface @@ -158,9 +159,22 @@ class BookInterface : InterfaceListener { if (pageSet == getAttribute(player, CURRENT_PAGE_ATTRIBUTE, 0)) { player.packetDispatch.sendInterfaceConfig(componentId, enableLineId, false) player.packetDispatch.sendModelOnInterface(modelId, componentId, drawLineId, 0) - player.packetDispatch.sendAngleOnInterface(componentId, drawLineId, zoom, pitch, yaw) - } else { - player.packetDispatch.sendInterfaceConfig(componentId, enableLineId, true) + player.packetDispatch.sendAngleOnInterface(componentId, drawLineId, zoom, pitch, yaw) + } + } + + /** Sets item on lineId of pageSet (0 index). Call this in the display function after pageSetup. */ + fun setItemOnPage(player: Player, pageSet: Int, itemId: Int, componentId: Int, enableLineId: Int, drawLineId: Int, zoom: Int, pitch: Int, yaw: Int) { + if (pageSet == getAttribute(player, CURRENT_PAGE_ATTRIBUTE, 0)) { + player.packetDispatch.sendInterfaceConfig(componentId, enableLineId, false) + player.packetDispatch.sendItemOnInterface(itemId, 1, componentId, drawLineId) + } + } + + /** Clears models(pictures) on lineId of pageSet (0 index). Call this in the display function after pageSetup. */ + fun clearModelsOnPage(player: Player, componentId: Int) { + BookInterface.FANCY_BOOK_2_27_IMAGE_ENABLE_DRAW_IDS.forEach { drawId -> + player.packetDispatch.sendInterfaceConfig(componentId, drawId, true); } } diff --git a/Server/src/main/content/global/handlers/iface/HairDresserInterface.kt b/Server/src/main/content/global/handlers/iface/HairDresserInterface.kt index b831471f2..d9058006e 100644 --- a/Server/src/main/content/global/handlers/iface/HairDresserInterface.kt +++ b/Server/src/main/content/global/handlers/iface/HairDresserInterface.kt @@ -174,7 +174,7 @@ class HairDresserInterface : ComponentPlugin(){ when(button){ 199 -> player.setAttribute("beard-setting",false) 200 -> player.setAttribute("beard-setting",true) - 196,274 -> pay(player) + 196,274,68,100 -> pay(player) else -> when(component?.id){ 592 -> { //Female if(femaleColorButtonRange.contains(button)){ diff --git a/Server/src/main/content/global/handlers/item/EmptyOptionListener.kt b/Server/src/main/content/global/handlers/item/EmptyOptionListener.kt index b4ae28212..89dd5caae 100644 --- a/Server/src/main/content/global/handlers/item/EmptyOptionListener.kt +++ b/Server/src/main/content/global/handlers/item/EmptyOptionListener.kt @@ -48,6 +48,7 @@ class EmptyOptionListener : InteractionListener { POTION(Items.POTION_195, Items.VIAL_229, "You empty the vial.", Sounds.LIQUID_2401), BURNT_STEW(Items.BURNT_STEW_2005, Items.BOWL_1923, "You empty the contents of the bowl onto the floor.", Sounds.LIQUID_2401), NETTLE_TEA(Items.NETTLE_TEA_4239, Items.BOWL_1923, "You empty the contents of the bowl onto the floor.", Sounds.LIQUID_2401), + CUP_OF_TEA(Items.CUP_OF_TEA_4242, Items.EMPTY_CUP_1980, "You empty the cup of tea.", Sounds.LIQUID_2401), NETTLE_WATER(Items.NETTLE_WATER_4237, Items.BOWL_1923, "You empty the contents of the bowl onto the floor.", Sounds.LIQUID_2401), NETTLE_TEA_MILKY(Items.NETTLE_TEA_4240, Items.BOWL_1923, "You empty the contents of the bowl onto the floor.", Sounds.LIQUID_2401), BURNT_CURRY(Items.BURNT_CURRY_2013, Items.BOWL_1923, "You empty the contents of the bowl onto the floor.", Sounds.LIQUID_2401), diff --git a/Server/src/main/content/global/handlers/item/EnchantJewelleryTabListener.kt b/Server/src/main/content/global/handlers/item/EnchantJewelleryTabListener.kt index c2d65fc08..c642372ff 100644 --- a/Server/src/main/content/global/handlers/item/EnchantJewelleryTabListener.kt +++ b/Server/src/main/content/global/handlers/item/EnchantJewelleryTabListener.kt @@ -1,85 +1,94 @@ package content.global.handlers.item +import core.ServerConstants import core.api.* import core.game.interaction.IntType import core.game.interaction.InteractionListener import core.game.interaction.QueueStrength +import core.game.node.entity.player.info.LogType +import core.game.node.entity.player.info.PlayerMonitor +import core.game.node.item.Item +import core.game.world.update.flag.context.Animation +import org.rs09.consts.Animations import org.rs09.consts.Items import org.rs09.consts.Sounds class EnchantJewelleryTabListener : InteractionListener { - private val LVL_1_ENCHANT = mapOf( - Items.SAPPHIRE_RING_1637 to Items.RING_OF_RECOIL_2550, - Items.SAPPHIRE_NECKLACE_1656 to Items.GAMES_NECKLACE8_3853, - Items.SAPPHIRE_AMULET_1694 to Items.AMULET_OF_MAGIC_1727, - Items.SAPPHIRE_BRACELET_11072 to Items.BRACELET_OF_CLAY_11074 + Items.SAPPHIRE_RING_1637 to Items.RING_OF_RECOIL_2550, + Items.SAPPHIRE_NECKLACE_1656 to Items.GAMES_NECKLACE8_3853, + Items.SAPPHIRE_AMULET_1694 to Items.AMULET_OF_MAGIC_1727, + Items.SAPPHIRE_BRACELET_11072 to Items.BRACELET_OF_CLAY_11074 ) private val LVL_2_ENCHANT = mapOf( - Items.EMERALD_RING_1639 to Items.RING_OF_DUELLING8_2552, - Items.EMERALD_NECKLACE_1658 to Items.BINDING_NECKLACE_5521, - Items.EMERALD_AMULET_1696 to Items.AMULET_OF_DEFENCE_1729, - Items.EMERALD_BRACELET_11076 to Items.CASTLEWAR_BRACE3_11079 + Items.EMERALD_RING_1639 to Items.RING_OF_DUELLING8_2552, + Items.EMERALD_NECKLACE_1658 to Items.BINDING_NECKLACE_5521, + Items.EMERALD_AMULET_1696 to Items.AMULET_OF_DEFENCE_1729, + Items.EMERALD_BRACELET_11076 to Items.CASTLEWAR_BRACE3_11079 ) - private val LVL_3_ENCHANT = mapOf( - Items.RUBY_RING_1641 to Items.RING_OF_FORGING_2568, - Items.RUBY_NECKLACE_1660 to Items.DIGSITE_PENDANT_5_11194, - Items.RUBY_AMULET_1698 to Items.AMULET_OF_STRENGTH_1725, - Items.RUBY_BRACELET_11085 to Items.INOCULATION_BRACE_11088 + Items.RUBY_RING_1641 to Items.RING_OF_FORGING_2568, + Items.RUBY_NECKLACE_1660 to Items.DIGSITE_PENDANT_5_11194, + Items.RUBY_AMULET_1698 to Items.AMULET_OF_STRENGTH_1725, + Items.RUBY_BRACELET_11085 to Items.INOCULATION_BRACE_11088 ) - private val LVL_4_ENCHANT = mapOf( - Items.DIAMOND_RING_1643 to Items.RING_OF_LIFE_2570, - Items.DIAMOND_NECKLACE_1662 to Items.PHOENIX_NECKLACE_11090, - Items.DIAMOND_AMULET_1700 to Items.AMULET_OF_POWER_1731, - Items.DIAMOND_BRACELET_11092 to Items.FORINTHRY_BRACE5_11095 + Items.DIAMOND_RING_1643 to Items.RING_OF_LIFE_2570, + Items.DIAMOND_NECKLACE_1662 to Items.PHOENIX_NECKLACE_11090, + Items.DIAMOND_AMULET_1700 to Items.AMULET_OF_POWER_1731, + Items.DIAMOND_BRACELET_11092 to Items.FORINTHRY_BRACE5_11095 ) - private val LVL_5_ENCHANT = mapOf( - Items.DRAGONSTONE_RING_1645 to Items.RING_OF_WEALTH4_14646, - Items.DRAGON_NECKLACE_1664 to Items.SKILLS_NECKLACE4_11105, - Items.DRAGONSTONE_AMMY_1702 to Items.AMULET_OF_GLORY4_1712, - Items.DRAGON_BRACELET_11115 to Items.COMBAT_BRACELET4_11118 + Items.DRAGONSTONE_RING_1645 to Items.RING_OF_WEALTH_2572, + Items.DRAGON_NECKLACE_1664 to Items.SKILLS_NECKLACE_11113, + Items.DRAGONSTONE_AMMY_1702 to Items.AMULET_OF_GLORY_1704, + Items.DRAGON_BRACELET_11115 to Items.COMBAT_BRACELET_11126 ) - private val LVL_6_ENCHANT = mapOf( - Items.ONYX_RING_6575 to Items.RING_OF_STONE_6583, - Items.ONYX_NECKLACE_6577 to Items.BERSERKER_NECKLACE_11128, - Items.ONYX_AMULET_6581 to Items.AMULET_OF_FURY_6585, - Items.ONYX_BRACELET_11130 to Items.REGEN_BRACELET_11133 + Items.ONYX_RING_6575 to Items.RING_OF_STONE_6583, + Items.ONYX_NECKLACE_6577 to Items.BERSERKER_NECKLACE_11128, + Items.ONYX_AMULET_6581 to Items.AMULET_OF_FURY_6585, + Items.ONYX_BRACELET_11130 to Items.REGEN_BRACELET_11133 + ) + private val TAB_MAPPING = arrayOf( + Pair(Items.ENCHANT_SAPPHIRE_8016, LVL_1_ENCHANT), + Pair(Items.ENCHANT_EMERALD_8017, LVL_2_ENCHANT), + Pair(Items.ENCHANT_RUBY_8018, LVL_3_ENCHANT), + Pair(Items.ENCHANT_DIAMOND_8019, LVL_4_ENCHANT), + Pair(Items.ENCHANT_DRAGONSTN_8020, LVL_5_ENCHANT), + Pair(Items.ENCHANT_ONYX_8021, LVL_6_ENCHANT) ) override fun defineListeners() { - on(IntType.ITEM, "break") {player, node -> - closeAllInterfaces(player) - delayEntity(player, 1) - queueScript(player, strength = QueueStrength.SOFT) { - - val items = when (node.id) { - 8016 -> LVL_1_ENCHANT //Sapphire - 8017 -> LVL_2_ENCHANT - 8018 -> LVL_3_ENCHANT - 8019 -> LVL_4_ENCHANT - 8020 -> LVL_5_ENCHANT - 8021 -> LVL_6_ENCHANT - else -> return@queueScript stopExecuting(player) - } - - if (inInventory(player, node.id)) { - for (item in player.inventory.toArray()) { - if (item == null) continue - val product = items[item.id] ?: continue - if (removeItem(player, node.id) && (removeItem(player, item.id)) && addItem(player, product)) { - playAudio(player, Sounds.POH_TABLET_BREAK_979) - animate(player, 4069, true) - break + for ((tablet, mapping) in TAB_MAPPING) { + on(tablet, IntType.ITEM, "break") { player, _ -> + sendMessage(player, "Try using the tablet on the item instead.") //TODO authentic message + return@on true + } + for ((unenchanted, enchanted) in mapping) { + onUseWith(IntType.ITEM, tablet, unenchanted) { player, tabItem, node -> + var product = enchanted + if (product == Items.RING_OF_WEALTH_2572 && ServerConstants.RING_OF_WEALTH_TELEPORT) { + product = Items.RING_OF_WEALTH_14638 + } + if (removeItem(player, Item(tabItem.id))) { + closeAllInterfaces(player) + playAudio(player, Sounds.POH_TABLET_BREAK_979) + val anim = Animation(Animations.POH_TABLET_BREAK_4069) + animate(player, anim, true) + delayEntity(player, anim.duration) + queueScript(player, anim.duration, QueueStrength.SOFT) { + val item = node.asItem() + val ret = replaceSlot(player, item.slot, Item(product), item) + if (ret != item) { + PlayerMonitor.log(player, LogType.DUPE_ALERT, "Unknown slot-replacement problem when enchanting jewellery (adding $product replaced $ret rather than $item)") + } + return@queueScript stopExecuting(player) } } + return@onUseWith true } - return@queueScript stopExecuting(player) } - return@on true - } - } + } + } } diff --git a/Server/src/main/content/global/handlers/item/equipment/BarrowsEquipment.kt b/Server/src/main/content/global/handlers/item/equipment/BarrowsEquipment.kt new file mode 100644 index 000000000..5cdbb874a --- /dev/null +++ b/Server/src/main/content/global/handlers/item/equipment/BarrowsEquipment.kt @@ -0,0 +1,244 @@ +package content.global.handlers.item.equipment + +import core.game.node.item.Item +import org.rs09.consts.Items + +/** + * Barrows equipment information and utilities. + * @author 'Vexia - original code + * @author Damighty - Kotlin conversion and refactor + */ +object BarrowsEquipment { + // Barrows equipment lasts for 15 hours of combat. Each piece has 4 degradation tiers (100, 75, 50, 25). + const val DEGRADATION_TICKS_PER_TIER = (15 * 6000) / 4 // 22500 ticks per tier + private const val MAX_DURABILITY = DEGRADATION_TICKS_PER_TIER * 4 + + /** + * A data class for each Barrows piece. Holds all information related to a specific piece of Barrows gear. + * + * @param brother The Barrows brother this item belongs to. + * @param equipmentType The slot type ("weapon", "body", "legs", "helm"). + * @param itemName The formatted name of the item. + * @param baseRepairCost The full repair cost in GP. + * @param degradationStates A list of item IDs, from fully repaired (index 0) to broken (index 5). + */ + data class BarrowsItemDefinition( + val brother: String, + val equipmentType: String, + val itemName: String, + val baseRepairCost: Int, + val degradationStates: List + ) { + val repairedId: Int = degradationStates.first() + val brokenId: Int = degradationStates.last() + + /** Checks if a given item ID belongs to this specific equipment set. */ + fun contains(itemId: Int): Boolean = itemId in degradationStates + + /** Gets the degradation index for a given item ID (0=repaired, 1=100, ..., 5=broken). */ + fun getDegradationIndex(itemId: Int): Int = degradationStates.indexOf(itemId) + } + + /** All Barrows equipment data. */ + private val barrowsItemDefinitions = listOf( + // Dharok + BarrowsItemDefinition("dharok", "helm", "Dharok's helm", 60_000, + listOf(Items.DHAROKS_HELM_4716, Items.DHAROKS_HELM_100_4880, + Items.DHAROKS_HELM_75_4881, Items.DHAROKS_HELM_50_4882, + Items.DHAROKS_HELM_25_4883, Items.DHAROKS_HELM_0_4884)), + BarrowsItemDefinition("dharok", "weapon", "Dharok's greataxe", 100_000, + listOf(Items.DHAROKS_GREATAXE_4718, Items.DHAROKS_AXE_100_4886, + Items.DHAROKS_AXE_75_4887, Items.DHAROKS_AXE_50_4888, + Items.DHAROKS_AXE_25_4889, Items.DHAROKS_AXE_0_4890)), + BarrowsItemDefinition("dharok", "body", "Dharok's platebody", 90_000, + listOf(Items.DHAROKS_PLATEBODY_4720, Items.DHAROKS_BODY_100_4892, + Items.DHAROKS_BODY_75_4893, Items.DHAROKS_BODY_50_4894, + Items.DHAROKS_BODY_25_4895, Items.DHAROKS_BODY_0_4896)), + BarrowsItemDefinition("dharok", "legs", "Dharok's platelegs", 80_000, + listOf(Items.DHAROKS_PLATELEGS_4722, Items.DHAROKS_LEGS_100_4898, + Items.DHAROKS_LEGS_75_4899, Items.DHAROKS_LEGS_50_4900, + Items.DHAROKS_LEGS_25_4901, Items.DHAROKS_LEGS_0_4902)), + // Guthan + BarrowsItemDefinition("guthan", "helm", "Guthan's helm", 60_000, + listOf(Items.GUTHANS_HELM_4724, Items.GUTHANS_HELM_100_4904, + Items.GUTHANS_HELM_75_4905, Items.GUTHANS_HELM_50_4906, + Items.GUTHANS_HELM_25_4907, Items.GUTHANS_HELM_0_4908)), + BarrowsItemDefinition("guthan", "weapon", "Guthan's warspear", 100_000, + listOf(Items.GUTHANS_WARSPEAR_4726, Items.GUTHANS_SPEAR_100_4910, + Items.GUTHANS_SPEAR_75_4911, Items.GUTHANS_SPEAR_50_4912, + Items.GUTHANS_SPEAR_25_4913, Items.GUTHANS_SPEAR_0_4914)), + BarrowsItemDefinition("guthan", "body", "Guthan's platebody", 90_000, + listOf(Items.GUTHANS_PLATEBODY_4728, Items.GUTHANS_BODY_100_4916, + Items.GUTHANS_BODY_75_4917, Items.GUTHANS_BODY_50_4918, + Items.GUTHANS_BODY_25_4919, Items.GUTHANS_BODY_0_4920)), + BarrowsItemDefinition("guthan", "legs", "Guthan's chainskirt", 80_000, + listOf(Items.GUTHANS_CHAINSKIRT_4730, Items.GUTHANS_SKIRT_100_4922, + Items.GUTHANS_SKIRT_75_4923, Items.GUTHANS_SKIRT_50_4924, + Items.GUTHANS_SKIRT_25_4925, Items.GUTHANS_SKIRT_0_4926)), + // Torag + BarrowsItemDefinition("torag", "helm", "Torag's helm", 60_000, + listOf(Items.TORAGS_HELM_4745, Items.TORAGS_HELM_100_4952, + Items.TORAGS_HELM_75_4953, Items.TORAGS_HELM_50_4954, + Items.TORAGS_HELM_25_4955, Items.TORAGS_HELM_0_4956)), + BarrowsItemDefinition("torag", "weapon", "Torag's hammers", 100_000, + listOf(Items.TORAGS_HAMMERS_4747, Items.TORAGS_HAMMER_100_4958, + Items.TORAGS_HAMMER_75_4959, Items.TORAGS_HAMMER_50_4960, + Items.TORAGS_HAMMER_25_4961, Items.TORAGS_HAMMER_0_4962)), + BarrowsItemDefinition("torag", "body", "Torag's platebody", 90_000, + listOf(Items.TORAGS_PLATEBODY_4749, Items.TORAGS_BODY_100_4964, + Items.TORAGS_BODY_75_4965, Items.TORAGS_BODY_50_4966, + Items.TORAGS_BODY_25_4967, Items.TORAGS_BODY_0_4968)), + BarrowsItemDefinition("torag", "legs", "Torag's platelegs", 80_000, + listOf(Items.TORAGS_PLATELEGS_4751, Items.TORAGS_LEGS_100_4970, + Items.TORAGS_LEGS_75_4971, Items.TORAGS_LEGS_50_4972, + Items.TORAGS_LEGS_25_4973, Items.TORAGS_LEGS_0_4974)), + // Verac + BarrowsItemDefinition("verac", "helm", "Verac's helm", 60_000, + listOf(Items.VERACS_HELM_4753, Items.VERACS_HELM_100_4976, + Items.VERACS_HELM_75_4977, Items.VERACS_HELM_50_4978, + Items.VERACS_HELM_25_4979, Items.VERACS_HELM_0_4980)), + BarrowsItemDefinition("verac", "weapon", "Verac's flail", 100_000, + listOf(Items.VERACS_FLAIL_4755, Items.VERACS_FLAIL_100_4982, + Items.VERACS_FLAIL_75_4983, Items.VERACS_FLAIL_50_4984, + Items.VERACS_FLAIL_25_4985, Items.VERACS_FLAIL_0_4986)), + BarrowsItemDefinition("verac", "body", "Verac's brassard", 90_000, + listOf(Items.VERACS_BRASSARD_4757, Items.VERACS_TOP_100_4988, + Items.VERACS_TOP_75_4989, Items.VERACS_TOP_50_4990, + Items.VERACS_TOP_25_4991, Items.VERACS_TOP_0_4992)), + BarrowsItemDefinition("verac", "legs", "Verac's plateskirt", 80_000, + listOf(Items.VERACS_PLATESKIRT_4759, Items.VERACS_SKIRT_100_4994, + Items.VERACS_SKIRT_75_4995, Items.VERACS_SKIRT_50_4996, + Items.VERACS_SKIRT_25_4997, Items.VERACS_SKIRT_0_4998)), + // Ahrim + BarrowsItemDefinition("ahrim", "helm", "Ahrim's hood", 60_000, + listOf(Items.AHRIMS_HOOD_4708, Items.AHRIMS_HOOD_100_4856, + Items.AHRIMS_HOOD_75_4857, Items.AHRIMS_HOOD_50_4858, + Items.AHRIMS_HOOD_25_4859, Items.AHRIMS_HOOD_0_4860)), + BarrowsItemDefinition("ahrim", "weapon", "Ahrim's staff", 100_000, + listOf(Items.AHRIMS_STAFF_4710, Items.AHRIMS_STAFF_100_4862, + Items.AHRIMS_STAFF_75_4863, Items.AHRIMS_STAFF_50_4864, + Items.AHRIMS_STAFF_25_4865, Items.AHRIMS_STAFF_0_4866)), + BarrowsItemDefinition("ahrim", "body", "Ahrim's robetop", 90_000, + listOf(Items.AHRIMS_ROBETOP_4712, Items.AHRIMS_TOP_100_4868, + Items.AHRIMS_TOP_75_4869, Items.AHRIMS_TOP_50_4870, + Items.AHRIMS_TOP_25_4871, Items.AHRIMS_TOP_0_4872)), + BarrowsItemDefinition("ahrim", "legs", "Ahrim's robeskirt", 80_000, + listOf(Items.AHRIMS_ROBESKIRT_4714, Items.AHRIMS_SKIRT_100_4874, + Items.AHRIMS_SKIRT_75_4875, Items.AHRIMS_SKIRT_50_4876, + Items.AHRIMS_SKIRT_25_4877, Items.AHRIMS_SKIRT_0_4878)), + // Karil + BarrowsItemDefinition("karil", "helm", "Karil's coif", 60_000, + listOf(Items.KARILS_COIF_4732, Items.KARILS_COIF_100_4928, + Items.KARILS_COIF_75_4929, Items.KARILS_COIF_50_4930, + Items.KARILS_COIF_25_4931, Items.KARILS_COIF_0_4932)), + BarrowsItemDefinition("karil", "weapon", "Karil's crossbow", 100_000, + listOf(Items.KARILS_CROSSBOW_4734, Items.KARILS_X_BOW_100_4934, + Items.KARILS_X_BOW_75_4935, Items.KARILS_X_BOW_50_4936, + Items.KARILS_X_BOW_25_4937, Items.KARILS_X_BOW_0_4938)), + BarrowsItemDefinition("karil", "body", "Karil's leathertop", 90_000, + listOf(Items.KARILS_LEATHERTOP_4736, Items.KARILS_TOP_100_4940, + Items.KARILS_TOP_75_4941, Items.KARILS_TOP_50_4942, + Items.KARILS_TOP_25_4943, Items.KARILS_TOP_0_4944)), + BarrowsItemDefinition("karil", "legs", "Karil's leatherskirt", 80_000, + listOf(Items.KARILS_LEATHERSKIRT_4738, Items.KARILS_SKIRT_100_4946, + Items.KARILS_SKIRT_75_4947, Items.KARILS_SKIRT_50_4948, + Items.KARILS_SKIRT_25_4949, Items.KARILS_SKIRT_0_4950)) + ) + + /** Cached access to an item's full definition from its ID */ + private val itemIdToDefinitionMap: Map by lazy { + barrowsItemDefinitions.flatMap { def -> + def.degradationStates.map { id -> id to def } + }.toMap() + } + + /** Gets all degradation state arrays for degradation registration */ + fun getAllEquipmentSets(): Collection = barrowsItemDefinitions.map { it.degradationStates.toIntArray() } + + /** Gets all repairable Barrows item IDs (anything not fully repaired) */ + fun getAllRepairableBarrowsIds(): List = itemIdToDefinitionMap.filter { !isFullyRepaired(it.key) }.keys.toList() + + /** Gets the full definition for a Barrows item */ + @JvmStatic + fun getDefinition(itemId: Int): BarrowsItemDefinition? = itemIdToDefinitionMap[itemId] + + /** Checks if an item ID is any Barrows item */ + @JvmStatic + fun isBarrowsItem(itemId: Int): Boolean = itemId in itemIdToDefinitionMap + + /** Checks if a Barrows item is fully repaired */ + @JvmStatic + fun isFullyRepaired(itemId: Int): Boolean = getDefinition(itemId)?.repairedId == itemId + + /** Checks if a Barrows item is broken */ + @JvmStatic + fun isBroken(itemId: Int): Boolean = getDefinition(itemId)?.brokenId == itemId + + /** + * Calculates the repair cost for a degraded Barrows item + * @param item The degraded Barrows item + * @return The repair cost in GP, or -1 if the item cannot be repaired + */ + fun getRepairCost(item: Item): Int { + val def = getDefinition(item.id) ?: return -1 + if (isFullyRepaired(item.id)) return -1 + + val totalRemainingDurability = calculateTotalRemainingDurability(item, def) + val durabilityLost = MAX_DURABILITY - totalRemainingDurability + val cost = ((durabilityLost.toDouble() / MAX_DURABILITY) * def.baseRepairCost).toInt() + + return cost + } + + /** + * Reduces the durability of a Barrows item by 20% of its total remaining durability + * @param item The Barrows item + * @return The item with reduced durability, or null if the item is not a valid Barrows piece + */ + @JvmStatic + fun graveDeathDurabilityReduction(item: Item): Item? { + val def = getDefinition(item.id) ?: return null + if (isBroken(item.id)) return item + + val totalRemainingDurability = calculateTotalRemainingDurability(item, def) + val durabilityReduction = (totalRemainingDurability * 0.2).toInt() + val newRemainingDurability = totalRemainingDurability - durabilityReduction + + return createItemFromDurability(def, newRemainingDurability, item.amount) + } + + /** + * Calculates the total remaining durability ticks (charge) for a Barrows item + */ + private fun calculateTotalRemainingDurability(item: Item, def: BarrowsItemDefinition): Int { + return when (val index = def.getDegradationIndex(item.id)) { + -1 -> 0 // Invalid + 0 -> MAX_DURABILITY // Fully repaired + 5 -> 0 // Broken + else -> { + // For degraded items (100, 75, 50, 25), durability is the number of fully remaining tiers below it, plus the charge of the current tier. + // Index 1 (100) has 3 tiers below it. Index 4 (25) has 0 tiers below it. + val remainingTiers = 4 - index + (remainingTiers * DEGRADATION_TICKS_PER_TIER) + item.charge + } + } + } + + /** + * Creates a new Item instance based on a total durability value + */ + private fun createItemFromDurability(def: BarrowsItemDefinition, durability: Int, amount: Int): Item { + if (durability <= 0) { + return Item(def.brokenId, amount) + } + // Tier is 1-indexed (1=25, 2=50, 3=75, 4=100) + val tier = ((durability - 1) / DEGRADATION_TICKS_PER_TIER) + 1 + // Degradation index is 4-indexed (4=25, 3=50, 2=75, 1=100) + val degradationIndex = 5 - tier + + val newId = def.degradationStates[degradationIndex] + val newCharge = (durability - 1) % DEGRADATION_TICKS_PER_TIER + 1 + + return Item(newId, amount, newCharge) + } +} diff --git a/Server/src/main/content/global/handlers/item/equipment/BarrowsEquipmentRegister.kt b/Server/src/main/content/global/handlers/item/equipment/BarrowsEquipmentRegister.kt index d7e57ab9e..1a0960711 100644 --- a/Server/src/main/content/global/handlers/item/equipment/BarrowsEquipmentRegister.kt +++ b/Server/src/main/content/global/handlers/item/equipment/BarrowsEquipmentRegister.kt @@ -4,73 +4,16 @@ import core.plugin.Initializable import core.plugin.Plugin @Initializable -class BarrowsEquipmentRegister : Plugin{ - public companion object { - // Barrows equipment lasts for 15 hours of combat, and each piece has 4 stages of degredation - @JvmField - public val TICKS = (15 * 6000) / 4 - } - - val DHAROK_HELM = arrayOf(4716,4880,4881,4882,4883,4884) - val DHAROK_AXE = arrayOf(4718,4886,4887,4888,4889,4890) - val DHAROK_BODY = arrayOf(4720,4892,4893,4894,4895,4896) - val DHAROK_LEGS = arrayOf(4722,4898,4899,4900,4901,4902) - - val GUTHAN_HELM = arrayOf(4724,4904,4905,4906,4907,4908) - val GUTHAN_SPEAR = arrayOf(4726,4910,4911,4912,4913,4914) - val GUTHAN_BODY = arrayOf(4728,4916,4917,4918,4919,4920) - val GUTHAN_SKIRT = arrayOf(4730,4922,4923,4924,4925,4926) - - val TORAG_HELM = arrayOf(4745,4952,4953,4954,4955,4956) - val TORAG_HAMMER = arrayOf(4747,4958,4959,4960,4961,4962) - val TORAG_BODY = arrayOf(4749,4964,4965,4966,4967,4968) - val TORAG_LEGS = arrayOf(4751,4970,4971,4972,4973,4974) - - val VERAC_HELM = arrayOf(4753,4976,4977,4978,4979,4980) - val VERAC_FLAIL = arrayOf(4755,4982,4983,4984,4985,4986) - val VERAC_BRASS = arrayOf(4757,4988,4989,4990,4991,4992) - val VERAC_SKIRT = arrayOf(4759,4994,4995,4996,4997,4998) - - val AHRIM_HOOD = arrayOf(4708,4856,4857,4858,4859,4860) - val AHRIM_STAFF = arrayOf(4710,4862,4863,4864,4865,4866) - val AHRIM_TOP = arrayOf(4712,4868,4869,4870,4871,4872) - val AHRIM_SKIRT = arrayOf(4714,4874,4875,4876,4877,4878) - - val KARIL_COIF = arrayOf(4732,4928,4929,4930,4931,4932) - val KARIL_CBOW = arrayOf(4734,4934,4935,4936,4937,4938) - val KARIL_TOP = arrayOf(4736,4940,4941,4942,4943,4944) - val KARIL_SKIRT = arrayOf(4738,4946,4947,4948,4949,4950) - +class BarrowsEquipmentRegister : Plugin { override fun newInstance(arg: Any?): Plugin { - EquipmentDegrader.registerSet(TICKS, AHRIM_HOOD) - EquipmentDegrader.registerSet(TICKS, AHRIM_STAFF) - EquipmentDegrader.registerSet(TICKS, AHRIM_TOP) - EquipmentDegrader.registerSet(TICKS, AHRIM_SKIRT) - EquipmentDegrader.registerSet(TICKS, KARIL_COIF) - EquipmentDegrader.registerSet(TICKS, KARIL_CBOW) - EquipmentDegrader.registerSet(TICKS, KARIL_TOP) - EquipmentDegrader.registerSet(TICKS, KARIL_SKIRT) - EquipmentDegrader.registerSet(TICKS, DHAROK_HELM) - EquipmentDegrader.registerSet(TICKS, DHAROK_AXE) - EquipmentDegrader.registerSet(TICKS, DHAROK_BODY) - EquipmentDegrader.registerSet(TICKS, DHAROK_LEGS) - EquipmentDegrader.registerSet(TICKS, GUTHAN_HELM) - EquipmentDegrader.registerSet(TICKS, GUTHAN_SPEAR) - EquipmentDegrader.registerSet(TICKS, GUTHAN_BODY) - EquipmentDegrader.registerSet(TICKS, GUTHAN_SKIRT) - EquipmentDegrader.registerSet(TICKS, TORAG_HELM) - EquipmentDegrader.registerSet(TICKS, TORAG_HAMMER) - EquipmentDegrader.registerSet(TICKS, TORAG_BODY) - EquipmentDegrader.registerSet(TICKS, TORAG_LEGS) - EquipmentDegrader.registerSet(TICKS, VERAC_HELM) - EquipmentDegrader.registerSet(TICKS, VERAC_FLAIL) - EquipmentDegrader.registerSet(TICKS, VERAC_BRASS) - EquipmentDegrader.registerSet(TICKS, VERAC_SKIRT) + BarrowsEquipment.getAllEquipmentSets().forEach { + degradationSet -> + EquipmentDegrader.registerSet(BarrowsEquipment.DEGRADATION_TICKS_PER_TIER, degradationSet.toTypedArray()) + } return this } override fun fireEvent(identifier: String?, vararg args: Any?): Any { return Unit } - } diff --git a/Server/src/main/content/global/handlers/item/withitem/GrindToothListener.kt b/Server/src/main/content/global/handlers/item/withitem/GrindToothListener.kt new file mode 100644 index 000000000..93a6989da --- /dev/null +++ b/Server/src/main/content/global/handlers/item/withitem/GrindToothListener.kt @@ -0,0 +1,24 @@ +package content.global.handlers.item.withitem + +import core.api.* +import org.rs09.consts.Items +import core.game.interaction.InteractionListener +import core.game.interaction.IntType +import core.game.node.entity.player.info.LogType +import core.game.node.entity.player.info.PlayerMonitor +import core.game.node.item.Item + +class GrindToothListener : InteractionListener { + override fun defineListeners() { + onUseWith(IntType.ITEM, Items.PESTLE_AND_MORTAR_233, Items.SUQAH_TOOTH_9079) { player, _, with -> + val item = with as Item + val res = replaceSlot(player, item.slot, Item(Items.GROUND_TOOTH_9082)) + if (res?.id == Items.SUQAH_TOOTH_9079) { + sendMessage(player, "You grind the suqah tooth to dust.") //https://www.youtube.com/watch?v=RdIcNH50v7I + } else { + PlayerMonitor.log(player, LogType.DUPE_ALERT, "Player ground item ${res?.name} instead of a suqah tooth - potential slot-based manipulation attempt") + } + return@onUseWith true + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/handlers/npc/BankerNPC.kt b/Server/src/main/content/global/handlers/npc/BankerNPC.kt index e59ac6b69..7dff2f0d7 100644 --- a/Server/src/main/content/global/handlers/npc/BankerNPC.kt +++ b/Server/src/main/content/global/handlers/npc/BankerNPC.kt @@ -1,62 +1,54 @@ package content.global.handlers.npc +import content.global.handlers.scenery.BankBoothListener +import core.ServerConstants import core.api.* +import core.game.dialogue.ChatAnim +import core.game.dialogue.DialogueLabeller +import core.game.dialogue.DialogueOption +import core.game.interaction.IntType +import core.game.interaction.InteractionListener import core.game.node.Node import core.game.node.entity.Entity import core.game.node.entity.npc.AbstractNPC import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player +import core.game.node.entity.player.link.IronmanMode import core.game.world.map.Direction import core.game.world.map.Location import core.plugin.Initializable import org.rs09.consts.NPCs -import core.game.interaction.InteractionListener -import core.game.interaction.IntType -import content.global.handlers.scenery.BankBoothListener /** * Provides dialogue tree for all generic banker NPCs as well as * handles all the common interactions like 'bank' and 'collect'. * * @author vddCore + * @author Player Name */ @Initializable class BankerNPC : AbstractNPC, InteractionListener { + val NPC_IDS = intArrayOf( + NPCs.BANKER_44, NPCs.BANKER_45, NPCs.BANKER_494, NPCs.BANKER_495, NPCs.BANKER_496, NPCs.BANKER_497, + NPCs.BANKER_498, NPCs.BANKER_499, NPCs.BANKER_1036, NPCs.BANKER_1360, NPCs.BANKER_2163, NPCs.BANKER_2164, + NPCs.BANKER_2354, NPCs.BANKER_2355, NPCs.BANKER_2568, NPCs.BANKER_2569, NPCs.BANKER_2570, NPCs.BANKER_3198, + NPCs.BANKER_3199, NPCs.BANKER_5258, NPCs.BANKER_5259, NPCs.BANKER_5260, NPCs.BANKER_5261, NPCs.BANKER_5776, + NPCs.BANKER_5777, NPCs.BANKER_5912, NPCs.BANKER_5913, NPCs.BANKER_6200, NPCs.BANKER_6532, NPCs.BANKER_6533, + NPCs.BANKER_6534, NPCs.BANKER_6535, NPCs.BANKER_7445, NPCs.BANKER_7446, NPCs.BANKER_7605, + NPCs.GUNDAI_902, + NPCs.GHOST_BANKER_1702, NPCs.GNOME_BANKER_166, NPCs.NARDAH_BANKER_3046, NPCs.MAGNUS_GRAM_5488, NPCs.TZHAAR_KET_ZUH_2619, + NPCs.SIRSAL_BANKER_4519, NPCs.FADLI_958, NPCs.BANK_TUTOR_4907, NPCs.JADE_4296, + NPCs.OGRESS_BANKER_7049, NPCs.OGRESS_BANKER_7050, + NPCs.BANKER_6538 + ) + companion object { private const val LUNAR_ISLE_BANK_REGION = 8253 - val SPECIAL_NPC_IDS = intArrayOf( - NPCs.SIRSAL_BANKER_4519, NPCs.FADLI_958, NPCs.BANK_TUTOR_4907, NPCs.JADE_4296, - NPCs.OGRESS_BANKER_7049, NPCs.OGRESS_BANKER_7050, NPCs.ARNOLD_LYDSPOR_3824, - - /* Maximillian Sackville - Near Wilderness bounty-hunter area. */ - NPCs.BANKER_6538 - ) - - val NPC_IDS = intArrayOf( - NPCs.BANKER_44, NPCs.BANKER_45, NPCs.BANKER_494, NPCs.BANKER_495, NPCs.BANKER_496, NPCs.BANKER_497, - NPCs.BANKER_498, NPCs.BANKER_499, NPCs.BANKER_1036, NPCs.BANKER_1360, NPCs.BANKER_2163, NPCs.BANKER_2164, - NPCs.BANKER_2354, NPCs.BANKER_2355, NPCs.BANKER_2568, NPCs.BANKER_2569, NPCs.BANKER_2570, NPCs.BANKER_3198, - NPCs.BANKER_3199, NPCs.BANKER_5258, NPCs.BANKER_5259, NPCs.BANKER_5260, NPCs.BANKER_5261, NPCs.BANKER_5776, - NPCs.BANKER_5777, NPCs.BANKER_5912, NPCs.BANKER_5913, NPCs.BANKER_6200, NPCs.BANKER_6532, NPCs.BANKER_6533, - NPCs.BANKER_6534, NPCs.BANKER_6535, NPCs.BANKER_7445, NPCs.BANKER_7446, NPCs.BANKER_7605, - NPCs.GUNDAI_902, - - NPCs.GHOST_BANKER_1702, NPCs.GNOME_BANKER_166, NPCs.NARDAH_BANKER_3046, NPCs.MAGNUS_GRAM_5488, NPCs.TZHAAR_KET_ZUH_2619, - ) - - private val ALL_BANKER_NPC_IDS = intArrayOf( - *SPECIAL_NPC_IDS, - *NPC_IDS - ) - /** * This is poorly named, but performs a few checks to see if the player * is trying to access the bank on the Lunar Isle and returns a boolean * controlling whether or not to pass the quick bank or collection use. - * - * TODO - * The location of this method is shit too. Find a better place for it? */ fun checkLunarIsleRestriction(player: Player, node: Node): Boolean { if (node.location.regionId != LUNAR_ISLE_BANK_REGION) @@ -121,37 +113,35 @@ class BankerNPC : AbstractNPC, InteractionListener { private fun provideDestinationOverride(entity: Entity, node: Node): Location { val npc = node as NPC - return when(npc.id) { /* Ogress bankers are 2x2 with their spawn being offset to south-western tile. */ - NPCs.OGRESS_BANKER_7049, - NPCs.OGRESS_BANKER_7050 -> npc.location.transform(3, 1, 0) - - NPCs.BANKER_6532, NPCs.BANKER_6533, - NPCs.BANKER_6534, NPCs.BANKER_6535 -> npc.location.transform(npc.direction, 1) - + NPCs.OGRESS_BANKER_7049, NPCs.OGRESS_BANKER_7050 -> npc.location.transform(3, 1, 0) /* Magnus has no bank booth nearby so we need to handle that edge case here. */ NPCs.MAGNUS_GRAM_5488 -> npc.location.transform(Direction.NORTH, 2) - + /* Special-cased NPCs, idk why */ + NPCs.BANKER_6532, NPCs.BANKER_6533, NPCs.BANKER_6534, NPCs.BANKER_6535 -> npc.location.transform(npc.direction, 1) else -> { if (npc is BankerNPC) { npc.findAdjacentBankBoothLocation()?.let { return it.second } } - return npc.location } } } override fun defineListeners() { - on(ALL_BANKER_NPC_IDS, IntType.NPC, "bank", handler = Companion::attemptBank) - on(ALL_BANKER_NPC_IDS, IntType.NPC, "collect", handler = Companion::attemptCollect) + on(NPC_IDS, IntType.NPC, "bank", handler = Companion::attemptBank) + on(NPC_IDS, IntType.NPC, "collect", handler = Companion::attemptCollect) + on(NPC_IDS, IntType.NPC, "talk-to") { player, node -> + DialogueLabeller.open(player, BankerDialogueLabellerFile(), node as NPC) + return@on true + } } override fun defineDestinationOverrides() { - setDest(IntType.NPC, ALL_BANKER_NPC_IDS, "bank", "collect", "talk-to", handler = ::provideDestinationOverride) + setDest(IntType.NPC, NPC_IDS, "bank", "collect", "talk-to", handler = ::provideDestinationOverride) } override fun init() { @@ -167,5 +157,167 @@ class BankerNPC : AbstractNPC, InteractionListener { } } - override fun getIds(): IntArray = ALL_BANKER_NPC_IDS + class BankerDialogueLabellerFile : DialogueLabeller() { + val BANKERS_WITH_EXTRA_OPTION = intArrayOf(NPCs.BANK_TUTOR_4907, NPCs.JADE_4296, NPCs.BANKER_6538) + + override fun addConversation() { + exec { player, npc -> + if (npc.id == NPCs.SIRSAL_BANKER_4519 && !hasSealOfPassage(player)) { + loadLabel(player, "no seal of passage") + } + if (hasIronmanRestriction(player, IronmanMode.ULTIMATE)) { + if (npc.id == NPCs.JADE_4296) loadLabel(player, "uim for jade") + loadLabel(player, "uim") + } + if (npc.id == NPCs.JADE_4296) loadLabel(player, "hello for jade") + loadLabel(player, "hello") + } + label("no seal of passage") + npc(ChatAnim.ANNOYED, "What are you doing here, Fremennik?!") + player(ChatAnim.WORRIED, "I have a Seal of Pass...") + npc(ChatAnim.ANGRY, "No you don't! Begone!") + // todo: kick them out, but only one of the two banker types should do so (see historical wiki) + goto("nowhere") + label("uim for jade") + npc(ChatAnim.ANNOYED, "Greetings, warrior. I wish I could help you, but our services are not available for Ultimate Iron@g[men,women].") + goto("nowhere") + label("uim") + npc(ChatAnim.ANNOYED, "My apologies, dear @g[sir,madam], our services are not available for Ultimate Iron@g[men,women]") + goto("nowhere") + label("hello for jade") + npc("Greetings warrior, how may I help you?") + exec { player, _ -> loadLabel(player, if (hasAwaitingGrandExchangeCollections(player)) "ge collect" else "main options") } + label("hello") + npc("Good day, would you like to access your bank account?") + exec { player, _ -> loadLabel(player, if (hasAwaitingGrandExchangeCollections(player)) "ge collect" else "main options") } + label("ge collect") + npc("Before we go any further, I should inform you that you have items ready for collection from the Grand Exchange.") + goto("main options") + + label("main options") + options( + DialogueOption("how to use", "How do I use the bank?") { _, npc -> return@DialogueOption npc.id == NPCs.BANK_TUTOR_4907 }, + DialogueOption("who is the bounty hunter banker", "Who are you?") { _, npc -> return@DialogueOption npc.id == NPCs.BANKER_6538 }, + DialogueOption("access", "I'd like to access my bank account please.", expression = ChatAnim.ASKING), + DialogueOption("buy second bank", "I'd like to open a secondary bank account.") { player, _ -> return@DialogueOption ServerConstants.SECOND_BANK && !hasActivatedSecondaryBankAccount(player) }, + DialogueOption("switch second bank", "I'd like to switch to my primary bank account.") { player, _ -> return@DialogueOption hasActivatedSecondaryBankAccount(player) && isUsingSecondaryBankAccount(player) }, + DialogueOption("switch second bank", "I'd like to switch to my secondary bank account.") { player, _ -> return@DialogueOption hasActivatedSecondaryBankAccount(player) && !isUsingSecondaryBankAccount(player) }, + DialogueOption("pin", "I'd like to check my PIN settings."), + DialogueOption("collect", "I'd like to collect items."), + DialogueOption("what is this place", "What is this place?") { _, npc -> return@DialogueOption npc.id !in BANKERS_WITH_EXTRA_OPTION }, + DialogueOption("jade's employment history", "How long have you worked here?") { _, npc -> return@DialogueOption npc.id == NPCs.JADE_4296 }, + DialogueOption("is the bounty hunter banker afraid", "Aren't you afraid of working in the Wilderness?") { _, npc -> return@DialogueOption npc.id == NPCs.BANKER_6538 && !ServerConstants.SECOND_BANK } + ) + + label("how to use") + options( + DialogueOption("using bank", "Using the bank itself.", "Using the bank itself. I'm not sure how....?"), + DialogueOption("using deposit", "Using bank deposit boxes.", "Using Bank deposit boxes.... what are they?"), + DialogueOption("using PIN", "What's this PIN thing that people keep talking about?", "What's this PIN thing that people keep talking about?"), + DialogueOption("nowhere", "Goodbye.") + ) + + label("using bank") + npc("Speak to any banker and ask to see your bank", "account. If you have set a PIN you will be asked for", "it, then all the belongings you have placed in the bank", "will appear in the window. To withdraw one item, left-") + npc("click on it once.") + npc("To withdraw many, right-click on the item and select", "from the menu. The same for depositing, left-click on", "the item in your inventory to deposit it in the bank.", "Right-click on it to deposit many of the same items.") + npc("To move things around in your bank: firstly select", "Swap or Insert as your default moving mode, you can", "find these buttons on the bank window itself. Then click", "and drag an item to where you want it to appear.") + npc("You may withdraw 'notes' or 'certificates' when the", "items you are trying to withdraw do not stack in your", "inventory. This will only work for items which are", "tradeable.") + npc("For instance, if you wanted to sell 100 logs to another", "player, they would not fit in one inventory and you", "would need to do multiple trades. Instead, click the", "Note button to withdraw the logs as 'certs' or 'notes'") + npc("then withdraw the items you need.") + goto("how to use") + + label("using deposit") + npc("They look like grey pillars, there's one just over there,", "near the desk. Bank deposit boxes save so much time.", "If you're simply wanting to deposit a single item, 'Use'", "it on the deposit box.") + npc("Otherwise, simply click once on the box and it will give", "you a choice of what to deposit in an interface very", "similar to the bank itself. Very quick for when you're", "simply fishing or mining etc.") + goto("how to use") + + label("using PIN") + npc("The PIN - Personal Identification Number - can be", "set on your bank account to protect the items there in", "case someone finds out your account password. It", "consists of four numbers that you remember and tell") + npc("no one.") + npc("So if someone did manage to get your password they", "couldn't steal your items if they were in the bank.") + goto("how to use") + + label("who is the bounty hunter banker") + npc(ChatAnim.NEUTRAL, "How inconsiderate of me, dear @g[sir,madam]. My name is Maximillian Sackville and I conduct operations here on behalf of The Bank of Gielinor.") + goto("main options") + + label("access") + exec { player, _ -> openBankAccount(player) } + goto("nowhere") + + label("buy second bank") + npc("Certainly. We offer secondary accounts to all our customers.") + npc("The secondary account comes with a standard fee of 5,000,000 coins. The fee is non-refundable and account activation is permanent.") + npc("If your inventory does not contain enough money to cover the costs, we will complement the amount with the money inside your primary bank account.") + npc("Knowing all this, would you like to proceed with opening your secondary bank account?") + options( + DialogueOption("buy second bank yes", "Yes, I am still interested.", expression = ChatAnim.HAPPY), + DialogueOption("buy second bank no", "Actually, I've changed my mind.", expression = ChatAnim.ANNOYED) + ) + + label("buy second bank yes") + exec { player, _ -> loadLabel(player, when (activateSecondaryBankAccount(player)) { + SecondaryBankAccountActivationResult.ALREADY_ACTIVE -> "already" + SecondaryBankAccountActivationResult.INTERNAL_FAILURE -> "failure" + SecondaryBankAccountActivationResult.NOT_ENOUGH_MONEY -> "not enough cash" + SecondaryBankAccountActivationResult.SUCCESS -> "success" + }) } + label("already") + npc(ChatAnim.FRIENDLY, "Your bank account was already activated, there is no need to pay twice.") + goto("main options") + label("failure") + npc(ChatAnim.ANNOYED, "I must apologize, the transaction was not successful. Please check your primary bank account and your inventory - if there's money missing, please screenshot your chatbox and contact the game developers.") + goto("main options") + label("not enough cash") + npc(ChatAnim.ANNOYED, "It appears that you do not have the money necessary to cover the costs associated with opening a secondary bank account. I will be waiting here until you do.") + goto("main options") + label("success") + npc(ChatAnim.FRIENDLY, "Your secondary bank account has been opened and can be accessed through any of the Bank of Gielinor's employees. Thank you for choosing our services.") + goto("main options") + + label("buy second bank no") + npc("Very well. Should you decide a secondary bank account is needed, do not hesitate to contact any of the Bank of Gielinor's stationary employees. We will be happy to help.") + goto("main options") + + label("switch second bank") + exec { player, _ -> + toggleBankAccount(player) + loadLabel(player, if (isUsingSecondaryBankAccount(player)) "now secondary" else "now primary") + } + label("now primary") + npc("Your active bank account has been switched. You can now access your primary account.") + goto("main options") + label("now secondary") + npc("Your active bank account has been switched. You can now access your secondary account.") + goto("main options") + + label("pin") + exec { player, _ -> openBankPinSettings(player) } + goto("nowhere") + + label("collect") + exec { player, _ -> openGrandExchangeCollectionBox(player) } + goto("nowhere") + + label("what is this place") + npc("This is a branch of the Bank of Gielinor. We have branches in many towns.") + player("And what do you do?") + npc("We will look after your items and money for you. Leave your valuables with us if you want to keep them safe.") + goto("main options") + + label("jade's employment history") + npc(ChatAnim.FRIENDLY, "Oh, ever since the Guild opened. I like it here.") + player(ChatAnim.ASKING, "Why's that?") + npc(ChatAnim.FRIENDLY, "Well... what with all these warriors around, there's not much chance of my bank being robbed, is there?") + goto("nowhere") + + label("is the bounty hunter banker afraid") + npc(ChatAnim.NEUTRAL, "While the Wilderness is quite a dangerous place, The Bank of Gielinor offers us - roving bankers - extraordinary benefits for our hard work in hazardous environments.") + npc(ChatAnim.NEUTRAL, "This allows us to provide our services to customers regardless of their current whereabouts. Our desire to serve is stronger than our fear of the Wilderness.") + goto("nowhere") + } + } + + override fun getIds(): IntArray = NPC_IDS } diff --git a/Server/src/main/content/global/handlers/npc/GuardNPC.java b/Server/src/main/content/global/handlers/npc/GuardNPC.java index ccdf7e10d..9cde38cc7 100644 --- a/Server/src/main/content/global/handlers/npc/GuardNPC.java +++ b/Server/src/main/content/global/handlers/npc/GuardNPC.java @@ -15,7 +15,7 @@ public final class GuardNPC extends AbstractNPC { /** * The NPC ids of NPCs using this plugin. */ - private static final int[] ID = { 9, 32, 163, 164, 196, 197, 206, 253, 254, 255, 256, 274, 275, 296, 298, 299, 447, 448, 449, 489, 609, 678, 799, 837, 842, 862, 870, 877, 917, 1200, 1203, 1204, 1317, 1710, 1711, 1712, 2073, 2074, 2134, 2135, 2136, 2236, 2571, 2699, 2700, 2701, 2702, 2703, 3228, 3229, 3230, 3231, 3232, 3233, 3241, 3407, 3408, 3715, 4257, 4258, 4259, 4260, 4307, 4308, 4309, 4310, 4311, 4336, 4375, 4603, 4604, 4605, 4606, 5800, 5801, 5919, 5920 }; + private static final int[] ID = { 9, 32, 163, 164, 196, 197, 206, 253, 254, 255, 256, 274, 275, 296, 298, 299, 447, 448, 449, 609, 678, 799, 837, 842, 862, 870, 877, 917, 1200, 1203, 1204, 1317, 1710, 1711, 1712, 2073, 2074, 2134, 2135, 2136, 2236, 2571, 2699, 2700, 2701, 2702, 2703, 3228, 3229, 3230, 3231, 3232, 3233, 3241, 3407, 3408, 3715, 4257, 4258, 4259, 4260, 4307, 4308, 4309, 4310, 4311, 4336, 4375, 4603, 4604, 4605, 4606, 5800, 5801, 5919, 5920 }; /** * Constructs a new {@code GuardNPC} {@code Object}. diff --git a/Server/src/main/content/global/handlers/npc/NPCTalkListener.kt b/Server/src/main/content/global/handlers/npc/NPCTalkListener.kt index 5235fc82a..4f5764b0d 100644 --- a/Server/src/main/content/global/handlers/npc/NPCTalkListener.kt +++ b/Server/src/main/content/global/handlers/npc/NPCTalkListener.kt @@ -12,6 +12,7 @@ import core.game.system.timer.impl.AntiMacro import core.game.worldevents.holiday.HolidayRandomEventNPC import core.game.worldevents.holiday.HolidayRandomEvents import core.game.worldevents.holiday.HolidayRandoms +import org.rs09.consts.NPCs /** * Handles the NPC talk-to option. @@ -37,11 +38,18 @@ class NPCTalkListener : InteractionListener { val npc = node.asNpc() if(RandomEvents.randomIDs.contains(node.id)){ if(AntiMacro.getEventNpc(player) == null || AntiMacro.getEventNpc(player) != node.asNpc() || AntiMacro.getEventNpc(player)?.finalized == true) { - player.sendMessage("They aren't interested in talking to you.") + // Why the fuck is this here of all places? Now look at what you've made me do: + if (npc.id == NPCs.SANDWICH_LADY_3117) { + // https://www.youtube.com/watch?v=ek8r3ZS929E + player.dialogueInterpreter.sendDialogue("The sandwich lady doesn't seem interested in selling you any", "refreshments.") + } else { + sendMessage(player, "They aren't interested in talking to you.") + } } else { AntiMacro.getEventNpc(player)?.talkTo(node.asNpc()) } return@on true + //TODO bring sanity here } if (HolidayRandomEvents.holidayRandomIDs.contains(node.id) && node is HolidayRandomEventNPC) { if(HolidayRandoms.getEventNpc(player) == null || HolidayRandoms.getEventNpc(player) != node.asNpc() || HolidayRandoms.getEventNpc(player)?.finalized == true) { @@ -79,4 +87,4 @@ class NPCTalkListener : InteractionListener { return@on player.dialogueInterpreter.open(npc.id, npc) } } -} \ No newline at end of file +} diff --git a/Server/src/main/content/global/skill/construction/Room.java b/Server/src/main/content/global/skill/construction/Room.java index 883a4776c..cb9d4a7c4 100644 --- a/Server/src/main/content/global/skill/construction/Room.java +++ b/Server/src/main/content/global/skill/construction/Room.java @@ -147,7 +147,7 @@ public final class Room { SceneryBuilder.replace(object, object.transform(id, object.getRotation(), chunk.getCurrentBase().transform(x, y, 0))); } else if (object.getId() == BuildHotspot.WINDOW.getObjectId(house.getStyle()) || (!house.isBuildingMode() && object.getId() == BuildHotspot.CHAPEL_WINDOW.getObjectId(house.getStyle()))) { - chunk.add(object.transform(house.getStyle().getWindowStyle().getObjectId(house.getStyle()), object.getRotation(), object.getType())); + SceneryBuilder.replace(object, object.transform(house.getStyle().getWindowStyle().getObjectId(house.getStyle()), object.getRotation(), object.getType())); } int[] pos = RegionChunk.getRotatedPosition(x, y, object.getSizeX(), object.getSizeY(), 0, rotation.toInteger()); spot.setCurrentX(pos[0]); diff --git a/Server/src/main/content/global/skill/construction/decoration/workshop/ArmourStand.kt b/Server/src/main/content/global/skill/construction/decoration/workshop/ArmourStand.kt index b30f862d1..44a050754 100644 --- a/Server/src/main/content/global/skill/construction/decoration/workshop/ArmourStand.kt +++ b/Server/src/main/content/global/skill/construction/decoration/workshop/ArmourStand.kt @@ -4,8 +4,7 @@ import core.game.dialogue.DialoguePlugin import content.data.RepairItem import core.game.interaction.NodeUsageEvent import core.game.interaction.UseWithHandler -import content.region.misthalin.lumbridge.dialogue.BobDialogue.BarrowsEquipment -import content.region.misthalin.lumbridge.dialogue.BobDialogue.BarrowsEquipment.BarrowsFullEquipment +import content.global.handlers.item.equipment.BarrowsEquipment import core.game.node.entity.player.Player import core.game.node.entity.skill.Skills import core.game.node.item.Item @@ -14,8 +13,10 @@ import core.plugin.Plugin import kotlin.math.ceil import org.rs09.consts.Items +private val ALL_REPAIRABLE_ITEM_IDS = (RepairItem.repairableItemIds + BarrowsEquipment.getAllRepairableBarrowsIds()).toIntArray() + @Initializable -class ArmourStand : UseWithHandler(494, 468, 496, 470, 498, 472, 500, 502, 474, 504, 476, 506, 478, 6741, 4856, 4857, 4858, 4859, 4860, 4862, 4863, 4864, 4865, 4866, 4868, 4869, 4870, 4871, 4872, 4874, 4875, 4876, 4877, 4878, 4880, 4881, 4882, 4883, 4884, 4886, 4887, 4888, 4889, 4890, 4892, 4893, 4894, 4895, 4896, 4898, 4899, 4900, 4901, 4902, 4904, 4905, 4906, 4907, 4908, 4910, 4911, 4912, 4913, 4914, 4916, 4917, 4918, 4919, 4920, 4922, 4923, 4924, 4925, 4926, 4928, 4929, 4930, 4931, 4932, 4934, 4935, 4936, 4937, 4938, 4940, 4941, 4942, 4943, 4944, 4946, 4947, 4948, 4949, 4950, 4952, 4953, 4954, 4955, 4956, 4958, 4959, 4960, 4961, 4962, 4964, 4965, 4966, 4967, 4968, 4970, 4971, 4972, 4973, 4974, 4976, 4977, 4978, 4979, 4980, 4982, 4983, 4984, 4985, 4986, 4988, 4989, 4990, 4991, 4992, 4994, 4995, 4996, 4997, 4998){ +class ArmourStand : UseWithHandler(*ALL_REPAIRABLE_ITEM_IDS) { override fun newInstance(arg: Any?): Plugin { addHandler(13715, OBJECT_TYPE, this) return this @@ -24,76 +25,90 @@ class ArmourStand : UseWithHandler(494, 468, 496, 470, 498, 472, 500, 502, 474, override fun handle(event: NodeUsageEvent?): Boolean { event ?: return false val player = event.player - val repairItem = RepairItem.forId(event.used.id) + val usedItem = event.used.asItem() - var baseCost = 0.0 + var baseCost = 0 var product: Item? = null - if(repairItem != null){ - baseCost = repairItem.cost * 1.0 - product = repairItem.product - } else if(BarrowsEquipment.isBarrowsItem(event.used.id)){ - //Begin terrible code thanks to Vexia - val type = BarrowsEquipment.formatedName(event.used.id) - val single = BarrowsEquipment.getSingleName(type) - val equipment = BarrowsEquipment.getEquipmentType(type) - val newString = type.toLowerCase().replace(single, "").trim { it <= ' ' }.replace("'s", "") - val newewString = StringBuilder() - newewString.append(newString).append(" $equipment") - val fullequip = BarrowsFullEquipment.forName(newewString.toString()) - baseCost = BarrowsEquipment.getFormatedCost(equipment,event.used.asItem()) * 1.0 - product = fullequip.full - //End terrible code thanks to Vexia - } + val repairItem = RepairItem.forId(usedItem.id) + val barrowsDef = BarrowsEquipment.getDefinition(usedItem.id) - if((repairItem == null && baseCost == 0.0)){ + if (repairItem != null) { + baseCost = repairItem.cost + product = repairItem.product + } else if (barrowsDef != null) { + if (BarrowsEquipment.isFullyRepaired(event.used.id)) { + player.sendMessage("That item can't be repaired.") + return true + } + baseCost = BarrowsEquipment.getRepairCost(usedItem) + product = Item(barrowsDef.repairedId) + } else { player.sendMessage("That item can't be repaired.") return true } - val cost: Int = ceil(((100.0 - (player.skills.getLevel(Skills.SMITHING) / 2.0) ) / 100.0) * baseCost).toInt() - - player.dialogueInterpreter.open(58824213,event.used,cost,product) + val discountMultiplier = (100.0 - (player.skills.getLevel(Skills.SMITHING) / 2.0)) / 100.0 + val cost = ceil(discountMultiplier * baseCost).toInt() + player.dialogueInterpreter.open(58824213,usedItem, cost, product) return true } @Initializable - class RepairDialogue(player: Player? = null) : DialoguePlugin(player){ - override fun newInstance(player: Player?): DialoguePlugin { - return RepairDialogue(player) - } - var item: Item? = null - var cost: Int = 0 - var product: Item? = null + class RepairDialogue(player: Player? = null) : DialoguePlugin(player) { + + private var item: Item? = null + private var cost: Int = 0 + private var product: Item? = null + + override fun newInstance(player: Player?): DialoguePlugin = RepairDialogue(player) override fun open(vararg args: Any?): Boolean { item = args[0] as Item cost = args[1] as Int product = args[2] as Item - player.dialogueInterpreter.sendDialogue("Would you like to repair your ${(item as Item).name.toLowerCase()}","for $cost gp?") + + val itemName = item?.name?.lowercase() ?: "item" + player.dialogueInterpreter.sendDialogue( + "Would you like to repair your $itemName", + "for $cost gp?" + ) stage = 0 return true } override fun handle(interfaceId: Int, buttonId: Int): Boolean { - item ?: return false - product ?: return false - when(stage){ - 0 -> options("Yes, please","No, thanks").also{stage++} - 1 -> when(buttonId){ - 1 -> exchangeItems(item as Item,cost,product as Item).also { end() } + val currentItem = item ?: return false + val currentProduct = product ?: return false + + when (stage) { + 0 -> { + options("Yes, please", "No, thanks") + stage++ + } + 1 -> when (buttonId) { + 1 -> { + exchangeItems(currentItem, cost, currentProduct) + end() + } 2 -> end() } } return true } - fun exchangeItems(item: Item, cost: Int, product: Item) { + private fun exchangeItems(item: Item, cost: Int, product: Item) { val coins = Item(Items.COINS_995, cost) + if (player.inventory.containsItem(coins) && player.inventory.containsItem(item)) { - player.inventory.remove(item, coins) - player.inventory.add(product) - player.sendMessage("You repair your ${product.name.toLowerCase()} for $cost.") + if (player.inventory.remove(item, coins)) { + if (player.inventory.add(product)) { + val costText = if (cost > 0) "${cost}gp" else "free" + player.sendMessage("You repair your ${product.name.lowercase()} for $costText.") + return + } + } + player.sendMessage("Report this to an administrator!") } else { player.sendMessage("You can't afford that.") } diff --git a/Server/src/main/content/global/skill/cooking/CookingRewrite.kt b/Server/src/main/content/global/skill/cooking/CookingRewrite.kt index 7bc993a8b..e65406487 100644 --- a/Server/src/main/content/global/skill/cooking/CookingRewrite.kt +++ b/Server/src/main/content/global/skill/cooking/CookingRewrite.kt @@ -1,17 +1,22 @@ package content.global.skill.cooking -import core.api.amountInInventory +import content.region.misc.tutisland.handlers.TutorialStage +import core.api.* import core.game.interaction.IntType import core.game.interaction.InteractionListener +import core.game.interaction.QueueStrength +import core.game.node.entity.impl.Animator import core.game.node.entity.player.Player import core.game.node.item.Item import core.game.node.scenery.Scenery +import core.game.world.update.flag.context.Animation import org.rs09.consts.Items import org.rs09.consts.Items.BREAD_DOUGH_2307 import org.rs09.consts.Items.RAW_BEAR_MEAT_2136 import org.rs09.consts.Items.RAW_BEEF_2132 import org.rs09.consts.Items.SEAWEED_401 import org.rs09.consts.Items.UNCOOKED_CAKE_1889 +import org.rs09.consts.Sounds /** * @author Ceikry @@ -32,8 +37,36 @@ class CookingRewrite : InteractionListener { } override fun defineListeners() { - onUseWith(IntType.SCENERY,RAW_FOODS, *COOKING_OBJs){ player, used, with -> + if (!getAttribute(player, "/save:tutorial:complete", false)) { + // On tutorial island, we don't want to show the cook-x menu and we always want to burn our first shrimp + // This requirement of a simplified and predictable cooking system means that, for the second time, I have to reinvent the wheel of cooking + // (We only need to care about shrimp here, the tutorial island range is special and has its own separate listener) + queueScript(player, 0, QueueStrength.WEAK) { stage -> + if (stage == 0) { + val FIRE_ANIMATION = Animation(897, Animator.Priority.HIGH) + lock(player, FIRE_ANIMATION.duration) + lockInteractions(player, FIRE_ANIMATION.duration) + animate(player, FIRE_ANIMATION) + playAudio(player, Sounds.FRY_2577) + return@queueScript delayScript(player, FIRE_ANIMATION.duration) + } + val tutStage = getAttribute(player, "/save:tutorial:stage", 0) + if (tutStage < 15) { + replaceSlot(player, used.asItem().slot, Item(Items.BURNT_SHRIMP_7954), used.asItem()) + setAttribute(player, "tutorial:stage", 15) + TutorialStage.load(player, 15) + } else { + replaceSlot(player, used.asItem().slot, Item(Items.SHRIMPS_315), used.asItem()) + if (tutStage == 15) { + setAttribute(player, "tutorial:stage", 16) + TutorialStage.load(player, 16) + } + } + return@queueScript stopExecuting(player) + } + return@onUseWith true + } val item = used.asItem() val obj = with.asScenery() val range = obj.name.toLowerCase().contains("range") @@ -66,7 +99,7 @@ class CookingRewrite : InteractionListener { } companion object { - val COOKING_OBJs = intArrayOf(24313,21302, 13528, 13529, 13533, 13531, 13536, 13539, 13542, 2728, 2729, 2730, 2731, 2732, 2859, 3038, 3039, 3769, 3775, 4265, 4266, 5249, 5499, 5631, 5632, 5981, 9682, 10433, 11404, 11405, 11406, 12102, 12796, 13337, 13881, 14169, 14919, 15156, 20000, 20001, 21620, 21792, 22713, 22714, 23046, 24283, 24284, 25155, 25156, 25465, 25730, 27297, 29139, 30017, 32099, 33500, 34495, 34546, 36973, 37597, 37629, 37726, 114, 4172, 5275, 8750, 16893, 22154, 34410, 34565, 114, 9085, 9086, 9087, 12269, 15398, 25440, 25441, 2724, 2725, 2726, 4618, 4650, 5165, 6093, 6094, 6095, 6096, 8712, 9374, 9439, 9440, 9441, 10824, 17640, 17641, 17642, 17643, 18039, 18170, 21795, 24285, 24329, 27251, 33498, 35449, 36815, 36816, 37426, 40110, 10377) + val COOKING_OBJs = intArrayOf(24313,21302, 13528, 13529, 13533, 13531, 13536, 13539, 13542, 2728, 2729, 2730, 2731, 2732, 2859, 3038, 3769, 3775, 4265, 4266, 5249, 5499, 5631, 5632, 5981, 9682, 10433, 11404, 11405, 11406, 12102, 12796, 13337, 13881, 14169, 14919, 15156, 20000, 20001, 21620, 21792, 22713, 22714, 23046, 24283, 24284, 25155, 25156, 25465, 25730, 27297, 29139, 30017, 32099, 33500, 34495, 34546, 36973, 37597, 37629, 37726, 114, 4172, 5275, 8750, 16893, 22154, 34410, 34565, 114, 9085, 9086, 9087, 12269, 15398, 25440, 25441, 2724, 2725, 2726, 4618, 4650, 5165, 6093, 6094, 6095, 6096, 8712, 9374, 9439, 9440, 9441, 10824, 17640, 17641, 17642, 17643, 18039, 18170, 21795, 24285, 24329, 27251, 33498, 35449, 36815, 36816, 37426, 40110, 10377) @JvmStatic fun cook(player: Player, `object`: Scenery?, initial: Int, product: Int, amount: Int) { diff --git a/Server/src/main/content/global/skill/cooking/DoughMakingListener.kt b/Server/src/main/content/global/skill/cooking/DoughMakingListener.kt index a60f89ff7..267d9ccfe 100644 --- a/Server/src/main/content/global/skill/cooking/DoughMakingListener.kt +++ b/Server/src/main/content/global/skill/cooking/DoughMakingListener.kt @@ -1,5 +1,6 @@ package content.global.skill.cooking +import content.region.misc.tutisland.handlers.TutorialStage import core.api.* import core.game.event.ResourceProducedEvent import core.game.node.entity.skill.Skills @@ -24,7 +25,16 @@ class DoughMakingListener : InteractionListener { FULL_WATER_CONTAINERS_TO_EMPTY_CONTAINERS.keys.toIntArray(), Items.POT_OF_FLOUR_1933 ) { player, waterContainer, flourContainer -> - openDialogue(player, DoughMakeDialogue(waterContainer.asItem(), flourContainer.asItem())) + if (getAttribute(player, "/save:tutorial:complete", false)) { + openDialogue(player, DoughMakeDialogue(waterContainer.asItem(), flourContainer.asItem())) + return@onUseWith true + } + // Continue the tutorial + replaceSlot(player, waterContainer.asItem().slot, Item(Items.BUCKET_1925), waterContainer.asItem()) + replaceSlot(player, flourContainer.asItem().slot, Item(Items.EMPTY_POT_1931), flourContainer.asItem()) + addItemOrDrop(player, Items.BREAD_DOUGH_2307) + setAttribute(player, "tutorial:stage", 20) + TutorialStage.load(player, 20) return@onUseWith true } } @@ -73,7 +83,7 @@ class DoughMakingListener : InteractionListener { sendMessage( player!!, - "You mix the flower and the water to make some ${selectedDoughProduct.itemName.toLowerCase()}." + "You mix the flour and the water to make some ${selectedDoughProduct.itemName.toLowerCase()}." ) } } else { diff --git a/Server/src/main/content/global/skill/cooking/NettleTeaListener.kt b/Server/src/main/content/global/skill/cooking/NettleTeaListener.kt new file mode 100644 index 000000000..ac1765428 --- /dev/null +++ b/Server/src/main/content/global/skill/cooking/NettleTeaListener.kt @@ -0,0 +1,18 @@ +package content.global.skill.cooking + +import org.rs09.consts.Items +import core.api.replaceSlot +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.item.Item + +class NettleTeaListener : InteractionListener { + + override fun defineListeners() { + onUseWith(IntType.ITEM, Items.EMPTY_CUP_1980, Items.NETTLE_TEA_4239) { player, used, with -> + replaceSlot(player, with.asItem().slot, Item(Items.BOWL_1923), with.asItem()) + replaceSlot(player, used.asItem().slot, Item(Items.CUP_OF_TEA_4242), used.asItem()) + return@onUseWith true + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/cooking/NettleTeaPlugin.java b/Server/src/main/content/global/skill/cooking/NettleTeaPlugin.java deleted file mode 100644 index 161c99991..000000000 --- a/Server/src/main/content/global/skill/cooking/NettleTeaPlugin.java +++ /dev/null @@ -1,61 +0,0 @@ -package content.global.skill.cooking; - -import core.game.interaction.NodeUsageEvent; -import core.game.interaction.UseWithHandler; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; -import core.plugin.Initializable; -import core.plugin.Plugin; - -/** - * Represents the plugin used to create nettle tea in a cup. - * @author 'Vexia - * @version 1.0 - */ -@Initializable -public final class NettleTeaPlugin extends UseWithHandler { - - /** - * Represents the empty cup item. - */ - private static final Item EMPTY_CUP = new Item(1980, 1); - - /** - * Represents the nettle tea item. - */ - private static final Item NETTLE_TEA = new Item(4239, 1); - - /** - * Represents the bowl item. - */ - private static final Item BOWL = new Item(1923); - - /** - * Represents the cup of tea item. - */ - private static final Item CUP_OF_TEA = new Item(4242, 1); - - /** - * Constructs a new {@code NettleTeaPlugin} {@code Object}. - */ - public NettleTeaPlugin() { - super(1980); - } - - @Override - public Plugin newInstance(Object arg) throws Throwable { - addHandler(4239, ITEM_TYPE, this); - return this; - } - - @Override - public boolean handle(NodeUsageEvent event) { - final Player player = event.getPlayer(); - if (player.getInventory().remove(EMPTY_CUP) && player.getInventory().remove(NETTLE_TEA)) { - player.getInventory().add(BOWL); - player.getInventory().add(CUP_OF_TEA); - } - return true; - } - -} diff --git a/Server/src/main/content/global/skill/cooking/NettleWaterListener.kt b/Server/src/main/content/global/skill/cooking/NettleWaterListener.kt new file mode 100644 index 000000000..bf52e8067 --- /dev/null +++ b/Server/src/main/content/global/skill/cooking/NettleWaterListener.kt @@ -0,0 +1,18 @@ +package content.global.skill.cooking + +import org.rs09.consts.Items +import core.api.replaceSlot +import core.api.removeItem +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.item.Item + +class NettleWaterListener : InteractionListener { + override fun defineListeners() { + onUseWith(IntType.ITEM, Items.BOWL_OF_WATER_1921, Items.NETTLES_4241) { player, used, with -> + replaceSlot(player, used.asItem().slot, Item(Items.NETTLE_WATER_4237), used.asItem()) + removeItem(player, with.asItem()) + return@onUseWith true + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/cooking/NettleWaterPlugin.java b/Server/src/main/content/global/skill/cooking/NettleWaterPlugin.java deleted file mode 100644 index e8dbeb385..000000000 --- a/Server/src/main/content/global/skill/cooking/NettleWaterPlugin.java +++ /dev/null @@ -1,35 +0,0 @@ -package content.global.skill.cooking; - -import core.game.interaction.NodeUsageEvent; -import core.game.interaction.UseWithHandler; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; -import core.plugin.Initializable; -import core.plugin.Plugin; - -/** - * @author Adam - */ -@Initializable -public class NettleWaterPlugin extends UseWithHandler { - - public NettleWaterPlugin() { - super(1921); - } - - @Override - public boolean handle(NodeUsageEvent event) { - final Player player = event.getPlayer(); - player.getInventory().remove(new Item(1921, 1)); - player.getInventory().remove(new Item(4241, 1)); - player.getInventory().add(new Item(4237, 1)); - return true; - } - - @Override - public Plugin newInstance(Object arg) throws Throwable { - addHandler(4241, ITEM_TYPE, this); - return this; - } - -} diff --git a/Server/src/main/content/global/skill/crafting/PotteryPlugin.java b/Server/src/main/content/global/skill/crafting/PotteryPlugin.java index f7fa44e3a..23ddac860 100644 --- a/Server/src/main/content/global/skill/crafting/PotteryPlugin.java +++ b/Server/src/main/content/global/skill/crafting/PotteryPlugin.java @@ -56,7 +56,7 @@ public final class PotteryPlugin extends UseWithHandler { @Override public boolean handle(final NodeUsageEvent event) { final Player player = event.getPlayer(); - new SkillDialogueHandler(player, SkillDialogue.FIVE_OPTION, (Object[]) getPottery(false)) { + new SkillDialogueHandler(player, SkillDialogue.FIVE_OPTION, getPottery(false)) { @Override public void create(final int amount, int index) { @@ -145,7 +145,7 @@ public final class PotteryPlugin extends UseWithHandler { * @return the dialogue handler. */ public SkillDialogueHandler getSkillHandler(final Player player) { - return new SkillDialogueHandler(player, SkillDialogue.FIVE_OPTION, (Object[]) getPottery(true)) { + return new SkillDialogueHandler(player, SkillDialogue.FIVE_OPTION, getPottery(true)) { @Override public void create(final int amount, final int index) { diff --git a/Server/src/main/content/global/skill/crafting/SnakeSkinPlugin.java b/Server/src/main/content/global/skill/crafting/SnakeSkinPlugin.java index f4929248e..09c2ed605 100644 --- a/Server/src/main/content/global/skill/crafting/SnakeSkinPlugin.java +++ b/Server/src/main/content/global/skill/crafting/SnakeSkinPlugin.java @@ -34,7 +34,7 @@ public class SnakeSkinPlugin extends UseWithHandler { @Override public boolean handle(final NodeUsageEvent event) { final Player player = event.getPlayer(); - new SkillDialogueHandler(player, SkillDialogue.FIVE_OPTION, (Object[]) getSkins()) { + new SkillDialogueHandler(player, SkillDialogue.FIVE_OPTION, getSkins()) { @Override public void create(final int amount, int index) { diff --git a/Server/src/main/content/global/skill/crafting/lightsources/LightSourceExtinguisher.kt b/Server/src/main/content/global/skill/crafting/lightsources/LightSourceExtinguisher.kt index 357b69ca8..0df72d1f2 100644 --- a/Server/src/main/content/global/skill/crafting/lightsources/LightSourceExtinguisher.kt +++ b/Server/src/main/content/global/skill/crafting/lightsources/LightSourceExtinguisher.kt @@ -1,12 +1,15 @@ package content.global.skill.crafting.lightsources +import content.data.LightSource import core.api.log +import core.api.* import core.cache.def.impl.ItemDefinition import core.game.container.Container import core.game.interaction.OptionHandler import core.game.node.Node import core.game.node.entity.player.Player import core.game.node.item.Item +import core.game.world.map.Location import core.tools.SystemLogger import core.plugin.Initializable import core.plugin.Plugin @@ -32,6 +35,12 @@ class LightSourceExtinguisher : OptionHandler(){ lightSource ?: return false.also { log(this::class.java, Log.WARN, "UNHANDLED EXTINGUISH OPTION: ID = ${node.id}") } + // For Temple of Ikov - if you are in the dark basement, do not let light source extinguish. + if(player.location.isInRegion(10648)) { + sendMessage(player, "Extinguishing the " + LightSource.getActiveLightSource(player).product.name.lowercase() + " would leave you without a light source.") + return true + } + player.inventory.replace(node.asItem(), Item(lightSource.fullID)) return true } diff --git a/Server/src/main/content/global/skill/crafting/lightsources/LightSourceLighter.kt b/Server/src/main/content/global/skill/crafting/lightsources/LightSourceLighter.kt index 81a1a98cc..d636ee588 100644 --- a/Server/src/main/content/global/skill/crafting/lightsources/LightSourceLighter.kt +++ b/Server/src/main/content/global/skill/crafting/lightsources/LightSourceLighter.kt @@ -1,12 +1,16 @@ package content.global.skill.crafting.lightsources +import core.api.* +import core.api.teleport import core.game.container.Container import core.game.event.LitLightSourceEvent import core.game.interaction.NodeUsageEvent import core.game.interaction.UseWithHandler +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.Location import core.plugin.Initializable import core.plugin.Plugin @@ -72,6 +76,14 @@ class LightSourceLighter : UseWithHandler(590,36,38){ return true } + // For Temple of Ikov - if you are in the dark basement and light a light source, switch to the light basement. + // For the listener that covers the firemaking cape perk, see content.global.skill.skillcapeperks.SkillcapePerks.kt + if(event.player.location.isInRegion(10648) && event.player.location.withinDistance(Location(2639,9738,0), 8)) { + teleport(event.player, Location.create(event.player.getLocation().getX(), event.player.getLocation().getY() + 23, event.player.getLocation().getZ())) + closeDialogue(event.player) + // Dark basement is region 10648, min 2639 9738 0, max 2643 9744 0. Add 23 to the Y loc to tele to light basement + } + if(!light(event.player,used,lightSource)){ event.player.sendMessage("You need a Firemaking level of at least ${lightSource.levelRequired} to light this.") } diff --git a/Server/src/main/content/global/skill/crafting/silver/SilverProduct.kt b/Server/src/main/content/global/skill/crafting/silver/SilverProduct.kt index 83becbf16..98bcc014e 100644 --- a/Server/src/main/content/global/skill/crafting/silver/SilverProduct.kt +++ b/Server/src/main/content/global/skill/crafting/silver/SilverProduct.kt @@ -27,7 +27,7 @@ enum class SilverProduct( val strungId: Int ) { HOLY(BUTTON_UNBLESSED, Items.HOLY_MOULD_1599, Items.UNSTRUNG_SYMBOL_1714, 1, 16, 50.0, Items.UNBLESSED_SYMBOL_1716), - UNHOLY(BUTTON_UNHOLY, Items.UNHOLY_MOULD_1594, Items.UNSTRUNG_EMBLEM_1720, 1, 17, 50.0, Items.UNHOLY_SYMBOL_1724), + UNHOLY(BUTTON_UNHOLY, Items.UNHOLY_MOULD_1594, Items.UNSTRUNG_EMBLEM_1720, 1, 17, 50.0, Items.UNPOWERED_SYMBOL_1722), SICKLE(BUTTON_SICKLE, Items.SICKLE_MOULD_2976, Items.SILVER_SICKLE_2961, 1, 18, 50.0, -1), TIARA(BUTTON_TIARA, Items.TIARA_MOULD_5523, Items.TIARA_5525, 1, 23, 52.5, -1), SILVTHRIL_CHAIN(BUTTON_SILVTHRIL_CHAIN, Items.CHAIN_LINK_MOULD_13153, Items.SILVTHRIL_CHAIN_13154, 1, 47, 100.0, -1), diff --git a/Server/src/main/content/global/skill/fletching/AchievementDiaryAttributeKeys.kt b/Server/src/main/content/global/skill/fletching/AchievementDiaryAttributeKeys.kt new file mode 100644 index 000000000..8e3d7fe33 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/AchievementDiaryAttributeKeys.kt @@ -0,0 +1,8 @@ +package content.global.skill.fletching + +object AchievementDiaryAttributeKeys { + /** + * This attribute signifies that the fletch of the magic shortbow is complete which is the first stage of getting the achievement + */ + const val FLETCHED_UNSTRUNG_MAGIC_SHORTBOW = "diary:seers:fletch-magic-short-bow" +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/Feathers.kt b/Server/src/main/content/global/skill/fletching/Feathers.kt new file mode 100644 index 000000000..f76b00a72 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/Feathers.kt @@ -0,0 +1,16 @@ +package content.global.skill.fletching + +import org.rs09.consts.Items + +object Feathers { + val all = intArrayOf( + Items.FEATHER_314, + Items.STRIPY_FEATHER_10087, + Items.RED_FEATHER_10088, + Items.BLUE_FEATHER_10089, + Items.YELLOW_FEATHER_10090, + Items.ORANGE_FEATHER_10091 + ) + + const val STANDARD = Items.FEATHER_314 +} diff --git a/Server/src/main/content/global/skill/fletching/Fletching.java b/Server/src/main/content/global/skill/fletching/Fletching.java deleted file mode 100644 index 3a6c6906e..000000000 --- a/Server/src/main/content/global/skill/fletching/Fletching.java +++ /dev/null @@ -1,355 +0,0 @@ -package content.global.skill.fletching; - -import core.game.node.item.Item; -import core.game.world.update.flag.context.Animation; - -import java.util.HashMap; -public class Fletching { - public static HashMaplogMap = new HashMap<>(); - public static HashMap boltMap = new HashMap<>(); - public static HashMap dartMap = new HashMap<>(); - public static HashMap arrowHeadMap = new HashMap<>(); - public static HashMap gemMap = new HashMap<>(); - public static HashMap tipMap = new HashMap<>(); - public static HashMap stringMap = new HashMap<>(); - public static HashMap limbMap = new HashMap<>(); - static{ - Items[] itemsArray = Items.values(); - int thisLength = itemsArray.length; - for(int x = 0; x < thisLength; x++){ - Items item = itemsArray[x]; - logMap.putIfAbsent(item.id, item.items); - } - Bolts[] boltArray = Bolts.values(); - thisLength = boltArray.length; - for(int x = 0; x < thisLength; x++){ - Bolts bolt = boltArray[x]; - boltMap.putIfAbsent(bolt.unfinished,bolt); - } - Darts[] dartArray = Darts.values(); - thisLength = dartArray.length; - for(int x = 0; x < thisLength; x++){ - Darts dart = dartArray[x]; - dartMap.putIfAbsent(dart.unfinished,dart); - } - ArrowHeads[] ahArray = ArrowHeads.values(); - thisLength = ahArray.length; - for(int x = 0; x < thisLength; x++){ - ArrowHeads arrowhead = ahArray[x]; - arrowHeadMap.putIfAbsent(arrowhead.unfinished,arrowhead); - } - GemBolts[] gbArray = GemBolts.values(); - thisLength = gbArray.length; - for(int x = 0; x < thisLength; x++){ - GemBolts gem = gbArray[x]; - gemMap.putIfAbsent(gem.gem,gem); - tipMap.putIfAbsent(gem.tip,gem); - } - String[] stringArray = String.values(); - thisLength = stringArray.length; - for(int x = 0; x < thisLength; x++){ - String bow = stringArray[x]; - stringMap.putIfAbsent(bow.unfinished,bow); - } - Limb[] limbsArray = Limb.values(); - thisLength = limbsArray.length; - for(int x = 0; x < thisLength; x++){ - Limb limb = limbsArray[x]; - limbMap.putIfAbsent(limb.stock, limb); - } - } - public static FletchingItems[] getEntries(int id){ - return logMap.get(id); - } - public static boolean isLog(int id){ - return logMap.get(id) != null; - } - public static boolean isBolt(int id){ - return boltMap.get(id) != null; - } - public static boolean isDart(int id){ - return dartMap.get(id) != null; - } - public static boolean isArrowHead(int id){ - return arrowHeadMap.get(id) != null; - } - public static boolean isGemTip(int id){ - return tipMap.get(id) != null; - } - public static Item[] getItems(int id){ - FletchingItems[] entry = getEntries(id); - Item items[] = {}; - switch(entry.length){ - case 1: - items = new Item[] {new Item(entry[0].id)}; - break; - case 2: - items = new Item[] {new Item(entry[0].id), new Item(entry[1].id)}; - break; - case 3: - items = new Item[] {new Item(entry[0].id), new Item(entry[1].id), new Item(entry[2].id)}; - break; - case 4: - items = new Item[] {new Item(entry[0].id), new Item(entry[1].id), new Item(entry[2].id), new Item(entry[3].id)}; - break; - } - return items; - } - public enum Limb { - WOODEN_STOCK(9440,9420,9454,9, 12, new Animation(4436)), - OAK_STOCK(9442,9422,9456,24, 32, new Animation(4437)), - WILLOW_STOCK(9444,9423,9457,39, 44, new Animation(4438)), - TEAK_STOCK(9446,9425,9459,46, 54, new Animation(4439)), - MAPLE_STOCK(9448,9427,9461,54, 64, new Animation(4440)), - MAHOGANY_STOCK(9450,9429,9463,61, 82, new Animation(4441)), - YEW_STOCK(9452,9431,9465,69, 100, new Animation(4442)); - - - public int stock, limb, product,level; - public double experience; - public Animation animation; - - Limb(int stock, int limb, int product, int level, double experience, Animation animation) { - this.stock = stock; - this.limb = limb; - this.product = product; - this.level = level; - this.experience = experience; - this.animation = animation; - } - } - public enum String{ - //Bows - SHORT_BOW((byte) 1,50,841,5, 5, new Animation(6678)), - LONG_BOW((byte) 1,48,839,10, 10, new Animation(6684)), - OAK_SHORTBOW((byte) 1,54,843,20, 16.5, new Animation(6679)), - OAK_LONGBOW((byte) 1,56,845,25, 25, new Animation(6685)), - WILLOW_SHORTBOW((byte) 1,60,849,35, 33.3, new Animation(6680)), - WILLOW_LONGBOW((byte) 1,58,847,40, 41.5, new Animation(6686)), - MAPLE_SHORTBOW((byte) 1,64,853,50, 50, new Animation(6681)), - MAPLE_LONGBOW((byte) 1,62,851,55, 58.3, new Animation(6687)), - YEW_SHORTBOW((byte) 1,68,857,65, 67.5, new Animation(6682)), - YEW_LONGBOW((byte) 1,66,855,70, 75, new Animation(6688)), - MAGIC_SHORTBOW((byte) 1,72,861,80, 83.3, new Animation(6683)), - MAGIC_LONGBOW((byte) 1,70,859,85, 91.5, new Animation(6689)), - OGRE_COMP_BOW((byte) 1,4825,4827,30, 45, new Animation(-1)), - - //crossbows - BRONZE_CBOW((byte) 2,9454,9174,9, 6, new Animation(6671)), - BLURITE_CBOW((byte) 2,9456,9176,24, 16, new Animation(6672)), - IRON_CBOW((byte) 2,9457,9177,39, 22, new Animation(6673)), - STEEL_CBOW((byte) 2,9459,9179,46, 27, new Animation(6674)), - MITHIRIL_CBOW((byte) 2,9461,9181,54, 32, new Animation(6675)), - ADAMANT_CBOW((byte) 2,9463,9183,61, 41, new Animation(6676)), - RUNITE_CBOW((byte) 2,9465,9185,69, 50, new Animation(6677)); - - - public int unfinished,product,string,level; - public final double experience; - public final Animation animation; - String(byte indicator, final int unfinished, final int product, final int level, final double experience, final Animation animation) { - this.unfinished = unfinished; - this.product = product; - this.level = level; - this.experience = experience; - this.animation = animation; - switch(indicator & 0xFF){ - case 1: - this.string = org.rs09.consts.Items.BOW_STRING_1777; - break; - case 2: - this.string = org.rs09.consts.Items.CROSSBOW_STRING_9438; - break; - default: - break; - } - } - } - public enum GemBolts { - OPAL(877, org.rs09.consts.Items.OPAL_1609, 45, 879, 11, 1.6), - PEARL(9140, org.rs09.consts.Items.OYSTER_PEARL_411, 46, 880, 41, 3.2), - PEARLS(9140, org.rs09.consts.Items.OYSTER_PEARLS_413, 46, 880, 41, 3.2), - JADE(9139, org.rs09.consts.Items.JADE_1611, 9187, 9335, 26, 2.4), - RED_TOPAZ(9141, org.rs09.consts.Items.RED_TOPAZ_1613, 9188, 9336, 48, 3.9), - SAPPHIRE(9142, org.rs09.consts.Items.SAPPHIRE_1607, 9189, 9337, 56, 4.7), - EMERALD(9142, org.rs09.consts.Items.EMERALD_1605, 9190, 9338, 58, 5.5), - RUBY(9143, org.rs09.consts.Items.RUBY_1603, 9191, 9339, 63, 6.3), - DIAMOND(9143, org.rs09.consts.Items.DIAMOND_1601, 9192, 9340, 65, 7), - DRAGONSTONE(9144, org.rs09.consts.Items.DRAGONSTONE_1615, 9193, 9341, 71, 8.2), - ONYX(9144, org.rs09.consts.Items.ONYX_6573, 9194, 9342, 73, 9.4); - - public int gem,tip,base,product,level; - public double experience; - GemBolts(int base, int gem, int tip, int product, int level, double experience){ - this.gem = gem; - this.tip = tip; - this.base = base; - this.product = product; - this.level = level; - this.experience = experience; - } - - } - public enum ArrowHeads { - BRONZE_ARROW(39, 882, 1, 1.3), - IRON_ARROW(40, 884, 15, 2.5), - STEEL_ARROW(41, 886, 30, 5), - MITHRIL_ARROW(42, 888, 45, 7.5), - ADAMANT_ARROW(43, 890, 60, 10), - RUNE_ARROW(44, 892, 75, 12.5), - DRAGON_ARROW(11237, 11212, 90, 15), - BROAD_ARROW(13278, 4160, 52, 15); - - public int unfinished,finished,level; - public double experience; - ArrowHeads(int unfinished, int finished, int level, double experience){ - this.unfinished = unfinished; - this.finished = finished; - this.level = level; - this.experience = experience; - } - public Item getFinished(){ - return new Item(finished); - } - public Item getUnfinished(){ - return new Item(unfinished); - } - } - public enum Darts{ - BRONZE_DART(819, 806, 1, 1.8), - IRON_DART(820, 807, 22, 3.8), - STEEL_DART(821, 808, 37, 7.5), - MITHRIL_DART(822, 809, 52, 11.2), - ADAMANT_DART(823, 810, 67, 15), - RUNE_DART(824, 811, 81, 18.8), - DRAGON_DART(11232, 11230, 95, 25); - - public int unfinished, finished, level; - public double experience; - Darts(int unfinished, int finished, int level, double experience){ - this.unfinished = unfinished; - this.finished = finished; - this.level = level; - this.experience = experience; - } - public Item getFinished(){ - return new Item(finished); - } - public Item getUnfinished(){ - return new Item(unfinished); - } - } - public enum Bolts{ - BRONZE_BOLT(9375, 877, 9, 0.5), - BLURITE_BOLT(9376, 9139, 24, 1), - IRON_BOLT(9377, 9140, 39, 1.5), - SILVER_BOLT(9382, 9145, 43, 2.5), - STEEL_BOLT(9378, 9141, 46, 3.5), - MITHRIL_BOLT(9379, 9142, 54, 5), - ADAMANTITE_BOLT(9380, 9143, 61, 7), - RUNITE_BOLT(9381, 9144, 69, 10), - BROAD_BOLT(13279, 13280, 55, 3); - - public int unfinished, finished, level; - public double experience; - Bolts(int unfinished, int finished, int level, double experience){ - this.unfinished = unfinished; - this.finished = finished; - this.level = level; - this.experience = experience; - } - public Item getFinished(){ - return new Item(finished); - } - public Item getUnfinished(){ - return new Item(unfinished); - } - } - private enum Items{ - STANDARD(1511,FletchingItems.ARROW_SHAFT, FletchingItems.SHORT_BOW, FletchingItems.LONG_BOW, FletchingItems.WOODEN_STOCK), - ACHEY(2862, FletchingItems.OGRE_ARROW_SHAFT, FletchingItems.OGRE_COMP_BOW), - OAK(1521, FletchingItems.OAK_SHORTBOW, FletchingItems.OAK_LONGBOW, FletchingItems.OAK_STOCK), - WILLOW(1519, FletchingItems.WILLOW_SHORTBOW, FletchingItems.WILLOW_LONGBOW, FletchingItems.WILLOW_STOCK), - MAPLE(1517, FletchingItems.MAPLE_SHORTOW, FletchingItems.MAPLE_LONGBOW, FletchingItems.MAPLE_STOCK), - YEW(1515, FletchingItems.YEW_SHORTBOW, FletchingItems.YEW_LONGBOW, FletchingItems.YEW_STOCK), - MAGIC(1513, FletchingItems.MAGIC_SHORTBOW, FletchingItems.MAGIC_LONGBOW), - TEAK(6333, FletchingItems.TEAK_STOCK), - MAHOGANY(6332, FletchingItems.MAHOGANY_STOCK); - - - FletchingItems[] items; - int id; - Items(int id, FletchingItems item_1, FletchingItems item_2, FletchingItems item_3, FletchingItems item_4){ - items = new FletchingItems[] {item_1, item_2, item_3, item_4}; - this.id = id; - } - Items(int id, FletchingItems item_1, FletchingItems item_2, FletchingItems item_3){ - items = new FletchingItems[] {item_1, item_2, item_3}; - this.id = id; - } - Items(int id, FletchingItems item_1, FletchingItems item_2){ - items = new FletchingItems[] {item_1, item_2}; - this.id = id; - } - Items(int id, FletchingItems item_1){ - items = new FletchingItems[] {item_1}; - this.id = id; - } - } - public enum FletchingItems { - //Standard logs - ARROW_SHAFT(52, 5, 1, 15), - SHORT_BOW(50, 5, 5, 1), - LONG_BOW(48, 10, 10, 1), - WOODEN_STOCK(9440, 6, 9, 1), - - //Achey logs - OGRE_ARROW_SHAFT(2864, 6.4, 5, 4), - OGRE_COMP_BOW(4825, 45, 30, 1), - - //Oak logs - OAK_SHORTBOW(54, 16.5, 20, 1), - OAK_LONGBOW(56,25,25,1), - OAK_STOCK(9442, 16, 24, 1), - - //Willow logs - WILLOW_SHORTBOW(60, 33.3, 35, 1), - WILLOW_LONGBOW(58, 41.5, 40, 1), - WILLOW_STOCK(9444, 22, 39, 1), - - //Maple logs - MAPLE_SHORTOW(64, 50, 50, 1), - MAPLE_LONGBOW(62, 58.3, 55, 1), - MAPLE_STOCK(9448, 32, 54, 1), - - //Yew logs - YEW_SHORTBOW(68, 67.5, 65, 1), - YEW_LONGBOW(66, 75, 70, 1), - YEW_STOCK(9452, 50, 69, 1), - - //Magic logs - MAGIC_SHORTBOW(72, 83.3, 80,1), - MAGIC_LONGBOW(70, 91.5, 85, 1), - - //Teak - TEAK_STOCK(9446, 27, 46,1), - - //Mahogany - MAHOGANY_STOCK(9450, 41.0, 61, 1); - - - int id,level,amount,logId; - double experience; - FletchingItems(int id, double experience, int level, int amount){ - this.id = id; - this.level = level; - this.amount = amount; - this.experience = experience; - } - - public Item getItem(){ - return new Item(id); - } - } - -} diff --git a/Server/src/main/content/global/skill/fletching/FletchingListeners.kt b/Server/src/main/content/global/skill/fletching/FletchingListeners.kt deleted file mode 100644 index 464a0b6ff..000000000 --- a/Server/src/main/content/global/skill/fletching/FletchingListeners.kt +++ /dev/null @@ -1,197 +0,0 @@ -package content.global.skill.fletching - -import content.data.Quests -import content.global.skill.fletching.items.arrow.ArrowHeadPulse -import content.global.skill.fletching.items.arrow.HeadlessArrowPulse -import content.global.skill.fletching.items.arrow.HeadlessOgreArrowPulse -import content.global.skill.fletching.items.bow.StringPulse -import content.global.skill.fletching.items.crossbow.LimbPulse -import core.api.* -import core.game.node.entity.skill.Skills -import core.game.node.item.Item -import core.net.packet.PacketRepository -import core.net.packet.context.ChildPositionContext -import core.net.packet.out.RepositionChild -import org.rs09.consts.Components -import org.rs09.consts.Items -import org.rs09.consts.Items.BLUE_FEATHER_10089 -import org.rs09.consts.Items.FEATHER_314 -import org.rs09.consts.Items.ORANGE_FEATHER_10091 -import org.rs09.consts.Items.RED_FEATHER_10088 -import org.rs09.consts.Items.STRIPY_FEATHER_10087 -import org.rs09.consts.Items.YELLOW_FEATHER_10090 -import core.game.dialogue.SkillDialogueHandler -import core.game.interaction.InteractionListener -import core.game.interaction.IntType -import core.game.node.entity.player.Player - -class FletchingListeners : InteractionListener { - - val LIMBIDs = Fletching.Limb.values().map(Fletching.Limb::limb).toIntArray() - val STOCKIDs = Fletching.Limb.values().map(Fletching.Limb::stock).toIntArray() - val MITHRIL_BOLT = Items.MITHRIL_BOLTS_9142 - val MITH_GRAPPLE_TIP = Items.MITH_GRAPPLE_TIP_9416 - val ROPE = Items.ROPE_954 - val MITH_GRAPPLE = Items.MITH_GRAPPLE_9418 - val ROPE_GRAPPLE = Items.MITH_GRAPPLE_9419 - val ARROW_SHAFT = Items.ARROW_SHAFT_52 - val OGRE_ARROW_SHAFT = Items.OGRE_ARROW_SHAFT_2864 - val FLETCHED_SHAFT = Items.HEADLESS_ARROW_53 - val FLIGHTED_OGRE_ARROW = Items.FLIGHTED_OGRE_ARROW_2865 - val UNFINISHED_ARROWS = Fletching.ArrowHeads.values().map(Fletching.ArrowHeads::unfinished).toIntArray() - val FEATHERS = intArrayOf(FEATHER_314,STRIPY_FEATHER_10087,RED_FEATHER_10088,BLUE_FEATHER_10089,YELLOW_FEATHER_10090,ORANGE_FEATHER_10091) - val UNSTRUNG_BOWS = Fletching.String.values().map(Fletching.String::unfinished).toIntArray() - val STRINGS = intArrayOf(Items.BOW_STRING_1777,Items.CROSSBOW_STRING_9438) - - override fun defineListeners() { - - onUseWith(IntType.ITEM,STRINGS,*UNSTRUNG_BOWS){ player, string, bow -> - val enum = Fletching.stringMap[bow.id] ?: return@onUseWith false - - if (bow.id == Items.UNSTRUNG_COMP_BOW_4825) { - // You shouldn't be able to string a bow - if (getQuestStage(player, Quests.ZOGRE_FLESH_EATERS) < 8) { - player.packetDispatch.sendMessage("You must have started Zogre Flesh Eaters and asked Grish to string this.") - return@onUseWith true - } - } - if(enum.string != string.id){ - player.sendMessage("That's not the right kind of string for this.") - return@onUseWith true - } - val handler: SkillDialogueHandler = - object : SkillDialogueHandler(player, SkillDialogue.ONE_OPTION, Item(enum.product)) { - override fun create(amount: Int, index: Int) { - player.pulseManager.run(StringPulse(player, string.asItem(), enum, amount)) - } - - override fun getAll(index: Int): Int { - return player.inventory.getAmount(string.asItem()) - } - } - handler.open() - PacketRepository.send(RepositionChild::class.java, ChildPositionContext(player, Components.SKILL_MULTI1_309, 2, 215, 10)) - return@onUseWith true - } - - onUseWith(IntType.ITEM,ARROW_SHAFT,*FEATHERS){ player, shaft, feather -> - val handler: SkillDialogueHandler = - object : SkillDialogueHandler(player, SkillDialogue.MAKE_SET_ONE_OPTION, Item(FLETCHED_SHAFT)) { - override fun create(amount: Int, index: Int) { - player.pulseManager.run(HeadlessArrowPulse(player, shaft.asItem(), Item(feather.id), amount)) - } - - override fun getAll(index: Int): Int { - return player.inventory.getAmount(FLETCHED_SHAFT) - } - } - handler.open() - return@onUseWith true - } - - onUseWith(IntType.ITEM,OGRE_ARROW_SHAFT,*FEATHERS){ player, shaft, feather -> - val handler: SkillDialogueHandler = - object : SkillDialogueHandler(player, SkillDialogue.MAKE_SET_ONE_OPTION, Item(FLIGHTED_OGRE_ARROW)) { - override fun create(amount: Int, index: Int) { - player.pulseManager.run(HeadlessOgreArrowPulse(player, shaft.asItem(), Item(feather.id, 4), amount)) - } - - override fun getAll(index: Int): Int { - return player.inventory.getAmount(FLIGHTED_OGRE_ARROW) - } - } - handler.open() - return@onUseWith true - } - - onUseWith(IntType.ITEM,FLETCHED_SHAFT,*UNFINISHED_ARROWS){ player, shaft, unfinished -> - val head = Fletching.arrowHeadMap[unfinished.id] ?: return@onUseWith false - val handler: SkillDialogueHandler = - object : SkillDialogueHandler(player, SkillDialogue.MAKE_SET_ONE_OPTION, head.getFinished()) { - override fun create(amount: Int, index: Int) { - player.pulseManager.run(ArrowHeadPulse(player, shaft.asItem(), head, amount)) - } - - override fun getAll(index: Int): Int { - return player.inventory.getAmount(head.getUnfinished()) - } - } - handler.open() - return@onUseWith true - } - - onUseWith(IntType.ITEM,MITHRIL_BOLT,MITH_GRAPPLE_TIP){ player, bolt, tip -> - if(player.skills.getLevel(Skills.FLETCHING) < 59){ - player.sendMessage("You need a fletching level of 59 to make this.") - return@onUseWith true - } - if(player.inventory.remove(Item(MITHRIL_BOLT,1),tip.asItem())){ - player.inventory.add(Item(MITH_GRAPPLE)) - } - return@onUseWith true - } - - onUseWith(IntType.ITEM,ROPE,MITH_GRAPPLE){ player, rope, grapple -> - if(player.skills.getLevel(Skills.FLETCHING) < 59){ - player.sendMessage("You need a fletching level of 59 to make this.") - return@onUseWith true - } - if(player.inventory.remove(rope.asItem(),grapple.asItem())){ - player.inventory.add(Item(ROPE_GRAPPLE)) - } - return@onUseWith true - } - - onUseWith(IntType.ITEM,LIMBIDs,*STOCKIDs){ player, limb, stock -> - val limbEnum = Fletching.limbMap[stock.id] ?: return@onUseWith false - if(limbEnum.limb != limb.id){ - player.sendMessage("That's not the right limb to attach to that stock.") - return@onUseWith true - } - val handler: SkillDialogueHandler = object : SkillDialogueHandler(player, SkillDialogue.ONE_OPTION, Item(limbEnum.product)){ - override fun create(amount: Int, index: Int) { - player.pulseManager.run(LimbPulse(player, stock.asItem(), limbEnum, amount)) - } - - override fun getAll(index: Int): Int { - return player.inventory.getAmount(stock.asItem()) - } - } - handler.open() - PacketRepository.send(RepositionChild::class.java, ChildPositionContext(player, Components.SKILL_MULTI1_309, 2, 210, 10)) - return@onUseWith true - } - - /** - * (Long) Kebbit bolts don't need feathers and go 6 at a time so use their own interaction - */ - fun makeKebbitBolt(player : Player, ingredient : Item) : Boolean{ - val longBolts = when(ingredient.id){ - Items.KEBBIT_SPIKE_10105 -> false - Items.LONG_KEBBIT_SPIKE_10107 -> true - else -> return false - } - val level = if(longBolts) 42 else 26 - if (getDynLevel(player, Skills.FLETCHING) < level){ - sendMessage(player, "You need a fletching level of $level to create ${if (longBolts) "long " else ""}kebbit bolts.") - return true - } - val finalProduct = if(longBolts) Items.LONG_KEBBIT_BOLTS_10159 else Items.KEBBIT_BOLTS_10158 - val xp = if(longBolts) 47.7 else 28.6 // source https://runescape.wiki/w/Fletching?oldid=1069981#Bolts_2 - if(removeItem(player, ingredient.id)){ - addItem(player, finalProduct, 6) - player.skills.addExperience(Skills.FLETCHING, xp) - animate(player, 885) - } - return true - } - onUseWith(IntType.ITEM, Items.CHISEL_1755, Items.KEBBIT_SPIKE_10105) { player, used, with -> - return@onUseWith makeKebbitBolt(player, with as Item) - } - - onUseWith(IntType.ITEM, Items.CHISEL_1755, Items.LONG_KEBBIT_SPIKE_10107) { player, used, with -> - return@onUseWith makeKebbitBolt(player, with as Item) - } - } - -} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/FletchingPlugin.java b/Server/src/main/content/global/skill/fletching/FletchingPlugin.java deleted file mode 100644 index 3ec122866..000000000 --- a/Server/src/main/content/global/skill/fletching/FletchingPlugin.java +++ /dev/null @@ -1,131 +0,0 @@ -package content.global.skill.fletching; - -import content.global.skill.fletching.items.bolts.BoltPulse; -import content.global.skill.fletching.items.darts.DartPulse; -import org.rs09.consts.Items; -import core.game.dialogue.SkillDialogueHandler; -import core.game.dialogue.SkillDialogueHandler.SkillDialogue; -import core.game.interaction.NodeUsageEvent; -import core.game.interaction.UseWithHandler; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; -import core.net.packet.PacketRepository; -import core.net.packet.context.ChildPositionContext; -import core.net.packet.out.RepositionChild; -import core.plugin.Initializable; -import core.plugin.Plugin; - -/** - * Represents the plugin used to open the fletching dialogue. - * @author Ceikry - */ -@Initializable -public class FletchingPlugin extends UseWithHandler { - - public FletchingPlugin() { - super(819,820,821,822,823,824,11232,9375,9376,9377,9382,9378,9379,9380,9381,13279,1511,1521,1519,1517,1515,1513,2862,6332,6333, Items.MAHOGANY_LOGS_6332); - } - - @Override - public Plugin newInstance(Object arg) throws Throwable { - // Knife - addHandler(946, ITEM_TYPE, this); - - // Feathers plus colored feathers - addHandler(314, ITEM_TYPE,this); - addHandler(10087, ITEM_TYPE, this); - addHandler(10088, ITEM_TYPE, this); - addHandler(10089, ITEM_TYPE, this); - addHandler(10090, ITEM_TYPE, this); - addHandler(10091, ITEM_TYPE, this); - - return this; - } - - @Override - public boolean handle(final NodeUsageEvent event) { - final Player player = event.getPlayer(); - - //handle darts - if(Fletching.isDart(event.getUsedItem().getId())){ - final Fletching.Darts dart = Fletching.dartMap.get(event.getUsedItem().getId()); - SkillDialogueHandler handler = new SkillDialogueHandler(player, SkillDialogue.MAKE_SET_ONE_OPTION, dart.getFinished()) { - @Override - public void create(final int amount, int index) { - player.getPulseManager().run(new DartPulse(player, event.getUsedItem(), dart, amount)); - } - @Override - public int getAll(int index) { - return player.getInventory().getAmount(event.getUsedItem()); - } - }; - handler.open(); - return true; - } - - //handle bolts - if(Fletching.isBolt(event.getUsedItem().getId()) || Fletching.isBolt(event.getUsedWith().getId())){ - // figure out which of the used items is a bolt, and which is potentially a feather - final Fletching.Bolts bolt - = Fletching.isBolt(event.getUsedItem().getId()) - ? Fletching.boltMap.get(event.getUsedItem().getId()) - : Fletching.boltMap.get(event.getUsedWith().getId()); - final int featherId - = Fletching.isBolt(event.getUsedItem().getId()) - ? event.getUsedWith().getId() - : event.getUsedItem().getId(); - final boolean hasFeather = (featherId == 314 || (featherId >= 10087 && featherId <= 10091)); - - if (hasFeather) { - SkillDialogueHandler handler = new SkillDialogueHandler(player, SkillDialogue.MAKE_SET_ONE_OPTION, bolt.getFinished()) { - @Override - public void create(final int amount, int index) { - player.getPulseManager().run(new BoltPulse(player, event.getUsedItem(), bolt, new Item(featherId), amount)); - } - @Override - public int getAll(int index) { - return player.getInventory().getAmount(event.getUsedItem()); - } - }; - handler.open(); - return true; - } - return false; - } - - //handle logs - if(Fletching.isLog(event.getUsedItem().getId()) && event.getUsedWith().getId() == 946) { - final Item log = event.getUsedItem(); - Item[] items = Fletching.getItems(log.getId()); - SkillDialogue dialLength = SkillDialogue.ONE_OPTION; - switch (items.length) { - case 2: - dialLength = SkillDialogue.TWO_OPTION; - break; - case 3: - dialLength = SkillDialogue.THREE_OPTION; - break; - case 4: - dialLength = SkillDialogue.FOUR_OPTION; - break; - } - SkillDialogueHandler handler = new SkillDialogueHandler(player, dialLength, items) { - - @Override - public void create(final int amount, int index) { - final Fletching.FletchingItems item = Fletching.getEntries(log.getId())[index]; - player.getPulseManager().run(new FletchingPulse(player, log, amount, item)); - } - - @Override - public int getAll(int index) { - return player.getInventory().getAmount(log); - } - - }; - handler.open(); - return true; - } - return false; - } -} diff --git a/Server/src/main/content/global/skill/fletching/FletchingPulse.java b/Server/src/main/content/global/skill/fletching/FletchingPulse.java deleted file mode 100644 index b02e40572..000000000 --- a/Server/src/main/content/global/skill/fletching/FletchingPulse.java +++ /dev/null @@ -1,145 +0,0 @@ -package content.global.skill.fletching; - -import core.tools.RandomFunction; -import core.game.node.entity.player.link.diary.DiaryType; -import core.game.world.map.zone.ZoneBorders; -import core.game.node.entity.skill.SkillPulse; -import core.game.node.entity.skill.Skills; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; -import core.game.world.update.flag.context.Animation; -import core.tools.StringUtils; -import content.data.Quests; - -/** - * fletching skill pulse - * @author ceik - */ -public final class FletchingPulse extends SkillPulse { - - /** - * Seers bank zone borders for the diary task - */ - private static final ZoneBorders bankZone = new ZoneBorders(2721,3493,2730,3487); - - /** - * Represents the animation used in this generic pulse. - */ - private static final Animation ANIMATION = new Animation(1248); - - /** - * Represents the item we are fletching. - */ - private Fletching.FletchingItems fletch; - - /** - * Represents the amount to fletch. - */ - private int amount = 0; - - /** - * Represents the amount to arrows fletched (for ogre arrow shafts which is a random number from 2-6). - */ - private int finalAmount = 0; - - /** - * Constructs a new {@code FletchingPulse.java} {@code Object}. - * @param player - * @param node - */ - public FletchingPulse(final Player player, final Item node, final int amount, final Fletching.FletchingItems fletch) { - super(player, node); - this.amount = amount; - this.fletch = fletch; - } - - @Override - public boolean checkRequirements() { - if (player.getSkills().getLevel(Skills.FLETCHING) < fletch.level) { - player.getDialogueInterpreter().sendDialogue("You need a Fletching skill of " + fletch.level + " or above to make " + (StringUtils.isPlusN(fletch.getItem().getName().replace("(u)", "").trim()) ? "an" : "a") + " " + fletch.getItem().getName().replace("(u)", "").trim()); - return false; - } - if (amount > player.getInventory().getAmount(node)) { - amount = player.getInventory().getAmount(node); - } - if (fletch == Fletching.FletchingItems.OGRE_ARROW_SHAFT) { - if (player.getQuestRepository().getQuest(Quests.BIG_CHOMPY_BIRD_HUNTING).getStage(player) == 0) { - player.getPacketDispatch().sendMessage("You must have started Big Chompy Bird Hunting to make those."); - return false; - } - } - if (fletch == Fletching.FletchingItems.OGRE_COMP_BOW) { - // Technically, this isn't supposed to show up till you've asked Grish. - if (player.getQuestRepository().getQuest(Quests.ZOGRE_FLESH_EATERS).getStage(player) < 8) { - player.getPacketDispatch().sendMessage("You must have started Zogre Flesh Eaters and asked Grish to make this."); - return false; - } - if (!player.getInventory().contains(2859, 1)) { - player.getPacketDispatch().sendMessage("You need to have Wolf Bones in order to make this."); - return false; - } - } - return true; - } - - @Override - public void animate() { - player.animate(ANIMATION); - } - - @Override - public boolean reward() { - if(bankZone.insideBorder(player) && fletch == Fletching.FletchingItems.MAGIC_SHORTBOW) { - player.getAchievementDiaryManager().finishTask(player, DiaryType.SEERS_VILLAGE, 2, 2); - } - if (getDelay() == 1) { - super.setDelay(4); - return false; - } - if (player.getInventory().remove(node)) { - final Item item = new Item(fletch.id,fletch.amount); - if ( fletch == Fletching.FletchingItems.OGRE_ARROW_SHAFT ) { - // The amount of shafts given is random; between two and six will be made. - finalAmount = RandomFunction.random(2,6); - item.setAmount(finalAmount); - } - if ( fletch == Fletching.FletchingItems.OGRE_COMP_BOW ) { - if (!player.getInventory().contains(2859, 1)) { - return false; - } else { - player.getInventory().remove(new Item(2859)); - } - } - player.getInventory().add(item); - player.getSkills().addExperience(Skills.FLETCHING, fletch.experience, true); - String message = getMessage(); - player.getPacketDispatch().sendMessage(message); - - if (fletch.id == Fletching.FletchingItems.MAGIC_SHORTBOW.id - && (new ZoneBorders(2721, 3489, 2724, 3493, 0).insideBorder(player) - || new ZoneBorders(2727, 3487, 2730, 3490, 0).insideBorder(player)) - && !player.getAchievementDiaryManager().hasCompletedTask(DiaryType.SEERS_VILLAGE, 2, 2)) { - player.setAttribute("/save:diary:seers:fletch-magic-short-bow", true); - } - } else { - return true; - } - amount--; - return amount == 0; - } - - /** - * Method used to get the message of the fletch. - * @return the message. - */ - public String getMessage() { - switch (fletch) { - case ARROW_SHAFT: - return "You carefully cut the wood into 15 arrow shafts."; - case OGRE_ARROW_SHAFT: - return "You carefully cut the wood into " + finalAmount + " arrow shafts."; - default: - return "You carefully cut the wood into " + (StringUtils.isPlusN(fletch.getItem().getName()) ? "an" : "a") + " " + fletch.getItem().getName().replace("(u)", "").trim() + "."; - } - } -} diff --git a/Server/src/main/content/global/skill/fletching/GemBoltListener.kt b/Server/src/main/content/global/skill/fletching/GemBoltListener.kt deleted file mode 100644 index 94c09c65b..000000000 --- a/Server/src/main/content/global/skill/fletching/GemBoltListener.kt +++ /dev/null @@ -1,69 +0,0 @@ -package content.global.skill.fletching - -import content.global.skill.fletching.Fletching.GemBolts -import content.global.skill.fletching.items.gem.GemBoltCutPulse -import content.global.skill.fletching.items.gem.GemBoltPulse -import core.api.amountInInventory -import core.game.dialogue.SkillDialogueHandler -import core.game.interaction.IntType -import core.game.interaction.InteractionListener -import core.game.node.item.Item -import core.net.packet.PacketRepository -import core.net.packet.context.ChildPositionContext -import core.net.packet.out.RepositionChild -import org.rs09.consts.Items -import kotlin.math.min - -class GemBoltListener : InteractionListener { - val gems = intArrayOf( - Items.OYSTER_PEARL_411, - Items.OYSTER_PEARLS_413, - Items.OPAL_1609, - Items.JADE_1611, - Items.RED_TOPAZ_1613, - Items.SAPPHIRE_1607, - Items.EMERALD_1605, - Items.RUBY_1603, - Items.DIAMOND_1601, - Items.DRAGONSTONE_1615, - Items.ONYX_6573 - ) - val boltBases = GemBolts.values().map { it.base }.toIntArray() - val boltTips = GemBolts.values().map { it.tip }.toIntArray() - - override fun defineListeners() { - onUseWith(IntType.ITEM, Items.CHISEL_1755, *gems) { player, used, with -> - val gem = Fletching.gemMap[with.id] ?: return@onUseWith true - - object : SkillDialogueHandler(player, SkillDialogue.ONE_OPTION, Item(gem.gem)) { - override fun create(amount: Int, index: Int) { - player.pulseManager.run(GemBoltCutPulse(player, used as? Item, gem, amount)) - } - - override fun getAll(index: Int): Int { - return player.inventory.getAmount(gem.gem) - } - }.open() - return@onUseWith true - } - - onUseWith(IntType.ITEM, boltBases, *boltTips) {player, used, with -> - val bolt = Fletching.tipMap[with.id] ?: return@onUseWith true - if (used.id != bolt.base || with.id != bolt.tip) return@onUseWith true - - - val handler: SkillDialogueHandler = - object : SkillDialogueHandler(player, SkillDialogue.MAKE_SET_ONE_OPTION, Item(bolt.product)) { - override fun create(amount: Int, index: Int) { - player.pulseManager.run(GemBoltPulse(player, used as? Item, bolt, amount)) - } - - override fun getAll(index: Int): Int { - return min(amountInInventory(player, used.id), amountInInventory(player, with.id)) - } - } - handler.open() - return@onUseWith true - } - } -} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/Zones.kt b/Server/src/main/content/global/skill/fletching/Zones.kt new file mode 100644 index 000000000..8c10f0c4f --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/Zones.kt @@ -0,0 +1,21 @@ +package content.global.skill.fletching + +import core.game.node.Node +import core.game.world.map.zone.ZoneBorders + +object Zones { + val seersMagicShortbowAchievementZones = arrayOf( + ZoneBorders(2721, 3489, 2724, 3493, 0), + ZoneBorders(2727, 3487, 2730, 3490, 0), + ZoneBorders(2721, 3493, 2730, 3487) + ) + + fun inAnyZone(node: Node, zones: Array): Boolean { + for (zone in zones) { + if (zone.insideBorder(node)) { + return true + } + } + return false + } +} diff --git a/Server/src/main/content/global/skill/fletching/arrow/ArrowCraftInfo.kt b/Server/src/main/content/global/skill/fletching/arrow/ArrowCraftInfo.kt new file mode 100644 index 000000000..087c688a9 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/arrow/ArrowCraftInfo.kt @@ -0,0 +1,24 @@ +package content.global.skill.fletching.arrow + +import org.rs09.consts.Items + +enum class ArrowCraftInfo(val tipItemId: Int, val arrowItemId: Int, val level: Int, val experience: Double) { + BRONZE_ARROW(Items.BRONZE_ARROWTIPS_39, Items.BRONZE_ARROW_882, 1, 1.3), + IRON_ARROW(Items.IRON_ARROWTIPS_40, Items.IRON_ARROW_884, 15, 2.5), + STEEL_ARROW(Items.STEEL_ARROWTIPS_41, Items.STEEL_ARROW_886, 30, 5.0), + MITHRIL_ARROW(Items.MITHRIL_ARROWTIPS_42, Items.MITHRIL_ARROW_888, 45, 7.5), + ADAMANT_ARROW(Items.ADAMANT_ARROWTIPS_43, Items.ADAMANT_ARROW_890, 60, 10.0), + RUNE_ARROW(Items.RUNE_ARROWTIPS_44, Items.RUNE_ARROW_892, 75, 12.5), + DRAGON_ARROW(Items.DRAGON_ARROWTIPS_11237,Items.DRAGON_ARROW_11212, 90, 15.0), + BROAD_ARROW(Items.BROAD_ARROW_HEADS_13278, Items.BROAD_ARROW_4160, 52, 15.0); + + companion object { + private val arrowCraftInfoByTipId = values().associateBy { it.tipItemId } + + val arrowTipIds: IntArray = values().map { arrowCraftInfo: ArrowCraftInfo -> arrowCraftInfo.tipItemId }.toIntArray() + + fun fromTipId(tipId: Int) : ArrowCraftInfo? { + return arrowCraftInfoByTipId[tipId] + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/arrow/ArrowListeners.kt b/Server/src/main/content/global/skill/fletching/arrow/ArrowListeners.kt new file mode 100644 index 000000000..2c061b3cb --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/arrow/ArrowListeners.kt @@ -0,0 +1,112 @@ +package content.global.skill.fletching.arrow + +import content.global.skill.fletching.Feathers +import core.api.* +import core.game.dialogue.SkillDialogueHandler +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import org.rs09.consts.Items + +@Suppress("unused") // Reflectively loaded +class ArrowListeners : InteractionListener { + private val arrowShaft = Items.ARROW_SHAFT_52 + val headlessArrow = Items.HEADLESS_ARROW_53 + + companion object { + const val FLIGHTED_OGRE_ARROW_LEVEL = 5 + fun sendLevelCheckFailDialog(player: Player, level: Int) { + sendDialogue( + player, + "You need a fletching level of $level to do this." + ) + } + } + + override fun defineListeners() { + onUseWith(IntType.ITEM, arrowShaft, *Feathers.all) { player, shaft, feather -> + val handler: SkillDialogueHandler = + object : SkillDialogueHandler(player, SkillDialogue.MAKE_SET_ONE_OPTION, Item(headlessArrow)) { + override fun create(amount: Int, index: Int) { + if (!hasSpaceFor(player, Item(headlessArrow))) { + sendDialogue(player, "You do not have enough inventory space.") + return + } + HeadlessArrowCraftScript(player, feather.id, amount).invoke() + } + + override fun getAll(index: Int): Int { + return amountInInventory(player, headlessArrow) + } + } + handler.open() + return@onUseWith true + } + + onUseWith(IntType.ITEM, headlessArrow, *ArrowCraftInfo.arrowTipIds) { player, headlessArrow, arrowTip -> + val arrowCraftInfo = ArrowCraftInfo.fromTipId(arrowTip.id) ?: return@onUseWith false + val handler: SkillDialogueHandler = + object : SkillDialogueHandler(player, SkillDialogue.MAKE_SET_ONE_OPTION, Item(arrowCraftInfo.arrowItemId)) { + override fun create(amount: Int, index: Int) { + if (!playerMeetsInitialRequirements()) return + TippedArrowCraftScript(player, arrowCraftInfo, amount).invoke() + } + + private fun playerMeetsInitialRequirements(): Boolean { + if (arrowCraftInfo == ArrowCraftInfo.BROAD_ARROW && !getSlayerFlags(player).isBroadsUnlocked()) { + sendDialogue(player, "You need to unlock the ability to create broad arrows.") + return false + } + + if (getDynLevel(player, Skills.FLETCHING) < arrowCraftInfo.level) { + sendLevelCheckFailDialog(player, arrowCraftInfo.level) + return false + } + + if (!hasSpaceFor(player, Item(arrowCraftInfo.arrowItemId))) { + sendDialogue(player, "You do not have enough inventory space.") + return false + } + return true + } + + override fun getAll(index: Int): Int { + return amountInInventory(player, arrowTip.id) + } + } + handler.open() + return@onUseWith true + } + + onUseWith(IntType.ITEM, Items.OGRE_ARROW_SHAFT_2864, *Feathers.all) { player, ogreArrowShaft, feather -> + val handler: SkillDialogueHandler = + object : SkillDialogueHandler(player, SkillDialogue.MAKE_SET_ONE_OPTION, Item(Items.FLIGHTED_OGRE_ARROW_2865)) { + override fun create(amount: Int, index: Int) { + if (!playerMeetsInitialRequirements()) return + FlightedOgreArrowCraftScript(player, feather.id, amount).invoke() + } + + private fun playerMeetsInitialRequirements(): Boolean { + if (getDynLevel(player, Skills.FLETCHING) < FLIGHTED_OGRE_ARROW_LEVEL) { + sendLevelCheckFailDialog(player, FLIGHTED_OGRE_ARROW_LEVEL) + return false + } + + if (!hasSpaceFor(player, Item(Items.FLIGHTED_OGRE_ARROW_2865))) { + sendDialogue(player, "You do not have enough inventory space.") + return false + } + return true + } + + override fun getAll(index: Int): Int { + return amountInInventory(player, Items.OGRE_ARROW_SHAFT_2864) + } + } + handler.open() + return@onUseWith true + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/arrow/FlightedOgreArrowCraftScript.kt b/Server/src/main/content/global/skill/fletching/arrow/FlightedOgreArrowCraftScript.kt new file mode 100644 index 000000000..126b72aee --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/arrow/FlightedOgreArrowCraftScript.kt @@ -0,0 +1,88 @@ +package content.global.skill.fletching.arrow + +import core.api.* +import core.game.interaction.Clocks +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import org.rs09.consts.Items +import kotlin.math.min + +/** + * Defines the queueScript for creating a flighted ogre arrow + * @author 'Vexia + * @param player the player. + * @param idOfFeatherUsed the feather to attach to the shaft. + * @param sets the amount of sets to complete. + */ +class FlightedOgreArrowCraftScript( + private val player: Player, + private val idOfFeatherUsed: Int, + private val sets: Int +) { + + private val flightedOgreArrow = Items.FLIGHTED_OGRE_ARROW_2865 + private val ogreArrowShaft = Items.OGRE_ARROW_SHAFT_2864 + private val maximumFlightedOgreArrowsCraftableInOneStage = 6 + + private val feathersPerArrow = 4 + + private val initialDelay = 1 + private val subsequentDelay = 3 + + private fun getAmountToCraftForThisStage(shaftsInInventory: Int, feathersInInventory: Int): Int { + val limitOfCraftableArrowsDueToFeathers = feathersInInventory / feathersPerArrow + + val totalCraftableArrows = min(shaftsInInventory, limitOfCraftableArrowsDueToFeathers) + return if (totalCraftableArrows > maximumFlightedOgreArrowsCraftableInOneStage) { + maximumFlightedOgreArrowsCraftableInOneStage + } else { + totalCraftableArrows + } + } + + fun invoke() { + queueScript(player, initialDelay) { stage -> + if (!clockReady(player, Clocks.SKILLING)) return@queueScript keepRunning(player) + + if (getDynLevel(player, Skills.FLETCHING) < ArrowListeners.FLIGHTED_OGRE_ARROW_LEVEL) { + ArrowListeners.sendLevelCheckFailDialog(player, ArrowListeners.FLIGHTED_OGRE_ARROW_LEVEL) + return@queueScript stopExecuting(player) // Check each iteration since dynLevel can change (status effects ending, skill assist session end...) + } + + val featherAmount = amountInInventory(player, idOfFeatherUsed) + val shaftAmount = amountInInventory(player, ogreArrowShaft) + + val amountToCraft = getAmountToCraftForThisStage(shaftAmount, featherAmount) + + if ( + removeItemsIfPlayerHasEnough( + player, + Item(ogreArrowShaft, amountToCraft), + Item(idOfFeatherUsed, amountToCraft * feathersPerArrow) + ) + ) { + when (amountToCraft) { + 1 -> sendMessage(player, "You attach $feathersPerArrow feathers to a shaft.") + else -> { + sendMessage( + player, + "You attach ${amountToCraft * feathersPerArrow} feathers to $amountToCraft arrow shafts." + ) + } + } + + rewardXP(player, Skills.FLETCHING, amountToCraft.toDouble()) + addItem(player, flightedOgreArrow, amountToCraft) + } else { + return@queueScript stopExecuting(player) + } + + if (stage >= sets - 1) { + return@queueScript stopExecuting(player) + } + + return@queueScript delayClock(player, Clocks.SKILLING, subsequentDelay, true) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/arrow/HeadlessArrowCraftScript.kt b/Server/src/main/content/global/skill/fletching/arrow/HeadlessArrowCraftScript.kt new file mode 100644 index 000000000..40ef4c235 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/arrow/HeadlessArrowCraftScript.kt @@ -0,0 +1,76 @@ +package content.global.skill.fletching.arrow + +import core.api.* +import core.game.interaction.Clocks +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import org.rs09.consts.Items +import kotlin.math.min + +/** + * Defines the queueScript for creating a headless arrow + * @author 'Vexia + * @param player the player. + * @param idOfFeatherUsed the feather to attach to the shaft. + * @param sets the amount of sets to complete. + */ +class HeadlessArrowCraftScript( + private val player: Player, + private val idOfFeatherUsed: Int, + private val sets: Int +) { + + private val headlessArrow = Items.HEADLESS_ARROW_53 + private val arrowShaft = Items.ARROW_SHAFT_52 + private val maximumHeadlessArrowsCraftableInOneStage = 15 + + private val initialDelay = 1 + private val subsequentDelay = 3 + + private fun getAmountToCraftForThisStage(shaftsInInventory: Int, feathersInInventory: Int): Int { + val smallerItemAmount = min(shaftsInInventory, feathersInInventory) + return if (smallerItemAmount > maximumHeadlessArrowsCraftableInOneStage) { + maximumHeadlessArrowsCraftableInOneStage + } else { + smallerItemAmount + } + } + + fun invoke() { + queueScript(player, initialDelay) { stage -> + if (!clockReady(player, Clocks.SKILLING)) return@queueScript keepRunning(player) + + val featherAmount = amountInInventory(player, idOfFeatherUsed) + val shaftAmount = amountInInventory(player, arrowShaft) + + val amountToCraft = getAmountToCraftForThisStage(shaftAmount, featherAmount) + + if ( + removeItemsIfPlayerHasEnough( + player, + Item(arrowShaft, amountToCraft), + Item(idOfFeatherUsed, amountToCraft) + ) + ) { + when (amountToCraft) { + 1 -> sendMessage(player, "You attach a feather to a shaft.") + else -> { + sendMessage(player, "You attach feathers to $amountToCraft arrow shafts.") + } + } + + rewardXP(player, Skills.FLETCHING, amountToCraft.toDouble()) + addItem(player, headlessArrow, amountToCraft) + } else { + return@queueScript stopExecuting(player) + } + + if (stage >= sets - 1) { + return@queueScript stopExecuting(player) + } + + return@queueScript delayClock(player, Clocks.SKILLING, subsequentDelay, true) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/arrow/TippedArrowCraftScript.kt b/Server/src/main/content/global/skill/fletching/arrow/TippedArrowCraftScript.kt new file mode 100644 index 000000000..d4249b660 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/arrow/TippedArrowCraftScript.kt @@ -0,0 +1,79 @@ +package content.global.skill.fletching.arrow + +import core.api.* +import core.game.interaction.Clocks +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import org.rs09.consts.Items +import kotlin.math.min + +/** + * Represents the arrow head pulse to complete the headless arrow. + * @author 'Vexia + * @param player the player. + * @param arrowCraftInfo provides information about the arrow we're crafting. + * @param sets the amount of sets to complete. + */ +class TippedArrowCraftScript( + private val player: Player, + private val arrowCraftInfo: ArrowCraftInfo, + private val sets: Int +) { + + private val headlessArrow = Items.HEADLESS_ARROW_53 + private val maximumArrowsCraftableInOneStage = 15 + private val initialDelay = 1 + private val subsequentDelay = 3 + + private fun getAmountToCraftForThisStage(headlessArrowsInInventory: Int, tipsInInventory: Int): Int { + val smallerItemAmount = min(headlessArrowsInInventory, tipsInInventory) + return if (smallerItemAmount > maximumArrowsCraftableInOneStage) { + maximumArrowsCraftableInOneStage + } else { + smallerItemAmount + } + } + + fun invoke() { + queueScript(player, initialDelay) { stage -> + if (!clockReady(player, Clocks.SKILLING)) return@queueScript keepRunning(player) + + if (getDynLevel(player, Skills.FLETCHING) < arrowCraftInfo.level) { + ArrowListeners.sendLevelCheckFailDialog(player, arrowCraftInfo.level) + return@queueScript stopExecuting(player) // Check each iteration since dynLevel can change (status effects ending, skill assist session end...) + } + + val tipsInInventory = amountInInventory(player, arrowCraftInfo.tipItemId) + val headlessArrowsInInventory = amountInInventory(player, headlessArrow) + + val amountToCraft = getAmountToCraftForThisStage(headlessArrowsInInventory, tipsInInventory) + + if ( + removeItemsIfPlayerHasEnough( + player, + Item(headlessArrow, amountToCraft), + Item(arrowCraftInfo.tipItemId, amountToCraft) + ) + ) { + when (amountToCraft) { + 1 -> sendMessage(player, "You attach an arrow head to an arrow shaft.") + else -> { + sendMessage(player, "You attach arrow heads to $amountToCraft arrow shafts.") + } + } + + rewardXP(player, Skills.FLETCHING, arrowCraftInfo.experience * amountToCraft) + addItem(player, arrowCraftInfo.arrowItemId, amountToCraft) + } else { + return@queueScript stopExecuting(player) + } + + if (stage >= sets - 1) { + return@queueScript stopExecuting(player) + } + + return@queueScript delayClock(player, Clocks.SKILLING, subsequentDelay, true) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/bolts/BoltCraftInfo.kt b/Server/src/main/content/global/skill/fletching/bolts/BoltCraftInfo.kt new file mode 100644 index 000000000..497332a59 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/bolts/BoltCraftInfo.kt @@ -0,0 +1,39 @@ +package content.global.skill.fletching.bolts + +import org.rs09.consts.Items + +/** + * Provides information pertaining to crafting different bolts + * @property unfinishedBoltItemId the unfinished bolt (unf) Item id used to craft the dart. + * @property finishedItemId the resulting finished bolt Item id. + * @property level the level required to craft. + * @property experience the experience gained by crafting. + */ +enum class BoltCraftInfo( + val unfinishedBoltItemId: Int, + val finishedItemId: Int, + val level: Int, + val experience: Double +) { + BRONZE_BOLT(Items.BRONZE_BOLTS_UNF_9375, Items.BRONZE_BOLTS_877, 9, 0.5), + BLURITE_BOLT(Items.BLURITE_BOLTS_UNF_9376, Items.BLURITE_BOLTS_9139, 24, 1.0), + IRON_BOLT(Items.IRON_BOLTS_UNF_9377, Items.IRON_BOLTS_9140, 39, 1.5), + SILVER_BOLT(Items.SILVER_BOLTS_UNF_9382, Items.SILVER_BOLTS_9145, 43, 2.5), + STEEL_BOLT(Items.STEEL_BOLTS_UNF_9378, Items.STEEL_BOLTS_9141, 46, 3.5), + MITHRIL_BOLT(Items.MITHRIL_BOLTS_UNF_9379, Items.MITHRIL_BOLTS_9142, 54, 5.0), + ADAMANTITE_BOLT(Items.ADAMANT_BOLTSUNF_9380, Items.ADAMANT_BOLTS_9143, 61, 7.0), + RUNITE_BOLT(Items.RUNITE_BOLTS_UNF_9381, Items.RUNE_BOLTS_9144, 69, 10.0), + BROAD_BOLT(Items.UNFINISHED_BROAD_BOLTS_13279, Items.BROAD_TIPPED_BOLTS_13280, 55, 3.0); + + companion object { + private val boltCraftInfoByUnfinishedBoltIds = values().associateBy { it.unfinishedBoltItemId } + + val unfinishedBoltIds: IntArray = BoltCraftInfo.values() + .map { unfinishedBoltToBoltMapping: BoltCraftInfo -> unfinishedBoltToBoltMapping.unfinishedBoltItemId } + .toIntArray() + + fun fromUnfinishedBoltId(unfinishedBoltId: Int): BoltCraftInfo? { + return boltCraftInfoByUnfinishedBoltIds[unfinishedBoltId] + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/bolts/BoltCraftScript.kt b/Server/src/main/content/global/skill/fletching/bolts/BoltCraftScript.kt new file mode 100644 index 000000000..e525e7055 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/bolts/BoltCraftScript.kt @@ -0,0 +1,79 @@ +package content.global.skill.fletching.bolts + +import core.api.* +import core.game.interaction.Clocks +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import kotlin.math.min + +/** + * Represents the bolt pulse class to make bolts. + * @author ceik + * @param player the player. + * @param boltCraftInfo contains information about what bolt we're crafting + * @param feather the feather we're using to craft + * @param sets the amount of sets to craft + */ +class BoltCraftScript( + private val player: Player, + private val boltCraftInfo: BoltCraftInfo, + private val feather: Item, + private val sets: Int +) { + + private val maximumBoltsCraftableInOneStage = 10 + private val initialDelay = 1 + private val subsequentDelay = 3 + + private fun getAmountToCraftForThisSet(feathersInInventory: Int, unfinishedBoltsInInventory: Int): Int { + val smallerItemAmount = min(feathersInInventory, unfinishedBoltsInInventory) + return if (smallerItemAmount > maximumBoltsCraftableInOneStage) { + maximumBoltsCraftableInOneStage + } else { + smallerItemAmount + } + } + + fun invoke() { + queueScript(player, initialDelay) { stage -> + if (!clockReady(player, Clocks.SKILLING)) return@queueScript keepRunning(player) + + if (getDynLevel(player, Skills.FLETCHING) < boltCraftInfo.level) { + BoltListeners.sendLevelCheckFailDialog(player, boltCraftInfo.level) + return@queueScript stopExecuting(player) // Check each iteration since dynLevel can change (status effects ending, skill assist session end...) + } + + val featherAmount = amountInInventory(player, feather.id) + val unfinishedBoltAmount = amountInInventory(player, boltCraftInfo.unfinishedBoltItemId) + + val amountToCraft = getAmountToCraftForThisSet(featherAmount, unfinishedBoltAmount) + + if ( + removeItemsIfPlayerHasEnough( + player, + Item(feather.id, amountToCraft), + Item(boltCraftInfo.unfinishedBoltItemId, amountToCraft) + ) + ) { + when (amountToCraft) { + 1 -> sendMessage(player, "You attach a feather to a bolt.") + else -> { + sendMessage(player, "You fletch $amountToCraft bolts") + } + } + + rewardXP(player, Skills.FLETCHING, boltCraftInfo.experience * amountToCraft) + addItem(player, boltCraftInfo.finishedItemId, amountToCraft) + } else { + return@queueScript stopExecuting(player) + } + + if (stage >= sets - 1) { + return@queueScript stopExecuting(player) + } + + return@queueScript delayClock(player, Clocks.SKILLING, subsequentDelay, true) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/bolts/BoltListeners.kt b/Server/src/main/content/global/skill/fletching/bolts/BoltListeners.kt new file mode 100644 index 000000000..ad9d806d8 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/bolts/BoltListeners.kt @@ -0,0 +1,65 @@ +package content.global.skill.fletching.bolts + +import content.global.skill.fletching.Feathers +import core.api.* +import core.game.dialogue.SkillDialogueHandler +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import kotlin.math.min + +@Suppress("unused") // Reflectively loaded +class BoltListeners : InteractionListener { + override fun defineListeners() { + onUseWith(IntType.ITEM, Feathers.all, *BoltCraftInfo.unfinishedBoltIds) { player, feather, unfinishedBolt -> + val boltCraftInfo = BoltCraftInfo.fromUnfinishedBoltId(unfinishedBolt.id) ?: return@onUseWith false + val handler: SkillDialogueHandler = + object : SkillDialogueHandler( + player, + SkillDialogue.MAKE_SET_ONE_OPTION, + Item(boltCraftInfo.finishedItemId) + ) { + override fun create(amount: Int, index: Int) { + if (!playerMeetsInitialRequirements()) return + BoltCraftScript(player, boltCraftInfo, feather.asItem(), amount).invoke() + } + + private fun playerMeetsInitialRequirements(): Boolean { + if (boltCraftInfo == BoltCraftInfo.BROAD_BOLT && !getSlayerFlags(player).isBroadsUnlocked()) { + sendDialogue(player, "You need to unlock the ability to create broad bolts.") + return false + } + if (getDynLevel(player, Skills.FLETCHING) < boltCraftInfo.level) { + sendLevelCheckFailDialog(player, boltCraftInfo.level) + return false + } + if (!hasSpaceFor(player, Item(boltCraftInfo.finishedItemId))) { + sendDialogue(player, "You do not have enough inventory space.") + return false + } + + return true + } + + override fun getAll(index: Int): Int { + return min( + amountInInventory(player, feather.id), + amountInInventory(player, unfinishedBolt.id) + ) + } + } + handler.open() + return@onUseWith true + } + } + companion object { + fun sendLevelCheckFailDialog(player: Player, level: Int) { + sendDialogue( + player, + "You need a fletching level of $level in order to do this." + ) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/crossbow/LimbingListener.kt b/Server/src/main/content/global/skill/fletching/crossbow/LimbingListener.kt new file mode 100644 index 000000000..0876d54c1 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/crossbow/LimbingListener.kt @@ -0,0 +1,80 @@ +package content.global.skill.fletching.crossbow + +import core.api.getDynLevel +import core.api.hasSpaceFor +import core.api.sendDialogue +import core.api.sendMessage +import core.game.dialogue.SkillDialogueHandler +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import core.net.packet.PacketRepository +import core.net.packet.context.ChildPositionContext +import core.net.packet.out.RepositionChild +import org.rs09.consts.Components + +@Suppress("unused") // Reflectively loaded +class LimbingListener : InteractionListener { + override fun defineListeners() { + onUseWith( + IntType.ITEM, + UnfinishedCrossbowCraftInfo.limbIds, + *UnfinishedCrossbowCraftInfo.stockIds + ) { player, limb, stock -> + val unfinishedCrossbowCraftInfo = UnfinishedCrossbowCraftInfo.forStockId(stock.id) ?: return@onUseWith false + if (unfinishedCrossbowCraftInfo.limbItemId != limb.id) { + sendMessage(player, "That's not the right limb to attach to that stock.") + return@onUseWith true + } + val handler: SkillDialogueHandler = object : SkillDialogueHandler( + player, + SkillDialogue.ONE_OPTION, + Item(unfinishedCrossbowCraftInfo.unstrungCrossbowItemId) + ) { + override fun create(amount: Int, index: Int) { + if (!playerMeetsInitialRequirements()) return + LimbingScript(player, unfinishedCrossbowCraftInfo, amount).invoke() + } + + private fun playerMeetsInitialRequirements(): Boolean { + if (getDynLevel(player, Skills.FLETCHING) < unfinishedCrossbowCraftInfo.level) { + sendLevelCheckFailDialog(player, unfinishedCrossbowCraftInfo.level) + return false + } + if (!hasSpaceFor(player, Item(unfinishedCrossbowCraftInfo.unstrungCrossbowItemId))) { + sendDialogue(player, "You do not have enough inventory space.") + return false + } + + return true + } + + override fun getAll(index: Int): Int { + return player.inventory.getAmount(stock.asItem()) + } + } + handler.open() + fixTextOverlappingTheCrossbowIcon(player) + return@onUseWith true + } + } + + + private fun fixTextOverlappingTheCrossbowIcon(player: Player) { + PacketRepository.send( + RepositionChild::class.java, + ChildPositionContext(player, Components.SKILL_MULTI1_309, 2, 210, 10) + ) + } + + companion object { + fun sendLevelCheckFailDialog(player: Player, level: Int) { + sendDialogue( + player, + "You need a fletching level of $level to attach these limbs." + ) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/crossbow/LimbingScript.kt b/Server/src/main/content/global/skill/fletching/crossbow/LimbingScript.kt new file mode 100644 index 000000000..07fb8ac43 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/crossbow/LimbingScript.kt @@ -0,0 +1,56 @@ +package content.global.skill.fletching.crossbow + +import core.api.* +import core.game.interaction.Clocks +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item + +/** + * The queue script for attaching limbs. + * @author Ceikry + * @param player The player + * @param unfinishedCrossbowCraftInfo info about the unfinished (unstrung) crossbow we're crafting + * @param amount to create + */ +class LimbingScript( + private val player: Player, + private val unfinishedCrossbowCraftInfo: UnfinishedCrossbowCraftInfo, + private val amount: Int +) { + + private val initialDelay = 1 + private val subsequentDelay = 6 + + fun invoke() { + queueScript(player, initialDelay) { stage -> + if (!clockReady(player, Clocks.SKILLING)) return@queueScript keepRunning(player) + + if (getDynLevel(player, Skills.FLETCHING) < unfinishedCrossbowCraftInfo.level) { + LimbingListener.sendLevelCheckFailDialog(player, unfinishedCrossbowCraftInfo.level) + return@queueScript stopExecuting(player) // Check each iteration since dynLevel can change (status effects ending, skill assist session end...) + } + + if ( + removeItemsIfPlayerHasEnough( + player, + Item(unfinishedCrossbowCraftInfo.stockItemId), + Item(unfinishedCrossbowCraftInfo.limbItemId) + ) + ) { + addItem(player, unfinishedCrossbowCraftInfo.unstrungCrossbowItemId, 1) + rewardXP(player, Skills.FLETCHING, unfinishedCrossbowCraftInfo.experience) + sendMessage(player, "You attach the metal limbs to the stock.") + animate(player, unfinishedCrossbowCraftInfo.animation) + } else { + return@queueScript stopExecuting(player) + } + + if (stage >= amount - 1) { + return@queueScript stopExecuting(player) + } + + return@queueScript delayClock(player, Clocks.SKILLING, subsequentDelay, true) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/crossbow/UnfinishedCrossbowCraftInfo.kt b/Server/src/main/content/global/skill/fletching/crossbow/UnfinishedCrossbowCraftInfo.kt new file mode 100644 index 000000000..7250b02e4 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/crossbow/UnfinishedCrossbowCraftInfo.kt @@ -0,0 +1,55 @@ +package content.global.skill.fletching.crossbow + +import core.game.world.update.flag.context.Animation +import org.rs09.consts.Items + +/** + * Provides information pertaining to crafting different unfinished (unstrung) crossbows + * @author 'Vexia + * @param stockItemId item id of the stock. + * @param limbItemId item id of the limb. + * @param unstrungCrossbowItemId item id of the product. + * @param level the level. + * @param experience the experience. + * @param animation the animation. + */ +enum class UnfinishedCrossbowCraftInfo + ( + val stockItemId: Int, + val limbItemId: Int, + val unstrungCrossbowItemId: Int, + val level: Int, + val experience: Double, + val animation: Animation +) { + WOODEN_STOCK(Items.WOODEN_STOCK_9440, Items.BRONZE_LIMBS_9420, Items.BRONZE_CBOW_U_9454, 9, 12.0, Animation(4436)), + OAK_STOCK(Items.OAK_STOCK_9442, Items.BLURITE_LIMBS_9422, Items.BLURITE_CROSSBOW_9176, 24, 32.0, Animation(4437)), + WILLOW_STOCK(Items.WILLOW_STOCK_9444, Items.IRON_LIMBS_9423, Items.IRON_CBOW_U_9457, 39, 44.0, Animation(4438)), + TEAK_STOCK(Items.TEAK_STOCK_9446, Items.STEEL_LIMBS_9425, Items.STEEL_CBOW_U_9459, 46, 54.0, Animation(4439)), + MAPLE_STOCK(Items.MAPLE_STOCK_9448, Items.MITHRIL_LIMBS_9427, Items.MITHRIL_CBOW_U_9461, 54, 64.0, Animation(4440)), + MAHOGANY_STOCK( + Items.MAHOGANY_STOCK_9450, + Items.ADAMANTITE_LIMBS_9429, + Items.ADAMANT_CBOW_U_9463, + 61, + 82.0, + Animation(4441) + ), + YEW_STOCK(Items.YEW_STOCK_9452, Items.RUNITE_LIMBS_9431, Items.RUNITE_CBOW_U_9465, 69, 100.0, Animation(4442)); + + companion object { + private val unfinishedCrossbowCraftInfoByStockId = values().associateBy { it.stockItemId } + + val limbIds: IntArray = + values().map { unfinishedCrossbowCraftInfo: UnfinishedCrossbowCraftInfo -> unfinishedCrossbowCraftInfo.limbItemId } + .toIntArray() + + val stockIds: IntArray = + values().map { unfinishedCrossbowCraftInfo: UnfinishedCrossbowCraftInfo -> unfinishedCrossbowCraftInfo.stockItemId } + .toIntArray() + + fun forStockId(stockId: Int): UnfinishedCrossbowCraftInfo? { + return unfinishedCrossbowCraftInfoByStockId[stockId] + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/darts/DartCraftInfo.kt b/Server/src/main/content/global/skill/fletching/darts/DartCraftInfo.kt new file mode 100644 index 000000000..488075b80 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/darts/DartCraftInfo.kt @@ -0,0 +1,36 @@ +package content.global.skill.fletching.darts + +import org.rs09.consts.Items + +/** + * Provides information pertaining to crafting different darts + * @author 'Vexia + * @property tipItemId the tip Item id used to craft the dart. + * @property dartItemId the resulting dart Item id. + * @property level the level required to craft. + * @property experience the experience gained by crafting. + */ +enum class DartCraftInfo( + val tipItemId: Int, + val dartItemId: Int, + val level: Int, + val experience: Double) { + + BRONZE_DART(Items.BRONZE_DART_TIP_819, Items.BRONZE_DART_806, 1, 1.8), + IRON_DART(Items.IRON_DART_TIP_820, Items.IRON_DART_807, 22, 3.8), + STEEL_DART(Items.STEEL_DART_TIP_821, Items.STEEL_DART_808, 37, 7.5), + MITHRIL_DART(Items.MITHRIL_DART_TIP_822, Items.MITHRIL_DART_809, 52, 11.2), + ADAMANT_DART(Items.ADAMANT_DART_TIP_823, Items.ADAMANT_DART_810, 67, 15.0), + RUNE_DART(Items.RUNE_DART_TIP_824, Items.RUNE_DART_811, 81, 18.8), + DRAGON_DART(Items.DRAGON_DART_TIP_11232, Items.DRAGON_DART_11230, 95, 25.0); + + companion object { + private val dartCraftInfoByTipIds = values().associateBy { it.tipItemId } + + val tipIDs: IntArray = values().map { dartCraftInfo: DartCraftInfo -> dartCraftInfo.tipItemId }.toIntArray() + + fun fromTipID(dartTipId: Int): DartCraftInfo? { + return dartCraftInfoByTipIds[dartTipId] + } + } +} diff --git a/Server/src/main/content/global/skill/fletching/darts/DartCraftScript.kt b/Server/src/main/content/global/skill/fletching/darts/DartCraftScript.kt new file mode 100644 index 000000000..baecff08c --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/darts/DartCraftScript.kt @@ -0,0 +1,78 @@ +package content.global.skill.fletching.darts + +import content.global.skill.fletching.Feathers +import core.api.* +import core.game.interaction.Clocks +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import kotlin.math.min + +/** + * Represents the queueScript to craft a dart. + * @author ceikry + * @param player the player. + * @param dartCraftInfo contains info about the dart we're crafting. + * @param sets count of sets to make + */ +class DartCraftScript( + private val player: Player, + private val dartCraftInfo: DartCraftInfo, + private var sets: Int +) { + + private val initialDelay = 1 + private val subsequentDelay = 3 + private val maximumDartsCraftableInOneStage = 10 + + private fun getAmountToCraftForThisStage(feathersInInventory: Int, dartTipsInInventory: Int): Int { + val smallerItemAmount = min(feathersInInventory, dartTipsInInventory) + return if (smallerItemAmount > maximumDartsCraftableInOneStage) { + maximumDartsCraftableInOneStage + } else { + smallerItemAmount + } + } + + fun invoke() { + queueScript(player, initialDelay) { stage -> + if (!clockReady(player, Clocks.SKILLING)) return@queueScript keepRunning(player) + + if (getDynLevel(player, Skills.FLETCHING) < dartCraftInfo.level) { + DartListeners.sendLevelCheckFailDialog(player, dartCraftInfo.level) + return@queueScript stopExecuting(player) // Check each iteration since dynLevel can change (status effects ending, skill assist session end...) + } + + val dartTipsInInventory = amountInInventory(player, dartCraftInfo.tipItemId) + val feathersInInventory = amountInInventory(player, Feathers.STANDARD) + + val amountToCraft = getAmountToCraftForThisStage(feathersInInventory, dartTipsInInventory) + + if ( + removeItemsIfPlayerHasEnough( + player, + Item(Feathers.STANDARD, amountToCraft), + Item(dartCraftInfo.tipItemId, amountToCraft) + ) + ) { + when (amountToCraft) { + 1 -> sendMessage(player, "You attach a feather to a dart.") + else -> { + sendMessage(player, "You attach feathers to $amountToCraft darts.") + } + } + + rewardXP(player, Skills.FLETCHING, dartCraftInfo.experience * amountToCraft) + addItem(player, dartCraftInfo.dartItemId, amountToCraft) + } else { + return@queueScript stopExecuting(player) + } + + if (stage >= sets - 1) { + return@queueScript stopExecuting(player) + } + + return@queueScript delayClock(player, Clocks.SKILLING, subsequentDelay, true) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/darts/DartListeners.kt b/Server/src/main/content/global/skill/fletching/darts/DartListeners.kt new file mode 100644 index 000000000..93b96d2ab --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/darts/DartListeners.kt @@ -0,0 +1,59 @@ +package content.global.skill.fletching.darts + +import content.data.Quests +import content.global.skill.fletching.Feathers +import core.api.* +import core.game.dialogue.SkillDialogueHandler +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import kotlin.math.min + +@Suppress("unused") // Reflectively loaded +class DartListeners : InteractionListener { + override fun defineListeners() { + onUseWith(IntType.ITEM, Feathers.STANDARD, *DartCraftInfo.tipIDs) { player, feather, dartTip -> + val dartCraftInfo = DartCraftInfo.fromTipID(dartTip.id) ?: return@onUseWith false + val handler: SkillDialogueHandler = + object : + SkillDialogueHandler(player, SkillDialogue.MAKE_SET_ONE_OPTION, Item(dartCraftInfo.dartItemId)) { + override fun create(amount: Int, index: Int) { + if (!playerMeetsInitialRequirements()) return + DartCraftScript(player, dartCraftInfo, amount).invoke() + } + + private fun playerMeetsInitialRequirements(): Boolean { + if (getDynLevel(player, Skills.FLETCHING) < dartCraftInfo.level) { + sendLevelCheckFailDialog(player, dartCraftInfo.level) + return false + } + if (!isQuestComplete(player, Quests.THE_TOURIST_TRAP)) { + sendDialogue(player, "You need to have completed Tourist Trap to fletch darts.") + return false + } + if (!hasSpaceFor(player, Item(dartCraftInfo.dartItemId))) { + sendDialogue(player, "You do not have enough inventory space.") + return false + } + return true + } + + override fun getAll(index: Int): Int { + return min( + amountInInventory(player, feather.id), + amountInInventory(player, dartTip.id) + ) + } + } + handler.open() + return@onUseWith true + } + } + companion object { + fun sendLevelCheckFailDialog(player: Player, level: Int) { + sendDialogue(player, "You need a fletching level of $level to do this.") + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/gem/AttachGemTipToBoltScript.kt b/Server/src/main/content/global/skill/fletching/gem/AttachGemTipToBoltScript.kt new file mode 100644 index 000000000..c6b1cfdfd --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/gem/AttachGemTipToBoltScript.kt @@ -0,0 +1,77 @@ +package content.global.skill.fletching.gem + +import core.api.* +import core.game.interaction.Clocks +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import kotlin.math.min + +/** + * Represents the attaching of a gem tip to a premade bolt. + * @author Ceikry + * @param player the player. + * @param gemBoltCraftInfo crafting info for adding the gem to the bolt + * @param sets the number of sets to craft. + */ +class AttachGemTipToBoltScript( + private val player: Player, + private val gemBoltCraftInfo: GemBoltsCraftInfo, + private val sets: Int +) { + + private val initialDelay = 1 + private val subsequentDelay = 3 + private val maximumGemBoltsCraftableInOneStage = 10 + + private fun getAmountToCraftForThisPulse(untippedBoltsInInventory: Int, tipsInInventory: Int): Int { + val smallerItemAmount = min(untippedBoltsInInventory, tipsInInventory) + return if (smallerItemAmount > maximumGemBoltsCraftableInOneStage) { + maximumGemBoltsCraftableInOneStage + } else { + smallerItemAmount + } + } + + fun invoke() { + queueScript(player, initialDelay) { stage -> + if (!clockReady(player, Clocks.SKILLING)) return@queueScript keepRunning(player) + + if (getDynLevel(player, Skills.FLETCHING) < gemBoltCraftInfo.level) { + GemBoltListeners.sendGemTipAttachLevelCheckFailDialog(player, gemBoltCraftInfo.level) + return@queueScript stopExecuting(player) // Check each iteration since dynLevel can change (status effects ending, skill assist session end...) + } + + val untippedBoltsInInventory = amountInInventory(player, gemBoltCraftInfo.untippedBoltItemId) + val tipsInInventory = amountInInventory(player, gemBoltCraftInfo.tipItemId) + + val amountToCraft = getAmountToCraftForThisPulse(untippedBoltsInInventory, tipsInInventory) + + if ( + removeItemsIfPlayerHasEnough( + player, + Item(gemBoltCraftInfo.untippedBoltItemId, amountToCraft), + Item(gemBoltCraftInfo.tipItemId, amountToCraft) + ) + ) { + when (amountToCraft) { + 1 -> sendMessage(player, "You attach the tip to the bolt.") + else -> { + sendMessage(player, "You fletch $amountToCraft bolts.") + } + } + + rewardXP(player, Skills.FLETCHING, gemBoltCraftInfo.experience * amountToCraft) + addItem(player, gemBoltCraftInfo.tippedBoltItemId, amountToCraft) + } else { + return@queueScript stopExecuting(player) + } + + if (stage >= sets - 1) { + return@queueScript stopExecuting(player) + } + + return@queueScript delayClock(player, Clocks.SKILLING, subsequentDelay, true) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/gem/CutGemsIntoBoltTipsScript.kt b/Server/src/main/content/global/skill/fletching/gem/CutGemsIntoBoltTipsScript.kt new file mode 100644 index 000000000..55ea058d0 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/gem/CutGemsIntoBoltTipsScript.kt @@ -0,0 +1,79 @@ +package content.global.skill.fletching.gem + +import core.api.* +import core.game.interaction.Clocks +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item + +/** + * Represents the queue script for cutting gems into bolt tips + * @author Ceikry + * @param player the player. + * @param gemBoltCraftInfo represents the crafting info for the gem we're cutting. + * @param amount the amount to make. + */ +class CutGemsIntoBoltTipsScript( + private val player: Player, + private val gemBoltCraftInfo: GemBoltsCraftInfo, + private val amount: Int +) { + + private val initialDelay = 1 + private val craftDelay = 5 + private val animationDelay = 6 + + private var craftingFinished = false + + private fun invokeAnimationLoop() { + queueScript(player, 0) { + if (craftingFinished) { + return@queueScript stopExecuting(player) + } + animate(player, gemBoltCraftInfo.gemCutAnimationId) + return@queueScript delayScript(player, animationDelay) + } + } + + private fun invokeCraftLoop() { + queueScript(player, 0) { stage -> + if (!clockReady(player, Clocks.SKILLING)) return@queueScript keepRunning(player) + + if (getDynLevel(player, Skills.FLETCHING) < gemBoltCraftInfo.level) { + craftingFinished = true + GemBoltListeners.sendGemTipCutLevelCheckFailDialog(player, gemBoltCraftInfo.level) + return@queueScript stopExecuting(player) // Check each iteration since dynLevel can change (status effects ending, skill assist session end...) + } + + val amountOfTipsToCraft = when (gemBoltCraftInfo) { + GemBoltsCraftInfo.PEARLS -> 24 + GemBoltsCraftInfo.PEARL -> 6 + GemBoltsCraftInfo.ONYX -> 24 + else -> 12 + } + + if (removeItem(player, Item(gemBoltCraftInfo.gemItemId))) { + addItem(player, gemBoltCraftInfo.tipItemId, amountOfTipsToCraft) + rewardXP(player, Skills.FLETCHING, gemBoltCraftInfo.experience) + } else { + craftingFinished = true + return@queueScript stopExecuting(player) + } + + if (stage >= amount - 1) { + craftingFinished = true + return@queueScript stopExecuting(player) + } + + return@queueScript delayClock(player, Clocks.SKILLING, craftDelay, true) + } + } + + fun invoke() { + queueScript(player, initialDelay) { + invokeAnimationLoop() + invokeCraftLoop() + return@queueScript stopExecuting(player) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/gem/GemBoltListeners.kt b/Server/src/main/content/global/skill/fletching/gem/GemBoltListeners.kt new file mode 100644 index 000000000..15ef1d38f --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/gem/GemBoltListeners.kt @@ -0,0 +1,90 @@ +package content.global.skill.fletching.gem + +import core.api.amountInInventory +import core.api.getDynLevel +import core.api.hasSpaceFor +import core.api.sendDialogue +import core.game.dialogue.SkillDialogueHandler +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import org.rs09.consts.Items +import kotlin.math.min + +@Suppress("unused") // Reflectively loaded +class GemBoltListeners : InteractionListener { + + override fun defineListeners() { + onUseWith(IntType.ITEM, Items.CHISEL_1755, *GemBoltsCraftInfo.gemIds) { player, _, gem -> + val gemBoltCraftInfo = GemBoltsCraftInfo.forGemId(gem.id) ?: return@onUseWith true + + object : SkillDialogueHandler(player, SkillDialogue.ONE_OPTION, Item(gemBoltCraftInfo.gemItemId)) { + override fun create(amount: Int, index: Int) { + if (getDynLevel(player, Skills.FLETCHING) < gemBoltCraftInfo.level) { + sendGemTipCutLevelCheckFailDialog(player, gemBoltCraftInfo.level) + return + } + CutGemsIntoBoltTipsScript(player, gemBoltCraftInfo, amount).invoke() + } + + override fun getAll(index: Int): Int { + return amountInInventory(player, gemBoltCraftInfo.gemItemId) + } + }.open() + return@onUseWith true + } + + onUseWith( + IntType.ITEM, + GemBoltsCraftInfo.untippedBoltIds, + *GemBoltsCraftInfo.boltTipIds + ) { player, untippedBolt, boltTip -> + val bolt = GemBoltsCraftInfo.forTipId(boltTip.id) ?: return@onUseWith false + if (untippedBolt.id != bolt.untippedBoltItemId) return@onUseWith false + + val handler: SkillDialogueHandler = + object : SkillDialogueHandler(player, SkillDialogue.MAKE_SET_ONE_OPTION, Item(bolt.tippedBoltItemId)) { + override fun create(amount: Int, index: Int) { + if (!playerMeetsInitialRequirements()) return + AttachGemTipToBoltScript(player, bolt, amount).invoke() + } + + private fun playerMeetsInitialRequirements(): Boolean { + if (getDynLevel(player, Skills.FLETCHING) < bolt.level) { + sendGemTipAttachLevelCheckFailDialog(player, bolt.level) + return false + } + if (!hasSpaceFor(player, Item(bolt.tippedBoltItemId))) { + sendDialogue(player, "You do not have enough inventory space.") + return false + } + return true + } + + override fun getAll(index: Int): Int { + return min(amountInInventory(player, untippedBolt.id), amountInInventory(player, boltTip.id)) + } + } + handler.open() + return@onUseWith true + } + } + + companion object { + fun sendGemTipCutLevelCheckFailDialog(player: Player, level: Int) { + sendDialogue( + player, + "You need a Fletching level of $level or above to do that." + ) + } + + fun sendGemTipAttachLevelCheckFailDialog(player: Player, level: Int) { + sendDialogue( + player, + "You need a Fletching level of $level or above to do that." + ) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/gem/GemBoltsCraftInfo.kt b/Server/src/main/content/global/skill/fletching/gem/GemBoltsCraftInfo.kt new file mode 100644 index 000000000..d0e2b0f11 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/gem/GemBoltsCraftInfo.kt @@ -0,0 +1,106 @@ +package content.global.skill.fletching.gem + +import org.rs09.consts.Items + +/** + * Provides information pertaining to crafting different gem tipped bolts + * @property untippedBoltItemId the base untipped bolt item id + * @property gemItemId the gem item id used to craft the bolt tips + * @property gemCutAnimationId the animation to play when the player cuts the gem + * @property tipItemId the item id of the resulting bolt tips + * @property tippedBoltItemId the item id of the finished gem-tipped bolt + * @property level the required level to craft the gem tipped bolt or tips themselves + * @property experience gained creating one set of gem tips, or per bolt tipped + */ +enum class GemBoltsCraftInfo( + var untippedBoltItemId: Int, + var gemItemId: Int, + var gemCutAnimationId: Int, + var tipItemId: Int, + var tippedBoltItemId: Int, + var level: Int, + var experience: Double +) { + OPAL(Items.BRONZE_BOLTS_877, Items.OPAL_1609, 890, Items.OPAL_BOLT_TIPS_45, Items.OPAL_BOLTS_879, 11, 1.6), + JADE(Items.BLURITE_BOLTS_9139, Items.JADE_1611, 891, Items.JADE_BOLT_TIPS_9187, Items.JADE_BOLTS_9335, 26, 2.4), + PEARL(Items.IRON_BOLTS_9140, Items.OYSTER_PEARL_411, 4470, Items.PEARL_BOLT_TIPS_46, Items.PEARL_BOLTS_880, 41, 3.2), + PEARLS( + Items.IRON_BOLTS_9140, + Items.OYSTER_PEARLS_413, + 4470, + Items.PEARL_BOLT_TIPS_46, + Items.PEARL_BOLTS_880, + 41, + 3.2 + ), + RED_TOPAZ( + Items.STEEL_BOLTS_9141, + Items.RED_TOPAZ_1613, + 892, + Items.TOPAZ_BOLT_TIPS_9188, + Items.TOPAZ_BOLTS_9336, + 48, + 3.9 + ), + SAPPHIRE( + Items.MITHRIL_BOLTS_9142, + Items.SAPPHIRE_1607, + 888, + Items.SAPPHIRE_BOLT_TIPS_9189, + Items.SAPPHIRE_BOLTS_9337, + 56, + 4.7 + ), + EMERALD( + Items.MITHRIL_BOLTS_9142, + Items.EMERALD_1605, + 889, + Items.EMERALD_BOLT_TIPS_9190, + Items.EMERALD_BOLTS_9338, + 58, + 5.5 + ), + RUBY(Items.ADAMANT_BOLTS_9143, Items.RUBY_1603, 887, Items.RUBY_BOLT_TIPS_9191, Items.RUBY_BOLTS_9339, 63, 6.3), + DIAMOND( + Items.ADAMANT_BOLTS_9143, + Items.DIAMOND_1601, + 886, + Items.DIAMOND_BOLT_TIPS_9192, + Items.DIAMOND_BOLTS_9340, + 65, + 7.0 + ), + DRAGONSTONE( + Items.RUNE_BOLTS_9144, + Items.DRAGONSTONE_1615, + 885, + Items.DRAGON_BOLT_TIPS_9193, + Items.DRAGON_BOLTS_9341, + 71, + 8.2 + ), + ONYX(Items.RUNE_BOLTS_9144, Items.ONYX_6573, 2717, Items.ONYX_BOLT_TIPS_9194, Items.ONYX_BOLTS_9342, 73, 9.4); + + companion object { + private val gemBoltCraftInfoByGemId = values().associateBy { it.gemItemId } + private val gemBoltCraftInfoByBoltTipId = values().associateBy { it.tipItemId } + + val untippedBoltIds: IntArray = + values().map { gemBoltsCraftInfo: GemBoltsCraftInfo -> gemBoltsCraftInfo.untippedBoltItemId }.distinct() + .toIntArray() + + val gemIds: IntArray = + values().map { gemBoltsCraftInfo: GemBoltsCraftInfo -> gemBoltsCraftInfo.gemItemId }.toIntArray() + + val boltTipIds: IntArray = + values().map { gemBoltsCraftInfo: GemBoltsCraftInfo -> gemBoltsCraftInfo.tipItemId }.toIntArray() + + fun forGemId(gemId: Int): GemBoltsCraftInfo? { + return gemBoltCraftInfoByGemId[gemId] + } + + fun forTipId(tipId: Int): GemBoltsCraftInfo? { + return gemBoltCraftInfoByBoltTipId[tipId] + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/grapple/GrappleListeners.kt b/Server/src/main/content/global/skill/fletching/grapple/GrappleListeners.kt new file mode 100644 index 000000000..46373cda0 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/grapple/GrappleListeners.kt @@ -0,0 +1,67 @@ +package content.global.skill.fletching.grapple + +import core.api.* +import core.game.interaction.Clocks +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import org.rs09.consts.Items + +@Suppress("unused") // Reflectively loaded +class GrappleListeners : InteractionListener { + private val mithrilBolt = Items.MITHRIL_BOLTS_9142 + private val mithrilGrappleTip = Items.MITH_GRAPPLE_TIP_9416 + private val rope = Items.ROPE_954 + private val mithrilGrapple = Items.MITH_GRAPPLE_9418 + private val mithrilGrappleWithRope = Items.MITH_GRAPPLE_9419 + + override fun defineListeners() { + onUseWith(IntType.ITEM, mithrilBolt, mithrilGrappleTip) { player, bolt, tip -> + if (getDynLevel(player, Skills.FLETCHING) < 59) { + sendMessage(player, "You need a fletching level of 59 to make this.") + return@onUseWith true + } + queueScript(player, 0) { _ -> + if (!clockReady(player, Clocks.SKILLING)) return@queueScript keepRunning(player) + if (removeItemsIfPlayerHasEnough( + player, + Item(mithrilBolt, 1), + tip.asItem() + ) + ) { + addItem(player, mithrilGrapple, 1) + } + + delayClock(player, Clocks.SKILLING, 3) + return@queueScript stopExecuting(player) + } + + + return@onUseWith true + } + + onUseWith(IntType.ITEM, rope, mithrilGrapple) { player, rope, grapple -> + if (getDynLevel(player, Skills.FLETCHING) < 59) { + sendMessage(player, "You need a fletching level of 59 to make this.") + return@onUseWith true + } + queueScript(player, 0) { _ -> + if (!clockReady(player, Clocks.SKILLING)) return@queueScript keepRunning(player) + if (removeItemsIfPlayerHasEnough( + player, + rope.asItem(), + grapple.asItem() + ) + ) { + addItem(player, mithrilGrappleWithRope, 1) + } + delayClock(player, Clocks.SKILLING, 3) + return@queueScript stopExecuting(player) + } + return@onUseWith true + } + + } + +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/items/arrow/ArrowHead.java b/Server/src/main/content/global/skill/fletching/items/arrow/ArrowHead.java deleted file mode 100644 index c7ab5ec7c..000000000 --- a/Server/src/main/content/global/skill/fletching/items/arrow/ArrowHead.java +++ /dev/null @@ -1,123 +0,0 @@ -/* -package core.game.node.entity.skill.fletching.items.arrow; - -import org.crandor.game.node.item.Item; - -*/ -/** - * Represents the enum storing the arrow head information. - * @author 'Vexia - * @note brutal arrows after quest. - *//* - -public enum ArrowHead { - BRONZE_ARROW(new Item(39), new Item(882), 1, 2.6), - IRON_ARROW(new Item(40), new Item(884), 15, 3.8), - STEEL_ARROW(new Item(41), new Item(886), 30, 6.3), - MITHRIL_ARROW(new Item(42), new Item(888), 45, 8.8), - ADAMANT_ARROW(new Item(43), new Item(890), 60, 10), - RUNE_ARROW(new Item(44), new Item(892), 75, 13.8), - DRAGON_ARROW(new Item(11237), new Item(11212), 90, 16.3), - BROAD_ARROW(new Item(13278), new Item(4160), 52, 10); - - */ -/** - * Constructs a new {@code ArrowHead.java} {@code Object}. - * @param item the item. - * @param product the product. - * @param level the level. - * @param experience the experience. - *//* - - ArrowHead(Item item, Item product, int level, double experience) { - this.item = item; - this.product = product; - this.level = level; - this.experience = experience; - } - - */ -/** - * Represents the arrow tip. - *//* - - private final Item item; - - */ -/** - * Represents the product item. - *//* - - private final Item product; - - */ -/** - * Represents the level required. - *//* - - private final int level; - - */ -/** - * Represents the experience gained. - *//* - - private final double experience; - - */ -/** - * Gets the item. - * @return The item. - *//* - - public Item getTips() { - return item; - } - - */ -/** - * Gets the product. - * @return The product. - *//* - - public Item getProduct() { - return product; - } - - */ -/** - * Gets the level. - * @return The level. - *//* - - public int getLevel() { - return level; - } - - */ -/** - * Gets the experience. - * @return The experience. - *//* - - public double getExperience() { - return experience; - } - - */ -/** - * Gets the arrow head. - * @param item the item. - * @return the arrow head. - *//* - - public static ArrowHead forItem(final Item item) { - for (ArrowHead arrow : ArrowHead.values()) { - if (arrow.getTips().getId() == item.getId()) { - return arrow; - } - } - return null; - } -} -*/ diff --git a/Server/src/main/content/global/skill/fletching/items/arrow/ArrowHeadPulse.java b/Server/src/main/content/global/skill/fletching/items/arrow/ArrowHeadPulse.java deleted file mode 100644 index 5ea27c2cc..000000000 --- a/Server/src/main/content/global/skill/fletching/items/arrow/ArrowHeadPulse.java +++ /dev/null @@ -1,110 +0,0 @@ -package content.global.skill.fletching.items.arrow; - -import content.global.skill.slayer.SlayerManager; -import core.game.node.entity.skill.SkillPulse; -import core.game.node.entity.skill.Skills; -import content.global.skill.fletching.Fletching; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; - -import static core.api.ContentAPIKt.*; - -/** - * Represents the arrow head pulse to complete the headless arrow. - * @author 'Vexia - */ -public class ArrowHeadPulse extends SkillPulse { - - /** - * Represents the headless arrow item. - */ - private static final Item HEADLESS_ARROW = new Item(53); - - /** - * Represents the arrow head. - */ - private final Fletching.ArrowHeads arrow; - - /** - * Represents the sets to do. - */ - private int sets; - - /** - * Constructs a new {@code ArrowHeadPulse.java} {@code Object}. - * @param player the player. - * @param node the node. - * @param arrow the arrow. - * @param sets the sets. - */ - public ArrowHeadPulse(final Player player, final Item node, final Fletching.ArrowHeads arrow, final int sets) { - super(player, node); - this.arrow = arrow; - this.sets = sets; - } - - @Override - public boolean checkRequirements() { - if (arrow.unfinished == 4160) { - if (!SlayerManager.getInstance(player).flags.isBroadsUnlocked()) { - player.getDialogueInterpreter().sendDialogue("You need to unlock the ability to create broad arrows."); - return false; - } - } - if (player.getSkills().getLevel(Skills.FLETCHING) < arrow.level) { - player.getDialogueInterpreter().sendDialogue("You need a fletching level of " + arrow.level + " to do this."); - return false; - } - if (!hasSpaceFor(player, arrow.getFinished())) { - sendDialogue(player, "You do not have enough inventory space."); - return false; - } - return true; - } - - @Override - public void animate() { - } - - @Override - public boolean reward() { - if (getDelay() == 1) { - super.setDelay(3); - } - Item tip = arrow.getUnfinished(); - int tipAmount = player.getInventory().getAmount(arrow.unfinished); - int shaftAmount = player.getInventory().getAmount(HEADLESS_ARROW); - if (tipAmount >= 15 && shaftAmount >= 15) { - HEADLESS_ARROW.setAmount(15); - tip.setAmount(15); - player.getPacketDispatch().sendMessage("You attach arrow heads to 15 arrow shafts."); - } else { - int amount = tipAmount > shaftAmount ? shaftAmount : tipAmount; - HEADLESS_ARROW.setAmount(amount); - tip.setAmount(amount); - player.getPacketDispatch().sendMessage(amount == 1 ? "You attach an arrow head to an arrow shaft." : "You attach arrow heads to " + amount + " arrow shafts."); - } - if (player.getInventory().remove(HEADLESS_ARROW, tip)) { - player.getSkills().addExperience(Skills.FLETCHING, arrow.experience * tip.getAmount(), true); - Item product = arrow.getFinished(); - product.setAmount(tip.getAmount()); - player.getInventory().add(product); - } - HEADLESS_ARROW.setAmount(1); - tip.setAmount(1); - if (!player.getInventory().containsItem(HEADLESS_ARROW)) { - return true; - } - if (!player.getInventory().containsItem(tip)) { - return true; - } - sets--; - return sets == 0; - } - - @Override - public void message(int type) { - - } - -} diff --git a/Server/src/main/content/global/skill/fletching/items/arrow/HeadlessArrowPulse.java b/Server/src/main/content/global/skill/fletching/items/arrow/HeadlessArrowPulse.java deleted file mode 100644 index 3125b5669..000000000 --- a/Server/src/main/content/global/skill/fletching/items/arrow/HeadlessArrowPulse.java +++ /dev/null @@ -1,145 +0,0 @@ -package content.global.skill.fletching.items.arrow; - -import org.rs09.consts.Items; -import core.game.node.entity.skill.SkillPulse; -import core.game.node.entity.skill.Skills; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; - -import static core.api.ContentAPIKt.*; - -/** - * Represents the arrow pulse for creating unfinished arrows. - * @author 'Vexia - */ -public final class HeadlessArrowPulse extends SkillPulse { - - /** - * Represents the headless arrow item. - */ - private final Item HEADLESS_ARROW = new Item(Items.HEADLESS_ARROW_53); - - /** - * Represents the arrow shaft item. - */ - private final Item ARROW_SHAFT = new Item(Items.ARROW_SHAFT_52); - - /** - * Represents the feather items. - */ - private static final Item[] FEATHER = new Item[] { - new Item(Items.FEATHER_314), - new Item(Items.STRIPY_FEATHER_10087), - new Item(Items.RED_FEATHER_10088), - new Item(Items.BLUE_FEATHER_10089), - new Item(Items.YELLOW_FEATHER_10090), - new Item(Items.ORANGE_FEATHER_10091) - }; - - /** - * The feather being used. - */ - private Item feather; - - /** - * Represents the amount to make. - */ - private int sets; - - /** - * Represents if we should use sets, meaning we have 15 & 15 arrow shafts and feathers. - */ - private boolean useSets = false; - - /** - * Constructs a new {@code ArrowPulse.java} {@code Object}. - * @param player the player. - * @param node the node. - */ - public HeadlessArrowPulse(Player player, Item node, Item feather, int sets) { - super(player, node); - this.sets = sets; - this.feather = feather; - } - - @Override - public boolean checkRequirements() { - if (!player.getInventory().containsItem(ARROW_SHAFT)) { - player.getDialogueInterpreter().sendDialogue("You don't have any arrow shafts."); - return false; - } - if (feather == null || !player.getInventory().containsItem(feather)) { - player.getDialogueInterpreter().sendDialogue("You don't have any feathers."); - return false; - } - if (player.getInventory().contains(ARROW_SHAFT.getId(), 15) && player.getInventory().contains(feather.getId(), 15)) { - useSets = true; - } else { - useSets = false; - } - if (!hasSpaceFor(player, HEADLESS_ARROW.asItem())) { - sendDialogue(player, "You do not have enough inventory space."); - return false; - } - return true; - } - - @Override - public void animate() { - } - - @Override - public boolean reward() { - int featherAmount = player.getInventory().getAmount(feather); - int shaftAmount = player.getInventory().getAmount(ARROW_SHAFT); - if (getDelay() == 1) { - super.setDelay(3); - } - if (featherAmount >= 15 && shaftAmount >= 15) { - feather.setAmount(15); - ARROW_SHAFT.setAmount(15); - player.getPacketDispatch().sendMessage("You attach feathers to 15 arrow shafts."); - } else { - int amount = Math.min(featherAmount, shaftAmount); - feather.setAmount(amount); - ARROW_SHAFT.setAmount(amount); - player.getPacketDispatch().sendMessage(amount == 1 - ? "You attach a feathers to a shaft." : "You attach feathers to " + amount + " arrow shafts."); - } - if (player.getInventory().remove(feather, ARROW_SHAFT)) { - HEADLESS_ARROW.setAmount(feather.getAmount()); - player.getSkills().addExperience(Skills.FLETCHING, HEADLESS_ARROW.getAmount(), true); - player.getInventory().add(HEADLESS_ARROW); - } - HEADLESS_ARROW.setAmount(1); - feather.setAmount(1); - ARROW_SHAFT.setAmount(1); - if (!player.getInventory().containsItem(ARROW_SHAFT)) { - return true; - } - if (!player.getInventory().containsItem(feather)) { - return true; - } - sets--; - return sets <= 0; - } - - @Override - public void message(int type) { - } - - /** - * Gets the feather item. - * @return the item. - */ - private Item getFeather() { - int length = FEATHER.length; - for (int i = 0; i < length; i++) { - Item f = FEATHER[i]; - if (player.getInventory().containsItem(f)) { - return f; - } - } - return null; - } -} diff --git a/Server/src/main/content/global/skill/fletching/items/arrow/HeadlessOgreArrowPulse.java b/Server/src/main/content/global/skill/fletching/items/arrow/HeadlessOgreArrowPulse.java deleted file mode 100644 index fd7af6731..000000000 --- a/Server/src/main/content/global/skill/fletching/items/arrow/HeadlessOgreArrowPulse.java +++ /dev/null @@ -1,131 +0,0 @@ -package content.global.skill.fletching.items.arrow; - -import core.game.node.entity.player.Player; -import core.game.node.entity.skill.SkillPulse; -import core.game.node.entity.skill.Skills; -import core.game.node.item.Item; -import org.rs09.consts.Items; - -import static core.api.ContentAPIKt.hasSpaceFor; -import static core.api.ContentAPIKt.sendDialogue; - -/** - * Represents the arrow pulse for creating unfinished ogre arrows. - * @author 'Vexia - */ -public final class HeadlessOgreArrowPulse extends SkillPulse { - - /** - * Represents the headless ogre arrow item. - */ - private final Item HEADLESS_ARROW = new Item(Items.FLIGHTED_OGRE_ARROW_2865); - - /** - * Represents the ogre arrow shaft item. - */ - private final Item ARROW_SHAFT = new Item(Items.OGRE_ARROW_SHAFT_2864); - - /** - * Represents the feather items. - */ - private static final Item[] FEATHER = new Item[] { - new Item(Items.FEATHER_314, 4), - }; - - /** - * The feather being used. - */ - private Item feather; - - /** - * Represents the amount to make. - */ - private int sets; - - /** - * Constructs a new {@code ArrowPulse.java} {@code Object}. - * @param player the player. - * @param node the node. - */ - public HeadlessOgreArrowPulse(Player player, Item node, Item feather, int sets) { - super(player, node); - this.sets = sets; - this.feather = feather; - } - - @Override - public boolean checkRequirements() { - if (!player.getInventory().containsItem(ARROW_SHAFT)) { - player.getDialogueInterpreter().sendDialogue("You don't have any arrow shafts."); - return false; - } - if (feather == null || !player.getInventory().containsItem(feather)) { - player.getDialogueInterpreter().sendDialogue("You don't have any feathers."); - return false; - } - if (!hasSpaceFor(player, HEADLESS_ARROW.asItem())) { - sendDialogue(player, "You do not have enough inventory space."); - return false; - } - return true; - } - - @Override - public void animate() { - } - - @Override - public boolean reward() { - int featherAmount = player.getInventory().getAmount(feather); - int shaftAmount = player.getInventory().getAmount(ARROW_SHAFT); - if (getDelay() == 1) { - super.setDelay(3); - } - if (featherAmount >= 24 && shaftAmount >= 6) { - feather.setAmount(24); - ARROW_SHAFT.setAmount(6); - player.getPacketDispatch().sendMessage("You attach 24 feathers to 6 ogre arrow shafts."); - } else { - int amount = Math.min(featherAmount / 4, shaftAmount); - feather.setAmount(amount*4); - ARROW_SHAFT.setAmount(amount); - player.getPacketDispatch().sendMessage(amount == 1 - ? "You attach a feathers to a shaft." : "You attach " + amount * 4 + " feathers to " + amount + " ogre arrow shafts."); - } - if (player.getInventory().remove(feather, ARROW_SHAFT)) { - HEADLESS_ARROW.setAmount(ARROW_SHAFT.getAmount()); - player.getSkills().addExperience(Skills.FLETCHING, HEADLESS_ARROW.getAmount(), true); - player.getInventory().add(HEADLESS_ARROW); - } - HEADLESS_ARROW.setAmount(1); - feather.setAmount(1); - ARROW_SHAFT.setAmount(1); - if (!player.getInventory().containsItem(ARROW_SHAFT)) { - return true; - } - if (!player.getInventory().containsItem(feather)) { - return true; - } - sets--; - return sets <= 0; - } - - @Override - public void message(int type) { - } - - /** - * Gets the feather item. - * @return the item. - */ - private Item getFeather() { - int length = FEATHER.length; - for (int i = 0; i < length; i++) { - Item f = FEATHER[i]; - if (player.getInventory().containsItem(f)) { - return f; - } - } - return null; - } -} diff --git a/Server/src/main/content/global/skill/fletching/items/bolts/Bolt.java b/Server/src/main/content/global/skill/fletching/items/bolts/Bolt.java deleted file mode 100644 index f877050db..000000000 --- a/Server/src/main/content/global/skill/fletching/items/bolts/Bolt.java +++ /dev/null @@ -1,123 +0,0 @@ -/* -package core.game.node.entity.skill.fletching.items.bolts; - -import org.crandor.game.node.item.Item; - -*/ -/** - * Represents an enum of bolts. - * @author 'Vexia - *//* - -public enum Bolt { - BRONZE_BOLT(new Item(9375), new Item(877), 9, 0.5), - BLURITE_BOLT(new Item(9376), new Item(9139), 24, 1), - IRON_BOLT(new Item(9377), new Item(9140), 39, 1.5), - SILVER_BOLT(new Item(9382), new Item(9145), 43, 2.5), - STEEL_BOLT(new Item(9378), new Item(9141), 46, 3.5), - MITHRIL_BOLT(new Item(9379), new Item(9142), 54, 5), - ADAMANTITE_BOLT(new Item(9380), new Item(9143), 61, 7), - RUNITE_BOLT(new Item(9381), new Item(9144), 69, 10), - BROAD_BOLT(new Item(13279), new Item(13280), 55, 3); - - */ -/** - * The item required. - *//* - - private final Item item; - - */ -/** - * The product recieved. - *//* - - private final Item product; - - */ -/** - * The level required. - *//* - - private final int level; - - */ -/** - * The experience gained. - *//* - - private final double experience; - - */ -/** - * Constructs a new {@code Bolt} {@code Object}. - * @param item the item. - * @param product the product. - * @param level the level. - * @param experience the experienece. - *//* - - Bolt(Item item, Item product, int level, double experience) { - this.item = item; - this.product = product; - this.level = level; - this.experience = experience; - } - - */ -/** - * Gets the item. - * @return The item. - *//* - - public Item getItem() { - return item; - } - - */ -/** - * Gets the product. - * @return The product. - *//* - - public Item getProduct() { - return product; - } - - */ -/** - * Gets the level. - * @return The level. - *//* - - public int getLevel() { - return level; - } - - */ -/** - * Gets the experience. - * @return The experience. - *//* - - public double getExperience() { - return experience; - } - - */ -/** - * Method used to get the bolt for the item. - * @param item the item. - * @return the bolt. - *//* - - public static Bolt forItem(final Item item) { - for (Bolt bolt : Bolt.values()) { - if (bolt.getItem().getId() == item.getId()) { - return bolt; - } - } - return null; - } -} -*/ diff --git a/Server/src/main/content/global/skill/fletching/items/bolts/BoltPulse.java b/Server/src/main/content/global/skill/fletching/items/bolts/BoltPulse.java deleted file mode 100644 index b345c4276..000000000 --- a/Server/src/main/content/global/skill/fletching/items/bolts/BoltPulse.java +++ /dev/null @@ -1,130 +0,0 @@ -package content.global.skill.fletching.items.bolts; - -import content.global.skill.slayer.SlayerManager; -import org.rs09.consts.Items; -import core.game.node.entity.skill.SkillPulse; -import core.game.node.entity.skill.Skills; -import content.global.skill.fletching.Fletching; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; - -/** - * Represents the bolt pulse class to make bolts. - * @author ceik - */ -public final class BoltPulse extends SkillPulse { - - /** - * Represents the feather item. - */ - private Item feather; - - /** - * Represents possible feather Items - */ - private static final Item[] FEATHER = new Item[] { - new Item(Items.FEATHER_314), - new Item(Items.STRIPY_FEATHER_10087), - new Item(Items.RED_FEATHER_10088), - new Item(Items.BLUE_FEATHER_10089), - new Item(Items.YELLOW_FEATHER_10090), - new Item(Items.ORANGE_FEATHER_10091) - }; - - /** - * Represents the bolt. - */ - private final Fletching.Bolts bolt; - - /** - * Represents the sets to do. - */ - private int sets; - - /** - * Represents if we're using sets. - */ - private boolean useSets = false; - - /** - * Constructs a new {@code BoltPulse.java} {@code Object}. - * @param player the player. - * @param node the node. - */ - public BoltPulse(Player player, Item node, final Fletching.Bolts bolt, final Item feather, final int sets) { - super(player, node); - this.bolt = bolt; - this.sets = sets; - this.feather = feather; - } - - @Override - public boolean checkRequirements() { - if (bolt.getUnfinished().getId() == 13279) { - if (!SlayerManager.getInstance(player).flags.isBroadsUnlocked()) { - player.getDialogueInterpreter().sendDialogue("You need to unlock the ability to create broad bolts."); - return false; - } - } - if (player.getSkills().getLevel(Skills.FLETCHING) < bolt.level) { - player.getDialogueInterpreter().sendDialogue("You need a fletching level of " + bolt.level + " in order to do this."); - return false; - } - if (!player.getInventory().containsItem(feather)) { - return false; - } - if (!player.getInventory().containsItem(bolt.getUnfinished())) { - return false; - } - if (!player.getInventory().hasSpaceFor(bolt.getFinished())) { - player.getDialogueInterpreter().sendDialogue("You do not have enough inventory space."); - return false; - } - - return true; - } - - @Override - public void animate() { - } - - @Override - public boolean reward() { - int featherAmount = player.getInventory().getAmount(feather); - int boltAmount = player.getInventory().getAmount(bolt.unfinished); - if (getDelay() == 1) { - super.setDelay(3); - } - final Item unfinished = bolt.getUnfinished(); - if (featherAmount >= 10 && boltAmount >= 10) { - feather.setAmount(10); - unfinished.setAmount(10); - player.getPacketDispatch().sendMessage("You fletch 10 bolts."); - } else { - int amount = featherAmount > boltAmount ? boltAmount : featherAmount; - feather.setAmount(amount); - unfinished.setAmount(amount); - player.getPacketDispatch().sendMessage(amount == 1 ? "You attach a feather to a bolt." : "You fletch " + amount + " bolts"); - } - if (player.getInventory().remove(feather, unfinished)) { - Item product = bolt.getFinished(); - product.setAmount(feather.getAmount()); - player.getSkills().addExperience(Skills.FLETCHING, product.getAmount() * bolt.experience, true); - player.getInventory().add(product); - } - feather.setAmount(1); - if (!player.getInventory().containsItem(feather)) { - return true; - } - if (!player.getInventory().containsItem(bolt.getUnfinished())) { - return true; - } - sets--; - return sets <= 0; - } - - @Override - public void message(int type) { - } - -} diff --git a/Server/src/main/content/global/skill/fletching/items/bow/StringBow.java b/Server/src/main/content/global/skill/fletching/items/bow/StringBow.java deleted file mode 100644 index 0187f17bb..000000000 --- a/Server/src/main/content/global/skill/fletching/items/bow/StringBow.java +++ /dev/null @@ -1,136 +0,0 @@ -/* -package core.game.node.entity.skill.fletching.items.bow; - -import org.crandor.game.node.item.Item; -import org.crandor.game.world.update.flag.context.Animation; - -*/ -/** - * Represents the enum of stringing bows. - * @author 'Vexia - *//* - -public enum StringBow { - SHORT_BOW(new Item(50), new Item(841), 5, 5, new Animation(6678)), - LONG_BOW(new Item(48), new Item(839), 10, 10, new Animation(6684)), - OAK_SHORTBOW(new Item(54), new Item(843), 20, 16.5, new Animation(6679)), - OAK_LONGBOW(new Item(56), new Item(845), 25, 25, new Animation(6685)), - WILLOW_SHORTBOW(new Item(60), new Item(849), 35, 33.3, new Animation(6680)), - WILLOW_LONGBOW(new Item(58), new Item(847), 40, 41.5, new Animation(6686)), - MAPLE_SHORTBOW(new Item(64), new Item(853), 50, 50, new Animation(6681)), - MAPLE_LONGBOW(new Item(62), new Item(851), 55, 58.3, new Animation(6687)), - YEW_SHORTBOW(new Item(68), new Item(857), 65, 66, new Animation(6682)), - YEW_LONGBOW(new Item(66), new Item(855), 70, 75, new Animation(6688)), - MAGIC_SHORTBOW(new Item(72), new Item(861), 80, 83.3, new Animation(6683)), - MAGIC_LONGBOW(new Item(70), new Item(859), 85, 91.5, new Animation(6689)); - - - StringBow(final Item item, final Item product, final int level, final double experience, final Animation animation) { - this.item = item; - this.product = product; - this.level = level; - this.experience = experience; - this.animation = animation; - } - - */ -/** - * The item required. - *//* - - private final Item item; - - */ -/** - * The item product. - *//* - - private final Item product; - - */ -/** - * The level required. - *//* - - private final int level; - - */ -/** - * The experience required. - *//* - - private final double experience; - - */ -/** - * The animation of stringing. - *//* - - private final Animation animation; - - */ -/** - * Gets the item. - * @return The item. - *//* - - public Item getItem() { - return item; - } - - */ -/** - * Gets the product. - * @return The product. - *//* - - public Item getProduct() { - return product; - } - - */ -/** - * Gets the level. - * @return The level. - *//* - - public int getLevel() { - return level; - } - - */ -/** - * Gets the experience. - * @return The experience. - *//* - - public double getExperience() { - return experience; - } - - */ -/** - * Method used to get the animation. - * @return the animation. - *//* - - public Animation getAnimation() { - return animation; - } - - */ -/** - * Method used to get the string bow for the item. - * @param item the item. - * @return the string bow. - *//* - - public static StringBow forItem(final int id) { - for (StringBow bw : StringBow.values()) { - if (bw.getItem().getId() == id) { - return bw; - } - } - return null; - } -}*/ diff --git a/Server/src/main/content/global/skill/fletching/items/bow/StringPulse.java b/Server/src/main/content/global/skill/fletching/items/bow/StringPulse.java deleted file mode 100644 index 0d8645e44..000000000 --- a/Server/src/main/content/global/skill/fletching/items/bow/StringPulse.java +++ /dev/null @@ -1,103 +0,0 @@ -package content.global.skill.fletching.items.bow; - -import core.game.node.entity.player.info.LogType; -import core.game.node.entity.player.info.PlayerMonitor; -import core.game.node.entity.player.link.diary.DiaryType; -import core.game.world.map.zone.ZoneBorders; -import core.game.node.entity.skill.SkillPulse; -import core.game.node.entity.skill.Skills; -import content.global.skill.fletching.Fletching; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; - -import static core.api.ContentAPIKt.amountInInventory; - -/** - * Represents the skill pulse of stringing. - * - * @author Ceikry - */ -public class StringPulse extends SkillPulse { - - /** - * Represents the string bow. - */ - private final Fletching.String bow; - - /** - * The amount. - */ - private int amount; - - private int initialAmount; - private int processedAmount; - - /** - * Constructs a new {@code StringbowPlugin.java} {@code Object}. - * - * @param player the player. - * @param node the node. - */ - public StringPulse(Player player, Item node, final Fletching.String bow, int amount) { - super(player, node); - this.bow = bow; - this.amount = amount; - this.initialAmount = amountInInventory(player, node.getId()); - this.processedAmount = 0; - } - - @Override - public boolean checkRequirements() { - if (getDelay() == 1) { - setDelay(2); - } - if (player.getSkills().getLevel(Skills.FLETCHING) < bow.level) { - player.getDialogueInterpreter().sendDialogue("You need a fletching level of " + bow.level + " to string this bow."); - return false; - } - if (!player.getInventory().containsItem(new Item(bow.unfinished))) { - return false; - } - if (!player.getInventory().containsItem(new Item(bow.string))) { - player.getDialogueInterpreter().sendDialogue("You seem to have run out of bow strings."); - return false; - } - animate(); - return true; - } - - @Override - public void animate() { - player.animate(bow.animation); - } - - @Override - public boolean reward() { - if (player.getInventory().remove(new Item(bow.unfinished), new Item(bow.string))) { - player.getInventory().add(new Item(bow.product)); - player.getSkills().addExperience(Skills.FLETCHING, bow.experience, true); - player.getPacketDispatch().sendMessage("You add a string to the bow."); - processedAmount++; - if (processedAmount > initialAmount) { - PlayerMonitor.log(player, LogType.DUPE_ALERT, "fletched item (" + player.getName() + ", " + bow.unfinished + "): initialAmount " + initialAmount + ", processedAmount " + processedAmount); - } - - if (bow == Fletching.String.MAGIC_SHORTBOW - && (new ZoneBorders(2721, 3489, 2724, 3493, 0).insideBorder(player) - || new ZoneBorders(2727, 3487, 2730, 3490, 0).insideBorder(player)) - && player.getAttribute("diary:seers:fletch-magic-short-bow", false)) { - player.getAchievementDiaryManager().finishTask(player, DiaryType.SEERS_VILLAGE, 2, 2); - } - } - if (!player.getInventory().containsItem(new Item(bow.string)) || !player.getInventory().containsItem(new Item(bow.unfinished))) { - return true; - } - amount--; - return amount == 0; - } - - @Override - public void message(int type) { - } - -} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/items/crossbow/CrossbowPulse.java b/Server/src/main/content/global/skill/fletching/items/crossbow/CrossbowPulse.java deleted file mode 100644 index cfe0cf53b..000000000 --- a/Server/src/main/content/global/skill/fletching/items/crossbow/CrossbowPulse.java +++ /dev/null @@ -1,97 +0,0 @@ -/* -package core.game.node.entity.skill.fletching.items.crossbow; - -import core.game.node.entity.skill.SkillPulse; -import core.game.node.entity.skill.Skills; -import org.crandor.game.node.entity.player.Player; -import org.crandor.game.node.item.Item; - -*/ -/** - * Represents the skill pulse of stringing. - * @author 'Vexia - *//* - -public class CrossbowPulse extends SkillPulse { - - */ -/** - * Represents the bow string item. - *//* - - private final Item BOW_STRING = new Item(9438); - - */ -/** - * Represents the string bow. - *//* - - private final StringCross bow; - - */ -/** - * Represents the amount. - *//* - - private int amount; - - */ -/** - * Constructs a new {@code StringcrossbowPlugin.java} {@code Object}. - * @param player the player. - * @param node the node. - *//* - - public CrossbowPulse(Player player, Item node, final StringCross bow, int amount) { - super(player, node); - this.bow = bow; - this.amount = amount; - } - - @Override - public boolean checkRequirements() { - if (player.getSkills().getLevel(Skills.FLETCHING) < bow.getLevel()) { - player.getDialogueInterpreter().sendDialogue("You need a fletching level of " + bow.getLevel() + " to string this crossbow."); - return false; - } - if (!player.getInventory().containsItem(BOW_STRING)) { - player.getDialogueInterpreter().sendDialogue("You seem to have run out of bow strings."); - return false; - } - return true; - } - - @Override - public void animate() { - player.animate(bow.getAnimation()); - } - - @Override - public boolean reward() { - if (getDelay() == 1) { - super.setDelay(5); - return false; - } - if (player.getInventory().remove(bow.getItem(), BOW_STRING)) { - player.getInventory().add(bow.getProduct()); - player.getSkills().addExperience(Skills.FLETCHING, bow.getExperience(), true); - player.getPacketDispatch().sendMessage("You add a string to the crossbow."); - } - if (!player.getInventory().containsItem(BOW_STRING) || !player.getInventory().containsItem(bow.getItem())) { - return true; - } - amount--; - return amount == 0; - } - - @Override - public void message(int type) { - switch (type) { - case 0: - break; - case 1: - break; - } - } - -}*/ diff --git a/Server/src/main/content/global/skill/fletching/items/crossbow/Limb.java b/Server/src/main/content/global/skill/fletching/items/crossbow/Limb.java deleted file mode 100644 index 6ffc01413..000000000 --- a/Server/src/main/content/global/skill/fletching/items/crossbow/Limb.java +++ /dev/null @@ -1,128 +0,0 @@ -package content.global.skill.fletching.items.crossbow; - -import core.game.node.item.Item; -import core.game.world.update.flag.context.Animation; - -/** - * Represents the enum for limbs. - * @author 'Vexia - */ -public enum Limb { - WOODEN_STOCK(new Item(9440), new Item(9420), new Item(9454), 9, 12, new Animation(4436)), - OAK_STOCK(new Item(9442), new Item(9422), new Item(9176), 24, 32, new Animation(4437)), - WILLOW_STOCK(new Item(9444), new Item(9423), new Item(9457), 39, 44, new Animation(4438)), - TEAK_STOCK(new Item(9446), new Item(9425), new Item(9459), 46, 54, new Animation(4439)), - MAPLE_STOCK(new Item(9448), new Item(9427), new Item(9461), 54, 64, new Animation(4440)), - MAHOGANY_STOCK(new Item(9450), new Item(9429), new Item(9463), 61, 82, new Animation(4441)), - YEW_STOCK(new Item(9452), new Item(9431), new Item(9465), 69, 100, new Animation(4442)); - - /** - * Constructs a new {@code StringcrosbowPlugin.java} {@code Object}. - * @param stock the stock. - * @param limb the limb. - * @param product the product. - * @param level the level. - * @param experience the experience. - * @param animation the animation. - */ - Limb(Item stock, Item limb, Item product, int level, double experience, Animation animation) { - this.stock = stock; - this.limb = limb; - this.product = product; - this.level = level; - this.experience = experience; - this.animation = animation; - } - - /** - * The stock. - */ - private final Item stock; - - /** - * The limb. - */ - private final Item limb; - - /** - * The product. - */ - private final Item product; - - /** - * The level. - */ - private final int level; - - /** - * The experience. - */ - private final double experience; - - /** - * The animation. - */ - private final Animation animation; - - /** - * Gets the stock. - * @return The stock. - */ - public Item getStock() { - return stock; - } - - /** - * Gets the limb. - * @return The limb. - */ - public Item getLimb() { - return limb; - } - - /** - * Gets the product. - * @return The product. - */ - public Item getProduct() { - return product; - } - - /** - * Gets the level. - * @return The level. - */ - public int getLevel() { - return level; - } - - /** - * Gets the experience. - * @return The experience. - */ - public double getExperience() { - return experience; - } - - /** - * Gets the animation. - * @return The animation. - */ - public Animation getAnimation() { - return animation; - } - - /** - * Method used to get the {@link Limb} for the item. - * @param item the item. - * @return the limb. - */ - public static Limb forItems(final Item item, final Item second) { - for (Limb l : Limb.values()) { - if (l.getLimb().getId() == item.getId() && l.getStock().getId() == second.getId() || l.getLimb().getId() == second.getId() && l.getStock().getId() == item.getId()) { - return l; - } - } - return null; - } -} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/items/crossbow/LimbPulse.kt b/Server/src/main/content/global/skill/fletching/items/crossbow/LimbPulse.kt deleted file mode 100644 index 2d6d35562..000000000 --- a/Server/src/main/content/global/skill/fletching/items/crossbow/LimbPulse.kt +++ /dev/null @@ -1,52 +0,0 @@ -package content.global.skill.fletching.items.crossbow - -import core.game.node.entity.player.Player -import core.game.node.entity.skill.SkillPulse -import core.game.node.entity.skill.Skills -import content.global.skill.fletching.Fletching -import core.game.node.item.Item - -/** - * Represents the skill pulse of attaching limbs. - * @author Ceikry - */ -class LimbPulse(player: Player?, node: Item, private val limb: Fletching.Limb, private var amount: Int) : SkillPulse(player, node) { - override fun checkRequirements(): Boolean { - if (player.skills.getLevel(Skills.FLETCHING) < limb.level) { - player.dialogueInterpreter.sendDialogue("You need a fletching level of " + limb.level + " to attach these limbs.") - return false - } - if (!player.inventory.containsItem(Item(limb.limb))) { - player.dialogueInterpreter.sendDialogue("That's not the correct limb to attach.") - return false - } - if(!player.inventory.containsItem(Item(limb.stock))){ - player.dialogueInterpreter.sendDialogue("That's not the correct stock for that limb.") - return false - } - return player.inventory.containsItem(Item(limb.stock)) - } - - override fun animate() { - player.animate(limb.animation) - } - - override fun reward(): Boolean { - if (delay == 1) { - super.setDelay(6) - return false - } - if (player.inventory.remove(Item(limb.stock), Item(limb.limb))) { - player.inventory.add(Item(limb.product)) - player.skills.addExperience(Skills.FLETCHING, limb.experience, true) - player.packetDispatch.sendMessage("You attach the metal limbs to the stock.") - } - if (!player.inventory.containsItem(Item(limb.limb))) { - return true - } - amount-- - return amount == 0 - } - - override fun message(type: Int) {} -} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/items/crossbow/StringCross.java b/Server/src/main/content/global/skill/fletching/items/crossbow/StringCross.java deleted file mode 100644 index 32671c480..000000000 --- a/Server/src/main/content/global/skill/fletching/items/crossbow/StringCross.java +++ /dev/null @@ -1,139 +0,0 @@ -/* -package core.game.node.entity.skill.fletching.items.crossbow; - -import org.crandor.game.node.item.Item; -import org.crandor.game.world.update.flag.context.Animation; - -*/ -/** - * Represents the enum of stringing crossbows. - * @author 'Vexia - *//* - -public enum StringCross { - BRONZE_CBOW(new Item(9454), new Item(9174), 9, 6, new Animation(6671)), - BLURITE_CBOW(new Item(9456), new Item(9176), 24, 16, new Animation(6672)), - IRON_CBOW(new Item(9457), new Item(9177), 39, 22, new Animation(6673)), - STEEL_CBOW(new Item(9459), new Item(9179), 46, 27, new Animation(6674)), - MITHIRIL_CBOW(new Item(9461), new Item(9181), 54, 32, new Animation(6675)), - ADAMANT_CBOW(new Item(9463), new Item(9183), 61, 41, new Animation(6676)), - RUNITE_CBOW(new Item(9465), new Item(9185), 69, 50, new Animation(6677)); - */ -/** - * Constructs a new {@code StringcrossbowPlugin.java} {@code Object}. - * @param item the item. - * @param product the product. - * @param level the level. - * @param experience the experience. - *//* - - StringCross(final Item item, final Item product, final int level, final double experience, final Animation animation) { - this.item = item; - this.product = product; - this.level = level; - this.experience = experience; - this.animation = animation; - } - - */ -/** - * The item required. - *//* - - private final Item item; - - */ -/** - * The item product. - *//* - - private final Item product; - - */ -/** - * The level required. - *//* - - private final int level; - - */ -/** - * The experience required. - *//* - - private final double experience; - - */ -/** - * The animation of stringing. - *//* - - private final Animation animation; - - */ -/** - * Gets the item. - * @return The item. - *//* - - public Item getItem() { - return item; - } - - */ -/** - * Gets the product. - * @return The product. - *//* - - public Item getProduct() { - return product; - } - - */ -/** - * Gets the level. - * @return The level. - *//* - - public int getLevel() { - return level; - } - - */ -/** - * Gets the experience. - * @return The experience. - *//* - - public double getExperience() { - return experience; - } - - */ -/** - * Method used to get the animation. - * @return the animation. - *//* - - public Animation getAnimation() { - return animation; - } - - */ -/** - * Method used to get the string bow for the item. - * @param item the item. - * @return the string bow. - *//* - - public static StringCross forItem(final Item item) { - for (StringCross bw : StringCross.values()) { - if (bw.getItem().getId() == item.getId()) { - return bw; - } - } - return null; - } -} -*/ diff --git a/Server/src/main/content/global/skill/fletching/items/darts/Dart.java b/Server/src/main/content/global/skill/fletching/items/darts/Dart.java deleted file mode 100644 index 235849496..000000000 --- a/Server/src/main/content/global/skill/fletching/items/darts/Dart.java +++ /dev/null @@ -1,96 +0,0 @@ -package content.global.skill.fletching.items.darts; - -import core.game.node.item.Item; - -/** - * Represents the enum to hold dart info. - * @author 'Vexia - */ -public enum Dart { - BRONZE_DART(new Item(819), new Item(806), 1, 1.8), - IRON_DART(new Item(820), new Item(807), 22, 3.8), - STEEL_DART(new Item(821), new Item(808), 37, 7.5), - MITHRIL_DART(new Item(822), new Item(809), 52, 11.2), - ADAMANT_DART(new Item(823), new Item(810), 67, 15), - RUNE_DART(new Item(824), new Item(811), 81, 18.8), - DRAGON_DART(new Item(11232), new Item(11230), 95, 25); - /** - * Constructs a new {@code Dart} {@code Object}. - * @param item the item. - * @param product the product. - * @param level the level. - * @param experience the experience. - */ - Dart(final Item item, final Item product, final int level, final double experience) { - this.item = item; - this.product = product; - this.level = level; - this.experience = experience; - } - - /** - * Represents the item required. - */ - private final Item item; - - /** - * Represents the product gained. - */ - private final Item product; - - /** - * Represents the level required. - */ - private final int level; - - /** - * Represents the experience gained. - */ - private final double experience; - - /** - * Gets the item. - * @return The item. - */ - public Item getItem() { - return item; - } - - /** - * Gets the product. - * @return The product. - */ - public Item getProduct() { - return product; - } - - /** - * Gets the level. - * @return The level. - */ - public int getLevel() { - return level; - } - - /** - * Gets the experience. - * @return The experience. - */ - public double getExperience() { - return experience; - } - - /** - * Method used to get the dart for the item. - * @param item the item. - * @return the dart. - */ - public static Dart forItem(final Item item) { - for (Dart dart : Dart.values()) { - if (dart.getItem().getId() == item.getId()) { - return dart; - } - } - return null; - } -} diff --git a/Server/src/main/content/global/skill/fletching/items/darts/DartPulse.java b/Server/src/main/content/global/skill/fletching/items/darts/DartPulse.java deleted file mode 100644 index d65fac185..000000000 --- a/Server/src/main/content/global/skill/fletching/items/darts/DartPulse.java +++ /dev/null @@ -1,105 +0,0 @@ -package content.global.skill.fletching.items.darts; - -import core.game.node.entity.skill.SkillPulse; -import core.game.node.entity.skill.Skills; -import content.global.skill.fletching.Fletching; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; - -import static core.api.ContentAPIKt.*; -import content.data.Quests; - -/** - * Represents the dart pulse. - * @author ceikry - */ -public final class DartPulse extends SkillPulse { - - /** - * Represents the feather item. - */ - private static final Item FEATHER = new Item(314); - - /** - * Represents the dart. - */ - private final Fletching.Darts dart; - - /** - * Represents the sets to make. - */ - private int sets; - - /** - * Constructs a new {@code DartPulse.java} {@code Object}. - * @param player the player. - * @param node the node. - */ - public DartPulse(Player player, Item node, Fletching.Darts dart, int sets) { - super(player, node); - this.dart = dart; - this.sets = sets; - } - - @Override - public boolean checkRequirements() { - if (player.getSkills().getLevel(Skills.FLETCHING) < dart.level) { - player.getDialogueInterpreter().sendDialogue("You need a fletching level of " + dart.level + " to do this."); - return false; - } - if (!player.getQuestRepository().isComplete(Quests.THE_TOURIST_TRAP)){ - player.getDialogueInterpreter().sendDialogue("You need to have completed Tourist Trap to fletch darts."); - return false; - } - if (!hasSpaceFor(player, dart.getFinished())) { - sendDialogue(player, "You do not have enough inventory space."); - return false; - } - return true; - } - - @Override - public void animate() { - - } - - @Override - public boolean reward() { - if (getDelay() == 1) { - super.setDelay(3); - } - final Item unfinished = dart.getUnfinished(); - final int dartAmount = player.getInventory().getAmount(unfinished); - final int featherAmount = player.getInventory().getAmount(FEATHER); - if (dartAmount >= 10 && featherAmount >= 10) { - FEATHER.setAmount(10); - unfinished.setAmount(10); - player.getPacketDispatch().sendMessage("You attach feathers to 10 darts."); - } else { - int amount = featherAmount > dartAmount ? dartAmount : featherAmount; - FEATHER.setAmount(amount); - unfinished.setAmount(amount); - player.getPacketDispatch().sendMessage(amount == 1 ? "You attach a feather to a dart." : "You attach feathers to " + amount + " darts."); - } - if (player.getInventory().remove(FEATHER, unfinished)) { - Item product = dart.getFinished(); - product.setAmount(FEATHER.getAmount()); - player.getSkills().addExperience(Skills.FLETCHING, dart.experience * product.getAmount(), true); - player.getInventory().add(product); - } - FEATHER.setAmount(1); - if (!player.getInventory().containsItem(FEATHER)) { - return true; - } - if (!player.getInventory().containsItem(dart.getUnfinished())) { - return true; - } - sets--; - return sets == 0; - } - - @Override - public void message(int type) { - } - -} diff --git a/Server/src/main/content/global/skill/fletching/items/gem/Gem.java b/Server/src/main/content/global/skill/fletching/items/gem/Gem.java deleted file mode 100644 index 6a3377aa4..000000000 --- a/Server/src/main/content/global/skill/fletching/items/gem/Gem.java +++ /dev/null @@ -1,101 +0,0 @@ -package content.global.skill.fletching.items.gem; - -import core.game.node.item.Item; - -/** - * Represents gems to cut into bolt tips. - * @author 'Vexia - * @date 01/12/2013 - */ -public enum Gem { - OPAL(new Item(1609), new Item(45, 12), 11, 1.5), - JADE(new Item(1611), new Item(9187, 12), 26, 2.4), - RED_TOPAZ(new Item(1613), new Item(9188, 12), 48, 3.9), - SAPPHIRE(new Item(1607), new Item(9189, 12), 56, 4), - EMERALD(new Item(1605), new Item(9190, 12), 58, 5.5), - RUBY(new Item(1603), new Item(9191, 12), 63, 6.3), - DIAMOND(new Item(1601), new Item(9192, 12), 65, 7), - DRAGONSTONE(new Item(1615), new Item(9193, 12), 71, 8.2), - ONYX(new Item(6573), new Item(9194, 24), 73, 9.4); - - /** - * Constructs a new {@code Gem.java} {@code Object}. - * @param gem the gem. - * @param bolt the bolt. - * @param level the level. - * @param experience the experience. - */ - Gem(Item gem, Item bolt, int level, double experience) { - this.gem = gem; - this.bolt = bolt; - this.level = level; - this.experience = experience; - } - - /** - * Represents the gem. - */ - private final Item gem; - - /** - * Represents the bolt. - */ - private final Item bolt; - - /** - * Represents the level required. - */ - private final int level; - - /** - * Represents the experience gained. - */ - private final double experience; - - /** - * Gets the gem. - * @return The gem. - */ - public Item getGem() { - return gem; - } - - /** - * Gets the bolt. - * @return The bolt. - */ - public Item getBolt() { - return bolt; - } - - /** - * Gets the level. - * @return The level. - */ - public int getLevel() { - return level; - } - - /** - * Gets the experience. - * @return The experience. - */ - public double getExperience() { - return experience; - } - - /** - * Method used to get a gem for the item. - * @param item the item. - * @return the gem. - */ - public static Gem forItem(final Item item) { - for (Gem gem : values()) { - if (gem.getGem().getId() == item.getId()) { - return gem; - } - } - return null; - } - -} diff --git a/Server/src/main/content/global/skill/fletching/items/gem/GemBolt.java b/Server/src/main/content/global/skill/fletching/items/gem/GemBolt.java deleted file mode 100644 index b21e6a164..000000000 --- a/Server/src/main/content/global/skill/fletching/items/gem/GemBolt.java +++ /dev/null @@ -1,144 +0,0 @@ -/* -package core.game.node.entity.skill.fletching.items.gem; - -import org.crandor.game.node.item.Item; - -*/ -/** - * Represents a gem bolt. - * @author 'Vexia - * @date 01/12/2013 - *//* - -public enum GemBolt { - OPAL(new Item(877, 10), new Item(45, 10), new Item(879, 10), 11, 1.5), - PEARL(new Item(9140, 10), new Item(46, 10), new Item(880, 10), 41, 3.2), - JADE(new Item(9139, 10), new Item(9187, 10), new Item(9335, 10), 26, 2.4), - RED_TOPAZ(new Item(9141, 10), new Item(9188, 10), new Item(9336, 10), 48, 3.9), - SAPPHIRE(new Item(9142, 10), new Item(9189, 10), new Item(9337, 10), 56, 4), - EMERALD(new Item(9142, 10), new Item(9190, 10), new Item(9338, 10), 58, 5.5), - RUBY(new Item(9143, 10), new Item(9191, 10), new Item(9339, 10), 63, 6.3), - DIAMOND(new Item(9143, 10), new Item(9192, 10), new Item(9340, 10), 65, 7), - DRAGONSTONE(new Item(9144, 10), new Item(9193, 10), new Item(9341, 10), 71, 8.2), - ONYX(new Item(9144, 10), new Item(9194, 10), new Item(9342, 10), 73, 9.4); - - */ -/** - * Constructs a new {@code GemBolt} {@code Object}. - * @param base the base. - * @param tip the tip. - * @param level the level. - * @param experience the experience. - *//* - - GemBolt(Item base, Item tip, Item product, int level, double experience) { - this.base = base; - this.tip = tip; - this.product = product; - this.level = level; - this.experience = experience; - } - - */ -/** - * Represents the base item. - *//* - - private final Item base; - - */ -/** - * Represents the tip to attach. - *//* - - private final Item tip; - - */ -/** - * Represents the product. - *//* - - private final Item product; - - */ -/** - * Represents the level. - *//* - - private final int level; - - */ -/** - * Represents the experience. - *//* - - private final double experience; - - */ -/** - * Gets the base. - * @return The base. - *//* - - public Item getBase() { - return base; - } - - */ -/** - * Gets the tip. - * @return The tip. - *//* - - public Item getTip() { - return tip; - } - - */ -/** - * Gets the product. - * @return The product. - *//* - - public Item getProduct() { - return product; - } - - */ -/** - * Gets the level. - * @return The level. - *//* - - public int getLevel() { - return level; - } - - */ -/** - * Gets the experience. - * @return The experience. - *//* - - public double getExperience() { - return experience; - } - - */ -/** - * Method used to get the gem bolt from the id. - * @param boltt the boltt. - * @param tip the tip. - * @return the bolt. - *//* - - public static GemBolt forItems(final Item boltt, final Item tip) { - for (GemBolt bolt : values()) { - if (bolt.getBase().getId() == boltt.getId() && bolt.getTip().getId() == tip.getId() || bolt.getBase().getId() == tip.getId() && bolt.getTip().getId() == boltt.getId()) { - return bolt; - } - } - return null; - } -} -*/ diff --git a/Server/src/main/content/global/skill/fletching/items/gem/GemBoltCutPulse.kt b/Server/src/main/content/global/skill/fletching/items/gem/GemBoltCutPulse.kt deleted file mode 100644 index e03781529..000000000 --- a/Server/src/main/content/global/skill/fletching/items/gem/GemBoltCutPulse.kt +++ /dev/null @@ -1,68 +0,0 @@ -package content.global.skill.fletching.items.gem - -import core.game.node.entity.player.Player -import core.game.node.entity.skill.SkillPulse -import core.game.node.entity.skill.Skills -import content.global.skill.fletching.Fletching.GemBolts -import core.game.node.item.Item -import core.game.world.update.flag.context.Animation -import org.rs09.consts.Items - -/** - * Represents the gem cutting pulse(gem to bolt). - * @author Ceikry - */ -class GemBoltCutPulse -/** - * Constructs a new `GemCutPulse.java` `Object`. - * @param player the player. - * @param node the node. - * @param amount the amount. - */(player: Player?, node: Item?, - /** - * Represents the gem we're cutting. - */ - private val gem: GemBolts, - /** - * Represents the amount to make. - */ - private var amount: Int) : SkillPulse(player, node) { - /** - * Represents the ticks passed. - */ - private var ticks = 0 - - override fun checkRequirements(): Boolean { - if (player.skills.getLevel(Skills.FLETCHING) < gem.level) { - player.dialogueInterpreter.sendDialogue("You need a Fletching level of " + gem.level + " or above to do that.") - return false - } - return player.inventory.containsItem(Item(gem.gem)) - } - - override fun animate() { - if (ticks % 6 == 0) { - player.animate(ANIMATION) - } - } - - override fun reward(): Boolean { - if (++ticks % 5 != 0) { - return false - } - val reward = if (gem.gem == Items.OYSTER_PEARLS_413) Item(gem.tip, 24) else Item(gem.tip, 12) - if (player.inventory.remove(Item(gem.gem))) { - player.inventory.add(reward) - player.skills.addExperience(Skills.FLETCHING, gem.experience, true) - } - amount-- - return amount <= 0 - } - - companion object { - /** - * Represents the cutting animation. - */ - private val ANIMATION = Animation(6702) - } -} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/items/gem/GemBoltPulse.java b/Server/src/main/content/global/skill/fletching/items/gem/GemBoltPulse.java deleted file mode 100644 index 4cd856e8a..000000000 --- a/Server/src/main/content/global/skill/fletching/items/gem/GemBoltPulse.java +++ /dev/null @@ -1,91 +0,0 @@ -package content.global.skill.fletching.items.gem; - -import core.game.node.entity.skill.SkillPulse; -import core.game.node.entity.skill.Skills; -import content.global.skill.fletching.Fletching; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; - -/** - * Represents the attaching of a gem bolt to a premade bolt. - * @author Ceikry - */ -public final class GemBoltPulse extends SkillPulse { - - /** - * Represents the gem bolt being made. - */ - private Fletching.GemBolts bolt; - - /** - * Represents the sets to make. - */ - private int sets = 0; - - /** - * Represents the ticks passed. - */ - private int ticks; - - /** - * Constructs a new {@code GemBoltPulse} {@code Object}. - * @param player the player. - * @param node the node. - * @param sets the sets. - */ - public GemBoltPulse(Player player, Item node, Fletching.GemBolts bolt, int sets) { - super(player, node); - this.bolt = bolt; - this.sets = sets; - } - - @Override - public boolean checkRequirements() { - if (player.getSkills().getLevel(Skills.FLETCHING) < bolt.level) { - player.getDialogueInterpreter().sendDialogue("You need a Fletching level of " + bolt.level + " or above to do that."); - return false; - } - if (!player.getInventory().containsItem(new Item(bolt.base)) || !player.getInventory().containsItem(new Item(bolt.tip))) { - return false; - } - if (!player.getInventory().hasSpaceFor(new Item(bolt.product))){ - player.getDialogueInterpreter().sendDialogue("You do not have enough inventory space."); - return false; - } - return true; - } - - @Override - public void animate() { - } - - @Override - public boolean reward() { - if (++ticks % 3 != 0) { - return false; - } - int baseAmount = player.getInventory().getAmount(bolt.base); - int tipAmount = player.getInventory().getAmount(bolt.tip); - Item base = new Item(bolt.base); - Item tip = new Item(bolt.tip); - Item product = new Item(bolt.product); - if(baseAmount >= 10 && tipAmount >= 10){ - base.setAmount(10); - tip.setAmount(10); - product.setAmount(10); - } else { - int amount = baseAmount > tipAmount ? tipAmount : baseAmount; - base.setAmount(amount); - tip.setAmount(amount); - product.setAmount(amount); - } - if (player.getInventory().remove(base,tip)) { - player.getInventory().add(product); - player.getSkills().addExperience(Skills.FLETCHING, bolt.experience * product.getAmount(), true); - player.getPacketDispatch().sendMessage(product.getAmount() == 1 ? "You attach the tip to the bolt." : "You fletch " + product.getAmount() + " bolts."); - } - sets--; - return sets <= 0; - } - -} diff --git a/Server/src/main/content/global/skill/fletching/items/grapple/GrapplePulse.java b/Server/src/main/content/global/skill/fletching/items/grapple/GrapplePulse.java deleted file mode 100644 index cbba3820f..000000000 --- a/Server/src/main/content/global/skill/fletching/items/grapple/GrapplePulse.java +++ /dev/null @@ -1,85 +0,0 @@ -package content.global.skill.fletching.items.grapple; - -import core.game.node.entity.skill.SkillPulse; -import core.game.node.entity.skill.Skills; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; - -/** - * Represents the skill pulse used to create a mith grapple. - * @author 'Vexia - * @date 21/12/2013 - */ -public final class GrapplePulse extends SkillPulse { - - /** - * Represents the mith grapple tip. - */ - private static final Item MITH_GRAPPLE = new Item(9418); - - /** - * Represents the mithril grapple tio. - */ - private static final Item GRAPPLE_TIP = new Item(9416); - - /** - * Represents the mithril bolt. - */ - private static final Item MITHRIL_BOLT = new Item(9142); - - /** - * Represents the amount of the grapple to make. - */ - private int amount; - - /** - * Constructs a new {@code GrapplePulse} {@code Object}. - * @param player the player. - * @param node the node. - * @param amount the amount. - */ - public GrapplePulse(Player player, Item node, int amount) { - super(player, node); - this.amount = amount; - } - - @Override - public boolean checkRequirements() { - int inventoryAmount = player.getInventory().getAmount(GRAPPLE_TIP); - if (amount > inventoryAmount) { - amount = inventoryAmount; - } - if (!player.getInventory().containsItem(GRAPPLE_TIP) || !player.getInventory().containsItem(MITHRIL_BOLT)) { - return false; - } - if (player.getSkills().getLevel(Skills.FLETCHING) < 59) { - player.getDialogueInterpreter().sendDialogue("You need a fletching level of at least 59 in order to do this."); - return false; - } - return true; - } - - @Override - public void animate() { - } - - @Override - public boolean reward() { - if (getDelay() == 1) { - setDelay(3); - return false; - } - if (player.getInventory().remove(GRAPPLE_TIP) && player.getInventory().remove(MITHRIL_BOLT)) { - player.getInventory().add(MITH_GRAPPLE); - player.getSkills().addExperience(Skills.FLETCHING, 5, true); - } - amount--; - return amount < 1; - } - - @Override - public void message(int type) { - - } - -} diff --git a/Server/src/main/content/global/skill/fletching/log/CraftItemWithLogScript.kt b/Server/src/main/content/global/skill/fletching/log/CraftItemWithLogScript.kt new file mode 100644 index 000000000..33e6c5524 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/log/CraftItemWithLogScript.kt @@ -0,0 +1,113 @@ +package content.global.skill.fletching.log + +import content.global.skill.fletching.AchievementDiaryAttributeKeys +import content.global.skill.fletching.Zones +import content.global.skill.fletching.log.GrammarHelpers.aOrAn +import content.global.skill.fletching.log.GrammarHelpers.makeFriendlyName +import core.api.* +import core.game.interaction.Clocks +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.update.flag.context.Animation +import core.tools.RandomFunction +import org.rs09.consts.Items + +/** + * Represents the queueScript used to craft things by carving a log + * @author ceik + * @param player the player. + * @param logCraftInfo crafting info about what we're making out of the log + * @param amount iterations of craft to run + */ +class CraftItemWithLogScript( + private val player: Player, + private val logCraftInfo: LogCraftInfo, + private val amount: Int +) { + + private val initialDelay = 1 + + // src https://gitlab.com/2009scape/2009scape/-/merge_requests/1960#note_2702231552 + private val subsequentDelay = 3 + + private val carveLogAnimation = Animation(1248) + + fun invoke() { + queueScript(player, initialDelay) { stage -> + if (!clockReady(player, Clocks.SKILLING)) return@queueScript keepRunning(player) + + if (getDynLevel(player, Skills.FLETCHING) < logCraftInfo.level) { + LogCraftableListeners.sendLevelCheckFailDialog(player, logCraftInfo) + return@queueScript stopExecuting(player) // Check each iteration since dynLevel can change (status effects ending, skill assist session end...) + } + + if (removeItem(player, logCraftInfo.logItemId.asItem())) { + player.animate(carveLogAnimation) + + val finishedItemName = makeFriendlyName(Item(logCraftInfo.finishedItemId).name) + + val amountToCraft = when (logCraftInfo) { + LogCraftInfo.OGRE_ARROW_SHAFT -> RandomFunction.random(2, 6) + LogCraftInfo.ARROW_SHAFT -> 15 + else -> 1 + } + + if (logCraftInfo == LogCraftInfo.OGRE_COMP_BOW) { + if (!removeItem(player, Items.WOLF_BONES_2859)) { + return@queueScript stopExecuting(player) + } + } + + sendCraftMessageToPlayer(amountToCraft, finishedItemName) + + addItem(player, logCraftInfo.finishedItemId, amountToCraft) + rewardXP(player, Skills.FLETCHING, logCraftInfo.experience) + + if (logCraftInfo == LogCraftInfo.MAGIC_SHORTBOW) { + handleSeersMagicShortbowAchievement() + } + } else { + return@queueScript stopExecuting(player) + } + + if (stage >= amount - 1) { + return@queueScript stopExecuting(player) + } + + return@queueScript delayClock(player, Clocks.SKILLING, subsequentDelay, true) + } + } + + private fun handleSeersMagicShortbowAchievement() { + if (Zones.inAnyZone(player, Zones.seersMagicShortbowAchievementZones) && + !player.achievementDiaryManager.hasCompletedTask(DiaryType.SEERS_VILLAGE, 2, 2) + ) { + setAttribute( + player, + "/save:${AchievementDiaryAttributeKeys.FLETCHED_UNSTRUNG_MAGIC_SHORTBOW}", + true + ) + } + } + + private fun sendCraftMessageToPlayer(amountToCraft: Int, finishedItemName: String) { + when (logCraftInfo) { + LogCraftInfo.ARROW_SHAFT -> sendMessage( + player, + "You carefully cut the wood into $amountToCraft arrow shafts." + ) + + LogCraftInfo.OGRE_ARROW_SHAFT -> sendMessage( + player, + "You carefully cut the wood into $amountToCraft arrow shafts." + ) + + else -> sendMessage( + player, + "You carefully cut the wood into ${aOrAn(finishedItemName)} $finishedItemName." + ) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/log/GrammarHelpers.kt b/Server/src/main/content/global/skill/fletching/log/GrammarHelpers.kt new file mode 100644 index 000000000..5863c51ea --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/log/GrammarHelpers.kt @@ -0,0 +1,20 @@ +package content.global.skill.fletching.log + +import core.tools.StringUtils + +object GrammarHelpers { + fun makeFriendlyName(name: String): String { + return name.replace("(u)", "").trim { it <= ' ' } + } + + /** + * Returns "a" or "an" depending on whether the word begins with a vowel + */ + @Suppress("GrazieInspection") + fun aOrAn(word: String): String { + return when (StringUtils.isPlusN(word)) { + true -> "an" + false -> "a" + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/log/LogCraftInfo.kt b/Server/src/main/content/global/skill/fletching/log/LogCraftInfo.kt new file mode 100644 index 000000000..2111606dc --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/log/LogCraftInfo.kt @@ -0,0 +1,63 @@ +package content.global.skill.fletching.log + +import org.rs09.consts.Items + +/** + * Provides information pertaining to crafting different items from logs + * @property logItemId the log item id that can be used to craft this item + * @property finishedItemId the resulting finished bolt Item id. + * @property level the level required to craft. + * @property experience the experience gained by crafting. + */ +enum class LogCraftInfo(val logItemId: Int, val finishedItemId: Int, val experience: Double, val level: Int) { + ARROW_SHAFT(Items.LOGS_1511, Items.ARROW_SHAFT_52, 5.0, 1), + SHORT_BOW(Items.LOGS_1511, Items.SHORTBOW_U_50, 5.0, 5), + LONG_BOW(Items.LOGS_1511, Items.LONGBOW_U_48, 10.0, 10), + WOODEN_STOCK(Items.LOGS_1511, Items.WOODEN_STOCK_9440, 6.0, 9), + + OGRE_ARROW_SHAFT(Items.ACHEY_TREE_LOGS_2862, Items.OGRE_ARROW_SHAFT_2864, 6.4, 5), + OGRE_COMP_BOW(Items.ACHEY_TREE_LOGS_2862, Items.UNSTRUNG_COMP_BOW_4825, 45.0, 30), + + OAK_SHORTBOW(Items.OAK_LOGS_1521, Items.OAK_SHORTBOW_U_54, 16.5, 20), + OAK_LONGBOW(Items.OAK_LOGS_1521, Items.OAK_LONGBOW_U_56, 25.0, 25), + OAK_STOCK(Items.OAK_LOGS_1521, Items.OAK_STOCK_9442, 16.0, 24), + + WILLOW_SHORTBOW(Items.WILLOW_LOGS_1519, Items.WILLOW_SHORTBOW_U_60, 33.3, 35), + WILLOW_LONGBOW(Items.WILLOW_LOGS_1519, Items.WILLOW_LONGBOW_U_58, 41.5, 40), + WILLOW_STOCK(Items.WILLOW_LOGS_1519, Items.WILLOW_STOCK_9444, 22.0, 39), + + MAPLE_SHORTBOW(Items.MAPLE_LOGS_1517, Items.MAPLE_SHORTBOW_U_64, 50.0, 50), + MAPLE_LONGBOW(Items.MAPLE_LOGS_1517, Items.MAPLE_LONGBOW_U_62, 58.3, 55), + MAPLE_STOCK(Items.MAPLE_LOGS_1517, Items.MAPLE_STOCK_9448, 32.0, 54), + + YEW_SHORTBOW(Items.YEW_LOGS_1515, Items.YEW_SHORTBOW_U_68, 67.5, 65), + YEW_LONGBOW(Items.YEW_LOGS_1515, Items.YEW_LONGBOW_U_66, 75.0, 70), + YEW_STOCK(Items.YEW_LOGS_1515, Items.YEW_STOCK_9452, 50.0, 69), + + MAGIC_SHORTBOW(Items.MAGIC_LOGS_1513, Items.MAGIC_SHORTBOW_U_72, 83.3, 80), + MAGIC_LONGBOW(Items.MAGIC_LOGS_1513, Items.MAGIC_LONGBOW_U_70, 91.5, 85), + + TEAK_STOCK(Items.TEAK_LOGS_6333, Items.TEAK_STOCK_9446, 27.0, 46), + + MAHOGANY_STOCK(Items.MAHOGANY_LOGS_6332, Items.MAHOGANY_STOCK_9450, 41.0, 61); + + + companion object { + val logIds: IntArray = LogCraftInfo.values().map { logCraftInfo: LogCraftInfo -> logCraftInfo.logItemId }.distinct().toIntArray() + + private val logCraftablesByLogId = associateCraftInfoByLogIds() + private fun associateCraftInfoByLogIds(): Map> { + val map = HashMap>() + + for (logId in logIds) { + map[logId] = LogCraftInfo.values().filter { logCraftInfo: LogCraftInfo -> logCraftInfo.logItemId == logId } + } + + return map + } + + fun forLogId(logId: Int): List? { + return logCraftablesByLogId[logId] + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/log/LogCraftableListeners.kt b/Server/src/main/content/global/skill/fletching/log/LogCraftableListeners.kt new file mode 100644 index 000000000..fde0d5ff4 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/log/LogCraftableListeners.kt @@ -0,0 +1,83 @@ +package content.global.skill.fletching.log + +import content.data.Quests +import content.global.skill.fletching.log.GrammarHelpers.aOrAn +import content.global.skill.fletching.log.GrammarHelpers.makeFriendlyName +import core.api.* +import core.game.dialogue.SkillDialogueHandler +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import org.rs09.consts.Items + +@Suppress("unused") // Reflectively loaded +class LogCraftableListeners : InteractionListener { + override fun defineListeners() { + onUseWith(IntType.ITEM, Items.KNIFE_946, *LogCraftInfo.logIds) { player, knife, log -> + val applicableCraftInfosForLog = LogCraftInfo.forLogId(log.id) ?: return@onUseWith false + val craftableItems = + applicableCraftInfosForLog.map { logCraftable -> Item(logCraftable.finishedItemId) }.toTypedArray() + + val handler: SkillDialogueHandler = object : SkillDialogueHandler( + player, + SkillDialogue.forLength(applicableCraftInfosForLog.size), + *craftableItems + ) { + override fun create(amount: Int, index: Int) { + if (!playerMeetsInitialRequirements(applicableCraftInfosForLog[index])) return + CraftItemWithLogScript(player, applicableCraftInfosForLog[index], amount).invoke() + } + + private fun playerMeetsInitialRequirements(logCraftInfo: LogCraftInfo): Boolean { + if (getDynLevel(player, Skills.FLETCHING) < logCraftInfo.level) { + sendLevelCheckFailDialog(player, logCraftInfo) + return false + } + if (logCraftInfo == LogCraftInfo.OGRE_ARROW_SHAFT && + !isQuestStarted(player, Quests.BIG_CHOMPY_BIRD_HUNTING) + ) { + sendDialogue(player, "You must have started ${Quests.BIG_CHOMPY_BIRD_HUNTING} to make those.") + return false + } + if (logCraftInfo == LogCraftInfo.OGRE_COMP_BOW) { + if (getQuestStage(player, Quests.ZOGRE_FLESH_EATERS) < 8) { + sendMessage( + player, + "You must have started Zogre Flesh Eaters and asked Grish to string this." + ) + return false + } + if (amountInInventory(player, Items.WOLF_BONES_2859) < 1) { + sendMessage(player, "You need to have Wolf Bones in order to make this.") + return false + } + } + return true + } + + override fun getAll(index: Int): Int { + return amountInInventory(player, log.id) + } + } + handler.open() + return@onUseWith true + } + } + + companion object { + fun sendLevelCheckFailDialog(player: Player, logCraftInfo: LogCraftInfo) { + val finishedItem = Item(logCraftInfo.finishedItemId) + val friendlyCraftedItemName = makeFriendlyName(finishedItem.name) + sendDialogue( + player, + "You need a Fletching skill of ${logCraftInfo.level} or above to make ${ + aOrAn( + friendlyCraftedItemName + ) + } $friendlyCraftedItemName." + ) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/stringing/StringItemScript.kt b/Server/src/main/content/global/skill/fletching/stringing/StringItemScript.kt new file mode 100644 index 000000000..633e29667 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/stringing/StringItemScript.kt @@ -0,0 +1,71 @@ +package content.global.skill.fletching.stringing + +import content.global.skill.fletching.AchievementDiaryAttributeKeys +import content.global.skill.fletching.Zones +import content.global.skill.fletching.stringing.StringableCraftInfo.Companion.applicableStringId +import core.api.* +import core.game.interaction.Clocks +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 + +/** + * Represents queueScript to string a bow/crossbow + * @author Ceikry + * @param player the player. + * @param stringableCraftInfo contains crafting information about what we're stringing + * @param amount the amount of items to string + */ +class StringItemScript( + private val player: Player, + private val stringableCraftInfo: StringableCraftInfo, + private var amount: Int +) { + private val initialDelay = 1 + private val subsequentDelay = 2 + + fun invoke() { + queueScript(player, initialDelay) { stage -> + if (!clockReady(player, Clocks.SKILLING)) return@queueScript keepRunning(player) + + if (getDynLevel(player, Skills.FLETCHING) < stringableCraftInfo.level) { + StringingListeners.sendLevelCheckFailDialogue(player, stringableCraftInfo.level) + return@queueScript stopExecuting(player) // Check each iteration since dynLevel can change (status effects ending, skill assist session end...) + } + + if ( + removeItemsIfPlayerHasEnough( + player, + Item(stringableCraftInfo.unstrungItemId), + Item(stringableCraftInfo.applicableStringId) + ) + ) { + player.animate(stringableCraftInfo.animation) + + addItem(player, stringableCraftInfo.strungItemId, 1) + rewardXP(player, Skills.FLETCHING, stringableCraftInfo.experience) + sendMessage(player, "You add a string to the bow.") + if (stringableCraftInfo == StringableCraftInfo.MAGIC_SHORTBOW) { + handleSeersMagicShortbowAchievement() + } + } else { + return@queueScript stopExecuting(player) + } + + if (stage >= amount - 1) { + return@queueScript stopExecuting(player) + } + + return@queueScript delayClock(player, Clocks.SKILLING, subsequentDelay, true) + } + } + + private fun handleSeersMagicShortbowAchievement() { + if (Zones.inAnyZone(player, Zones.seersMagicShortbowAchievementZones) && + getAttribute(player, AchievementDiaryAttributeKeys.FLETCHED_UNSTRUNG_MAGIC_SHORTBOW, false) + ) { + player.achievementDiaryManager.finishTask(player, DiaryType.SEERS_VILLAGE, 2, 2) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/stringing/StringableCraftInfo.kt b/Server/src/main/content/global/skill/fletching/stringing/StringableCraftInfo.kt new file mode 100644 index 000000000..38c089621 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/stringing/StringableCraftInfo.kt @@ -0,0 +1,66 @@ +package content.global.skill.fletching.stringing + +import core.game.world.update.flag.context.Animation +import org.rs09.consts.Items + +/** + * Provides information pertaining to crafting different strung items + * @property isCrossbow is this enum for a crossbow? + * @property unstrungItemId the unstrung item Id + * @property strungItemId the resulting strung item Id + * @property level the level required to string the item + * @property experience experience gained from stringing + * @property animation the stringing animation to play + */ +enum class StringableCraftInfo( + val isCrossbow: Boolean, + val unstrungItemId: Int, + val strungItemId: Int, + val level: Int, + val experience: Double, + val animation: Animation +) { + SHORT_BOW(false, Items.SHORTBOW_U_50, Items.SHORTBOW_841, 5, 5.0, Animation(6678)), + LONG_BOW(false, Items.LONGBOW_U_48, Items.LONGBOW_839, 10, 10.0, Animation(6684)), + OAK_SHORTBOW(false, Items.OAK_SHORTBOW_U_54, Items.OAK_SHORTBOW_843, 20, 16.5, Animation(6679)), + OAK_LONGBOW(false, Items.OAK_LONGBOW_U_56, Items.OAK_LONGBOW_845, 25, 25.0, Animation(6685)), + OGRE_COMP_BOW(false, Items.UNSTRUNG_COMP_BOW_4825, Items.COMP_OGRE_BOW_4827, 30, 45.0, Animation(-1)), + WILLOW_SHORTBOW(false, Items.WILLOW_SHORTBOW_U_60, Items.WILLOW_SHORTBOW_849, 35, 33.3, Animation(6680)), + WILLOW_LONGBOW(false, Items.WILLOW_LONGBOW_U_58, Items.WILLOW_LONGBOW_847, 40, 41.5, Animation(6686)), + MAPLE_SHORTBOW(false, Items.MAPLE_SHORTBOW_U_64, Items.MAPLE_SHORTBOW_853, 50, 50.0, Animation(6681)), + MAPLE_LONGBOW(false, Items.MAPLE_LONGBOW_U_62, Items.MAPLE_LONGBOW_851, 55, 58.3, Animation(6687)), + YEW_SHORTBOW(false, Items.YEW_SHORTBOW_U_68, Items.YEW_SHORTBOW_857, 65, 67.5, Animation(6682)), + YEW_LONGBOW(false, Items.YEW_LONGBOW_U_66, Items.YEW_LONGBOW_855, 70, 75.0, Animation(6688)), + MAGIC_SHORTBOW(false, Items.MAGIC_SHORTBOW_U_72, Items.MAGIC_SHORTBOW_861, 80, 83.3, Animation(6683)), + MAGIC_LONGBOW(false, Items.MAGIC_LONGBOW_U_70, Items.MAGIC_LONGBOW_859, 85, 91.5, Animation(6689)), + + BRONZE_CBOW(true, Items.BRONZE_CBOW_U_9454, Items.BRONZE_CROSSBOW_9174, 9, 6.0, Animation(6671)), + BLURITE_CBOW(true, Items.BLURITE_CBOW_U_9456, Items.BLURITE_CROSSBOW_9176, 24, 16.0, Animation(6672)), + IRON_CBOW(true, Items.IRON_CBOW_U_9457, Items.IRON_CROSSBOW_9177, 39, 22.0, Animation(6673)), + STEEL_CBOW(true, Items.STEEL_CBOW_U_9459, Items.STEEL_CROSSBOW_9179, 46, 27.0, Animation(6674)), + MITHIRIL_CBOW(true, Items.MITHRIL_CBOW_U_9461, Items.MITH_CROSSBOW_9181, 54, 32.0, Animation(6675)), + ADAMANT_CBOW(true, Items.ADAMANT_CBOW_U_9463, Items.ADAMANT_CROSSBOW_9183, 61, 41.0, Animation(6676)), + RUNITE_CBOW(true, Items.RUNITE_CBOW_U_9465, Items.RUNE_CROSSBOW_9185, 69, 50.0, Animation(6677)); + + companion object { + val unstrungBowIds: IntArray = values().map { stringableCraftInfo: StringableCraftInfo -> stringableCraftInfo.unstrungItemId }.toIntArray() + val stringItemIds: IntArray = intArrayOf(Items.BOW_STRING_1777,Items.CROSSBOW_STRING_9438) + + private val stringableCraftInfoByUnstrungItemId = values().associateBy { it.unstrungItemId } + + fun forUnstrungBowItemId(unstrungItemId: Int): StringableCraftInfo? { + return stringableCraftInfoByUnstrungItemId[unstrungItemId] + } + + /** + * Returns the item id that should be used for stringing this enums unstrung bow + */ + val StringableCraftInfo.applicableStringId: Int + get() { + return when { + this.isCrossbow -> Items.CROSSBOW_STRING_9438 + else -> Items.BOW_STRING_1777 + } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/stringing/StringingListeners.kt b/Server/src/main/content/global/skill/fletching/stringing/StringingListeners.kt new file mode 100644 index 000000000..051f8102b --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/stringing/StringingListeners.kt @@ -0,0 +1,69 @@ +package content.global.skill.fletching.stringing + +import content.data.Quests +import content.global.skill.fletching.stringing.StringableCraftInfo.Companion.applicableStringId +import core.api.* +import core.game.dialogue.SkillDialogueHandler +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import core.net.packet.PacketRepository +import core.net.packet.context.ChildPositionContext +import core.net.packet.out.RepositionChild +import org.rs09.consts.Components + +@Suppress("unused") // Reflectively loaded +class StringingListeners : InteractionListener { + override fun defineListeners() { + onUseWith( + IntType.ITEM, + StringableCraftInfo.stringItemIds, + *StringableCraftInfo.unstrungBowIds + ) { player, string, unstrungBow -> + val stringableCraftInfo = StringableCraftInfo.forUnstrungBowItemId(unstrungBow.id) ?: return@onUseWith false + if (string.id != stringableCraftInfo.applicableStringId) { + sendMessage(player, "That's not the right kind of string for this.") + return@onUseWith true + } + if (stringableCraftInfo == StringableCraftInfo.OGRE_COMP_BOW && getQuestStage(player, Quests.ZOGRE_FLESH_EATERS) < 8) { + sendMessage(player, "You must have started Zogre Flesh Eaters and asked Grish to string this.") + return@onUseWith true + } + val handler: SkillDialogueHandler = + object : + SkillDialogueHandler(player, SkillDialogue.ONE_OPTION, Item(stringableCraftInfo.strungItemId)) { + override fun create(amount: Int, index: Int) { + if (getDynLevel(player, Skills.FLETCHING) < stringableCraftInfo.level) { + sendLevelCheckFailDialogue(player, stringableCraftInfo.level) + return + } + StringItemScript(player, stringableCraftInfo, amount).invoke() + } + + override fun getAll(index: Int): Int { + return amountInInventory(player, string.id) + } + } + handler.open() + fixSpacingBetweenTextAndCraftedItemIcon(player) + return@onUseWith true + } + } + + private fun fixSpacingBetweenTextAndCraftedItemIcon(player: Player) { + PacketRepository.send( + RepositionChild::class.java, + ChildPositionContext(player, Components.SKILL_MULTI1_309, 2, 215, 10) + ) + } + companion object { + fun sendLevelCheckFailDialogue(player: Player, level: Int) { + sendDialogue( + player, + "You need a fletching level of $level to string this bow." + ) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/gather/mining/MiningListener.kt b/Server/src/main/content/global/skill/gather/mining/MiningListener.kt index dffafdb7f..ea9de8c49 100644 --- a/Server/src/main/content/global/skill/gather/mining/MiningListener.kt +++ b/Server/src/main/content/global/skill/gather/mining/MiningListener.kt @@ -26,11 +26,11 @@ import org.rs09.consts.Items class MiningListener : InteractionListener { override fun defineListeners() { defineInteraction( - IntType.SCENERY, - MiningNode.values().map { it.id }.toIntArray(), - "mine", - persistent = true, allowedDistance = 1, - handler = ::handleMining + IntType.SCENERY, + MiningNode.values().map { it.id }.toIntArray(), + "mine", + persistent = true, allowedDistance = 1, + handler = ::handleMining ) } private val GEM_REWARDS = arrayOf(ChanceItem(1623, 1, DropFrequency.COMMON), ChanceItem(1621, 1, DropFrequency.COMMON), ChanceItem(1619, 1, DropFrequency.UNCOMMON), ChanceItem(1617, 1, DropFrequency.RARE)) @@ -38,25 +38,25 @@ class MiningListener : InteractionListener { private fun handleMining(player: Player, node: Node, state: Int) : Boolean { val resource = MiningNode.forId(node.id) val tool = SkillingTool.getPickaxe(player) - val isEssence = resource.id == 2491 + val isEssence = resource == MiningNode.RUNE_ESSENCE val isGems = resource.identifier == MiningNode.GEM_ROCK_0.identifier if (!finishedMoving(player)) return true if (state == 0) { - if (!checkRequirements(player, resource, node)) { - player.scripts.reset() - return true - } + if (!checkRequirements(player, resource, node)) { + player.scripts.reset() + return true + } anim(player, tool) - sendMessage(player, "You swing your pickaxe at the rock...") - return delayScript(player, getDelay(resource)) + sendMessage(player, "You swing your pickaxe at the rock.") + return delayScript(player, getDelay(resource, tool)) } anim(player, tool) if (!checkReward(player, resource, tool)) - return delayScript(player, getDelay(resource)) + return delayScript(player, getDelay(resource, tool)) // Reward logic var reward = resource!!.reward @@ -94,13 +94,23 @@ class MiningListener : InteractionListener { if (reward == Items.GOLD_ORE_444 && inBorders(player, familyCrestGoldOreArea)) { reward = Items.PERFECT_GOLD_ORE_446 } - val rewardName = getItemName(reward).lowercase() + + // Prepare reward name - gems stay lowercase without "Uncut", others are lowercase + val rewardName = if (isGems) { + getItemName(reward).replace("Uncut ", "").lowercase() + } else { + getItemName(reward).substringBefore(" (").lowercase().removeSuffix(" ore") + } // Send the message for the resource reward if (isGems) { - sendMessage(player, "You get ${prependArticle(rewardName)}.") - } else { - sendMessage(player, "You get some ${rewardName.lowercase()}.") + val withArticle = prependArticle(rewardName) // "an emerald" or "a red topaz" + val parts = withArticle.split(" ", limit = 2) // ["an", "emerald"] or ["a", "red topaz"] + val article = parts[0] // "an" or "a" + val gemName = parts[1].replaceFirstChar { it.uppercase() } // "Emerald" or "Red topaz" + sendMessage(player, "You just mined $article $gemName!") + } else if (!isEssence) { + sendMessage(player, "You manage to mine some ${rewardName}.") } // Give the mining reward, increment 'rocks mined' attribute @@ -124,7 +134,7 @@ class MiningListener : InteractionListener { } if (RandomFunction.roll(chance)) { val gem = GEM_REWARDS.random() - sendMessage(player,"You find a ${gem.name}!") + sendMessage(player,"You just found ${prependArticle(gem.name.replace("Uncut ", "").lowercase())}!") if (freeSlots(player) == 0) { sendMessage(player,"You do not have enough space in your inventory, so you drop the gem on the floor.") } @@ -138,6 +148,14 @@ class MiningListener : InteractionListener { node.setActive(false) return true } + + // For essence, check inventory and continue mining + if (isEssence) { + if (freeSlots(player) == 0) { + return true + } + return delayScript(player, getDelay(resource, tool)) + } } return true } @@ -189,6 +207,10 @@ class MiningListener : InteractionListener { } private fun checkReward(player: Player, resource: MiningNode?, tool: SkillingTool): Boolean { + // Essence mining always succeeds + if (resource == MiningNode.RUNE_ESSENCE) { + return true + } val level = 1 + getDynLevel(player, Skills.MINING) + getFamiliarBoost(player, Skills.MINING) val hostRatio = Math.random() * (100.0 * resource!!.rate) var toolRatio = tool.ratio @@ -199,8 +221,20 @@ class MiningListener : InteractionListener { return hostRatio < clientRatio } - fun getDelay(resource: MiningNode) : Int { - return if (resource.id == 2491) 3 else 4 + fun getDelay(resource: MiningNode, tool: SkillingTool) : Int { + if (resource == MiningNode.RUNE_ESSENCE) { // Essence mining - speed varies by pickaxe + return when (tool) { + SkillingTool.BRONZE_PICKAXE -> 7 + SkillingTool.IRON_PICKAXE -> 6 + SkillingTool.STEEL_PICKAXE -> 5 + SkillingTool.MITHRIL_PICKAXE -> 4 + SkillingTool.ADAMANT_PICKAXE -> 3 + SkillingTool.RUNE_PICKAXE -> 2 + SkillingTool.INFERNO_ADZE2 -> if (RandomFunction.random(2) == 0) 1 else 2 //https://www.youtube.com/watch?v=9XhjSdJ4qro + else -> 4 // fallback + } + } + return 4 // normal rocks } fun anim(player: Player, tool: SkillingTool) { @@ -222,9 +256,13 @@ class MiningListener : InteractionListener { sendDialogue(player,"Your inventory is too full to hold any more gems.") return false } + if (resource == MiningNode.RUNE_ESSENCE) { + sendDialogue(player,"Your inventory is too full to hold any more essence.") + return false + } sendDialogue(player,"Your inventory is too full to hold any more ${ItemDefinition.forId(resource!!.reward).name.lowercase()}.") return false } return node.isActive } -} +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt b/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt index 87efe8614..6b1be0ab0 100644 --- a/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt +++ b/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt @@ -1,5 +1,6 @@ package content.global.skill.gather.woodcutting +import content.data.Quests import content.data.skill.SkillingTool import content.data.tables.BirdNest import content.global.skill.farming.FarmingPatch.Companion.forObject @@ -72,6 +73,17 @@ class WoodcuttingListener : InteractionListener { if (clockReady(player, Clocks.SKILLING)) { animateWoodcutting(player) + + if (resource == WoodcuttingNode.DRAMEN_TREE) { + // Reward after one chop and then abort chopping (this is authentic) + queueScript(player, 1, QueueStrength.STRONG) { + sendMessage(player, "You cut a branch from the Dramen tree.") + addItem(player, resource.getReward()) + return@queueScript clearScripts(player) + } + return delayClock(player, Clocks.SKILLING, 1) + } + if (!checkReward(player, resource, tool) && !getAttribute(player, "instachop", false)) return delayClock(player, Clocks.SKILLING, 3) @@ -115,9 +127,7 @@ class WoodcuttingListener : InteractionListener { player.getSkills().addExperience(Skills.WOODCUTTING, experience, true) //send the message for the resource reward - if (resource == WoodcuttingNode.DRAMEN_TREE) { - player.packetDispatch.sendMessage("You cut a branch from the Dramen tree.") - } else if (reward == Items.BARK_3239 && rewardAmount == 0) { + if (reward == Items.BARK_3239 && rewardAmount == 0) { player.packetDispatch.sendMessage("You chop away some bark, but it falls to pieces before you can pick it up.") } else { player.packetDispatch.sendMessage("You get some " + ItemDefinition.forId(reward).name.lowercase(Locale.getDefault()) + ".") @@ -229,6 +239,10 @@ class WoodcuttingListener : InteractionListener { player.packetDispatch.sendMessage("You do not have an axe to use.") return false } + if (node.id == org.rs09.consts.Scenery.DRAMEN_TREE_1292 && getQuestStage(player, Quests.LOST_CITY) <= 20) { + //TODO: find out if there is any authentic message to be shown + return false + } if (player.inventory.freeSlots() < 1 && node.isActive) { player.sendMessage("Your inventory is too full to hold any more " + ItemDefinition.forId(resource.getReward()).name.lowercase(Locale.getDefault()) + ".") return false diff --git a/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingSkillPulse.java b/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingSkillPulse.java index 1fe03050c..d69c7fea1 100644 --- a/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingSkillPulse.java +++ b/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingSkillPulse.java @@ -162,13 +162,8 @@ public class WoodcuttingSkillPulse extends Pulse { player.getSkills().addExperience(Skills.WOODCUTTING, experience, true); - //send the message for the resource reward, and in the case of the dramen tree, authentically abort the chopping action - if (resource == WoodcuttingNode.DRAMEN_TREE) { - player.getPacketDispatch().sendMessage("You cut a branch from the Dramen tree."); - stop(); - } else { - player.getPacketDispatch().sendMessage("You get some " + ItemDefinition.forId(reward).getName().toLowerCase() + "."); - } + //send the message for the resource reward + player.getPacketDispatch().sendMessage("You get some " + ItemDefinition.forId(reward).getName().toLowerCase() + "."); //give the reward player.getInventory().add(new Item(reward, rewardAmount)); player.dispatch(new ResourceProducedEvent(reward, rewardAmount, node, -1)); diff --git a/Server/src/main/content/global/skill/herblore/GrindItemPlugin.kt b/Server/src/main/content/global/skill/herblore/GrindItemPlugin.kt index ec9cd70c4..b87ead3f4 100644 --- a/Server/src/main/content/global/skill/herblore/GrindItemPlugin.kt +++ b/Server/src/main/content/global/skill/herblore/GrindItemPlugin.kt @@ -61,8 +61,7 @@ class GrindItemPlugin : UseWithHandler(233) { override fun reward(): Boolean { if (node.id == Items.FISHING_BAIT_313) { - var quantity = 0 - quantity = if (amountInInventory(player, FISHING_BAIT) >= 10) { + val quantity: Int = if (amountInInventory(player, FISHING_BAIT) >= 10) { 10 } else { amountInInventory(player, FISHING_BAIT) diff --git a/Server/src/main/content/global/skill/magic/HomeTeleportHelper.kt b/Server/src/main/content/global/skill/magic/HomeTeleportHelper.kt new file mode 100644 index 000000000..1b67a38e9 --- /dev/null +++ b/Server/src/main/content/global/skill/magic/HomeTeleportHelper.kt @@ -0,0 +1,46 @@ +package content.global.skill.magic + +import core.api.delayScript +import core.api.playGlobalAudio +import core.api.queueScript +import core.api.sendMessage +import core.api.stopExecuting +import core.game.event.TeleportEvent +import core.game.interaction.QueueStrength +import core.game.node.entity.player.Player +import core.game.node.entity.player.link.TeleportManager +import core.game.world.map.Location +import core.game.world.map.zone.ZoneRestriction + +val HOME_ANIMATIONS = arrayOf(1722, 1723, 1724, 1725, 2798, 2799, 2800, 3195, 4643, 4645, 4646, 4847, 4848, 4849, 4850, 4851, 4852, 65535) +val HOME_GRAPHICS = arrayOf(775, 800, 801, 802, 803, 804, 1703, 1704, 1705, 1706, 1707, 1708, 1709, 1710, 1711, 1712, 1713, 65535) +fun getAudio(count: Int): Int { + return when (count) { + 0 -> 193 + 4 -> 194 + 11 -> 195 + else -> -1 + } +} + +fun homeTeleport(player: Player, dest: Location) { + if (player.timers.getTimer("teleblock") != null) { + sendMessage(player, "A magical force prevents you from teleporting.") + return + } + if (player.locks.isTeleportLocked || player.zoneMonitor.isRestricted(ZoneRestriction.TELEPORT)) { + sendMessage(player, "A magical force has stopped you from teleporting.") + return + } + queueScript(player, 0, QueueStrength.WEAK) { stage -> + if (stage == 18) { + player.properties.teleportLocation = dest + player.dispatch(TeleportEvent(TeleportManager.TeleportType.NORMAL, TeleportMethod.SPELL, -1, dest)) + return@queueScript stopExecuting(player) + } + playGlobalAudio(player.location, getAudio(stage)) + player.packetDispatch.sendGraphic(HOME_GRAPHICS[stage]) + player.packetDispatch.sendAnimation(HOME_ANIMATIONS[stage]) + return@queueScript delayScript(player, 1) + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/magic/SpellListeners.kt b/Server/src/main/content/global/skill/magic/SpellListeners.kt index 95ff686a0..e390516e3 100644 --- a/Server/src/main/content/global/skill/magic/SpellListeners.kt +++ b/Server/src/main/content/global/skill/magic/SpellListeners.kt @@ -1,14 +1,14 @@ package content.global.skill.magic +import core.api.hasLineOfSight +import core.api.log import core.game.event.SpellCastEvent -import core.api.* +import core.game.interaction.MovementPulse import core.game.node.Node import core.game.node.entity.player.Player import core.game.node.entity.player.link.SpellBookManager -import core.tools.Log -import core.tools.SystemLogger -import core.game.interaction.* import core.game.world.map.path.Pathfinder +import core.tools.Log object SpellListeners { val castMap = HashMap Unit>() @@ -44,7 +44,7 @@ object SpellListeners { range = next.first method = next.second ?: return } - + player.scripts.removeWeakScripts() if (type in intArrayOf (SpellListener.NPC, SpellListener.OBJECT, SpellListener.PLAYER, SpellListener.GROUND_ITEM)) { player.pulseManager.run (object : MovementPulse (player, node, Pathfinder.SMART) { override fun pulse() : Boolean { diff --git a/Server/src/main/content/global/skill/magic/ancient/AncientTeleportPlugin.java b/Server/src/main/content/global/skill/magic/ancient/AncientTeleportPlugin.java index 3944c3c7e..8b8e2c693 100644 --- a/Server/src/main/content/global/skill/magic/ancient/AncientTeleportPlugin.java +++ b/Server/src/main/content/global/skill/magic/ancient/AncientTeleportPlugin.java @@ -1,5 +1,6 @@ package content.global.skill.magic.ancient; +import core.game.node.entity.player.Player; import core.game.node.entity.player.link.diary.DiaryType; import core.game.node.entity.combat.spell.MagicSpell; import core.game.node.entity.combat.spell.Runes; @@ -13,8 +14,12 @@ import core.game.world.GameWorld; import core.game.world.map.Location; import core.plugin.Initializable; import core.plugin.Plugin; +import core.tools.Log; import core.tools.RandomFunction; +import static content.global.skill.magic.HomeTeleportHelperKt.homeTeleport; +import static core.api.ContentAPIKt.log; + /** * Represents the plugin used to handle all ancient teleporting plugins. * @author 'Vexia @@ -55,7 +60,14 @@ public final class AncientTeleportPlugin extends MagicSpell { entity.asPlayer().sendMessage("A magical force has stopped you from teleporting."); return false; } - if (entity.getTeleporter().send(location.transform(0, RandomFunction.random(3), 0), getSpellId() == 28 ? TeleportType.HOME : TeleportType.ANCIENT)) { + boolean isHomeTeleport = getSpellId() == 28; + if (isHomeTeleport) { + if (!entity.isPlayer()) { + log(this.getClass(), Log.ERR, "Why the fuck is a non-player entity trying to cast ancient-magick home teleport?!"); + return false; + } + homeTeleport((Player) entity, Location.create(3087, 3495, 0)); + } else if (entity.getTeleporter().send(location.transform(0, RandomFunction.random(3), 0), TeleportType.ANCIENT)) { if (!super.meetsRequirements(entity, true, true)) { entity.getTeleporter().getCurrentTeleport().stop(); return false; @@ -86,7 +98,7 @@ public final class AncientTeleportPlugin extends MagicSpell { // dareeyak teleport SpellBook.ANCIENT.register(24, new AncientTeleportPlugin(78, 88, Location.create(2966, 3696, 0), new Item(Runes.LAW_RUNE.getId(), 2), new Item(Runes.FIRE_RUNE.getId(), 3), new Item(Runes.AIR_RUNE.getId(), 2))); // carralangar teleport - SpellBook.ANCIENT.register(25, new AncientTeleportPlugin(84, 82, Location.create(3163, 3664, 0), new Item(Runes.SOUL_RUNE.getId(), 2), new Item(Runes.LAW_RUNE.getId(), 2))); + SpellBook.ANCIENT.register(25, new AncientTeleportPlugin(84, 82, Location.create(3217, 3676, 0), new Item(Runes.SOUL_RUNE.getId(), 2), new Item(Runes.LAW_RUNE.getId(), 2))); // annakarl teleport SpellBook.ANCIENT.register(26, new AncientTeleportPlugin(90, 100, Location.create(3287, 3883, 0), new Item(Runes.BLOOD_RUNE.getId(), 2), new Item(Runes.LAW_RUNE.getId(), 2))); // ghorrock teleport diff --git a/Server/src/main/content/global/skill/magic/ancient/IceSpells.java b/Server/src/main/content/global/skill/magic/ancient/IceSpells.java index e4f287574..4b0e29ff4 100644 --- a/Server/src/main/content/global/skill/magic/ancient/IceSpells.java +++ b/Server/src/main/content/global/skill/magic/ancient/IceSpells.java @@ -1,162 +1,160 @@ -package content.global.skill.magic.ancient; - -import java.util.List; - -import core.game.node.entity.combat.spell.Runes; -import core.game.node.Node; -import core.game.node.entity.Entity; -import core.game.node.entity.combat.BattleState; -import core.game.node.entity.combat.spell.CombatSpell; -import core.game.node.entity.combat.spell.SpellType; -import core.game.node.entity.impl.Projectile; -import core.game.node.entity.impl.Animator.Priority; -import core.game.node.entity.player.link.SpellBookManager.SpellBook; -import core.game.node.item.Item; -import core.game.world.update.flag.context.Animation; -import core.game.world.update.flag.context.Graphics; -import core.plugin.Initializable; -import core.plugin.Plugin; -import org.rs09.consts.Sounds; - -import static core.api.ContentAPIKt.*; - -/** - * Handles the Ice spells from the Ancient spellbook. - * @author Emperor - * @version 1.0 - */ -@Initializable -public final class IceSpells extends CombatSpell { - - /** - * The barrage orb GFX. - */ - private static final Graphics BARRAGE_ORB = new Graphics(1677, 96); //119 - - /** - * The projectile for Ice rush. - */ - private static final Projectile RUSH_PROJECTILE = Projectile.create((Entity) null, null, 360, 40, 36, 52, 75, 15, 11); - - /** - * The end graphic for Ice rush. - */ - private static final Graphics RUSH_END = new Graphics(361, 96); - - /** - * The projectile for Ice barrage. - */ - private static final Projectile BURST_PROJECTILE = Projectile.create((Entity) null, null, 362, 40, 36, 52, 75, 15, 11); - - /** - * The end graphic for Ice barrage. - */ - private static final Graphics BURST_END = new Graphics(363, 0); - - /** - * The start graphic for Ice rush. - */ - private static final Graphics BLITZ_START = new Graphics(366, 96); - - /** - * The end graphic for Ice rush. - */ - private static final Graphics BLITZ_END = new Graphics(367, 96); - - /** - * The projectile for Ice barrage. - */ - private static final Projectile BARRAGE_PROJECTILE = Projectile.create((Entity) null, null, 368, 40, 36, 52, 75, 15, 11); - - /** - * The end graphic for Ice barrage. - */ - private static final Graphics BARRAGE_END = new Graphics(369, 0); - - /** - * Constructs a new {@code IceSpells} {@code Object}. - */ - public IceSpells() { - /* - * empty. - */ - } - - /** - * Constructs a new {@code IceSpells} {@Code Object} - * @param type The spell type. - * @param impactSound The impact sound id. - * @param anim The animation. - * @param start The start graphics. - * @param projectile The projectile. - * @param end The end graphics. - */ - private IceSpells(SpellType type, int level, double baseExperience, int impactSound, Animation anim, Graphics start, Projectile projectile, Graphics end, Item... runes) { - super(type, SpellBook.ANCIENT, level, baseExperience, Sounds.ICE_CAST_171, impactSound, anim, start, projectile, end, runes); - } - - @Override - public void visualize(Entity entity, Node target) { - entity.graphics(graphic); - if (projectile != null) { - projectile.transform(entity, (Entity) target, false, 58, 10).send(); - } - entity.animate(animation); - playGlobalAudio(entity.getLocation(), audio.id, 20); - - } - - @Override - public void visualizeImpact(Entity entity, Entity target, BattleState state) { - if (state.isFrozen()) { - playGlobalAudio(target.getLocation(), impactAudio, 20); - target.graphics(BARRAGE_ORB); - return; - } - super.visualizeImpact(entity, target, state); - } - - @Override - public Plugin newInstance(SpellType arg) throws Throwable { - SpellBook.ANCIENT.register(0, new IceSpells(SpellType.RUSH, 58, 34.0, Sounds.ICE_RUSH_IMPACT_173, new Animation(1978, Priority.HIGH), null, RUSH_PROJECTILE, RUSH_END, Runes.DEATH_RUNE.getItem(2), Runes.CHAOS_RUNE.getItem(2), Runes.WATER_RUNE.getItem(2))); - SpellBook.ANCIENT.register(2, new IceSpells(SpellType.BURST, 70, 40.0, Sounds.ICE_BURST_IMPACT_170, new Animation(1979, Priority.HIGH), null, BURST_PROJECTILE, BURST_END, Runes.DEATH_RUNE.getItem(2), Runes.CHAOS_RUNE.getItem(4), Runes.WATER_RUNE.getItem(4))); - SpellBook.ANCIENT.register(1, new IceSpells(SpellType.BLITZ, 82, 46.0, Sounds.ICE_BLITZ_IMPACT_169, new Animation(1978, Priority.HIGH), BLITZ_START, null, BLITZ_END, Runes.BLOOD_RUNE.getItem(2), Runes.DEATH_RUNE.getItem(2), Runes.WATER_RUNE.getItem(3))); - SpellBook.ANCIENT.register(3, new IceSpells(SpellType.BARRAGE, 94, 52.0, Sounds.ICE_BARRAGE_IMPACT_168, new Animation(1979, Priority.HIGH), null, BARRAGE_PROJECTILE, BARRAGE_END, Runes.BLOOD_RUNE.getItem(2), Runes.DEATH_RUNE.getItem(4), Runes.WATER_RUNE.getItem(6))); - return this; - } - - @Override - public void fireEffect(Entity entity, Entity victim, BattleState state) { - if (state.getEstimatedHit() == -1) { - return; - } - int ticks = (1 + (type.ordinal() - SpellType.RUSH.ordinal())) * 8; - if (state.getEstimatedHit() > -1) { - if (!hasTimerActive(victim, "frozen:immunity")) { - registerTimer(victim, spawnTimer("frozen", ticks, true)); - } else if (type == SpellType.BARRAGE) { - state.setFrozen(true); - } - } - } - - @Override - public BattleState[] getTargets(Entity entity, Entity target) { - if (animation.getId() == 1978 || !entity.getProperties().isMultiZone() || !target.getProperties().isMultiZone()) { - return super.getTargets(entity, target); - } - List list = getMultihitTargets(entity, target, 9); - BattleState[] targets = new BattleState[list.size()]; - int index = 0; - for (Entity e : list) { - targets[index++] = new BattleState(entity, e); - } - return targets; - } - - @Override - public int getMaximumImpact(Entity entity, Entity victim, BattleState state) { - return getType().getImpactAmount(entity, victim, 4); - } - -} +package content.global.skill.magic.ancient; + +import java.util.List; + +import core.game.node.entity.combat.spell.Runes; +import core.game.node.Node; +import core.game.node.entity.Entity; +import core.game.node.entity.combat.BattleState; +import core.game.node.entity.combat.spell.CombatSpell; +import core.game.node.entity.combat.spell.SpellType; +import core.game.node.entity.impl.Projectile; +import core.game.node.entity.impl.Animator.Priority; +import core.game.node.entity.player.link.SpellBookManager.SpellBook; +import core.game.node.item.Item; +import core.game.world.update.flag.context.Animation; +import core.game.world.update.flag.context.Graphics; +import core.plugin.Initializable; +import core.plugin.Plugin; +import org.rs09.consts.Sounds; + +import static core.api.ContentAPIKt.*; + +/** + * Handles the Ice spells from the Ancient spellbook. + * @author Emperor + * @version 1.0 + */ +@Initializable +public final class IceSpells extends CombatSpell { + + /** + * The barrage orb GFX. + */ + private static final Graphics BARRAGE_ORB = new Graphics(1677, 96); //119 + + /** + * The projectile for Ice rush. + */ + private static final Projectile RUSH_PROJECTILE = Projectile.create((Entity) null, null, 360, 40, 36, 52, 75, 15, 11); + + /** + * The end graphic for Ice rush. + */ + private static final Graphics RUSH_END = new Graphics(361, 96); + + /** + * The projectile for Ice barrage. + */ + private static final Projectile BURST_PROJECTILE = Projectile.create((Entity) null, null, 362, 40, 36, 52, 75, 15, 11); + + /** + * The end graphic for Ice barrage. + */ + private static final Graphics BURST_END = new Graphics(363, 0); + + /** + * The start graphic for Ice rush. + */ + private static final Graphics BLITZ_START = new Graphics(366, 96); + + /** + * The end graphic for Ice rush. + */ + private static final Graphics BLITZ_END = new Graphics(367, 96); + + /** + * The projectile for Ice barrage. + */ + private static final Projectile BARRAGE_PROJECTILE = Projectile.create((Entity) null, null, 368, 40, 36, 52, 75, 15, 11); + + /** + * The end graphic for Ice barrage. + */ + private static final Graphics BARRAGE_END = new Graphics(369, 0); + + /** + * Constructs a new {@code IceSpells} {@code Object}. + */ + public IceSpells() { + /* + * empty. + */ + } + + /** + * Constructs a new {@code IceSpells} {@Code Object} + * @param type The spell type. + * @param impactSound The impact sound id. + * @param anim The animation. + * @param start The start graphics. + * @param projectile The projectile. + * @param end The end graphics. + */ + private IceSpells(SpellType type, int level, double baseExperience, int impactSound, Animation anim, Graphics start, Projectile projectile, Graphics end, Item... runes) { + super(type, SpellBook.ANCIENT, level, baseExperience, Sounds.ICE_CAST_171, impactSound, anim, start, projectile, end, runes); + } + + @Override + public void visualize(Entity entity, Node target) { + entity.graphics(graphic); + if (projectile != null) { + projectile.transform(entity, (Entity) target, false, 58, 10).send(); + } + entity.animate(animation); + playGlobalAudio(entity.getLocation(), audio.id, 20); + + } + + @Override + public void visualizeImpact(Entity entity, Entity target, BattleState state) { + if (state.isFrozen()) { + playGlobalAudio(target.getLocation(), impactAudio, 20); + target.graphics(BARRAGE_ORB); + return; + } + super.visualizeImpact(entity, target, state); + } + + @Override + public Plugin newInstance(SpellType arg) throws Throwable { + SpellBook.ANCIENT.register(0, new IceSpells(SpellType.RUSH, 58, 34.0, Sounds.ICE_RUSH_IMPACT_173, new Animation(1978, Priority.HIGH), null, RUSH_PROJECTILE, RUSH_END, Runes.DEATH_RUNE.getItem(2), Runes.CHAOS_RUNE.getItem(2), Runes.WATER_RUNE.getItem(2))); + SpellBook.ANCIENT.register(2, new IceSpells(SpellType.BURST, 70, 40.0, Sounds.ICE_BURST_IMPACT_170, new Animation(1979, Priority.HIGH), null, BURST_PROJECTILE, BURST_END, Runes.DEATH_RUNE.getItem(2), Runes.CHAOS_RUNE.getItem(4), Runes.WATER_RUNE.getItem(4))); + SpellBook.ANCIENT.register(1, new IceSpells(SpellType.BLITZ, 82, 46.0, Sounds.ICE_BLITZ_IMPACT_169, new Animation(1978, Priority.HIGH), BLITZ_START, null, BLITZ_END, Runes.BLOOD_RUNE.getItem(2), Runes.DEATH_RUNE.getItem(2), Runes.WATER_RUNE.getItem(3))); + SpellBook.ANCIENT.register(3, new IceSpells(SpellType.BARRAGE, 94, 52.0, Sounds.ICE_BARRAGE_IMPACT_168, new Animation(1979, Priority.HIGH), null, BARRAGE_PROJECTILE, BARRAGE_END, Runes.BLOOD_RUNE.getItem(2), Runes.DEATH_RUNE.getItem(4), Runes.WATER_RUNE.getItem(6))); + return this; + } + + @Override + public void fireEffect(Entity entity, Entity victim, BattleState state) { + if (state.getEstimatedHit() == -1) { + return; + } + int ticks = (type.ordinal() - 4) * 8; + if (hasTimerActive(victim, "frozen") || hasTimerActive(victim, "frozen:immunity")) { + if (type == SpellType.BARRAGE) { state.setFrozen(true); } + return; + } + registerTimer(victim, spawnTimer("frozen", ticks, true)); + } + + @Override + public BattleState[] getTargets(Entity entity, Entity target) { + if (animation.getId() == 1978 || !entity.getProperties().isMultiZone() || !target.getProperties().isMultiZone()) { + return super.getTargets(entity, target); + } + List list = getMultihitTargets(entity, target, 9); + BattleState[] targets = new BattleState[list.size()]; + int index = 0; + for (Entity e : list) { + targets[index++] = new BattleState(entity, e); + } + return targets; + } + + @Override + public int getMaximumImpact(Entity entity, Entity victim, BattleState state) { + return getType().getImpactAmount(entity, victim, 4); + } + +} diff --git a/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt b/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt index 632173652..b89b1a3c7 100644 --- a/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt +++ b/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt @@ -5,6 +5,7 @@ import content.global.skill.farming.CompostBins import content.global.skill.farming.CompostType import content.global.skill.farming.FarmingPatch import content.global.skill.magic.SpellListener +import content.global.skill.magic.homeTeleport import content.global.skill.magic.spellconsts.Lunar import core.api.* import core.game.component.CloseEvent @@ -22,6 +23,7 @@ import core.game.system.command.Privilege import core.game.system.config.NPCConfigParser import core.game.system.task.Pulse import core.game.system.timer.impl.PoisonImmunity +import core.game.system.timer.impl.SkillRestore import core.game.world.map.Location import core.game.world.map.RegionManager import core.game.world.repository.Repository @@ -36,7 +38,7 @@ class LunarListeners : SpellListener("lunar"), Commands { // Level 0 onCast(Lunar.HOME_TELEPORT, NONE) { player, _ -> requires(player) - player.teleporter.send(Location.create(2100, 3914, 0),TeleportManager.TeleportType.HOME) + homeTeleport(player, Location.create(2100, 3914, 0)) setDelay(player,true) } @@ -298,7 +300,7 @@ class LunarListeners : SpellListener("lunar"), Commands { if(playerPies.isEmpty()){ player.sendMessage("You have no pies which you have the level to cook.") - return + throw IllegalStateException() } player.pulseManager.run(object : Pulse(){ @@ -326,33 +328,33 @@ class LunarListeners : SpellListener("lunar"), Commands { fun curePlant(player: Player, obj: Scenery) { if (CompostBins.forObject(obj) != null) { sendMessage(player, "Bins don't often get diseased.") - return + throw IllegalStateException() } val fPatch = FarmingPatch.forObject(obj) if (fPatch == null) { sendMessage(player, "Umm... this spell won't cure that!") - return + throw IllegalStateException() } val patch = fPatch.getPatchFor(player) if (patch.isWeedy()) { sendMessage(player, "The weeds are healthy enough already.") - return + throw IllegalStateException() } if (patch.isEmptyAndWeeded()) { sendMessage(player, "There's nothing there to cure.") - return + throw IllegalStateException() } if (patch.isGrown()) { sendMessage(player, "That's not diseased.") - return + throw IllegalStateException() } if (patch.isDead) { sendMessage(player, "It says 'Cure' not 'Resurrect'. Although death may arise from disease, it is not in itself a disease and hence cannot be cured. So there.") - return + throw IllegalStateException() } if (!patch.isDiseased) { sendMessage(player, "It is growing just fine.") - return + throw IllegalStateException() } patch.cureDisease() @@ -366,7 +368,7 @@ class LunarListeners : SpellListener("lunar"), Commands { private fun monsterExamine(player: Player, npc: NPC){ if(!npc.location.withinDistance(player.location)){ sendMessage(player, "You must get closer to use this spell.") - return + throw IllegalStateException() } face(player, npc) visualizeSpell(player, Animations.LUNAR_SPELLBOOK_STATSPY_6293, Graphics.LUNAR_SPELLBOOK_STAT_SPY_OVER_PLAYER_1060, soundID = Sounds.LUNAR_STAT_SPY_3620) @@ -402,20 +404,20 @@ class LunarListeners : SpellListener("lunar"), Commands { private fun cureOther(player: Player, target: Node) { if(!isPlayer(target)) { sendMessage(player, "You can only cast this spell on other players.") - return + throw IllegalStateException() } val p = target.asPlayer() if(!p.isActive || p.locks.isInteractionLocked) { sendMessage(player, "This player is busy.") - return + throw IllegalStateException() } if(!p.settings.isAcceptAid) { sendMessage(player, "This player is not accepting any aid.") - return + throw IllegalStateException() } if(!isPoisoned(p)) { sendMessage(player, "This player is not poisoned.") - return + throw IllegalStateException() } player.face(p) visualizeSpell(player, Animations.LUNAR_SPELLBOOK_CURE_OTHER_4411, Graphics.LUNAR_SPELLBOOK_CURE_OTHER_736, 130, Sounds.LUNAR_CURE_OTHER_2886) @@ -460,7 +462,7 @@ class LunarListeners : SpellListener("lunar"), Commands { private fun cureMe(player: Player) { if(!isPoisoned(player)) { sendMessage(player, "You are not poisoned.") - return + throw IllegalStateException() } removeRunes(player, true) visualizeSpell(player, Animations.LUNAR_SPELLBOOK_CURE_ME_4411, Graphics.LUNAR_SPELLBOOK_CURE_ME_742, 90, Sounds.LUNAR_CURE_2884) @@ -503,7 +505,7 @@ class LunarListeners : SpellListener("lunar"), Commands { private fun statSpy(player: Player, target: Node) { if(target !is Player) { sendMessage(player, "You can only cast this spell on players.") - return + throw IllegalStateException() } val stat = Components.DREAM_PLAYER_STATS_523 val statCloseEvent = CloseEvent { p, _ -> @@ -582,33 +584,36 @@ class LunarListeners : SpellListener("lunar"), Commands { private fun dream(player: Player) { if(player.skills.lifepoints >= getStatLevel(player, Skills.HITPOINTS)) { sendMessage(player, "You have no need to cast this spell since your hitpoints are already full.") - return + throw IllegalStateException() } + // https://runescape.wiki/w/Dream?oldid=880976 claims Dream makes you heal 1 hp every 20 seconds + // https://oldschool.runescape.wiki/w/Dream claims that Dream has its own timer, so the Dream heals don't need + // to align with the natural heals + val timer = getOrStartTimer(player) animate(player, Animations.LUNAR_SPELLBOOK_DREAM_START_6295) + removeRunes(player, true) + addXP(player, 82.0) delayEntity(player, 4) queueScript(player, 4, QueueStrength.WEAK) { stage: Int -> - when(stage) { - 0 -> { - animate(player, Animations.LUNAR_SPELLBOOK_DREAM_MID_6296) - sendGraphics(Graphics.LUNAR_SPELLBOOK_DREAM_1056, player.location) - playAudio(player, Sounds.LUNAR_SLEEP_3619) - return@queueScript delayScript(player, 5) - } - else -> { - sendGraphics(Graphics.LUNAR_SPELLBOOK_DREAM_1056, player.location) - // This heals 2 HP every min. Naturally you heal 1 for a total of 3 - // The script steps every 5 ticks and we want 50 ticks before a heal - if (stage.mod(10) == 0){ - heal(player, 1) - if(player.skills.lifepoints >= getStatLevel(player, Skills.HITPOINTS)) { - animate(player, Animations.LUNAR_SPELLBOOK_DREAM_END_6297) - return@queueScript stopExecuting(player) - } - } - return@queueScript delayScript(player, 5) + if (stage == 0) { + sendGraphics(Graphics.LUNAR_SPELLBOOK_DREAM_1056, player.location) + playAudio(player, Sounds.LUNAR_SLEEP_3619) + return@queueScript delayScript(player, 5) + } + animate(player, Animations.LUNAR_SPELLBOOK_DREAM_MID_6296) + sendGraphics(Graphics.LUNAR_SPELLBOOK_DREAM_1056, player.location) + // This heals 2 HP every min. Naturally you heal 1 for a total of 3 + // The script steps every 5 ticks and we want 50 ticks before a heal + if (stage.mod(10) == 0) { + val amt = timer.getHealAmount(player) //accounts for regen brace + heal(player, amt) + if (player.skills.lifepoints >= getStatLevel(player, Skills.HITPOINTS)) { + animate(player, Animations.LUNAR_SPELLBOOK_DREAM_END_6297) + return@queueScript stopExecuting(player) } } + return@queueScript delayScript(player, 5) } } @@ -660,23 +665,23 @@ class LunarListeners : SpellListener("lunar"), Commands { private fun fertileSoil(player: Player, target: Scenery) { if (CompostBins.forObjectID(target.id) != null) { sendMessage(player, "No, that would be silly.") - return + throw IllegalStateException() } val fPatch = FarmingPatch.forObject(target) if(fPatch == null) { sendMessage(player, "Um... I don't want to fertilize that!") - return + throw IllegalStateException() } val patch = fPatch.getPatchFor(player) if (patch.isGrown()) { sendMessage(player, "Composting isn't going to make it get any bigger.") - return + throw IllegalStateException() } if (patch.isFertilized()) { sendMessage(player, "This patch has already been composted.") - return + throw IllegalStateException() } removeRunes(player, true) animate(player, Animations.LUNAR_SPELLBOOK_FERTILE_SOIL_4413) @@ -697,11 +702,11 @@ class LunarListeners : SpellListener("lunar"), Commands { val plankType = PlankType.getForLog(item) if (plankType == null) { sendMessage(player, "You need to use this spell on logs.") - return + throw IllegalStateException() } if (amountInInventory(player, Items.COINS_995) < plankType.price || !removeItem(player, Item(Items.COINS_995, plankType.price))) { sendMessage(player, "You need ${plankType.price} coins to convert that log into a plank.") - return + throw IllegalStateException() } lock(player, 3) setDelay(player, false) @@ -716,20 +721,20 @@ class LunarListeners : SpellListener("lunar"), Commands { private fun energyTransfer(player: Player, target: Node) { if(!isPlayer(target)) { sendMessage(player, "You can only cast this spell on other players.") - return + throw IllegalStateException() } val targetPlayer = target.asPlayer() if(!targetPlayer.isActive || targetPlayer.locks.isInteractionLocked) { sendMessage(player, "This player is busy.") - return + throw IllegalStateException() } if(!targetPlayer.settings.isAcceptAid) { sendMessage(player, "This player is not accepting any aid.") - return + throw IllegalStateException() } if(10 >= player.skills.lifepoints) { sendMessage(player, "You need more hitpoints to cast this spell.") - return + throw IllegalStateException() } player.face(targetPlayer) visualizeSpell(player, Animations.LUNAR_SPELLBOOK_ENERGY_TRANSFER_4411, Graphics.LUNAR_SPELLBOOK_ENERGY_TRANSFER_738, 90, Sounds.LUNAR_ENERGY_TRANSFER_2885) diff --git a/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt b/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt index e36eaa577..d817cd31c 100644 --- a/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt +++ b/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt @@ -4,10 +4,12 @@ import content.data.Quests import content.global.skill.magic.SpellListener import content.global.skill.magic.SpellUtils.hasRune import content.global.skill.magic.TeleportMethod +import content.global.skill.magic.homeTeleport import content.global.skill.magic.spellconsts.Modern import content.global.skill.prayer.Bones import content.global.skill.smithing.smelting.Bar import content.global.skill.smithing.smelting.SmeltingPulse +import content.region.kandarin.ardougne.quest.plaguecity.PlagueCityListeners import core.ServerConstants import core.api.* import core.game.event.ItemAlchemizationEvent @@ -23,7 +25,6 @@ import core.game.node.entity.player.Player import core.game.node.entity.player.link.TeleportManager import core.game.node.entity.player.link.diary.DiaryType import core.game.node.entity.skill.Skills -import content.region.kandarin.ardougne.quest.plaguecity.PlagueCityListeners import core.game.node.item.Item import core.game.world.map.Location import core.game.world.update.flag.context.Animation @@ -39,7 +40,7 @@ class ModernListeners : SpellListener("modern"){ return@onCast } requires(player) - player.teleporter.send(ServerConstants.HOME_LOCATION,TeleportManager.TeleportType.HOME) + homeTeleport(player, ServerConstants.HOME_LOCATION ?: Location(3222, 3218, 0)) setDelay(player,true) } @@ -96,7 +97,7 @@ class ModernListeners : SpellListener("modern"){ if (!hasRequirement(player, Quests.MONKEY_MADNESS)) return@onCast requires(player,64, arrayOf(Item(Items.FIRE_RUNE_554,2),Item(Items.WATER_RUNE_555,2),Item(Items.LAW_RUNE_563,2),Item(Items.BANANA_1963))) - sendTeleport(player,74.0, Location.create(2754, 2784, 0)) + sendTeleport(player,74.0, Location.create(2795, 2798, 1)) } onCast(Modern.TELEPORT_TO_HOUSE, NONE){ player, _ -> diff --git a/Server/src/main/content/global/skill/skillcapeperks/SkillcapeEquipmentPlugin.kt b/Server/src/main/content/global/skill/skillcapeperks/SkillcapeEquipmentPlugin.kt index 0ad090fcd..36a76e8cd 100644 --- a/Server/src/main/content/global/skill/skillcapeperks/SkillcapeEquipmentPlugin.kt +++ b/Server/src/main/content/global/skill/skillcapeperks/SkillcapeEquipmentPlugin.kt @@ -1,6 +1,8 @@ package content.global.skill.skillcapeperks +import core.api.sendMessage import core.game.interaction.InteractionListener +import core.game.world.GameWorld class SkillcapeEquipmentPlugin : InteractionListener { override fun defineListeners() { @@ -19,6 +21,13 @@ class SkillcapeEquipmentPlugin : InteractionListener { onUnequip(capes){player, node -> val skillcape = Skillcape.forId(node.id) + + // For Temple of Ikov. Do not let player unequip firemaking skillcape in the dark basement (need to keep an active light source). + if(player.location.isInRegion(10648) && (node.id == 9804 || node.id == 9805) && GameWorld.settings?.skillcape_perks == true) { + sendMessage(player, "Unequipping that skillcape would leave you without a light source.") + return@onUnequip false + } + val perk = SkillcapePerks.forSkillcape(skillcape) perk.deactivate(player) return@onUnequip true diff --git a/Server/src/main/content/global/skill/skillcapeperks/SkillcapePerks.kt b/Server/src/main/content/global/skill/skillcapeperks/SkillcapePerks.kt index 7dbc1992b..eae541c70 100644 --- a/Server/src/main/content/global/skill/skillcapeperks/SkillcapePerks.kt +++ b/Server/src/main/content/global/skill/skillcapeperks/SkillcapePerks.kt @@ -131,8 +131,15 @@ enum class SkillcapePerks(val attribute: String, val effect: ((Player) -> Unit)? player.setAttribute("/save:$attribute",true) } player.debug("Activated ${this.name}") - if(this == CONSTANT_GLOW) + if(this == CONSTANT_GLOW) { DarkZone.checkDarkArea(player) + // For Temple of Ikov - if you are in the dark basement and put on the firemaking cape with its 2009Scape light source perk, switch to the light basement. + // For the listener that teleports if you light a normal light source, see content.global.skill.crafting.lightsources.LightSourceLighter.kt + if(player.location.isInRegion(10648) && player.location.withinDistance(Location(2639,9738,0), 8)) { + teleport(player, Location.create(player.getLocation().getX(), player.getLocation().getY() + 23, player.getLocation().getZ())) + closeDialogue(player) + } + } } fun operate(player: Player){ diff --git a/Server/src/main/content/global/skill/slayer/SlayerMasterDialogue.java b/Server/src/main/content/global/skill/slayer/SlayerMasterDialogue.java index cb6aa6570..c8a57c44a 100644 --- a/Server/src/main/content/global/skill/slayer/SlayerMasterDialogue.java +++ b/Server/src/main/content/global/skill/slayer/SlayerMasterDialogue.java @@ -42,11 +42,6 @@ public final class SlayerMasterDialogue extends DialoguePlugin { */ private static final Item HOLY_SYMBOL = new Item(1718); - /** - * Represents the items to use. - */ - private static final Item[] ITEMS = new Item[]{new Item(9813), new Item(9814)}; - /** * Represents the coins item. */ @@ -69,8 +64,6 @@ public final class SlayerMasterDialogue extends DialoguePlugin { private final int level = 2; - private int rerolls = 0; - /** * Constructs a new {@code SlayerMasterDialogue} {@code Object}. */ @@ -120,7 +113,6 @@ public final class SlayerMasterDialogue extends DialoguePlugin { @Override public boolean handle(int interfaceId, int buttonId) { - rerolls = ServerStore.getInt(getStoreFile(), player.getUsername().toLowerCase(), 0); if (isDiary) { switch (stage) { case 999: @@ -482,36 +474,19 @@ public final class SlayerMasterDialogue extends DialoguePlugin { stage = 999; break; } - if (!SlayerManager.getInstance(player).hasTask()) { + boolean hasTask = SlayerManager.getInstance(player).hasTask(); + boolean reroll = hasTask && master == Master.TURAEL && !Master.hasSameTask(master, player); + if (!hasTask || reroll) { SlayerManager.getInstance(player).generate(master); - if (SlayerManager.getInstance(player).getTask() == Tasks.JAD) { - interpreter.sendDialogues(master.getNpc(), getExpression(master), "Excellent, you're doing great. Your new task is to", "defeat the almighty TzTok-Jad."); - } else { - interpreter.sendDialogues(master.getNpc(), getExpression(master), "Excellent, you're doing great. Your new task is to kill", "" + SlayerManager.getInstance(player).getAmount() + " " + SlayerUtils.pluralise(SlayerManager.getInstance(player).getTaskName()) + "."); - } + interpreter.sendDialogues(master.getNpc(), getExpression(master), "Excellent, you're doing great. Your new task is to kill", "" + SlayerManager.getInstance(player).getAmount() + " " + SlayerUtils.pluralise(SlayerManager.getInstance(player).getTaskName()) + "."); stage = 844; break; } - if (Master.hasSameTask(master, player)) { - interpreter.sendDialogues(master.getNpc(), getExpression(master), "You're still hunting something. But let me check something..."); - stage = 847; - } else { - SlayerManager.getInstance(player).flags.setTaskStreak(0); - SlayerManager.getInstance(player).generate(master); - if (SlayerManager.getInstance(player).getTask() == Tasks.JAD) { - interpreter.sendDialogues(master.getNpc(), getExpression(master), "Excellent, you're doing great. Your new task is to", "defeat the almighty TzTok-Jad."); - } else { - interpreter.sendDialogues(master.getNpc(), getExpression(master), "Excellent, you're doing great. Your new task is to kill", "" + SlayerManager.getInstance(player).getAmount() + " " + SlayerUtils.pluralise(SlayerManager.getInstance(player).getTaskName()) + "."); - } - stage = 844; - } + interpreter.sendDialogues(master.getNpc(), getExpression(master), "You're still hunting something. Come back when you've","finished your task."); + stage = END_DIALOGUE; break; case 844: - if (GameWorld.getSettings().getAllow_slayer_reroll()) { - options("Got any tips for me?", "Okay, great!", "I'd like to re-roll that task."); - } else { - options("Got any tips for me?", "Okay, great!"); - } + options("Got any tips for me?", "Okay, great!"); stage++; break; case 845: @@ -524,47 +499,6 @@ public final class SlayerMasterDialogue extends DialoguePlugin { player("Okay, great!"); stage = 999; break; - case 3: - player("I'd like to re-roll this task."); - if(rerolls == 10){ - stage++; - } else { - SlayerManager.getInstance(player).clear(); - getStoreFile().put(player.getUsername().toLowerCase(), rerolls + 1); - stage = 701; - } - } - break; - case 846: - npcl(FacialExpression.NEUTRAL, "Actually, you're out of free rerolls. You can buy a reroll from my reward store, though."); - stage = END_DIALOGUE; - break; - case 847: - if(rerolls < 10){ - npcl(FacialExpression.NEUTRAL, "You do have " + (10 - rerolls) + " rerolls left today, would you like to use one?"); - stage++; - } - else { - npcl(FacialExpression.NEUTRAL, "And it also seems you're out of rerolls for today. That's unfortunate."); - stage = END_DIALOGUE; - } - break; - case 848: - options("Yes, please.", "No, thanks."); - stage++; - break; - case 849: - switch(buttonId){ - case 1: - playerl(FacialExpression.FRIENDLY, "Yes, please."); - SlayerManager.getInstance(player).clear(); - getStoreFile().put(player.getUsername().toLowerCase(), rerolls + 1); - stage = 701; - break; - case 2: - playerl(FacialExpression.NEUTRAL, "No, thanks."); - stage = END_DIALOGUE; - break; } break; case 860: @@ -608,56 +542,6 @@ public final class SlayerMasterDialogue extends DialoguePlugin { } stage = 999; break; - case 906: - switch (buttonId) { - case 1: - player("May I buy a Quest Point cape?"); - stage = 907; - break; - case 2: - interpreter.sendDialogues(master.getNpc(), FacialExpression.HALF_GUILTY, "'Ello, and what are you after, then?"); - stage = 0; - break; - } - break; - case 907: - npc("You bet, " + player.getUsername() + "! Right when you give me 99000 coins."); - stage = 908; - break; - case 908: - options("Okay, here you go.", "No, thanks."); - stage = 909; - break; - case 909: - switch (buttonId) { - case 1: - player("Okay, here you go."); - stage = 910; - break; - case 2: - end(); - break; - } - break; - case 910: - if (player.getInventory().freeSlots() < 2) { - player("I don't seem to have enough inventory space."); - stage = 999; - return true; - } - if (!player.getInventory().containsItem(COINS)) { - player("I don't seem to have enough coins with", "me at this time."); - stage = 999; - return true; - } - if (player.getInventory().remove(COINS) && player.getInventory().add(ITEMS)) { - npc("Have fun with it."); - stage = 999; - } else { - player("I don't seem to have enough coins with", "me at this time."); - stage = 999; - } - break; } return true; } @@ -698,9 +582,4 @@ public final class SlayerMasterDialogue extends DialoguePlugin { public int[] getIds() { return new int[]{70, 1598, 1596, 1597, 1599, 7780, 8275, 8273, 8274, 8649}; } - - private JSONObject getStoreFile() { - return ServerStore.getArchive("daily-slayer-rerolls"); - } - } diff --git a/Server/src/main/content/global/skill/slayer/SlayerPlugin.java b/Server/src/main/content/global/skill/slayer/SlayerPlugin.java index 53e426aa3..eb02797d6 100644 --- a/Server/src/main/content/global/skill/slayer/SlayerPlugin.java +++ b/Server/src/main/content/global/skill/slayer/SlayerPlugin.java @@ -1,7 +1,6 @@ package content.global.skill.slayer; import core.cache.def.impl.SceneryDefinition; -import core.game.global.action.ClimbActionHandler; import core.game.global.action.DigAction; import core.game.global.action.DigSpadeHandler; import core.game.interaction.OptionHandler; @@ -34,8 +33,6 @@ public class SlayerPlugin extends OptionHandler { SceneryDefinition.forId(15767).getHandlers().put("option:enter", this); SceneryDefinition.forId(15811).getHandlers().put("option:exit", this); SceneryDefinition.forId(15812).getHandlers().put("option:exit", this); - SceneryDefinition.forId(96).getHandlers().put("option:climb-up", this); - SceneryDefinition.forId(35121).getHandlers().put("option:climb-down", this); for (Location loc : BRYNE_DIGS) { DigSpadeHandler.register(loc, new DigAction() { @Override @@ -53,27 +50,25 @@ public class SlayerPlugin extends OptionHandler { public boolean handle(Player player, Node node, String option) { switch (node.getId()) { case 8785: + // Outside of trap door in East Ardy player.teleport(new Location(2543, 3327, 0)); break; case 23158: case 23157: + // Outside brine rat cave player.teleport(new Location(2729, 3733, 0)); break; case 15767: if (!hasRequirement(player, Quests.CABIN_FEVER)) return true; player.teleport(new Location(3748, 9373, 0)); + // Cave horrors - inside of cave break; case 15811: case 15812: + // Cave horrors - outside of cave player.teleport(new Location(3749, 2973, 0)); break; - case 96: - ClimbActionHandler.climb(player, null, new Location(2649, 9804, 0)); - break; - case 35121: - ClimbActionHandler.climb(player, null, new Location(2641, 9763, 0)); - break; } return true; } @@ -81,11 +76,9 @@ public class SlayerPlugin extends OptionHandler { @Override public Location getDestination(Node node, Node n) { if (n.getId() == 23158 || n.getId() == 23157) { + // Inside brine rate cave return new Location(2690, 10124, 0); } - if (n.getId() == 96) { - return new Location(2641, 9763, 0); - } return null; } diff --git a/Server/src/main/content/global/skill/smithing/FurnaceOptionPlugin.java b/Server/src/main/content/global/skill/smithing/FurnaceOptionPlugin.java index e34e71cc7..be0190327 100644 --- a/Server/src/main/content/global/skill/smithing/FurnaceOptionPlugin.java +++ b/Server/src/main/content/global/skill/smithing/FurnaceOptionPlugin.java @@ -78,26 +78,6 @@ public final class FurnaceOptionPlugin extends OptionHandler { player.getPacketDispatch().sendItemZoomOnInterface(2363, 150, 311, 12); } - /** - * Method used to handle the tutorial island interaction. - * @param player the player. - */ - private final void handleTutorialIsland(final Player player) { - if (player.getInventory().containItems(438, 436)) { - player.animate(ANIMATION); - GameWorld.getPulser().submit(new Pulse(2, player) { - @Override - public boolean pulse() { - player.getInventory().remove(ITEMS); - player.getInventory().add(Bar.BRONZE.getProduct()); - player.getSkills().addExperience(Skills.SMITHING, Bar.BRONZE.getExperience()); - return true; - } - - }); - } - } - /** * Represents the plugin used to handle the ore on the furance. * @author 'Vexia diff --git a/Server/src/main/content/minigame/bountyhunter/MaximillianSackvilleDialogue.kt b/Server/src/main/content/minigame/bountyhunter/MaximillianSackvilleDialogue.kt deleted file mode 100644 index a5b047041..000000000 --- a/Server/src/main/content/minigame/bountyhunter/MaximillianSackvilleDialogue.kt +++ /dev/null @@ -1,120 +0,0 @@ -package content.minigame.bountyhunter - -import core.api.* -import core.game.dialogue.DialoguePlugin -import core.game.dialogue.FacialExpression -import core.game.node.entity.player.Player -import core.game.node.entity.player.link.IronmanMode -import core.plugin.Initializable -import org.rs09.consts.NPCs -import core.game.dialogue.IfTopic -import core.game.dialogue.Topic -import core.tools.END_DIALOGUE -import core.tools.START_DIALOGUE - -/** - * Provides dialogue tree for Maximillian Sackville, - * the Bounty Hounter roving banker. - * - * @author vddCore - */ -@Initializable -class MaximillianSackvilleDialogue(player: Player? = null) : DialoguePlugin(player) { - override fun handle(interfaceId: Int, buttonId: Int): Boolean { - when (stage) { - START_DIALOGUE -> when { - hasIronmanRestriction(player, IronmanMode.ULTIMATE) -> { - npcl( - FacialExpression.NEUTRAL, - "My apologies, dear ${if (player.isMale) "sir" else "madam"}, " + - "our services are not available for Ultimate ${if (player.isMale) "Ironmen" else "Ironwomen"}." - ).also { stage = END_DIALOGUE } - } - - else -> { - npcl( - FacialExpression.NEUTRAL, - "Good day, how may I help you?" - ).also { - if (hasAwaitingGrandExchangeCollections(player)) { - stage++ - } else { - stage += 2 - } - } - } - } - - 1 -> npcl( - FacialExpression.NEUTRAL, - "Before we go any further, I should inform you that you " + - "have items ready for collection from the Grand Exchange." - ).also { stage++ } - - 2 -> playerl( - FacialExpression.ASKING, - "Who are you?" - ).also { stage++ } - - 3 -> npcl( - FacialExpression.NEUTRAL, - "How inconsiderate of me, dear ${if (player.isMale) "sir" else "madam"}. " + - "My name is Maximillian Sackville and I conduct operations here on behalf " + - "of The Bank of Gielinor." - ).also { stage++ } - - 4 -> showTopics( - Topic(FacialExpression.NEUTRAL, "I'd like to access my bank account.", 10), - IfTopic( - FacialExpression.NEUTRAL, - "I'd like to switch to my ${getBankAccountName(player, true)} bank account.", - 11, - hasActivatedSecondaryBankAccount(player) - ), - Topic(FacialExpression.NEUTRAL, "I'd like to check my PIN settings.", 12), - Topic(FacialExpression.NEUTRAL, "I'd like to collect items.", 13), - Topic(FacialExpression.ASKING, "Aren't you afraid of working in the Wilderness?", 5) - ) - - 5 -> npcl( - FacialExpression.NEUTRAL, - "While the Wilderness is quite a dangerous place, The Bank of Gielinor offers " + - "us - roving bankers - extraordinary benefits for our hard work in hazardous environments." - ).also { stage++ } - - 6 -> npcl( - FacialExpression.NEUTRAL, - "This allows us to provide our services to customers regardless of their current " + - "whereabouts. Our desire to serve is stronger than our fear of the Wilderness." - ).also { stage = END_DIALOGUE } - - 10 -> { - openBankAccount(player) - end() - } - - 11 -> { - toggleBankAccount(player) - - npcl( - FacialExpression.NEUTRAL, - "Naturally. You can now access your ${getBankAccountName(player)} bank account." - ).also { stage = END_DIALOGUE } - } - - 12 -> { - openBankPinSettings(player) - end() - } - - 13 -> { - openGrandExchangeCollectionBox(player) - end() - } - } - - return true - } - - override fun getIds() = intArrayOf(NPCs.BANKER_6538) -} \ No newline at end of file diff --git a/Server/src/main/content/minigame/castlewars/CastlewarsBook.kt b/Server/src/main/content/minigame/castlewars/CastlewarsBook.kt new file mode 100644 index 000000000..8395ece6a --- /dev/null +++ b/Server/src/main/content/minigame/castlewars/CastlewarsBook.kt @@ -0,0 +1,146 @@ +package content.minigame.castlewars + +import content.data.Quests +import content.global.handlers.iface.BookInterface +import content.global.handlers.iface.BookLine +import content.global.handlers.iface.Page +import content.global.handlers.iface.PageSet +import core.api.getAttribute +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.player.Player +import core.game.system.command.sets.ModelViewerCommandSet.Companion.ATTRIBUTE_MODEL_NUMBER +import core.game.system.command.sets.ModelViewerCommandSet.Companion.ATTRIBUTE_PITCH +import core.game.system.command.sets.ModelViewerCommandSet.Companion.ATTRIBUTE_YAW +import core.game.system.command.sets.ModelViewerCommandSet.Companion.ATTRIBUTE_ZOOM +import core.game.system.command.sets.ModelViewerCommandSet.Companion.DEF_BOOK +import core.game.world.GameWorld +import org.rs09.consts.Items + +class CastlewarsBook : InteractionListener { + + companion object { + private val TITLE = "Castle Wars Manual" + private val CONTENTS = arrayOf( + PageSet( + Page( + BookLine("Objective:", 38), + BookLine("The aim is to get into your", 39), + BookLine("opponents castle and take", 40), + BookLine("their team standard. Then", 41), + BookLine("bring that back and capture", 42), + BookLine("it on your teams standard. ", 43), + ), + Page( + BookLine("Toolkit:", 58), + BookLine("This useful item allows you", 59), + BookLine("to repair broken doors and", 60), + BookLine("catapults. Simply use it on", 61), + BookLine("the item to be repaired, or", 62), + BookLine("have one in your inventory", 63), + BookLine("when you select the option,", 64), + BookLine("and you'll rebuild it!", 65) + ) + ), + PageSet( + Page( + BookLine("Bandages:", 43), + BookLine("These can be used to heal", 44), + BookLine("some health and restore some", 45), + BookLine("of your running energy.", 46), + BookLine("You can also use them to", 47), + BookLine("heal fellow players.", 48), + ), + Page( + BookLine("Explosive Potion:", 58), + BookLine("A simple but effective item,", 59), + BookLine("use it to blow up your", 60), + BookLine("opponents catapult and", 61), + BookLine("barricades! It can also be", 62), + BookLine("used to clear the tunnels", 63), + BookLine("under the arena for some,", 64), + BookLine("sneak attacks into your", 65), + BookLine("opponents castle!", 66) + ) + ), + PageSet( + Page( + BookLine("Barricade:", 43), + BookLine("Use these constructs to block", 44), + BookLine("your opponents movement", 45), + BookLine("and prevent them accessing", 46), + BookLine("your castle. Each team can", 47), + BookLine("only have 10 built at any one", 48), + BookLine("time.", 49), + ), + Page( + BookLine("Bucket:", 58), + BookLine("Fill a bucket with water and", 59), + BookLine("you can use it to put out a", 60), + BookLine("burning catapult or barricade,", 61), + BookLine("but be quick or it'll be", 62), + BookLine("destroyed.", 63), + ) + ), + PageSet( + Page( + BookLine("Tinderbox:", 43), + BookLine("Logs aren't all that's", 44), + BookLine("flammable, use a tinderbox to", 45), + BookLine("set light to your opponents", 46), + BookLine("You can also use them to", 47), + BookLine("catapult and barricades.", 48), + ), + Page( + BookLine("Pickaxe:", 58), + BookLine("Use a pickaxe to mine your", 59), + BookLine("way through the tunnels", 60), + BookLine("under the arena for a sneak", 61), + BookLine("attack into your opponents", 62), + BookLine("castle. Don't forget to", 63), + BookLine("collapse the tunnels into your", 64), + BookLine(" own castle though! ", 65), + ) + ), + PageSet( + Page( + BookLine("Catapult:", 43), + BookLine("Use this war machine to", 44), + BookLine("launch rocks at your", 45), + BookLine("opponents. Just give it rough", 46), + BookLine("coordinates and let the rock", 47), + BookLine("fly, just be careful not to hit", 48), + BookLine("your team with it!", 49), + ), + Page( + BookLine("Rock:", 58), + BookLine("Used as ammo for the", 59), + BookLine("catapult, and not much else.", 60), + BookLine("Brings new meaning to the", 61), + BookLine("phrase 'flies like a rock'.", 62), + ) + ), + ) + private fun display(player: Player, pageNum: Int, buttonID: Int) : Boolean { + BookInterface.pageSetup(player, BookInterface.FANCY_BOOK_2_27, TITLE, CONTENTS) + BookInterface.clearModelsOnPage(player, BookInterface.FANCY_BOOK_2_27); + BookInterface.setItemOnPage(player, 0, Items.TOOLKIT_4051, BookInterface.FANCY_BOOK_2_27, BookInterface.FANCY_BOOK_2_27_IMAGE_ENABLE_DRAW_IDS[17], BookInterface.FANCY_BOOK_2_27_IMAGE_DRAW_IDS[17], 768, 192, 1792) + BookInterface.setItemOnPage(player, 1, Items.BANDAGES_4049, BookInterface.FANCY_BOOK_2_27, BookInterface.FANCY_BOOK_2_27_IMAGE_ENABLE_DRAW_IDS[2], BookInterface.FANCY_BOOK_2_27_IMAGE_DRAW_IDS[2], 768, 192, 1792) + BookInterface.setItemOnPage(player, 1, Items.EXPLOSIVE_POTION_4045, BookInterface.FANCY_BOOK_2_27, BookInterface.FANCY_BOOK_2_27_IMAGE_ENABLE_DRAW_IDS[17], BookInterface.FANCY_BOOK_2_27_IMAGE_DRAW_IDS[17], 768, 192, 1792) + BookInterface.setItemOnPage(player, 2, Items.BARRICADE_4053, BookInterface.FANCY_BOOK_2_27, BookInterface.FANCY_BOOK_2_27_IMAGE_ENABLE_DRAW_IDS[2], BookInterface.FANCY_BOOK_2_27_IMAGE_DRAW_IDS[2], 768, 192, 1792) + BookInterface.setItemOnPage(player, 2, Items.BUCKET_OF_WATER_1929, BookInterface.FANCY_BOOK_2_27, BookInterface.FANCY_BOOK_2_27_IMAGE_ENABLE_DRAW_IDS[17], BookInterface.FANCY_BOOK_2_27_IMAGE_DRAW_IDS[17], 768, 192, 1792) + BookInterface.setItemOnPage(player, 3, Items.TINDERBOX_590, BookInterface.FANCY_BOOK_2_27, BookInterface.FANCY_BOOK_2_27_IMAGE_ENABLE_DRAW_IDS[2], BookInterface.FANCY_BOOK_2_27_IMAGE_DRAW_IDS[2], 768, 192, 1792) + BookInterface.setItemOnPage(player, 3, Items.BRONZE_PICKAXE_1265, BookInterface.FANCY_BOOK_2_27, BookInterface.FANCY_BOOK_2_27_IMAGE_ENABLE_DRAW_IDS[17], BookInterface.FANCY_BOOK_2_27_IMAGE_DRAW_IDS[17], 768, 192, 1792) + BookInterface.setModelOnPage(player, 4, 38202, BookInterface.FANCY_BOOK_2_27, BookInterface.FANCY_BOOK_2_27_IMAGE_ENABLE_DRAW_IDS[4], BookInterface.FANCY_BOOK_2_27_IMAGE_DRAW_IDS[4], 4048, 192, 768) + BookInterface.setItemOnPage(player, 4, Items.ROCK_4043, BookInterface.FANCY_BOOK_2_27, BookInterface.FANCY_BOOK_2_27_IMAGE_ENABLE_DRAW_IDS[17], BookInterface.FANCY_BOOK_2_27_IMAGE_DRAW_IDS[17], 768, 192, 1792) + return true + } + } + + override fun defineListeners() { + on(Items.CASTLEWARS_MANUAL_4055, IntType.ITEM, "read") { player, _ -> + BookInterface.openBook(player, BookInterface.FANCY_BOOK_2_27, ::display) + return@on true + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/minigame/mta/EnchantSpell.kt b/Server/src/main/content/minigame/mta/EnchantSpell.kt index d6df04fe8..53f05bc13 100644 --- a/Server/src/main/content/minigame/mta/EnchantSpell.kt +++ b/Server/src/main/content/minigame/mta/EnchantSpell.kt @@ -1,6 +1,8 @@ package content.minigame.mta import content.minigame.mta.impl.EnchantingZone.Shapes +import core.ServerConstants +import core.api.replaceSlot import core.game.node.Node import core.game.node.entity.Entity import core.game.node.entity.combat.spell.SpellType @@ -9,6 +11,8 @@ import core.game.node.entity.player.link.SpellBookManager.SpellBook import core.game.node.entity.player.link.audio.Audio import core.game.node.entity.combat.spell.MagicSpell import core.game.node.entity.combat.spell.Runes +import core.game.node.entity.player.info.LogType +import core.game.node.entity.player.info.PlayerMonitor import core.game.node.item.Item import core.game.world.update.flag.context.Animation import core.game.world.update.flag.context.Graphics @@ -47,7 +51,7 @@ class EnchantSpell : MagicSpell { return false } entity.interfaceManager.setViewedTab(6) - val enchanted = jewellery?.getOrDefault(target.id,null) + var enchanted = jewellery?.getOrDefault(target.id,null) if (enchanted == null) { entity.packetDispatch.sendMessage("You can't use this spell on this item.") @@ -56,12 +60,15 @@ class EnchantSpell : MagicSpell { if (!meetsRequirements(entity, true, true)) { return false } - - if (entity.inventory.remove(target)) { - visualize(entity, target) - entity.inventory.add(enchanted) + if (enchanted.id == Items.RING_OF_WEALTH_2572 && ServerConstants.RING_OF_WEALTH_TELEPORT) { + enchanted = Item(Items.RING_OF_WEALTH_14638) } + visualize(entity, target) + val ret = replaceSlot(entity, target.slot, enchanted) + if (ret != target) { + PlayerMonitor.log(entity, LogType.DUPE_ALERT, "Unknown slot-replacement problem when enchanting jewellery (adding $enchanted replaced $ret rather than $target)") + } //MTA-Specific Code if (entity.zoneMonitor.isInZone("Enchantment Chamber")) { @@ -184,10 +191,10 @@ class EnchantSpell : MagicSpell { SpellBook.MODERN.register(51, EnchantSpell(68, 78.0, mapOf( //Begin Jewelry Enchantment - Items.DRAGONSTONE_RING_1645 to Item(14646), - Items.DRAGON_NECKLACE_1664 to Item(Items.SKILLS_NECKLACE4_11105), - Items.DRAGONSTONE_AMMY_1702 to Item(Items.AMULET_OF_GLORY4_1712), - Items.DRAGON_BRACELET_11115 to Item(Items.COMBAT_BRACELET4_11118), + Items.DRAGONSTONE_RING_1645 to Item(Items.RING_OF_WEALTH_2572), + Items.DRAGON_NECKLACE_1664 to Item(Items.SKILLS_NECKLACE_11113), + Items.DRAGONSTONE_AMMY_1702 to Item(Items.AMULET_OF_GLORY_1704), + Items.DRAGON_BRACELET_11115 to Item(Items.COMBAT_BRACELET_11126), //Begin MTA-Specific Enchantments Items.CUBE_6899 to Item(Items.ORB_6902), Items.CYLINDER_6898 to Item(Items.ORB_6902), diff --git a/Server/src/main/content/region/asgarnia/burthorpe/dialogue/EmeraldBenedictDialogue.kt b/Server/src/main/content/region/asgarnia/burthorpe/dialogue/EmeraldBenedictDialogue.kt index 9a200f9e5..e59b3d907 100644 --- a/Server/src/main/content/region/asgarnia/burthorpe/dialogue/EmeraldBenedictDialogue.kt +++ b/Server/src/main/content/region/asgarnia/burthorpe/dialogue/EmeraldBenedictDialogue.kt @@ -1,5 +1,6 @@ package content.region.asgarnia.burthorpe.dialogue +import core.ServerConstants import core.api.* import core.game.dialogue.DialoguePlugin import core.game.dialogue.FacialExpression @@ -21,7 +22,7 @@ import core.tools.START_DIALOGUE class EmeraldBenedictDialogue(player: Player? = null) : DialoguePlugin(player) { override fun handle(interfaceId: Int, buttonId: Int): Boolean { when (stage) { - START_DIALOGUE -> if(hasIronmanRestriction(player, IronmanMode.ULTIMATE)) { + START_DIALOGUE -> if (hasIronmanRestriction(player, IronmanMode.ULTIMATE)) { npcl( FacialExpression.ANNOYED, "Get lost, tin can." @@ -53,6 +54,12 @@ class EmeraldBenedictDialogue(player: Player? = null) : DialoguePlugin(player) { 4, hasActivatedSecondaryBankAccount(player) ), + IfTopic( + FacialExpression.ASKING, + "Yes, but can you open a secondary bank account for me?", + 7, + ServerConstants.SECOND_BANK && !hasActivatedSecondaryBankAccount(player) + ), Topic(FacialExpression.ASKING, "Yes, but can you show me my PIN settings?", 5), Topic(FacialExpression.ASKING, "Yes, but can you show me my collection box?", 6), Topic(FacialExpression.ANNOYED, "Yes, thanks. And I'll keep hold of it too.", END_DIALOGUE) @@ -68,7 +75,7 @@ class EmeraldBenedictDialogue(player: Player? = null) : DialoguePlugin(player) { npcl( FacialExpression.SUSPICIOUS, "Sure thing. Feel free to rummage through whatever's in your ${getBankAccountName(player)} now." - ).also { stage = END_DIALOGUE } + ).also { stage = 2 } } 5 -> { @@ -80,10 +87,24 @@ class EmeraldBenedictDialogue(player: Player? = null) : DialoguePlugin(player) { openGrandExchangeCollectionBox(player) end() } + + 7 -> { + npcl( + FacialExpression.SUSPICIOUS, + "Sure, just give me five million in gold and I'll take care of it." + ).also { stage++ } + } + + 8 -> { + playerl( + FacialExpression.SUSPICIOUS, + "On second thought, I think I'll ask somebody more reputable..." + ).also { stage = END_DIALOGUE } + } } return true } override fun getIds(): IntArray = intArrayOf(NPCs.EMERALD_BENEDICT_2271) -} \ No newline at end of file +} diff --git a/Server/src/main/content/region/asgarnia/burthorpe/dialogue/JadeDialogue.kt b/Server/src/main/content/region/asgarnia/burthorpe/dialogue/JadeDialogue.kt deleted file mode 100644 index 85ebda973..000000000 --- a/Server/src/main/content/region/asgarnia/burthorpe/dialogue/JadeDialogue.kt +++ /dev/null @@ -1,106 +0,0 @@ -package content.region.asgarnia.burthorpe.dialogue - -import core.api.* -import core.game.dialogue.DialoguePlugin -import core.game.dialogue.FacialExpression -import core.game.node.entity.player.Player -import core.game.node.entity.player.link.IronmanMode -import core.plugin.Initializable -import org.rs09.consts.NPCs -import core.game.dialogue.IfTopic -import core.game.dialogue.Topic -import core.tools.END_DIALOGUE -import core.tools.START_DIALOGUE - -/** - * Provides a dialogue tree for Jade inside Warriors' Guild. - * - * @author vddCore - */ -@Initializable -class JadeDialogue(player: Player? = null) : DialoguePlugin(player) { - override fun handle(interfaceId: Int, buttonId: Int): Boolean { - when (stage) { - START_DIALOGUE -> if (hasIronmanRestriction(player, IronmanMode.ULTIMATE)) { - npcl( - FacialExpression.NEUTRAL, - "Greetings, warrior. I wish I could help you, but " + - "our services are not available for Ultimate ${if (player.isMale) "Ironmen" else "Ironwomen"}." - ).also { stage = END_DIALOGUE } - } - else { - npcl( - FacialExpression.NEUTRAL, - "Greetings warrior, how may I help you?" - ).also { - if (hasAwaitingGrandExchangeCollections(player)) { - stage++ - } else { - stage += 2 - } - } - } - - 1 -> npcl( - FacialExpression.NEUTRAL, - "Before we go any further, I should inform you that you " + - "have items ready for collection from the Grand Exchange." - ).also { stage++ } - - 2 -> showTopics( - Topic(FacialExpression.NEUTRAL, "I'd like to access my bank account, please.", 10), - IfTopic( - FacialExpression.NEUTRAL, - "I'd like to switch to my ${getBankAccountName(player, true)} bank account.", - 13, - hasActivatedSecondaryBankAccount(player) - ), - Topic(FacialExpression.NEUTRAL, "I'd like to check my PIN settings.", 11), - Topic(FacialExpression.NEUTRAL, "I'd like to see my collection box.", 12), - Topic(FacialExpression.ASKING, "How long have you worked here?", 3) - ) - - 3 -> npcl( - FacialExpression.FRIENDLY, - "Oh, ever since the Guild opened. I like it here." - ).also { stage++ } - - 4 -> playerl( - FacialExpression.ASKING, - "Why's that?" - ).also { stage++ } - - 5 -> npcl( - FacialExpression.FRIENDLY, - "Well... What with all these warriors around, there's not much chance of my bank being robbed, is there?" - ).also { stage = 2 } - - 10 -> { - openBankAccount(player) - end() - } - - 11 -> { - openBankPinSettings(player) - end() - } - - 12 -> { - openGrandExchangeCollectionBox(player) - end() - } - - 13 -> { - toggleBankAccount(player) - npcl( - FacialExpression.FRIENDLY, - "Of course! Your ${getBankAccountName(player)} account is now active!" - ).also { stage = END_DIALOGUE } - } - } - - return true - } - - override fun getIds(): IntArray = intArrayOf(NPCs.JADE_4296) -} \ No newline at end of file diff --git a/Server/src/main/content/region/asgarnia/burthorpe/handlers/HeroGuildPlugin.java b/Server/src/main/content/region/asgarnia/burthorpe/handlers/HeroGuildPlugin.java index cc4e8fb62..60e6c86fd 100644 --- a/Server/src/main/content/region/asgarnia/burthorpe/handlers/HeroGuildPlugin.java +++ b/Server/src/main/content/region/asgarnia/burthorpe/handlers/HeroGuildPlugin.java @@ -1,5 +1,6 @@ package content.region.asgarnia.burthorpe.handlers; +import core.ServerConstants; import core.cache.def.impl.SceneryDefinition; import content.data.EnchantedJewellery; import core.game.global.action.DoorActionHandler; @@ -18,11 +19,15 @@ import core.plugin.Initializable; import core.plugin.ClassScanner; import static core.api.ContentAPIKt.hasRequirement; +import static core.api.ContentAPIKt.sendMessage; + import content.data.Quests; +import org.rs09.consts.Items; /** * Represents the hero guild. * @author Vexia + * @author Player Name */ @Initializable public final class HeroGuildPlugin extends OptionHandler { @@ -43,8 +48,8 @@ public final class HeroGuildPlugin extends OptionHandler { switch (id) { case 2624: case 2625: - if (!hasRequirement(player, Quests.HEROES_QUEST)) - return true; + if (!hasRequirement(player, Quests.HEROES_QUEST)) + return true; DoorActionHandler.handleAutowalkDoor(player, (Scenery) node); break; } @@ -56,6 +61,7 @@ public final class HeroGuildPlugin extends OptionHandler { /** * Handles the recharging of dragonstone jewellery. * @author Vexia + * @author Player Name */ public static final class JewelleryRechargePlugin extends UseWithHandler { @@ -65,8 +71,7 @@ public final class HeroGuildPlugin extends OptionHandler { private static final int[] IDS = new int[] { 1710, 1708, 1706, 1704, 11107, 11109, 11111, 11113, 11120, 11122, 11124, 11126, 10354, 10356, 10358, 10360, 10362, 14644,14642,14640,14638, 2572 }; /** - * Constructs a new {@Code JewelleryRechargePlugin} {@Code - * Object} + * Constructs a new JewelleryRechargePlugin object */ public JewelleryRechargePlugin() { super(IDS); @@ -83,18 +88,24 @@ public final class HeroGuildPlugin extends OptionHandler { @Override public boolean handle(NodeUsageEvent event) { final Player player = event.getPlayer(); - if (!hasRequirement(player, Quests.HEROES_QUEST)) - return true; + if (!hasRequirement(player, Quests.HEROES_QUEST)) { + return true; //hasRequirement shows the message + } final EnchantedJewellery jewellery; assert event.getUsedItem() != null; jewellery = EnchantedJewellery.Companion.getIdMap().get(event.getUsedItem().getId()); - if (!hasRequirement(player, Quests.HEROES_QUEST)) - return true; - if (jewellery == EnchantedJewellery.COMBAT_BRACELET || jewellery == EnchantedJewellery.SKILLS_NECKLACE) - if (!hasRequirement(player, Quests.LEGENDS_QUEST)) - return true; - if (jewellery == null && event.getUsedItem().getId() != 2572) { - return true; + if (jewellery == null) { + return false; //nothing interesting happens + } + if (jewellery == EnchantedJewellery.RING_OF_WEALTH) { + if (!ServerConstants.RING_OF_WEALTH_TELEPORT) { + return false; + } + } + if (jewellery == EnchantedJewellery.COMBAT_BRACELET || jewellery == EnchantedJewellery.SKILLS_NECKLACE) { + if (!hasRequirement(player, Quests.LEGENDS_QUEST)) { + return true; + } } boolean fam = event.getUsedWith() instanceof NPC; if (fam && jewellery != EnchantedJewellery.AMULET_OF_GLORY & jewellery != EnchantedJewellery.AMULET_OF_GLORY_T) { @@ -113,9 +124,9 @@ public final class HeroGuildPlugin extends OptionHandler { player.getInventory().replace(rechargedItem, event.getUsedItem().getSlot()); String name = jewellery.getJewelleryName(rechargedItem); if (!fam) { - player.sendMessage("You dip the " + name + " in the fountain..."); + sendMessage(player, "You dip the " + name.toLowerCase() + " in the fountain..."); } else { - player.sendMessage("Your titan recharges the glory."); + sendMessage(player, "Your titan recharges the " + name.toLowerCase() + "."); } return true; } diff --git a/Server/src/main/content/region/asgarnia/falador/dialogue/WysonTheGardenerDialogue.kt b/Server/src/main/content/region/asgarnia/falador/dialogue/WysonTheGardenerDialogue.kt index 1d2592ad8..b26643bba 100644 --- a/Server/src/main/content/region/asgarnia/falador/dialogue/WysonTheGardenerDialogue.kt +++ b/Server/src/main/content/region/asgarnia/falador/dialogue/WysonTheGardenerDialogue.kt @@ -3,12 +3,12 @@ package content.region.asgarnia.falador.dialogue import content.data.tables.BirdNest import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player -import core.game.node.entity.player.link.diary.DiaryType -import core.game.node.item.GroundItemManager import core.game.node.item.Item import core.plugin.Initializable import org.rs09.consts.Items -import core.game.diary.DiaryLevel +import core.api.* +import core.game.dialogue.DialoguePlugin +import core.game.node.entity.player.link.diary.* /** * Represents the Wyson the gardener dialogue. @@ -16,46 +16,53 @@ import core.game.diary.DiaryLevel * @version 1.0 */ @Initializable -class WysonTheGardenerDialogue : core.game.dialogue.DialoguePlugin { - /** - * If its a bird nest reward. - */ - private var birdNest = false - - /** - * Constructs a new `WysonTheGardenerDialogue` `Object`. - */ - constructor() { - /** - * empty. - */ - } +class WysonTheGardenerDialogue(player: Player? = null) : DialoguePlugin(player) { /** * Constructs a new `WysonTheGardenerDialogue` `Object`. + * Mole part dialogue source: https://www.youtube.com/watch?v=Dw-P9T7EhZk and https://www.youtube.com/watch?v=krZiIRupKbs * @param player the player. */ - constructor(player: Player?) : super(player) {} + //constructor(player: Player?) : super(player) {} - override fun newInstance(player: Player): core.game.dialogue.DialoguePlugin { + override fun newInstance(player: Player): DialoguePlugin { return WysonTheGardenerDialogue(player) } + /** + * Choose greeting. Either you have mole parts or just the normal greeting. + */ override fun open(vararg args: Any): Boolean { npc = args[0] as NPC - birdNest = player.inventory.containsItem(MOLE_CLAW) || player.inventory.containsItem(MOLE_SKIN) - if (birdNest) { - npc("If I'm not mistaken, you've got some mole bits there!", "I'll trade 'em for bird nest if ye likes.") + if (inInventory(player, Items.MOLE_CLAW_7416, 1) && inInventory(player, Items.MOLE_SKIN_7418, 1)) { + npc("If I'm not mistaken, you've got some claws and skin", " from a big mole there! I'll trade 'em for bird nests if ye", "likes. Or was ye wantin' some woad leaves instead?") + stage = 102 + return true + } + if (inInventory(player, Items.MOLE_SKIN_7418, 1)) { + npc("If I'm not mistaken, you've got some skin from a big", "mole there! I'll trade it for bird nests if ye likes. Or", "was ye wantin' some woad leaves instead?") stage = 100 return true } + if (inInventory(player, Items.MOLE_CLAW_7416, 1)) { + npc("If I'm not mistaken, you've got some claws from a big", "mole there! I'll trade it for bird nests if ye likes. Or", "was ye wantin' some woad leaves instead?") + stage = 101 + return true + } npc("I'm the head gardener around here.", "If you're looking for woad leaves, or if you need help", "with owt, I'm yer man.") stage = 0 return true } + /** + * Dialogue. + */ override fun handle(interfaceId: Int, buttonId: Int): Boolean { when (stage) { + + /** + * Dialogue options: woad leaves. + */ 0 -> { options("Yes please, I need woad leaves.", "Sorry, but I'm not interested.") stage = 1 @@ -105,15 +112,15 @@ class WysonTheGardenerDialogue : core.game.dialogue.DialoguePlugin { npc("Mmmm... ok, that sounds fair.") stage = 131 } - 131 -> if (player.inventory.contains(995, 15)) { - player.inventory.remove(COINS[0]) - player.inventory.add(WOAD_LEAF) + 131 -> if (removeItem(player,Item(Items.COINS_995, 15) ,Container.INVENTORY)) { + addItemOrDrop(player, Items.WOAD_LEAF_1793, 1) + player("Thanks.") - player.packetDispatch.sendMessage("You buy a woad leaf from Wyson.") + sendMessage(player, "You buy a woad leaf from Wyson.") stage = 132 } else { end() - player.packetDispatch.sendMessage("You need 15 cold coins to buy a woad leaf.") + sendMessage(player, "You need 15 gold coins to buy a woad leaf.") } 132 -> { npc("I'll be around if you have any more gardening needs.") @@ -124,32 +131,67 @@ class WysonTheGardenerDialogue : core.game.dialogue.DialoguePlugin { npc("Thanks for being generous", "here's an extra woad leaf.") stage = 141 } - 141 -> if (player.inventory.contains(995, 20)) { - player.inventory.remove(COINS[1]) - var i = 0 - while (i < 2) { - player.inventory.add(WOAD_LEAF, player) - i++ - } + 141 -> if (removeItem(player,Item(Items.COINS_995, 20) ,Container.INVENTORY)) { + addItemOrDrop(player, Items.WOAD_LEAF_1793, 2) player("Thanks.") - player.packetDispatch.sendMessage("You buy two woad leaves from Wyson.") + sendMessage(player, "You buy two woad leaves from Wyson.") stage = 132 } else { end() - player.packetDispatch.sendMessage("You need 15 cold coins to buy a woad leaf.") + sendMessage(player, "You need 20 gold coins to buy a woad leaf.") } 200 -> { npc("Fair enough.") stage = 201 } 201 -> end() + + /** + * Dialogue options: mole parts. + */ 100 -> { - options("Yes, I will trade the mole claws.", "Okay, I will trade the mole skin.", "I'd like to trade both.", "No, thanks.") + options("Ok, I will trade the mole skin.", "Yes please, I need woad leaves.", "Sorry, but I'm not interested.") stage = 900 } + 101 -> { + options("Yeah, I will trade the mole claws.", "Yes please, I need woad leaves.", "Sorry, but I'm not interested.") + stage = 901 + } + 102 -> { + options("Yeah, I will trade the mole claws.", "Okay, I will trade the mole skin.", "Alright, I'll trade the claws and skin.", "Yes please, I need woad leaves.", "Sorry, but I'm not interested.") + stage = 902 + } 900 -> when (buttonId) { 1 -> { - player("Yes, I will trade the mole claws.") + player("Ok, I will trade the mole skin.") + stage = 920 + } + 2 -> { + player("Yes please, I need woad leaves.") + stage = 10 + } + 3 -> { + player("Sorry, but I'm not interested.") + stage = 200 + } + } + 901 -> when (buttonId) { + 1 -> { + player("Yeah, I will trade the mole claws.") + stage = 910 + } + 2 -> { + player("Yes please, I need woad leaves.") + stage = 10 + } + 3 -> { + player("Sorry, but I'm not interested.") + stage = 200 + } + } + 902 -> when (buttonId) { + 1 -> { + player("Yeah, I will trade the mole claws.") stage = 910 } 2 -> { @@ -157,37 +199,48 @@ class WysonTheGardenerDialogue : core.game.dialogue.DialoguePlugin { stage = 920 } 3 -> { - player("I'd like to trade both.") + player("Alright, I'll trade the claws and skin.") stage = 930 } 4 -> { - player("No, thanks.") - stage = 999 + player("Yes please, I need woad leaves.") + stage = 10 + } + 5 -> { + player("Sorry, but I'm not interested.") + stage = 200 } } 910 -> { - if (!player.inventory.containsItem(MOLE_CLAW)) { + if (!inInventory(player, Items.MOLE_CLAW_7416, 1)) { player("Sorry, I don't have any mole claws.") stage = 999 + } else { + addClawRewards() + npc("Pleasure doing business with ya.") + stage = 999 } - end() - addRewards() } 920 -> { - if (!player.inventory.containsItem(MOLE_SKIN)) { + if (!inInventory(player, Items.MOLE_SKIN_7418, 1)) { player("Sorry, I don't have any mole skins.") stage = 999 + } else { + addSkinRewards() + npc("Pleasure doing business with ya.") + stage = 999 } - end() - addRewards() } 930 -> { - if (!player.inventory.containsItem(MOLE_CLAW) && !player.inventory.containsItem(MOLE_SKIN)) { + if (!inInventory(player, Items.MOLE_CLAW_7416, 1) || !inInventory(player, Items.MOLE_SKIN_7418, 1)) { player("Sorry, I don't have any.") stage = 999 + } else { + addClawRewards() + addSkinRewards() + npc("Pleasure doing business with ya.") + stage = 999 } - addRewards() - end() } 999 -> end() } @@ -196,26 +249,31 @@ class WysonTheGardenerDialogue : core.game.dialogue.DialoguePlugin { /** * Adds nests. - * @param nestAmount the amount. */ - private fun addRewards() { - val moleClaws = player.inventory.getAmount(Items.MOLE_CLAW_7416) - val moleSkin = player.inventory.getAmount(Items.MOLE_SKIN_7418) - val nestAmount = moleClaws + moleSkin - - //Remove claws and skins - player.inventory.remove(Item(Items.MOLE_CLAW_7416,moleClaws)) - player.inventory.remove(Item(Items.MOLE_SKIN_7418, moleSkin)) - - //Add white lily seeds if the player has the hard diary done - if(moleSkin > 0 && player.achievementDiaryManager.getDiary(DiaryType.FALADOR).checkComplete(DiaryLevel.HARD)) { - player.inventory.add(Item(14589, moleSkin), player) + private fun addClawRewards() { + // count the number of claws + val nestAmount = amountInInventory(player, Items.MOLE_CLAW_7416) + // remove the counted number of skins + if(removeItem(player, Item(Items.MOLE_CLAW_7416, nestAmount), Container.INVENTORY)){ + // add the counted number of nests. one by one so they each have random contents + for (i in 0 until nestAmount) { + addItemOrDrop(player, BirdNest.getRandomNest(true).nest.id, 1) + } } + } - //Add nests - for (i in 0 until nestAmount) { - if(!player.inventory.add(Item(BirdNest.getRandomNest(true).nest.id, 1), player)){ - GroundItemManager.create(Item(BirdNest.getRandomNest(true).nest.id,1),player.location,player) + private fun addSkinRewards() { + // count the number of skins + val nestAmount = amountInInventory(player, Items.MOLE_SKIN_7418) + // remove the counted number of skins + if(removeItem(player, Item(Items.MOLE_SKIN_7418, nestAmount), Container.INVENTORY)) { + // add the counted number of nests. one by one so they each have random contents + // if Falador Hard diary is complete, add a white lilly seed + for (i in 0 until nestAmount) { + addItemOrDrop(player, BirdNest.getRandomNest(true).nest.id, 1) + if (player.achievementDiaryManager.getDiary(DiaryType.FALADOR).isComplete(2)) { + addItemOrDrop(player, Items.WHITE_LILY_SEED_14589, 1) + } } } } @@ -224,25 +282,4 @@ class WysonTheGardenerDialogue : core.game.dialogue.DialoguePlugin { return intArrayOf(36) } - companion object { - /** - * Represents the coins item that can be used. - */ - private val COINS = arrayOf(Item(995, 15), Item(995, 20)) - - /** - * Represents the woad leaf item. - */ - private val WOAD_LEAF = Item(1793, 1) - - /** - * The mole claw item. - */ - private val MOLE_CLAW = Item(7416) - - /** - * The mole skin. - */ - private val MOLE_SKIN = Item(7418) - } } \ No newline at end of file diff --git a/Server/src/main/content/region/fremennik/lunarisle/dialogue/SirsalBankerDialogue.kt b/Server/src/main/content/region/fremennik/lunarisle/dialogue/SirsalBankerDialogue.kt deleted file mode 100644 index ebe225e11..000000000 --- a/Server/src/main/content/region/fremennik/lunarisle/dialogue/SirsalBankerDialogue.kt +++ /dev/null @@ -1,209 +0,0 @@ -package content.region.fremennik.lunarisle.dialogue - -import core.api.* -import core.game.dialogue.DialoguePlugin -import core.game.dialogue.FacialExpression -import core.game.node.entity.player.Player -import core.game.node.entity.player.link.IronmanMode -import core.plugin.Initializable -import org.rs09.consts.NPCs -import core.game.dialogue.IfTopic -import core.game.dialogue.Topic -import core.tools.END_DIALOGUE -import core.tools.START_DIALOGUE - -/** - * Handles Sirsal banker dialogue tree. - * - * @author vddCore - */ -@Initializable -class SirsalBankerDialogue(player: Player? = null) : DialoguePlugin(player) { - override fun handle(interfaceId: Int, buttonId: Int): Boolean { - when (stage) { - START_DIALOGUE -> if (hasSealOfPassage(player)) { - if (hasIronmanRestriction(player, IronmanMode.ULTIMATE)) { - npcl( - FacialExpression.NEUTRAL, - "My apologies, dear ${if (player.isMale) "sir" else "madam"}, " + - "our services are not available for Ultimate ${if (player.isMale) "Ironmen" else "Ironwomen"}" - ).also { stage = END_DIALOGUE } - } else { - - npcl( - FacialExpression.NEUTRAL, - "Good day, how may I help you?" - ).also { - if (hasAwaitingGrandExchangeCollections(player)) { - stage++ - } else { - stage += 2 - } - } - } - } else { - playerl(FacialExpression.HALF_WORRIED, "Hi, I...") - stage = 30 - } - - 1 -> npcl( - FacialExpression.NEUTRAL, - "Before we go any further, I should inform you that you " + - "have items ready for collection from the Grand Exchange." - ).also { stage++ } - - 2 -> showTopics( - Topic(FacialExpression.NEUTRAL, "I'd like to access my bank account, please.", 10), - IfTopic( - FacialExpression.NEUTRAL, - "I'd like to switch to my ${getBankAccountName(player, true)} bank account.", - 13, - hasActivatedSecondaryBankAccount(player) - ), - IfTopic( - FacialExpression.NEUTRAL, - "I'd like to open a secondary bank account.", - 20, - !hasActivatedSecondaryBankAccount(player) - ), - Topic(FacialExpression.NEUTRAL, "I'd like to check my PIN settings.", 11), - Topic(FacialExpression.NEUTRAL, "I'd like to collect items.", 12), - Topic(FacialExpression.ASKING, "What is this place?", 3), - ) - - 3 -> npcl( - FacialExpression.NEUTRAL, - "This is a branch of the Bank of Gielinor. We have branches in many towns." - ).also { stage++ } - - 4 -> playerl( - FacialExpression.ASKING, - "And what do you do?" - ).also { stage++ } - - 5 -> npcl( - FacialExpression.NEUTRAL, - "We will look after your items and money for you. " + - "Leave your valuables with us if you want to keep them safe." - ).also { stage = END_DIALOGUE } - - 10 -> { - openBankAccount(player) - end() - } - - 11 -> { - openBankPinSettings(player) - end() - } - - 12 -> { - openGrandExchangeCollectionBox(player) - end() - } - - 13 -> { - toggleBankAccount(player) - - npcl( - FacialExpression.NEUTRAL, - "Your active bank account has been switched. " + - "You can now access your ${getBankAccountName(player)} account." - ).also { stage = END_DIALOGUE } - } - - 20 -> npcl( - FacialExpression.NEUTRAL, - "Certainly. We offer secondary accounts to all our customers." - ).also { stage++ } - - 21 -> npcl( - FacialExpression.NEUTRAL, - "The secondary account comes with a standard fee of 5,000,000 coins. The fee is non-refundable " + - "and account activation is permanent." - ).also { stage++ } - - 22 -> npcl( - FacialExpression.NEUTRAL, - "If your inventory does not contain enough money to cover the costs, we will complement " + - "the amount with the money inside your primary bank account." - ).also { stage++ } - - 23 -> npcl( - FacialExpression.ASKING, - "Knowing all this, would you like to proceed with opening your secondary bank account?" - ).also { stage++ } - - 24 -> showTopics( - Topic(FacialExpression.NEUTRAL, "Yes, I am still interested.", 25), - Topic(FacialExpression.NEUTRAL, "Actually, I've changed my mind.", 26) - ) - - 25 -> { - when (activateSecondaryBankAccount(player)) { - SecondaryBankAccountActivationResult.ALREADY_ACTIVE -> { - npcl( - FacialExpression.NEUTRAL, - "Your bank account was already activated, there is no need to pay twice." - ).also { stage = END_DIALOGUE } - } - - SecondaryBankAccountActivationResult.INTERNAL_FAILURE -> { - npcl( - FacialExpression.NEUTRAL, - "I must apologize, the transaction was not successful. Please check your " + - "primary bank account and your inventory - if there's money missing, please " + - "screenshot your chat box and contact the game developers." - ).also { stage = END_DIALOGUE } - } - - SecondaryBankAccountActivationResult.NOT_ENOUGH_MONEY -> { - npcl( - FacialExpression.NEUTRAL, - "It appears that you do not have the money necessary to cover the costs " + - "associated with opening a secondary bank account. I will be waiting here " + - "until you do." - ).also { stage = END_DIALOGUE } - } - - SecondaryBankAccountActivationResult.SUCCESS -> { - npcl( - FacialExpression.NEUTRAL, - "Your secondary bank account has been opened and can be accessed through any " + - "of the Bank of Gielinor's employees. Thank you for choosing our services." - ).also { stage = END_DIALOGUE } - } - } - } - - 26 -> npcl( - FacialExpression.NEUTRAL, - "Very well. Should you decide a secondary bank account is needed, do not hesitate to " + - "contact any of the Bank of Gielinor's stationary employees. We will be happy to help." - ).also { stage = END_DIALOGUE } - - 30 -> npcl( - FacialExpression.ANNOYED, - "What are you doing here, Fremennik?!" - ).also { stage++ } - - 31 -> playerl( - FacialExpression.WORRIED, - "I have a Seal of Pass..." - ).also { stage++ } - - 32 -> npcl( - FacialExpression.ANGRY, - "No you don't! Begone!" - ).also { stage = END_DIALOGUE } - - /* TODO: Is the above related to Lunar Diplomacy? */ - } - - return true - } - - override fun getIds(): IntArray { - return intArrayOf(NPCs.SIRSAL_BANKER_4519) - } -} diff --git a/Server/src/main/content/region/kandarin/dialogue/AstronomyBook.kt b/Server/src/main/content/region/kandarin/dialogue/AstronomyBook.kt deleted file mode 100644 index 80df6aa8c..000000000 --- a/Server/src/main/content/region/kandarin/dialogue/AstronomyBook.kt +++ /dev/null @@ -1,130 +0,0 @@ -package content.region.kandarin.dialogue - -import content.global.handlers.iface.BookInterface -import content.global.handlers.iface.BookLine -import content.global.handlers.iface.Page -import content.global.handlers.iface.PageSet -import core.ServerConstants -import core.api.setAttribute -import core.game.interaction.IntType -import core.game.interaction.InteractionListener -import core.game.node.entity.player.Player -import core.plugin.Initializable -import org.rs09.consts.Items - -@Initializable -class AstronomyBook : InteractionListener { - companion object { - private val TITLE = "The tales of Scorpius" - private val CONTENTS = arrayOf( - PageSet( - Page( - BookLine("A History of Astronomy", 55), - BookLine("in ${ServerConstants.SERVER_NAME}.", 56), - BookLine("", 57), - BookLine("At the start of the 4th age,", 58), - BookLine("a learned man by the", 59), - BookLine("name of Scorpius, known well", 60), - BookLine("for his powers of vision and", 61), - BookLine("magic, sought communion with", 62), - BookLine("the gods of the world.", 63), - BookLine("So many unanswered questions", 64), - BookLine("had he that devoted his entire", 65), - ), - Page( - BookLine("life to this cause.", 66), - BookLine("After many years of study,", 67), - BookLine("seeking knowledge from the", 68), - BookLine("wise of that time, he developed", 69), - BookLine("a machine infused with", 70), - BookLine("magical power, infused with", 71), - BookLine("the ability to pierce", 72), - BookLine("into the heavens - a huge eye", 73), - BookLine("that gave the user", 74), - BookLine("incredible site, like never", 75), - BookLine("seen before.", 76), - ) - ), - PageSet( - Page( - BookLine("As time passed, Scorpius", 55), - BookLine("grew adept at using his", 56), - BookLine("specialized skills, and followed", 57), - BookLine("the movements of stars", 58), - BookLine("in ${ServerConstants.SERVER_NAME}, which are", 59), - BookLine("mapped and named, and still", 60), - BookLine("used to this day.", 61), - BookLine("", 62), - BookLine("Before long, Scorpius", 63), - BookLine("used his knowledge", 64), - BookLine("for predicting the future,", 65), - ), - Page( - BookLine("and, in turn, he called upon", 66), - BookLine("the dark knowledge of", 67), - BookLine("Zamorakian worshipers", 68), - BookLine("to further his cause. Living", 69), - BookLine("underground, the followers", 70), - BookLine("of the dark god remained", 71), - BookLine("until the civilization of", 72), - BookLine("Ardougne grew in strength", 73), - BookLine("and control.", 74), - ) - ), - PageSet( - Page( - BookLine("The kings of that time", 55), - BookLine("worked to banish the", 56), - BookLine("Zamorakian followers in", 57), - BookLine("the area, hiding all", 58), - BookLine("reference to Scorpius's", 59), - BookLine("invention, due to", 60), - BookLine("its 'evil nature'.", 61), - BookLine("", 62), - BookLine("Years later, when the", 63), - BookLine("minds of the kings lent", 64), - BookLine("more towards the research", 65), - ), - Page( - BookLine("of the unknown, the plans", 66), - BookLine("of Scorpius were uncovered", 67), - BookLine("and the heavenly eye", 68), - BookLine("constructed again. Since then,", 69), - BookLine("many have studied the ways of", 70), - BookLine("the astronomer, Scorpius", 71), - BookLine("and in his memory a grave", 72), - BookLine("was constructed near the", 73), - BookLine("Observatory. Some claim his", 74), - BookLine("ghost still wanders nearby,", 75), - BookLine("in torment as he seeks", 76), - ) - ), - PageSet( - Page( - BookLine("the secrets of the heavens", 55), - BookLine("that can never be solved.", 56), - BookLine("Tales tell that he will", 57), - BookLine("grant those adept in the", 58), - BookLine("arts of the astronomer a", 59), - BookLine("blessing of unusual power.", 60), - BookLine("", 61), - BookLine("Here ends the tale of", 62), - BookLine("how astronomy entered", 63), - BookLine("the known world.", 64), - ) - ), - ) - } - - private fun display(player: Player, pageNum: Int, buttonID: Int): Boolean { - BookInterface.pageSetup(player, BookInterface.FANCY_BOOK_3_49, TITLE, CONTENTS) - return true - } - - override fun defineListeners() { - on(Items.ASTRONOMY_BOOK_600, IntType.ITEM, "read") { player, _ -> - BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display) - return@on true - } - } -} diff --git a/Server/src/main/content/region/kandarin/dialogue/EniolaDialogue.kt b/Server/src/main/content/region/kandarin/dialogue/EniolaDialogue.kt index de5a3e3c8..fd294e309 100644 --- a/Server/src/main/content/region/kandarin/dialogue/EniolaDialogue.kt +++ b/Server/src/main/content/region/kandarin/dialogue/EniolaDialogue.kt @@ -1,8 +1,10 @@ package content.region.kandarin.dialogue +import core.ServerConstants import core.api.* import core.game.dialogue.DialoguePlugin import core.game.dialogue.FacialExpression +import core.game.dialogue.IfTopic import core.game.node.entity.player.Player import core.game.node.entity.player.link.IronmanMode import core.plugin.Initializable @@ -54,11 +56,16 @@ class EniolaDialogue(player: Player? = null) : DialoguePlugin(player) { 4 -> showTopics( Topic(FacialExpression.HALF_THINKING, "If you work for the bank, what are you doing here?", 10), Topic(FacialExpression.NEUTRAL, "I'd like to access my bank account, please.", 30), + IfTopic(FacialExpression.NEUTRAL, "I'd like to open a secondary bank account.", 5, ServerConstants.SECOND_BANK && !hasActivatedSecondaryBankAccount(player)), + IfTopic(FacialExpression.NEUTRAL, "I'd like to switch to my ${getBankAccountName(player, true)} bank account.", 40, hasActivatedSecondaryBankAccount(player)), Topic(FacialExpression.NEUTRAL, "I'd like to check my PIN settings.", 31), Topic(FacialExpression.NEUTRAL, "I'd like to see my collection box.", 32), - Topic(FacialExpression.NEUTRAL, "Never mind.", END_DIALOGUE) + IfTopic(FacialExpression.NEUTRAL, "Never mind.", END_DIALOGUE, !ServerConstants.SECOND_BANK) ) + 5 -> npcl(FacialExpression.ASKING, "Sorry, ${if (player.isMale) "sir" else "ma'am"}, the bank didn't license me for that.").also { stage++ } + 6 -> playerl(FacialExpression.HALF_GUILTY, "Oh, okay, I'll ask a banker who is stationed in an actual bank.").also { stage = END_DIALOGUE } + 10 -> npcl( FacialExpression.NEUTRAL, "My presence here is the start of a new enterprise of travelling banks. " + @@ -133,7 +140,7 @@ class EniolaDialogue(player: Player? = null) : DialoguePlugin(player) { 22 -> npcl( FacialExpression.NEUTRAL, - "I'm sorry to hear that, dear ${if (player.isMale) "sir" else "madam"}. " + "I'm sorry to hear that, dear ${if (player.isMale) "sir" else "madam"}." ).also { stage++ } 23 -> npcl( @@ -158,10 +165,15 @@ class EniolaDialogue(player: Player? = null) : DialoguePlugin(player) { openInterface(player, Components.BANK_CHARGE_ZMI_619) end() } + + 40 -> { + toggleBankAccount(player) + npcl( FacialExpression.NEUTRAL, "Your active bank account has been switched. You can now access your ${getBankAccountName(player)} account.").also { stage = END_DIALOGUE } + } } return true } override fun getIds(): IntArray = intArrayOf(NPCs.ENIOLA_6362) -} \ No newline at end of file +} diff --git a/Server/src/main/content/region/kandarin/handlers/GnomeTortoiseNPC.kt b/Server/src/main/content/region/kandarin/handlers/GnomeTortoiseNPC.kt new file mode 100644 index 000000000..84d32fdbc --- /dev/null +++ b/Server/src/main/content/region/kandarin/handlers/GnomeTortoiseNPC.kt @@ -0,0 +1,284 @@ +package content.region.kandarin.handlers + +import core.api.* +import core.game.node.entity.Entity +import core.game.node.entity.combat.CombatStyle +import core.game.node.entity.combat.CombatSwingHandler +import core.game.node.entity.combat.MultiSwingHandler +import core.game.node.entity.combat.equipment.SwitchAttack +import core.game.node.entity.impl.Animator.Priority +import core.game.node.entity.impl.Projectile +import core.game.node.entity.npc.AbstractNPC +import core.game.node.entity.npc.NPC +import core.game.node.entity.npc.NPCBehavior +import core.game.node.entity.npc.agg.AggressiveBehavior +import core.game.node.entity.npc.agg.AggressiveHandler +import core.game.node.entity.player.Player +import core.game.world.GameWorld +import core.game.world.map.Direction +import core.game.world.map.Location +import core.game.world.update.flag.context.Animation +import core.plugin.Initializable +import org.rs09.consts.NPCs + +/* + * Behavior is based on the following sources: + * https://www.youtube.com/watch?v=u2A-_ihV_2w (november 2008) + * https://www.youtube.com/watch?v=O0EIZu7-iys (august 2009) + * https://runescape.wiki/w/Tortoise?oldid=815524 https://web.archive.org/web/20090308094532/http://runescape.wikia.com/wiki/Tortoise + * + * todo fix att, str, def? I'm not sure what is the typical way to calculate these when they are unknown. + * level 79: HP 101, max hit 12, "low attack but good defence" + * level 92: HP 121, max hit 12, "low attack but good defence" + * + * https://runescape.wiki/w/Gnome_Archer?oldid=949978 https://web.archive.org/web/20090929124104/http://runescape.wikia.com/wiki/Gnome_archer + * https://runescape.wiki/w/Gnome_Driver?oldid=1235409 https://web.archive.org/web/20090929124109/http://runescape.wikia.com:80/wiki/Gnome_driver + * https://runescape.wiki/w/Gnome_Mage?oldid=1425756 https://web.archive.org/web/20090928131013/http://runescape.wikia.com:80/wiki/Gnome_mage + */ + +@Initializable +class GnomeTortoiseNPC(id: Int = 0, location: Location? = null) : AbstractNPC(id, location) { + + override fun construct(id: Int, location: Location, vararg objects: Any): AbstractNPC { + return GnomeTortoiseNPC(id, location) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.TORTOISE_3808) + } + + fun spawnGnomes(location: Location, direction: Direction) { + + //todo: sometimes the child spawns or direction is wrong on death. does a move trigger right before it dies? + + var archerLoc = location + var driverLoc = location + var mageLoc = location + + // X D X 3x3 turtle, C center + // M C A D driver, M mage, A archer + // X X X + + // If I was smart I could probably do this with vectors, but I'm dumb so just doing the possibilities by hand. + if(direction == Direction.NORTH) { + archerLoc = location.transform(1,0,0) + driverLoc = location.transform(0,1,0) + mageLoc = location.transform(-1,0,0) + }else if(direction == Direction.NORTH_EAST) { + archerLoc = location.transform(1,-1,0) + driverLoc = location.transform(1,1,0) + mageLoc = location.transform(-1,1,0) + }else if(direction == Direction.EAST) { + archerLoc = location.transform(0,-1,0) + driverLoc = location.transform(1,0,0) + mageLoc = location.transform(0,1,0) + }else if(direction == Direction.SOUTH_EAST) { + archerLoc = location.transform(-1,-1,0) + driverLoc = location.transform(1,-1,0) + mageLoc = location.transform(1,1,0) + }else if(direction == Direction.SOUTH) { + archerLoc = location.transform(-1,0,0) + driverLoc = location.transform(0,-1,0) + mageLoc = location.transform(1,0,0) + }else if(direction == Direction.SOUTH_WEST) { + archerLoc = location.transform(-1,1,0) + driverLoc = location.transform(-1,-1,0) + mageLoc = location.transform(1,-1,0) + }else if(direction == Direction.WEST) { + archerLoc = location.transform(0,1,0) + driverLoc = location.transform(-1,0,0) + mageLoc = location.transform(0,-1,0) + }else if(direction == Direction.NORTH_WEST) { + archerLoc = location.transform(1,1,0) + driverLoc = location.transform(-1,1,0) + mageLoc = location.transform(-1,-1,0) + } + + val npcArcher = GnomeArcherNPC(NPCs.GNOME_ARCHER_3814, archerLoc) + npcArcher.sendChat("Argh!") + npcArcher.init() + + val npcDriver = GnomeDriverNPC(NPCs.GNOME_DRIVER_3815, driverLoc) + npcDriver.sendChat("Nooooo! Dobbie's dead!") + npcDriver.init() + + val npcMage = GnomeMageNPC(NPCs.GNOME_MAGE_3816, mageLoc) + npcMage.sendChat("Kill the infidel!") + npcMage.init() + } + + override fun finalizeDeath(killer: Entity?) { + val turtleLoc = this.centerLocation + val turtleDir = this.direction + spawnGnomes(turtleLoc, turtleDir) + super.finalizeDeath(killer) + // todo remove this debug if not needed. It's just telling me the "direction" the tortoise dies in so I can verify the direction that the child NPCs should spawn. + if (killer is Player) { + killer.debug(direction.toString()) + } + } +} + +//handles the attack switching +class GnomeTortoiseBehavior : NPCBehavior(NPCs.TORTOISE_3808) { + + private val combatHandler = MultiSwingHandler( + true, + // per wiki source, melee has a max hit of 12 + SwitchAttack( + CombatStyle.MELEE.swingHandler, + Animation(3953, Priority.HIGH) + ), + // todo correct the projectile locations (they should originate from the range or mage gnome, not the center). not sure how to do this. + SwitchAttack( + CombatStyle.RANGE.swingHandler, + Animation(3954, Priority.HIGH), + null, + null, + Projectile.create( + null as Entity?, + null, + 10, //bronze arrow + 35, + 30, + 10, + 50, + 14, + 255 + ) + ), + // per wiki source above, spell should be water strike with the sounds of ice barrage + SwitchAttack( + CombatStyle.MAGIC.swingHandler, + Animation(3955, Priority.HIGH), + null, + null, + Projectile.create( + null as Entity?, + null, + 94, //water strike + 35, + 30, + 10, + 50, + 14, + 255 + ) + ) + ) + + override fun getSwingHandlerOverride(self: NPC, original: CombatSwingHandler): CombatSwingHandler { + return combatHandler + } +} + +// Handles Gnome Archer behavior +class GnomeArcherNPC(id: Int = 0, location: Location? = null) : AbstractNPC(id, location) { + + override fun construct(id: Int, location: Location, vararg objects: Any): AbstractNPC { + return GnomeArcherNPC(id, location) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.GNOME_ARCHER_3814) + } + + override fun init() { + super.init() + this.isRespawn = false + this.isAggressive = true + this.aggressiveHandler = AggressiveHandler(this, object : AggressiveBehavior() { + override fun ignoreCombatLevelDifference(): Boolean { + return true + } + }) + } +} + +class GnomeArcherBehavior : NPCBehavior(NPCs.GNOME_ARCHER_3814) { + override fun onCreation(self: NPC) { + // stops the entity from instantly moving. + delayEntity(self, 1) + setAttribute(self, "despawn-time", GameWorld.ticks + 25) + } + + override fun tick(self: NPC): Boolean { + if (!self.inCombat() && (getAttribute(self, "despawn-time", 0) <= GameWorld.ticks)) + self.clear() + return true + } +} + +// Handles Gnome Mage behavior +class GnomeMageNPC(id: Int = 0, location: Location? = null) : AbstractNPC(id, location) { + + override fun construct(id: Int, location: Location, vararg objects: Any): AbstractNPC { + return GnomeMageNPC(id, location) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.GNOME_MAGE_3816) + } + + override fun init() { + super.init() + this.isRespawn = false + this.isAggressive = true + this.aggressiveHandler = AggressiveHandler(this, object : AggressiveBehavior() { + override fun ignoreCombatLevelDifference(): Boolean { + return true + } + }) + } +} + +class GnomeMageBehavior : NPCBehavior(NPCs.GNOME_MAGE_3816) { + override fun onCreation(self: NPC) { + // stops the entity from instantly moving. + delayEntity(self, 1) + setAttribute(self, "despawn-time", GameWorld.ticks + 25) + } + + override fun tick(self: NPC): Boolean { + if (!self.inCombat() && (getAttribute(self, "despawn-time", 0) <= GameWorld.ticks)) + self.clear() + return true + } +} + +// Handles Gnome Driver behavior +class GnomeDriverNPC(id: Int = 0, location: Location? = null) : AbstractNPC(id, location) { + + override fun construct(id: Int, location: Location, vararg objects: Any): AbstractNPC { + return GnomeDriverNPC(id, location) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.GNOME_DRIVER_3815) + } + + override fun init() { + super.init() + this.isRespawn = false + this.isAggressive = true + this.aggressiveHandler = AggressiveHandler(this, object : AggressiveBehavior() { + override fun ignoreCombatLevelDifference(): Boolean { + return true + } + }) + } +} + +class GnomeDriverBehavior : NPCBehavior(NPCs.GNOME_DRIVER_3815) { + override fun onCreation(self: NPC) { + // stops the entity from instantly moving. + delayEntity(self, 1) + setAttribute(self, "despawn-time", GameWorld.ticks + 25) + } + + override fun tick(self: NPC): Boolean { + if (!self.inCombat() && (getAttribute(self, "despawn-time", 0) <= GameWorld.ticks)) + self.clear() + return true + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/pisc/dialogue/ArnoldLydsporDialogue.kt b/Server/src/main/content/region/kandarin/pisc/dialogue/ArnoldLydsporDialogue.kt index 1fd390535..b1233b698 100644 --- a/Server/src/main/content/region/kandarin/pisc/dialogue/ArnoldLydsporDialogue.kt +++ b/Server/src/main/content/region/kandarin/pisc/dialogue/ArnoldLydsporDialogue.kt @@ -1,115 +1,101 @@ package content.region.kandarin.pisc.dialogue -import core.api.* -import core.game.dialogue.DialoguePlugin -import core.game.dialogue.FacialExpression -import core.game.node.entity.player.Player +import core.ServerConstants +import core.api.addItemOrDrop +import core.api.hasActivatedSecondaryBankAccount +import core.api.hasIronmanRestriction +import core.api.isUsingSecondaryBankAccount +import core.api.openBankAccount +import core.api.openBankPinSettings +import core.api.openGrandExchangeCollectionBox +import core.api.openNpcShop +import core.api.toggleBankAccount +import core.game.dialogue.ChatAnim +import core.game.dialogue.DialogueLabeller +import core.game.dialogue.DialogueOption +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.npc.NPC import core.game.node.entity.player.link.IronmanMode -import core.plugin.Initializable +import core.game.node.item.Item import org.rs09.consts.Items import org.rs09.consts.NPCs -import core.game.dialogue.IfTopic -import core.game.dialogue.Topic -import core.tools.END_DIALOGUE -import core.tools.START_DIALOGUE -/** - * Provides the regular dialogue for Arnold Lydspor. - * TODO: Swan Song quest will need a special case handling. - * - * @author vddCore - */ -@Initializable -class ArnoldLydsporDialogue(player: Player? = null) : DialoguePlugin(player) { - override fun handle(interfaceId: Int, buttonId: Int): Boolean { - when (stage) { - START_DIALOGUE -> npcl( - FacialExpression.FRIENDLY, - "Ah, you come back! What you want from Arnold, heh?" - ).also { stage++ } - - 1 -> showTopics( - IfTopic( - FacialExpression.ASKING, - "Can you open my bank account, please?", - 2, - !hasIronmanRestriction(player, IronmanMode.ULTIMATE) - ), - - IfTopic( - FacialExpression.NEUTRAL, - "I'd like to check my bank PIN settings.", - 3, - !hasIronmanRestriction(player, IronmanMode.ULTIMATE) - ), - IfTopic( - FacialExpression.NEUTRAL, - "I'd like to collect items.", - 4, - !hasIronmanRestriction(player, IronmanMode.ULTIMATE) - ), - Topic(FacialExpression.ASKING, "Would you like to trade?", 5), - Topic(FacialExpression.FRIENDLY, "Nothing, I just came to chat.", 7) - ) - - 2 -> { - openBankAccount(player) - end() - } - - 3 -> { - openBankPinSettings(player) - end() - } - - 4 -> { - openGrandExchangeCollectionBox(player) - end() - } - - 5 -> npcl( - FacialExpression.FRIENDLY, - "Ja, I have wide range of stock..." - ).also { stage++ } - - 6 -> { - openNpcShop(player, NPCs.ARNOLD_LYDSPOR_3824) - end() - } - - 7 -> npcl(FacialExpression.FRIENDLY, - "Ah, that is nice - always I like to chat, but " + - "Herr Caranos tell me to get back to work! " + - "Here, you been nice, so have a present." - ).also { stage++ } - - 8 -> sendItemDialogue( - player, - Items.CABBAGE_1965, - "Arnold gives you a cabbage." - ).also { - addItemOrDrop(player, Items.CABBAGE_1965) - stage++ - } - - 9 -> playerl( - FacialExpression.HALF_THINKING, - "A cabbage?" - ).also { stage++ } - - 10 -> npcl( - FacialExpression.HAPPY, - "Ja, cabbage is good for you!" - ).also { stage++ } - - 11 -> playerl( - FacialExpression.NEUTRAL, - "Um... Thanks!" - ).also { stage = END_DIALOGUE } +class ArnoldLydsporDialogue : InteractionListener { + override fun defineListeners() { + on(NPCs.ARNOLD_LYDSPOR_3824, IntType.NPC, "talk-to") { player, node -> + DialogueLabeller.open(player, ArnoldLydsporLabellerFile(), node as NPC) + return@on true + } + on(NPCs.ARNOLD_LYDSPOR_3824, IntType.NPC, "bank") { player, _ -> + openBankAccount(player) + return@on true + } + on(NPCs.ARNOLD_LYDSPOR_3824, IntType.NPC, "collect") { player, _ -> + openGrandExchangeCollectionBox(player) + return@on true } - - return true } - override fun getIds(): IntArray = intArrayOf(NPCs.ARNOLD_LYDSPOR_3824) -} \ No newline at end of file + class ArnoldLydsporLabellerFile : DialogueLabeller() { + override fun addConversation() { + npc(ChatAnim.FRIENDLY, "Ah, you come back! What you want from Arnold, heh?") + goto("main options") + + label("main options") + options( + DialogueOption("access", "Can you open my bank account please?", expression = ChatAnim.ASKING) { player, _ -> !hasIronmanRestriction(player, IronmanMode.ULTIMATE) }, + DialogueOption("buy second bank", "I'd like to open a secondary bank account.") { player, _ -> return@DialogueOption !hasIronmanRestriction(player, IronmanMode.ULTIMATE) && ServerConstants.SECOND_BANK && !hasActivatedSecondaryBankAccount(player) }, + DialogueOption("switch second bank", "I'd like to switch to my primary bank account.") { player, _ -> return@DialogueOption hasActivatedSecondaryBankAccount(player) && isUsingSecondaryBankAccount(player) }, + DialogueOption("switch second bank", "I'd like to switch to my secondary bank account.") { player, _ -> return@DialogueOption hasActivatedSecondaryBankAccount(player) && !isUsingSecondaryBankAccount(player) }, + DialogueOption("pin", "I'd like to check my PIN settings.", expression = ChatAnim.NEUTRAL) { player, _ -> !hasIronmanRestriction(player, IronmanMode.ULTIMATE) }, + DialogueOption("collect", "I'd like to collect items.", expression = ChatAnim.NEUTRAL) { player, _ -> !hasIronmanRestriction(player, IronmanMode.STANDARD) }, + DialogueOption("trade", "Would you like to trade?", expression = ChatAnim.ASKING), + DialogueOption("chat", "Nothing, I just came to chat.", expression = ChatAnim.FRIENDLY) { player, _ -> return@DialogueOption hasIronmanRestriction(player, IronmanMode.STANDARD) || !ServerConstants.SECOND_BANK } + ) + + label("access") + exec { player, _ -> openBankAccount(player) } + goto("nowhere") + + label("buy second bank") + npc(ChatAnim.GUILTY, "I'm so sorry! My little shop does not have the fibre-optic connections to be able to open a second account. Try asking again in a brick-and-mortar building in a big city!") + player(ChatAnim.FRIENDLY, "That's okay, I understand. I will ask again in a big bank like Varrock's.") + goto("nowhere") + + label("switch second bank") + exec { + player, _ -> toggleBankAccount(player) + loadLabel(player, if (isUsingSecondaryBankAccount(player)) "now secondary" else "now primary") + } + label("now primary") + npc("Your active bank account has been switched. You can now access your primary account.") + goto("main options") + label("now secondary") + npc("Your active bank account has been switched. You can now access your secondary account.") + goto("main options") + + label("pin") + exec { player, _ -> openBankPinSettings(player) } + goto("nowhere") + + label("collect") + exec { player, _ -> openGrandExchangeCollectionBox(player) } + goto("nowhere") + + label("trade") + npc(ChatAnim.FRIENDLY, "Ja, I have wide range of stock...") + exec { player, _ -> openNpcShop(player, NPCs.ARNOLD_LYDSPOR_3824) } + goto("nowhere") + + label("chat") + npc("Ah, that is nice - always I like to chat, but Herr Caranos tell me to get back to work! Here, you been nice, so have a present.") + exec { player, _ -> addItemOrDrop(player, Items.CABBAGE_1965) } + item(Item(Items.CABBAGE_1965), "Arnold gives you a cabbage.") + player(ChatAnim.HALF_THINKING, "A cabbage?") + npc(ChatAnim.HAPPY, "Ja, cabbage is good for you!") + player(ChatAnim.NEUTRAL, "Um... Thanks!") + goto("nowhere") + } + } +} diff --git a/Server/src/main/content/region/kandarin/quest/grandtree/BlackDemonCutscene.kt b/Server/src/main/content/region/kandarin/quest/grandtree/BlackDemonCutscene.kt index 2afe9f1cf..186d85616 100644 --- a/Server/src/main/content/region/kandarin/quest/grandtree/BlackDemonCutscene.kt +++ b/Server/src/main/content/region/kandarin/quest/grandtree/BlackDemonCutscene.kt @@ -1,20 +1,16 @@ package content.region.kandarin.quest.grandtree -import content.region.misthalin.dorgeshuun.quest.thelosttribe.LostTribeCutscene import core.ServerConstants -import core.api.openDialogue import core.api.sendChat -import core.api.sendDialogue -import core.api.sendMessage import core.game.activity.Cutscene import core.game.dialogue.FacialExpression import core.game.node.entity.player.Player import core.game.world.map.Direction import core.game.world.map.Location -import core.game.global.action.DoorActionHandler -import core.game.node.entity.npc.NPC +import core.game.world.update.flag.context.Animation import org.rs09.consts.NPCs +// Source video https://www.youtube.com/watch?v=LS1Xtuz0sLA class BlackDemonCutscene(player: Player) : Cutscene(player) { override fun setup() { setExit(Location.create(2491, 9864, 0)) @@ -23,7 +19,6 @@ class BlackDemonCutscene(player: Player) : Cutscene(player) { } loadRegion(9882) addNPC(NPCs.GLOUGH_671, 48, 8, Direction.WEST) - addNPC(NPCs.BLACK_DEMON_677, 43, 9, Direction.EAST) } override fun runStage(stage: Int) { @@ -40,6 +35,8 @@ class BlackDemonCutscene(player: Player) : Cutscene(player) { rotateCamera(0, 0) sendChat(player, "Hello?") player.face(getNPC(NPCs.GLOUGH_671)!!) + addNPC(NPCs.BLACK_DEMON_677, 39, 8, Direction.EAST) + move(getNPC(NPCs.BLACK_DEMON_677)!!, 41, 8) timedUpdate(3) } 2 -> { @@ -53,7 +50,7 @@ class BlackDemonCutscene(player: Player) : Cutscene(player) { timedUpdate(10) } 4 -> { - moveCamera(55, 4,2000) + moveCamera(55, 4, 2000) rotateCamera(55, 6) timedUpdate(4) } @@ -62,24 +59,27 @@ class BlackDemonCutscene(player: Player) : Cutscene(player) { playerDialogueUpdate(FacialExpression.SCARED, "Glough?") } 6 -> { - dialogueUpdate(NPCs.GLOUGH_671, FacialExpression.ANGRY, "You really are becoming a headache! Well, at least now you can die knowing you were right, it will save me having to hunt you down like all the other human filth of " + ServerConstants.SERVER_NAME + "!") + dialogueUpdate(NPCs.GLOUGH_671, FacialExpression.OLD_ANGRY1, "You really are becoming a headache! Well, at least now you can die knowing you were right, it will save me having to hunt you down like all the other human filth of " + ServerConstants.SERVER_NAME + "!") } 7 -> { playerDialogueUpdate(FacialExpression.SCARED, "You're crazy, Glough!") } 8 -> { - dialogueUpdate(NPCs.GLOUGH_671, FacialExpression.ANGRY, "Bah! Well, soon you'll see, the gnomes are ready to fight. In three weeks this tree will be dead wood, in ten weeks it will be 30 battleships! Finally we will rid the world of the disease called humanity!") + dialogueUpdate(NPCs.GLOUGH_671, FacialExpression.OLD_ANGRY1, "Bah! Well, soon you'll see, the gnomes are ready to fight. In three weeks this tree will be dead wood, in ten weeks it will be 30 battleships! Finally we will rid the world of the disease called humanity!") } 9 -> { playerDialogueUpdate(FacialExpression.SCARED, "What makes you think I'll let you get away with it?") } 10 -> { - moveCamera(47,9) - rotateCamera(40, 9) - dialogueUpdate(NPCs.GLOUGH_671, FacialExpression.ANGRY, "Fool...meet my little friend!") + moveCamera(55, 8, 700) + rotateCamera(48, 8) + val demon = getNPC(NPCs.BLACK_DEMON_677)!! + move(demon, 46, 8) + demon.animate(Animation(64, 140)) + dialogueUpdate(NPCs.GLOUGH_671, FacialExpression.OLD_ANGRY1, "Fool...meet my little friend!") } 11 -> { - end{ + end(fade = false){ BlackDemonNPC(NPCs.BLACK_DEMON_677, Location.create(2485, 9864, 0)).init() } } diff --git a/Server/src/main/content/region/kandarin/quest/observatoryquest/AstronomyBook.kt b/Server/src/main/content/region/kandarin/quest/observatoryquest/AstronomyBook.kt new file mode 100644 index 000000000..f228e0864 --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/observatoryquest/AstronomyBook.kt @@ -0,0 +1,169 @@ +package content.region.kandarin.quest.observatoryquest + +import content.global.handlers.iface.* +import core.ServerConstants +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.player.Player +import org.rs09.consts.Items + +/** + * Astronomy Book + */ +class AstronomyBook : InteractionListener { + companion object { + private const val TITLE = "THE TALE OF SCORPIUS" + private val CONTENTS = arrayOf( + PageSet( + Page( + BookLine("THE TALE OF", 55), + BookLine("SCORPIUS: ", 56), + BookLine("", 57), + BookLine("A History of Astronomy", 58), + BookLine("in ${ServerConstants.SERVER_NAME}.", 59), + BookLine("", 60), + BookLine("At the start of the 4th", 61), + BookLine("Age, a learned man by", 62), + BookLine("the name of Scorpius,", 63), + BookLine("known well for his powers", 64), + BookLine("of vision and magic,", 65), + ), + Page( + BookLine("sought communion with", 66), + BookLine("the gods of the world. So", 67), + BookLine("many unanswered", 68), + BookLine("questions had he that he", 69), + BookLine("devoted his entire lif to", 70), + BookLine("the cause.", 71), + BookLine("", 72), + BookLine("After many years of", 73), + BookLine("study, seeking knowledge", 74), + BookLine("from the wise of that", 75), + BookLine("time, he developed a", 76), + ) + ), + PageSet( + Page( + BookLine("machine infused with", 55), + BookLine("magical power, infused", 56), + BookLine("with the ability to pierce", 57), + BookLine("into the very heavens - a", 58), + BookLine("huge eye that gave the", 59), + BookLine("user incredible sight, like", 60), + BookLine("never before.", 61), + BookLine("", 62), + BookLine("", 63), + BookLine("", 64), + BookLine("", 65), + ), + Page( + BookLine("As time passed, Scorpius", 66), + BookLine("grew adept at using his", 67), + BookLine("specialized skills, and", 68), + BookLine("followed the movements of", 69), + BookLine("the stars in ${ServerConstants.SERVER_NAME}", 70), + BookLine("which he mapped and", 71), + BookLine("named, and are still used", 72), + BookLine("to this very day.", 73), + BookLine("", 74), + BookLine("Before long, Scorpius", 75), + BookLine("used his knowledge for", 76), + ) + ), + PageSet( + Page( + BookLine("predicting the future,", 55), + BookLine("and, in turn, he called", 56), + BookLine("upon the dark knowledge", 57), + BookLine("of Zamorakian", 58), + BookLine("worshippers to further his", 59), + BookLine("cause. Living below", 60), + BookLine("ground, the followers of", 61), + BookLine("the dark god remained", 62), + BookLine("until the civilisation of", 63), + BookLine("Ardougne grew in", 64), + BookLine("strength and control.", 65), + ), + Page( + BookLine("", 66), + BookLine("", 67), + BookLine("", 68), + BookLine("", 69), + BookLine("", 70), + BookLine("", 71), + BookLine("The kings of that time", 72), + BookLine("worked to banish the", 73), + BookLine("Zamorakian followers in", 74), + BookLine("the area, hiding all", 75), + BookLine("references to Scorpius's", 76), + ) + ), + PageSet( + Page( + BookLine("invention, due to its 'evil", 55), + BookLine("nature'.", 56), + BookLine("", 57), + BookLine("Years after, when the", 58), + BookLine("minds of the kings lent", 59), + BookLine("more towards the", 60), + BookLine("research of the unknown,", 61), + BookLine("the plans of Scorpius", 62), + BookLine("were uncovered and the", 63), + BookLine("heavenly eye constructed", 64), + BookLine("again.", 65), + ), + Page( + BookLine("Since then, many have", 66), + BookLine("studied the ways of the", 67), + BookLine("astronomer, Scorpius, and", 68), + BookLine("in his memory a grave", 69), + BookLine("was constructed near the", 70), + BookLine("Observatory.", 71), + BookLine("", 72), + BookLine("", 73), + BookLine("", 74), + BookLine("", 75), + BookLine("", 76), + ) + ), + + PageSet( + Page(), + Page( + BookLine("Some claim his ghost still", 66), + BookLine("wanders nearby, in", 67), + BookLine("torment as he seeks the", 68), + BookLine("secrets of the heavens", 69), + BookLine("that can never be solved.", 70), + BookLine("Tales tell that he will", 71), + BookLine("grant those adept in the", 72), + BookLine("arts of the astronomer a", 73), + BookLine("blessing of unusual", 74), + BookLine("power.", 75), + BookLine("", 76), + ) + ), + PageSet( + Page( + BookLine("Here ends the tale of how", 55), + BookLine("astronomy entered the", 56), + BookLine("known world.", 57), + ), + Page() + ), + ) + private fun display(player:Player, pageNum: Int, buttonID: Int) : Boolean { + BookInterface.pageSetup(player, BookInterface.FANCY_BOOK_3_49, TITLE, CONTENTS) + BookInterface.setModelOnPage(player,2, 27071, BookInterface.FANCY_BOOK_3_49, 33, 34, 1020, 0, 0) + BookInterface.setModelOnPage(player,4, 27069, BookInterface.FANCY_BOOK_3_49, 17, 18, 1224, 0, 0) + return true + } + } + + override fun defineListeners() { + on(Items.ASTRONOMY_BOOK_600, IntType.ITEM, "read") { player, _ -> + BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display) + return@on true + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/observatoryquest/GoblinDialogues.kt b/Server/src/main/content/region/kandarin/quest/observatoryquest/GoblinDialogues.kt new file mode 100644 index 000000000..5d676d5de --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/observatoryquest/GoblinDialogues.kt @@ -0,0 +1,152 @@ +package content.region.kandarin.quest.observatoryquest + +import core.api.* +import core.game.dialogue.* +import core.game.interaction.InteractionListener +import core.game.node.entity.npc.AbstractNPC +import core.game.node.entity.npc.NPC +import core.game.world.map.Location +import core.plugin.Initializable +import core.tools.RandomFunction +import org.rs09.consts.NPCs + +class GoblinDialogues : InteractionListener { + override fun defineListeners() { + on(NPCs.GREASYCHEEKS_6127, NPC, "talk-to") { player, node -> + openDialogue(player, GreasycheeksDialogueFile(), node as NPC) + return@on true + } + on(NPCs.SMELLYTOES_6128, NPC, "talk-to") { player, node -> + openDialogue(player, SmellytoesDialogueFile(), node as NPC) + return@on true + } + on(NPCs.CREAKYKNEES_6129, NPC, "talk-to") { player, node -> + openDialogue(player, CreakykneesDialogueFile(), node as NPC) + return@on true + } + } +} + +class GreasycheeksDialogueFile : DialogueLabeller() { + override fun addConversation() { + assignToIds(NPCs.GREASYCHEEKS_6127) + player("Hello.") + npc(ChatAnim.OLD_NORMAL, "Shush! I'm concentrating.") + player("Oh, sorry.") + } +} + +class SmellytoesDialogueFile : DialogueLabeller() { + override fun addConversation() { + assignToIds(NPCs.SMELLYTOES_6128) + player("Hi there.") + npc(ChatAnim.OLD_NORMAL, "Hey, ids me matesh!") + player("Sorry, have we met?") + npc(ChatAnim.OLD_NORMAL, "Yeah! you wazsh wiv me in dat pub overy by hill!") + player("I have no idea what you're going on about.") + npc(ChatAnim.OLD_NORMAL, "Glad yeeash remembers.") + } +} + +class CreakykneesDialogueFile : DialogueLabeller() { + override fun addConversation() { + assignToIds(NPCs.CREAKYKNEES_6129) + player("Where did you get that lens?") + npc(ChatAnim.OLD_NORMAL, "From that strange metal thing up on the hill.") + player(ChatAnim.ANGRY, "You should give that back!") + npc(ChatAnim.OLD_NORMAL, "Even if it's cracked?") + player("Ah, well, I suppose it's of no use. But, still.") + } +} + +@Initializable +class GreasycheeksNPC(id: Int = 0, location: Location? = null) : AbstractNPC(id, location) { + override fun construct(id: Int, location: Location?, vararg objects: Any?): AbstractNPC { + return GreasycheeksNPC(id, location) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.GREASYCHEEKS_6127) + } + + override fun handleTickActions() { + super.handleTickActions() + if (RandomFunction.roll(12)) { + sendChat(this, arrayOf( + "Cook, cook, cook!", + "I'm so hungry!", + "This is gonna taste sooo good!", + ).random()) + } + } +} + +@Initializable +class SmellytoesNPC(id: Int = 0, location: Location? = null) : AbstractNPC(id, location) { + override fun construct(id: Int, location: Location?, vararg objects: Any?): AbstractNPC { + return SmellytoesNPC(id, location) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.SMELLYTOES_6128) + } + + override fun handleTickActions() { + super.handleTickActions() + if (RandomFunction.roll(12)) { + sendChat(this, arrayOf( + "Doh ray meeee laa doh faaa!", + "La la la! Do di dum dii!", + ).random()) + } + } +} + +@Initializable +class CreakykneesNPC(id: Int = 0, location: Location? = null) : AbstractNPC(id, location) { + override fun construct(id: Int, location: Location?, vararg objects: Any?): AbstractNPC { + return CreakykneesNPC(id, location) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.CREAKYKNEES_6129) + } + + override fun handleTickActions() { + super.handleTickActions() + if (RandomFunction.roll(12)) { + sendChat(this, arrayOf( + "Was that a spark?", + "Come on! Please light!", + ).random()) + } + } +} +@Initializable +class LostGoblinNPC(id: Int = 0, location: Location? = null) : AbstractNPC(id, location) { + override fun construct(id: Int, location: Location?, vararg objects: Any?): AbstractNPC { + return LostGoblinNPC(id, location) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.GOBLIN_6125) + } + + override fun handleTickActions() { + super.handleTickActions() + if (RandomFunction.roll(12)) { + sendChat(this, arrayOf( + "Which way should I go?", + "These dungeons are such a maze.", + "Where's the exit?!?", + "This is the fifth time this week. I'm lost!", + "I've been wandering around down here for hours.", + "How do you get back to the village?", + "I hate being so lost!", + "How could I be so disoriented?", + "Where am I? I'm so lost.", + "I know the exit's around here, somewhere." + ).random()) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/observatoryquest/GoblinGuardNPC.kt b/Server/src/main/content/region/kandarin/quest/observatoryquest/GoblinGuardNPC.kt new file mode 100644 index 000000000..963438628 --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/observatoryquest/GoblinGuardNPC.kt @@ -0,0 +1,31 @@ +package content.region.kandarin.quest.observatoryquest + +import content.data.Quests +import core.api.* +import core.game.node.entity.Entity +import core.game.node.entity.npc.AbstractNPC +import core.game.node.entity.player.Player +import core.game.world.map.Location +import core.plugin.Initializable +import org.rs09.consts.NPCs + +@Initializable +class GoblinGuardNPC(id: Int = 0, location: Location? = null) : AbstractNPC(id, location) { + override fun construct(id: Int, location: Location, vararg objects: Any): AbstractNPC { + return GoblinGuardNPC(id, location) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.SLEEPING_GUARD_6122, NPCs.GOBLIN_GUARD_489) + } + + override fun finalizeDeath(entity: Entity) { + if (entity is Player) { + val player = entity.asPlayer() + if (getQuestStage(player, Quests.OBSERVATORY_QUEST) >= 4) { + setAttribute(player, ObservatoryQuest.attributeKilledGuard, true) + } + super.finalizeDeath(player) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryAssistantDialogue.kt b/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryAssistantDialogue.kt new file mode 100644 index 000000000..5c7f1f7eb --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryAssistantDialogue.kt @@ -0,0 +1,238 @@ +package content.region.kandarin.quest.observatoryquest + +import content.data.Quests +import content.region.kandarin.quest.observatoryquest.ObservatoryQuest.Companion.attributeReceivedWine +import core.api.* +import core.game.dialogue.* +import core.game.node.entity.player.Player +import core.game.node.item.Item +import core.plugin.Initializable +import org.rs09.consts.Items +import org.rs09.consts.NPCs + +@Initializable +class ObservatoryAssistantDialogue (player: Player? = null) : DialoguePlugin(player) { + override fun newInstance(player: Player): DialoguePlugin { + return ObservatoryAssistantDialogue(player) + } + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + openDialogue(player, ObservatoryAssistantDialogueFile(), npc) + return false + } + override fun getIds(): IntArray { + return intArrayOf(NPCs.OBSERVATORY_ASSISTANT_6118, 10022) + } +} + +class ObservatoryAssistantDialogueFile : DialogueLabeller() { + override fun addConversation() { + assignToIds(NPCs.OBSERVATORY_ASSISTANT_6118) + + exec { player, npc -> + if (getQuestStage(player, Quests.OBSERVATORY_QUEST) == 0) { + loadLabel(player, "observatoryQuest0") + } else if (getAttribute(player, attributeReceivedWine, false)) { + loadLabel(player, "receivedwine") + } else if (getQuestStage(player, Quests.OBSERVATORY_QUEST) == 100) { + loadLabel(player, "observatoryqueststage100") + } else { + loadLabel(player, "randomStart" + (1..4).random()) + } + } + + label("observatoryQuest0") + player(FacialExpression.ASKING, "Hi, are you busy?") + npc("Me? I'm always busy.") + npc("See that man there? That's the professor. If he had his way, I think he'd never let me sleep!") + npc("Anyway, how might I help you?") + options( + DialogueOption("wonderingwhat", "I was wondering what you do here."), + DialogueOption("justlooking", "Just looking around, thanks."), + DialogueOption("looktelescope", "Can I have a look through that telescope?", expression = FacialExpression.ASKING) + ) + + label("wonderingwhat") + npc("Glad you ask. This is the Observatory reception. Up on the cliff is the Observatory dome, from which you can view the heavens. Usually...") + player(FacialExpression.ASKING, "What do you mean, 'usually'?") + npc("*Ahem*. Back to work, please.") + npc("I'd speak with the professor. He'll explain.") + + label("justlooking") + npc("Okay, just don't break anything. If you need any help, let me know.") + line("The assistant continues with his work.") + + label("looktelescope") + npc("You can. You won't see much though.") + player(FacialExpression.ASKING, "And that's because?") + npc("Just talk to the professor. He'll fill you in.") + + label("randomStart1") + player(FacialExpression.ASKING, "Can I interrupt you?") + npc("I suppose so. Please be quick though.") + goto("afterRandomStart") + + label("randomStart2") + player(FacialExpression.ASKING, "Might I have a word?") + npc("Sure, how can I help you?") + goto("afterRandomStart") + + label("randomStart3") + player("Hello there.") + npc("Yes?") + goto("afterRandomStart") + + label("randomStart4") + player(FacialExpression.ASKING, "Can I speak with you?") + npc("Why, of course. What is it?") + goto("afterRandomStart") + + + label("afterRandomStart") + exec { player, npc -> + loadLabel(player, "observatoryqueststage" + getQuestStage(player, Quests.OBSERVATORY_QUEST)) + } + + label("observatoryqueststage1") + exec { player, _ -> + if (inInventory(player, Items.PLANK_960, 3)) { + loadLabel(player, "enoughplanks") + } + else if (inInventory(player, Items.PLANK_960)) { + loadLabel(player, "someplanks") + } + else { + loadLabel(player, "notenoughplanks") + } + } + + label("notenoughplanks") + player(FacialExpression.ASKING,"Where can I find planks of wood? I need some for the telescope's base.") + npc("I understand planks can be found at Port Khazard, to the east of here. There are some at the Barbarian Outpost, too.") + npc("Failing that, you could always ask the Sawmill Operator. He's to the north-east of Varrock, by the Lumber Yard.") + + label("enoughplanks") + player("I've got some planks for the telescope's base.") + npc("Good work! The professor has been eagerly awaiting them.") + + label("someplanks") + player("I've got a plank.") + npc("That's nice.") + player("You know, for the telescope's base.") + npc("Well done. Remember that you'll need three in total.") + + label("observatoryqueststage2") + exec { player, _ -> + if(inInventory(player, Items.BRONZE_BAR_2349)) { + loadLabel(player, "hasbronzebar") + } else { + loadLabel(player, "nohasbronzebar") + } + } + + label("nohasbronzebar") + player(FacialExpression.ASKING, "Can you help me? How do I go about getting a bronze bar?") + npc("You'll need to use tin and copper ore on a furnace to produce this metal.") + player("Right you are.") + + label("hasbronzebar") + player("The bronze bar is ready, and waiting for the professor.") + npc("He'll surely be pleased. Go ahead and give it to him.") + + label("observatoryqueststage3") + exec { player, _ -> + if(inInventory(player, Items.MOLTEN_GLASS_1775)) { + loadLabel(player, "hasmoltenglass") + } else { + loadLabel(player, "nohasmoltenglass") + } + } + + label("nohasmoltenglass") + player(FacialExpression.ASKING, "What's the best way for me to get molten glass?") + npc("There are many ways, but I'd suggest making it yourself. Get yourself a bucket of sand and some soda ash, which you can get from using seaweed with a furnace. Use the soda ash and sand together in a") + npc("furnace and bang - molten glass is all yours. There's a book about it on the table if you want to know more.") + player("Thank you!") + + label("hasmoltenglass") + player("I've managed to get hold of some molten glass.") + npc("I suggest you have a word with the professor, in that case.") + + label("observatoryqueststage4") + exec { player, _ -> + if(inInventory(player, Items.LENS_MOULD_602)) { + loadLabel(player, "haslensmould") + } else { + loadLabel(player, "nohaslensmould") + } + } + + label("nohaslensmould") + player(FacialExpression.ASKING, "Where can I find this lens mould you mentioned?") + npc("I'm sure I heard one of those goblins talking about it. I bet they've hidden it somewhere. Probably using it for some strange purpose, I'm sure.") + player(FacialExpression.ASKING, "What makes you say that?") + npc("I had a nice new star chart, until recently. I went out to do an errand for the professor the other day, only to see a goblin using it...") + npc("...as some kind of makeshift hankey to blow his nose!") + player("Oh dear.") + npc("You may want to look through the dungeon they have under their little village.") + player("Thanks for the advice.") + + label("haslensmould") + player("I have the lens mould.") + npc("Well done on finding that! I am honestly quite impressed. Make sure you take it straight to the professor.") + player("Will do.") + + label("observatoryqueststage5") + exec { player, _ -> + if(inInventory(player, Items.OBSERVATORY_LENS_603)) { + loadLabel(player, "haslens") + } else { + loadLabel(player, "nohaslens") + } + } + + label("nohaslens") + player(FacialExpression.ASKING, "How should I make this lens?") + npc("Just use the molten glass with the mould. Simple.") + player("Thanks!") + + label("haslens") + player("Do you like this lens? Good, huh?") + npc("Nice. What's that scratch?") + player("Oh, erm, that's a feature.") + player("Yes, that's it! Indubitably, it facilitates the triangulation of photonic illumination to the correct...") + npc("Stop! You can't confuse me with big words. Just pray the professor doesn't notice.") + + label("observatoryqueststage6") + player("Hello again.") + npc("Ah, it's the telescope repairman! The professor is waiting for you in the Observatory.") + player("How can I get to the Observatory?") + npc("Well, since the bridge was ruined, you will have to travel through the dungeon under the goblins' settlement.") + + label("observatoryqueststage100") + player("Hi assistant.") + player("Wait a minute.") + player(FacialExpression.ASKING, "What's your real name?") + npc(FacialExpression.ASKING, "My real name?") + player(FacialExpression.ASKING, "I only know you as the professor's assistant. What's your actual name?") + npc("Patrick.") + player("Hi Patrick, I'm @name.") + npc("Well, hello again, and thanks for helping out the professor.") + npc(" Oh, and, believe it or not, his name is Mambo-duna-roona, but don't tell him I told you.") + player(FacialExpression.AMAZED, "You made that up!") + npc("No, honest! Anyways, you've made my life much easier. Have a drink on me!") + item(Item(Items.JUG_OF_WINE_1993), "The assistant gives you some wine.") + exec { player, _ -> + addItemOrDrop(player, Items.JUG_OF_WINE_1993) + setAttribute(player, attributeReceivedWine, true) + } + player("Thanks very much.") + npc("No problem. Scorpius would be proud.") + player(FacialExpression.ASKING, "Sorry?") + npc("You may want to check out that book on the table, and perhaps look around for a grave...") + + label("receivedwine") + npc(FacialExpression.ASKING, "How was the wine?") + player(FacialExpression.DRUNK, "That was good stuff, *hic*! Wheresh the professher?") + npc(FacialExpression.ASKING, "The professor? He's up in the Observatory. Since the bridge was ruined, you will have to travel through the dungeon under the goblins' settlement.") + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryCutscene.kt b/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryCutscene.kt new file mode 100644 index 000000000..0eae2b1ab --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryCutscene.kt @@ -0,0 +1,199 @@ +package content.region.kandarin.quest.observatoryquest + +import core.api.* +import core.game.activity.Cutscene +import core.game.dialogue.FacialExpression +import core.game.node.entity.player.Player +import core.game.world.map.Direction +import org.rs09.consts.Animations +import org.rs09.consts.NPCs +import content.region.kandarin.quest.observatoryquest.ObservatoryQuest.Companion.attributeFinishedCutscene +import core.game.world.map.Location + +class ObservatoryCutscene(player: Player) : Cutscene(player) { + override fun setup() { + setExit(Location(2438, 3163)) + loadRegion(9777) + addNPC(PROFESSOR, 7, 25, Direction.SOUTH) + } + + override fun runStage(stage: Int) { + when (stage) { + 0 -> { + fadeToBlack() + timedUpdate(5) + } + 1 -> { + teleport(player, 6, 27) + face(player, getNPC(PROFESSOR)!!.location) + rotateCamera(9, 25, 300) + moveCamera(25, 22, 1800) + timedUpdate(5) + } + 2 -> { + fadeFromBlack() + dialogueLinesUpdate("~ The Observatory ~", "The great eye into the heavens.") + } + 3 -> { + moveCamera(14, 10, 1800, 10) + timedUpdate(3) + } + 4 -> { + moveCamera(6, 10, 1800, 10) + timedUpdate(3) + } + 5 -> { + moveCamera(0, 32, 1800, 10) + timedUpdate(3) + } + 6 -> { + moveCamera(4, 36, 1600, 10) + timedUpdate(3) + } + 7 -> { + rotateCamera(7, 25, 300, 5) + moveCamera(13, 40, 1800, 10) + timedUpdate(3) + } + 8 -> { + moveCamera(12, 36, 1800, 5) + timedUpdate(5) + } + 9 -> { + fadeToBlack() + timedUpdate(5) + } + 10 -> { + moveCamera(5, 21, 500) + rotateCamera(6, 27, 100) + timedUpdate(3) + } + 11 -> { + fadeFromBlack() + timedUpdate(5) + } + 12 -> { + playerDialogueUpdate(FacialExpression.NEUTRAL, "Hi professor!") + } + 13 -> { + face(getNPC(PROFESSOR)!!, player.location) + timedUpdate(3) + } + 14 -> { + animate(getNPC(PROFESSOR)!!, WAVE) + dialogueUpdate(PROFESSOR, FacialExpression.HAPPY, "Oh, hi there.") + } + 15 -> { + dialogueUpdate(PROFESSOR, FacialExpression.HAPPY, "I'm just adding the finishing touches.") + } + 16 -> { + playerDialogueUpdate(FacialExpression.HAPPY, "Okay, don't let me interrupt.") + } + 17 -> { + dialogueUpdate(PROFESSOR, FacialExpression.NEUTRAL, "Thank you.") + } + 18 -> { + face(getNPC(PROFESSOR)!!, Location(136, 25)) + dialogueUpdate(PROFESSOR, FacialExpression.NEUTRAL, "Right, let's get this finished.") + } + 19 -> { + animate(getNPC(PROFESSOR)!!, THINKING) + sendChat(getNPC(PROFESSOR)!!, "Hmmmm...") + timedUpdate(5) + } + 20 -> { + move(getNPC(PROFESSOR)!!, 7, 23) + rotateCamera(9, 23, 100) + moveCamera(12, 30, 300) + timedUpdate(3) + } + 21 -> { + move(getNPC(PROFESSOR)!!, 10, 26) + timedUpdate(10) + } + 22 -> { + face(getNPC(PROFESSOR)!!, Location(135, 25)) + animate(getNPC(PROFESSOR)!!, FIX) + sendChat(getNPC(PROFESSOR)!!, "Bit of a tap here...") + timedUpdate(3) + } + 23 -> { + move(getNPC(PROFESSOR)!!, 11, 26) + timedUpdate(5) + } + 24 -> { + face(getNPC(PROFESSOR)!!, player.location) + dialogueUpdate(PROFESSOR, FacialExpression.HAPPY, "@name, I'm just going upstairs to finish off.") + } + 25 -> { + playerDialogueUpdate(FacialExpression.HAPPY, "Right-oh.") + } + 26 -> { + face(getNPC(PROFESSOR)!!, Location(140, 25)) + timedUpdate(3) + } + 27 -> { + teleport(getNPC(PROFESSOR)!!, 11, 22, 1) + teleport(player, 14,29,1) + face(getNPC(PROFESSOR)!!, Location(2442, 3158, 1)) + moveCamera(6, 20, 400) + rotateCamera(10, 24, 400) + timedUpdate(5) + } + 28 -> { + move(getNPC(PROFESSOR)!!, 7, 27) + moveCamera(10, 20, 400, 1) + rotateCamera(7, 27, 50, 20) + timedUpdate(10) + } + 29 -> { + sendChat(getNPC(PROFESSOR)!!, "In goes the lens.") + timedUpdate(3) + } + 30 -> { + animate(getNPC(PROFESSOR)!!, TUNEUP) + timedUpdate(20) + } + 31 -> { + sendChat(getNPC(PROFESSOR)!!, "And one final adjustment.") + move(getNPC(PROFESSOR)!!, 10, 29) + timedUpdate(6) + } + 32 -> { + move(getNPC(PROFESSOR)!!, 10, 25) + face(getNPC(PROFESSOR)!!, Location(137, 26)) + timedUpdate(6) + } + 33 -> { + face(getNPC(PROFESSOR)!!, Location(137, 26)) + animate(getNPC(PROFESSOR)!!, CRANK) + sendChat(getNPC(PROFESSOR)!!, "And all our work pays off.") + timedUpdate(6) + } + 34 -> { + animate(getNPC(PROFESSOR)!!, CHEER) + timedUpdate(10) + } + 35 -> { + moveCamera(6, 27, 500, 5) + rotateCamera(10, 25, 50, 5) + timedUpdate(3) + } + 36 -> { + end { + setAttribute(player, attributeFinishedCutscene, true) + } + } + } + } + + companion object { + val PROFESSOR = NPCs.OBSERVATORY_PROFESSOR_6121 + val WAVE = Animations.HUMAN_WAVE_863 + val THINKING = 6844 + val FIX = 6847 + val TUNEUP = 6848 + val CRANK = 6845 + val CHEER = Animations.HUMAN_CHEER_862 + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryProfessorDialogue.kt b/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryProfessorDialogue.kt new file mode 100644 index 000000000..380334049 --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryProfessorDialogue.kt @@ -0,0 +1,676 @@ +package content.region.kandarin.quest.observatoryquest + +import content.data.Quests +import core.api.* +import core.game.dialogue.* +import content.region.kandarin.quest.observatoryquest.ObservatoryQuest.Companion.attributeReceivedWine +import core.game.interaction.QueueStrength +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import core.plugin.Initializable +import org.rs09.consts.Components +import org.rs09.consts.Items +import org.rs09.consts.NPCs + +@Initializable +class ObservatoryProfessorDialogue (player: Player? = null) : DialoguePlugin(player) { + override fun newInstance(player: Player): DialoguePlugin { + return ObservatoryProfessorDialogue(player) + } + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + openDialogue(player, ObservatoryProfessorDialogueFile(), npc) + return false + } + override fun getIds(): IntArray { + return intArrayOf(NPCs.OBSERVATORY_ASSISTANT_6119, NPCs.OBSERVATORY_ASSISTANT_6120) + } +} + +class ObservatoryProfessorDialogueFile : DialogueLabeller() { + override fun addConversation() { + assignToIds(NPCs.OBSERVATORY_PROFESSOR_488) + + npc("What would you like to talk about?") + options( + DialogueOption("observatoryquest", "Talk about the Observatory Quest.", skipPlayer = true), + DialogueOption("treasuretrails", "Talk about the Treasure Trails.", skipPlayer = true), + ) + + label("observatoryquest") + exec { player, npc -> + loadLabel(player, "observatoryqueststage" + getQuestStage(player, Quests.OBSERVATORY_QUEST)) + } + + label("observatoryqueststage0") + player("Hi, I was...") + npc(FacialExpression.FRIENDLY, "Welcome to the magnificent wonder of the Observatory, where wonder is all around you, where the stars can be clutched from the heavens!") + player("Wow, nice intro.") + npc(FacialExpression.FRIENDLY, "Why, thanks! How might I help you?") + options( + DialogueOption("totallylost", "I'm totally lost."), + DialogueOption("whatwhat", "An Observatory?", expression = FacialExpression.THINKING), + DialogueOption("nah", "I'm just passing through."), + ) + + label("totallylost") + npc(FacialExpression.THINKING, "Lost? It must have been those pesky goblins that led you astray.") + npc("Head north-east to find the city of Ardougne.") + player("I'm sure I'll find the way.") + player("Thanks for all your help.") + npc(FacialExpression.FRIENDLY, "No problem at all. Come and visit again!") + line("The professor carries on with his studies.") + + label("nah") + npc("Fair enough. Not everyone is interested in this place, I suppose.") + + label("whatwhat") + npc(FacialExpression.FRIENDLY, "Of course. We have a superb telescope up in the Observatory, on the hill.") + npc(FacialExpression.FRIENDLY, "A truly marvellous invention, the likes of which you'll never behold again.") + npc(FacialExpression.NEUTRAL, NPCs.OBSERVATORY_ASSISTANT_6118, "Well, it would be if it worked.") + npc(FacialExpression.ANGRY, "Don't interrupt!") + player(FacialExpression.THINKING, "What? It doesn't work?") + npc("Oh, no, no, no. Don't listen to him, he's joking. Aren't you, my FAITHFUL assistant?") + npc(FacialExpression.NEUTRAL, NPCs.OBSERVATORY_ASSISTANT_6118, "Nope, dead serious. Hasn't been working for a long time.") + npc(FacialExpression.ANGRY, "Arghhh! Get back to work and stop sticking your nose in!") + player("So, it's broken. How come?") + npc(FacialExpression.SAD, "Oh, I suppose there's no use keeping it secret. Did you see those houses outside?") + player("Up on the hill? Yes, I've seen them.") + npc("It's a horde of goblins.") + npc(FacialExpression.SAD, "Since they moved here they have caused nothing but trouble.") + npc("Last week, my telescope was tampered with.") + npc(FacialExpression.ANGRY, "Now, parts need replacing before it can be used again. They've even been messing around in the dungeons under this area. Something needs to be done.") + npc(FacialExpression.NEUTRAL, NPCs.OBSERVATORY_ASSISTANT_6118, "Strikes me that this visitor could help us.") + npc(FacialExpression.ANGRY, "Stop being so rude.") + npc("...") + npc("Although, he has a point. What do you say?") + player(FacialExpression.THINKING, "What, me?") + options( + DialogueOption("yestoquest", "Sounds interesting. How can I help?", "Sounds interesting, what can I do for you?", FacialExpression.FRIENDLY), + DialogueOption("notoquest", "Oh, sorry, I don't have time for that."), + ) + + label("notoquest") + npc("Oh dear. I really do need some help.") + npc("If you see anyone who can help then please send them my way.") + + label("yestoquest") + exec { player, _ -> + if(getQuestStage(player, Quests.OBSERVATORY_QUEST) == 0) { + setQuestStage(player, Quests.OBSERVATORY_QUEST, 1) + } + } + npc(FacialExpression.FRIENDLY, "Oh, thanks so much.") + npc(FacialExpression.FRIENDLY, "I shall need some materials for the telescope, so it can be used again.") + npc("Let's start with three planks of wood for the telescope base. My assistant will help with obtaining these, won't you?") + npc(FacialExpression.NEUTRAL, NPCs.OBSERVATORY_ASSISTANT_6118, "As if I don't have enough work to do. Seems I don't have a choice.") + npc(FacialExpression.FRIENDLY, "Go talk to him if you need some advice.") + player(FacialExpression.FRIENDLY, "Okay, I'll be right back.") + + label("observatoryqueststage1") + player(FacialExpression.FRIENDLY, "Hi again!") + npc(FacialExpression.FRIENDLY, "It's my helping hand, back again.") + npc(FacialExpression.THINKING, "Do you have the planks yet?") + exec { player, _ -> + if(inInventory(player, Items.PLANK_960, 3)) { + loadLabel(player, "enoughplanks") + } else { + loadLabel(player, "notenoughplanks") + } + } + + label("notenoughplanks") + player("Sorry, not yet. Three planks was it?") + npc("It was indeed.") + + label("enoughplanks") + player("Yes, I've got them. Here they are.") + exec { player, _ -> + if(removeItem(player, Item(Items.PLANK_960, 3))) { + if (getQuestStage(player, Quests.OBSERVATORY_QUEST) == 1) { + setQuestStage(player, Quests.OBSERVATORY_QUEST, 2) + } + } + } + npc(FacialExpression.FRIENDLY, "Well done. This will make a big difference.") + npc(FacialExpression.FRIENDLY, "Now, the bronze for the tube. Oh, assistant!") + npc(FacialExpression.NEUTRAL, NPCs.OBSERVATORY_ASSISTANT_6118, "Okay, okay, ask me if you need any help, @name.") + + label("observatoryqueststage2") + player("Hi.") + npc("The traveller returns!") + player("Still working hard?") + npc(FacialExpression.NEUTRAL, NPCs.OBSERVATORY_ASSISTANT_6118, "Some of us are.") + npc(FacialExpression.ANGRY, "What did I tell you about speaking when spoken to?") + npc(FacialExpression.THINKING, "So, @name, you have the bronze bar?") + exec { player, _ -> + if(inInventory(player, Items.BRONZE_BAR_2349)) { + loadLabel(player, "hasbronzebar") + } else { + loadLabel(player, "nohasbronzebar") + } + } + + label("nohasbronzebar") + player("Not yet.") + npc("Please bring me one, then.") + + label("hasbronzebar") + player(FacialExpression.FRIENDLY, "I certainly do. Here you go.") + exec { player, _ -> + if(removeItem(player, Item(Items.BRONZE_BAR_2349))) { + if (getQuestStage(player, Quests.OBSERVATORY_QUEST) == 2) { + setQuestStage(player, Quests.OBSERVATORY_QUEST, 3) + } + } + } + npc(FacialExpression.FRIENDLY, "Great. Now all I need is the lens made.") + npc(FacialExpression.FRIENDLY, "Please get me some molten glass.") + npc("Oi! Lazy bones!") + player(FacialExpression.AMAZED, "What? I'm not lazy.") + npc("Not you! I'm talking to my assistant.") + npc(FacialExpression.NEUTRAL, NPCs.OBSERVATORY_ASSISTANT_6118, "Calm down old man, I heard. @name, I'm here if you need any help.") + npc("Thank you. Wait a minute, who are you calling 'old'?") + + label("observatoryqueststage3") + npc("How are you getting on finding me some molten glass?") + exec { player, _ -> + if(inInventory(player, Items.MOLTEN_GLASS_1775)) { + loadLabel(player, "hasmoltenglass") + } else { + loadLabel(player, "nohasmoltenglass") + } + } + + label("nohasmoltenglass") + player("Still working on it.") + npc("I really need it. Please hurry.") + + label("hasmoltenglass") + player(FacialExpression.FRIENDLY, "Here it is.") + exec { player, _ -> + if(removeItem(player, Item(Items.MOLTEN_GLASS_1775))) { + if (getQuestStage(player, Quests.OBSERVATORY_QUEST) == 3) { + setQuestStage(player, Quests.OBSERVATORY_QUEST, 4) + } + } + } + npc(FacialExpression.FRIENDLY, "Excellent work, let's make the lens.") + npc(FacialExpression.NEUTRAL, NPCs.OBSERVATORY_ASSISTANT_6118, "It'll need to be made to an exact shape and size.") + npc("Well, obviously, hence why we have a lens mould.") + npc(FacialExpression.NEUTRAL, NPCs.OBSERVATORY_ASSISTANT_6118, "Not any more. One of those goblins took it.") + npc(FacialExpression.SAD, "Great, just what I need. @name, I don't suppose you could find it?") + player(FacialExpression.FRIENDLY, "I'll have a look - where should I start?") + npc(FacialExpression.FRIENDLY, "No idea. You could ask my USELESS assistant if you want.") + npc(FacialExpression.NEUTRAL, NPCs.OBSERVATORY_ASSISTANT_6118, "What have I done to deserve this?") + + label("observatoryqueststage4") + npc(FacialExpression.THINKING, "Did you bring me the mould?") + exec { player, _ -> + if(inInventory(player, Items.LENS_MOULD_602)) { + loadLabel(player, "haslensmould") + } else { + loadLabel(player, "nohaslensmould") + } + } + + label("nohaslensmould") + player("Still looking for it.") + npc("Please try and find it; my assistant may be able to help.") + + label("haslensmould") + player(FacialExpression.HAPPY, "I certainly have. You'll never guess what they were doing with it.") + npc("Well, from the smell I'd guess cooking some vile concoction.") + player(FacialExpression.HAPPY, "Wow, good guess. Well, here you go.") + npc(FacialExpression.NEUTRAL, NPCs.OBSERVATORY_ASSISTANT_6118, "Please don't give that to him. Last time he tried any Crafting, I had to spend a week cleaning up after the explosion.") + player("Explosion?") + npc(FacialExpression.SUSPICIOUS, "Erm, yes. I think in this instance you had probably better do it.") + player("I suppose it's better I don't ask.") + npc(FacialExpression.HAPPY,"You can use the mould with molten glass to make a new lens.") + exec { player, _ -> + if(inInventory(player, Items.LENS_MOULD_602)) { + addItemOrDrop(player, Items.MOLTEN_GLASS_1775) + if (getQuestStage(player, Quests.OBSERVATORY_QUEST) == 4) { + setQuestStage(player, Quests.OBSERVATORY_QUEST, 5) + } + } + } + item(Item(Items.MOLTEN_GLASS_1775), "The professor gives you back the molten glass.") + + + label("observatoryqueststage5") + npc("Is the lens finished?") + exec { player, _ -> + if(inInventory(player, Items.OBSERVATORY_LENS_603)) { + loadLabel(player, "haslens") + } else { + loadLabel(player, "nohaslens") + } + } + + label("nohaslens") + player("How do I make it again?") + npc("Use the molten glass with the mould.") + player("Huh. Simple.") + + label("haslens") + player("Yes, here it is. You may as well take this mould too.") //No source if no mould present + line("The player hands the observatory professor the observatory lens.") + npc("Wonderful, at last I can fix the telescope.") + npc("Would you accompany me to the Observatory? You simply must see the telescope in operation.") + player("Sounds interesting. Count me in.") + npc("Superb. You'll have to go via the dungeon under the goblin settlement, seeing as the bridge is broken. You'll find stairs up to the Observatory from there.") + player("Okay. See you there.") + exec { player, _ -> + setVarbit(player, ObservatoryQuest.telescopeVarbit, 1, true) + queueScript(player, 0, QueueStrength.SOFT) { stage: Int -> + when (stage) { + 0 -> { + openOverlay(player, Components.FADE_TO_BLACK_120) + return@queueScript delayScript(player, 4) + } + 1 -> { + if(removeItem(player, Item(Items.OBSERVATORY_LENS_603))) { + removeItem(player, Item(Items.LENS_MOULD_602)) + if (getQuestStage(player, Quests.OBSERVATORY_QUEST) == 5) { + setQuestStage(player, Quests.OBSERVATORY_QUEST, 6) + } + } + return@queueScript delayScript(player, 1) + } + 2 -> { + openOverlay(player, Components.FADE_FROM_BLACK_170) + return@queueScript delayScript(player, 2) + } + 3 -> { + sendDialogueLines(player, "The professor has gone ahead to the Observatory dome. Best you", "follow him to see the finished telescope.") + return@queueScript stopExecuting(player) + } + else -> return@queueScript stopExecuting(player) + } + } + + + } + + label("observatoryqueststage6") + npc("Hello, friend.") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) != null) { + loadLabel(player, "hasviewtelescope") + } else { + loadLabel(player, "nohasviewtelescope") + } + } + label("nohasviewtelescope") + // http://youtu.be/cK5suqcw3qU + player("Hi, this really is impressive.") + npc("Certainly is. Please, take a look through the telescope and tell me what you see.") + + label("hasviewtelescope") + player("I've had a look through the telescope.") + npc("What did you see? If you're not sure, you can find out by looking at the star charts dotted around the walls downstairs.") + player("It was...") + goto("page1") + + + label("page1") + options( + DialogueOption("Aquarius", "Aquarius", skipPlayer = true), + DialogueOption("Capricorn", "Capricorn", skipPlayer = true), + DialogueOption("Sagittarius", "Sagittarius", skipPlayer = true), + DialogueOption("Scorpio", "Scorpio", skipPlayer = true), + DialogueOption("page2", "~ next ~", skipPlayer = true), + ) + + label("page2") + options( + DialogueOption("page1", "~ previous ~", skipPlayer = true), + DialogueOption("Libra", "Libra", skipPlayer = true), + DialogueOption("Virgo", "Virgo", skipPlayer = true), + DialogueOption("Leo", "Leo", skipPlayer = true), + DialogueOption("page3", "~ next ~", skipPlayer = true), + ) + + label("page3") + options( + DialogueOption("page2", "~ previous ~", skipPlayer = true), + DialogueOption("Cancer", "Cancer", skipPlayer = true), + DialogueOption("Gemini", "Gemini", skipPlayer = true), + DialogueOption("Taurus", "Taurus", skipPlayer = true), + DialogueOption("page4", "~ next ~", skipPlayer = true), + ) + + label("page4") + options( + DialogueOption("page3", "~ previous ~", skipPlayer = true), + DialogueOption("Aries", "Aries", skipPlayer = true), + DialogueOption("Pisces", "Pisces", skipPlayer = true), + ) + + label("Aquarius") + player("Aquarius!") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == 19) { + loadLabel(player, "Aquariuscorrect") + } else { + loadLabel(player, "wrongstar") + } + } + + label("Aquariuscorrect") + npc("That's exactly it!") + player("Yes! Woo hoo!") + npc("That's Aquarius, the water-bearer.") + npc("It seems suitable, then, to award you with water runes!") + // Usually it is given here, but I don't want loopholes. + npc("By Saradomin's earlobes! You must be a friend of the gods indeed.") + npc("Look in your backpack for your reward, in payment for your work.") + exec { player, _ -> + addItemOrDrop(player, Items.WATER_RUNE_555, 25) + addItemOrDrop(player, Items.UNCUT_SAPPHIRE_1623) + finishQuest(player, Quests.OBSERVATORY_QUEST) + } + + label("Aries") + player("Aries!") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == 20) { + loadLabel(player, "Ariescorrect") + } else { + loadLabel(player, "wrongstar") + } + } + + label("Ariescorrect") + npc("That's exactly it!") + player("Yes! Woo hoo!") + npc("That's Aries, the ram.") + npc("A fierce fighter. I'm sure he'll look down on you and improve your Attack for such insight.") + // Usually it is given here, but I don't want loopholes. + npc("By Saradomin's earlobes! You must be a friend of the gods indeed.") + npc("Look in your backpack for your reward, in payment for your work.") + exec { player, _ -> + rewardXP(player, Skills.ATTACK, 875.0) + addItemOrDrop(player, Items.UNCUT_SAPPHIRE_1623) + finishQuest(player, Quests.OBSERVATORY_QUEST) + } + + label("Cancer") + player("Cancer!") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == 21) { + loadLabel(player, "Cancercorrect") + } else { + loadLabel(player, "wrongstar") + } + } + + label("Cancercorrect") + npc("That's exactly it!") + player("Yes! Woo hoo!") + npc("That's Cancer, the crab.") + npc("An armoured creature - I think I shall reward you with an amulet of protection.") + // Usually it is given here, but I don't want loopholes. + npc("By Saradomin's earlobes! You must be a friend of the gods indeed.") + npc("Look in your backpack for your reward, in payment for your work.") + exec { player, _ -> + addItemOrDrop(player, Items.AMULET_OF_DEFENCE_1729) + addItemOrDrop(player, Items.UNCUT_SAPPHIRE_1623) + finishQuest(player, Quests.OBSERVATORY_QUEST) + } + + label("Capricorn") + player("Capricorn!") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == 22) { + loadLabel(player, "Capricorncorrect") + } else { + loadLabel(player, "wrongstar") + } + } + + label("Capricorncorrect") + npc("That's exactly it!") + player("Yes! Woo hoo!") + npc("That's Capricorn, the goat.") + npc("Capricorn will surely reward your insight with an increase to your Strength.") + // Usually it is given here, but I don't want loopholes. + npc("By Saradomin's earlobes! You must be a friend of the gods indeed.") + npc("Look in your backpack for your reward, in payment for your work.") + exec { player, _ -> + rewardXP(player, Skills.STRENGTH, 875.0) + addItemOrDrop(player, Items.UNCUT_SAPPHIRE_1623) + finishQuest(player, Quests.OBSERVATORY_QUEST) + } + + label("Gemini") + player("Gemini!") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == 23) { + loadLabel(player, "Geminicorrect") + } else { + loadLabel(player, "wrongstar") + } + } + + label("Geminicorrect") + npc("That's exactly it!") + player("Yes! Woo hoo!") + npc("That's Gemini, the twins.") + npc("With the double nature of Gemini, I can't offer you anything more suitable than a two-handed weapon.") + // Usually it is given here, but I don't want loopholes. + npc("By Saradomin's earlobes! You must be a friend of the gods indeed.") + npc("Look in your backpack for your reward, in payment for your work.") + exec { player, _ -> + addItemOrDrop(player, Items.BLACK_2H_SWORD_1313) + addItemOrDrop(player, Items.UNCUT_SAPPHIRE_1623) + finishQuest(player, Quests.OBSERVATORY_QUEST) + } + + label("Leo") + player("Leo!") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == 24) { + loadLabel(player, "Leocorrect") + } else { + loadLabel(player, "wrongstar") + } + } + + label("Leocorrect") + npc("That's exactly it!") + player("Yes! Woo hoo!") + npc("That's Leo, the lion.") + npc("I think the majestic power of the lion will raise your Hitpoints.") + // Usually it is given here, but I don't want loopholes. + npc("By Saradomin's earlobes! You must be a friend of the gods indeed.") + npc("Look in your backpack for your reward, in payment for your work.") + exec { player, _ -> + rewardXP(player, Skills.HITPOINTS, 875.0) + addItemOrDrop(player, Items.UNCUT_SAPPHIRE_1623) + finishQuest(player, Quests.OBSERVATORY_QUEST) + } + + label("Libra") + player("Libra!") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == 25) { + loadLabel(player, "Libracorrect") + } else { + loadLabel(player, "wrongstar") + } + } + + label("Libracorrect") + npc("That's exactly it!") + player("Yes! Woo hoo!") + npc("That's Libra, the scales.") + npc("Hmmm, balance, law, order - I shall award you with law runes!") + // Usually it is given here, but I don't want loopholes. + npc("By Saradomin's earlobes! You must be a friend of the gods indeed.") + npc("Look in your backpack for your reward, in payment for your work.") + exec { player, _ -> + addItemOrDrop(player, Items.LAW_RUNE_563, 25) + addItemOrDrop(player, Items.UNCUT_SAPPHIRE_1623) + finishQuest(player, Quests.OBSERVATORY_QUEST) + } + + label("Pisces") + player("Pisces!") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == 26) { + loadLabel(player, "Piscescorrect") + } else { + loadLabel(player, "wrongstar") + } + } + + label("Piscescorrect") + npc("That's exactly it!") + player("Yes! Woo hoo!") + npc("That's Pisces, the fish.") + npc("What's more suitable as a reward than some tuna?") + // Usually it is given here, but I don't want loopholes. + npc("By Saradomin's earlobes! You must be a friend of the gods indeed.") + npc("Look in your backpack for your reward, in payment for your work.") + exec { player, _ -> + addItemOrDrop(player, Items.TUNA_361, 3) + addItemOrDrop(player, Items.UNCUT_SAPPHIRE_1623) + finishQuest(player, Quests.OBSERVATORY_QUEST) + } + + label("Sagittarius") + player("Sagittarius!") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == 27) { + loadLabel(player, "Sagittariuscorrect") + } else { + loadLabel(player, "wrongstar") + } + } + + label("Sagittariuscorrect") + npc("That's exactly it!") + player("Yes! Woo hoo!") + npc("That's Sagittarius, the centaur.") + npc("As you've spotted the archer, I shall reward you with a maple longbow.") + // Usually it is given here, but I don't want loopholes. + npc("By Saradomin's earlobes! You must be a friend of the gods indeed.") + npc("Look in your backpack for your reward, in payment for your work.") + exec { player, _ -> + addItemOrDrop(player, Items.MAPLE_LONGBOW_851) + addItemOrDrop(player, Items.UNCUT_SAPPHIRE_1623) + finishQuest(player, Quests.OBSERVATORY_QUEST) + } + + label("Scorpio") + player("Scorpio!") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == 28) { + loadLabel(player, "Scorpiocorrect") + } else { + loadLabel(player, "wrongstar") + } + } + + label("Scorpiocorrect") + npc("That's exactly it!") + player("Yes! Woo hoo!") + npc("That's Scorpio, the scorpion.") + npc("I think weapon poison would make a suitable reward.") + // Usually it is given here, but I don't want loopholes. + npc("By Saradomin's earlobes! You must be a friend of the gods indeed.") + npc("Look in your backpack for your reward, in payment for your work.") + exec { player, _ -> + addItemOrDrop(player, Items.WEAPON_POISON_187) + addItemOrDrop(player, Items.UNCUT_SAPPHIRE_1623) + finishQuest(player, Quests.OBSERVATORY_QUEST) + } + + label("Taurus") + player("Taurus!") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == 29) { + loadLabel(player, "Tauruscorrect") + } else { + loadLabel(player, "wrongstar") + } + } + + label("Tauruscorrect") + npc("That's exactly it!") + player("Yes! Woo hoo!") + npc("That's Taurus, the bull.") + npc("This Strength potion should be a suitable reward.") + // Usually it is given here, but I don't want loopholes. + npc("By Saradomin's earlobes! You must be a friend of the gods indeed.") + npc("Look in your backpack for your reward, in payment for your work.") + exec { player, _ -> + addItemOrDrop(player, Items.SUPER_STRENGTH1_161) + addItemOrDrop(player, Items.UNCUT_SAPPHIRE_1623) + finishQuest(player, Quests.OBSERVATORY_QUEST) + } + + label("Virgo") + player("Virgo!") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == 30) { + loadLabel(player, "Virgocorrect") + } else { + loadLabel(player, "wrongstar") + } + } + + label("Virgocorrect") + npc("That's exactly it!") + player("Yes! Woo hoo!") + npc("That's Virgo, the virtuous.") + npc("Virgo will surely provide you with an increase to Defence.") + // Usually it is given here, but I don't want loopholes. + npc("By Saradomin's earlobes! You must be a friend of the gods indeed.") + npc("Look in your backpack for your reward, in payment for your work.") + exec { player, _ -> + rewardXP(player, Skills.DEFENCE, 875.0) + addItemOrDrop(player, Items.UNCUT_SAPPHIRE_1623) + finishQuest(player, Quests.OBSERVATORY_QUEST) + } + + label("wrongstar") + npc("I'm afraid not. Have another look. Remember, you can check the star charts on the walls for reference.") + + // http://youtu.be/Z5RRnZl2vTg + label("observatoryqueststage100") + npc("Thanks for all your help with the telescope. What can I do for you?") + options( + DialogueOption("needmorehelp", "Do you need any more help with the telescope?", expression = FacialExpression.ASKING), + DialogueOption("mambodunaroona", "Is it true your name is Mambo-duna-roona?", expression = FacialExpression.ASKING) { player, _ -> + return@DialogueOption getAttribute(player, attributeReceivedWine, false) + }, + DialogueOption("nevermind", "Nothing, thanks."), + ) + + label("needmorehelp") + npc("Not right now,") + npc("but the stars may hold a secret for you.") + + label("nevermind") + npc("Okay, no problem. See you again.") + + label("treasuretrails") + npc("Welcome back! How can I help you today?") + player("Can you teach me to solve treasure trail clues?") + npc("Ah, I get asked about treasure trails all the time! Listen carefully and I shall tell you what I know...") + npc("Lots of clues have degrees and minutes written on them. These are the coordinates of the place where the treasure is buried.") + npc("You have to walk to the correct spot, so that your coordinates are exactly the same as the values written on the clue scroll.") + npc("To do this, you must use a sextant, a watch and a chart to find your own coordinates.") + npc("Once you know the coordinates of the place where you are, you know which way you have to walk to get to the place where the treasure is!") + player("Riiight. So where do I get those items from?") + npc("I think Murphy, the owner of the fishing trawler moored south of Ardougne, might be able to spare you a sextant. Then the nearest Clock Tower is south of Ardougne - you could probably get a watch there. I've") + npc("got plenty of charts myself, here have one.") + + label("mambodunaroona") + npc(FacialExpression.AMAZED, "How do you know tha-") + npc(FacialExpression.SUSPICIOUS, "I mean, of course not, what a silly idea.") + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryQuest.kt b/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryQuest.kt new file mode 100644 index 000000000..ce6ba2513 --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryQuest.kt @@ -0,0 +1,159 @@ +package content.region.kandarin.quest.observatoryquest + +import content.data.Quests +import core.api.* +import core.api.setVarp +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 + +// http://youtu.be/TWIkCRRea8A The initial quest log. +// http://youtu.be/onHNm9Z5L-o The best full quest log. +// http://youtu.be/cq-jGTXXHuE +// http://youtu.be/ekYr30h43ag Scorpius. +/** + * Observatory Quest + */ +@Initializable +class ObservatoryQuest : Quest(Quests.OBSERVATORY_QUEST, 96, 95, 2,112, 0, 1, 7) { + + companion object { + val questName = Quests.OBSERVATORY_QUEST + const val observatoryVarp = 112 + const val goblinStoveVarbit = 3837 + const val telescopeVarbit = 3838 + + const val starChartsInterface = 104 + const val telescopeInterface = 552 + const val dungeonWarning = 560 + + const val attributeKilledGuard = "/save:quest:observatoryquest-killedguard" + const val attributeUnlockedGate = "/save:quest:observatoryquest-unlockedgate" + const val attributeTelescopeStar = "/save:quest:observatoryquest-telescopestar" // NULL - not seen telescope, 19 - 30 one of the random star patterns + const val attributeReceivedWine = "/save:quest:observatoryquest-receivedwine" + const val attributeReceivedMould = "/save:quest:observatoryquest-receivedmould" + const val attributeRandomChest = "/save:quest:observatoryquest-randomchest" + const val attributeFinishedCutscene = "/save:quest:observatoryquest-finishedcutscene" + + } + override fun drawJournal(player: Player, stage: Int) { + super.drawJournal(player, stage) + var line = 12 + var stage = getStage(player) + + var started = getQuestStage(player, questName) > 0 + + if (!started) { + line(player, "I can start this quest by speaking to the !!professor?? in the", line++, false) + line(player, "!!Observatory reception, south-west of Ardougne.??", line++, false) + } else { + line(player, "I can start this quest by speaking to the professor in the", line++, true) + line(player, "Observatory reception, south-west of Ardougne.", line++, true) + + if (stage >= 5) { + line(player, "I should take the letter the Examiner has given me to the", line++, true) + line(player, "Curator of Varrock Museum, for his approval.", line++, true) + line++ + } else if (stage >= 1) { + line(player, "Seems the observatory telescope needs repairing, due to", line++) + line(player, "the nearby goblins. The !!professor?? wants me to help by", line++) + line(player, "getting the following, with the help of his !!assistant??:", line++) + line++ + } + if (stage >= 2) { + line(player, "!!3 plain wooden planks??", line++, true) + } else if (stage >= 1) { + line(player, "!!3 plain wooden planks??", line++, inInventory(player, Items.PLANK_960, 3)) + } + if (stage >= 3) { + line(player, "!!1 bronze bar??", line++, true) + } else if (stage >= 2) { + line(player, "!!1 bronze bar??", line++, inInventory(player, Items.BRONZE_BAR_2349)) + } + if (stage >= 4) { + line(player, "!!1 molten glass??", line++, true) + } else if (stage >= 3) { + line(player, "!!1 molten glass??", line++, inInventory(player, Items.MOLTEN_GLASS_1775)) + } + if (stage >= 5) { + line(player, "!!1 lens mould??", line++, true) + } else if (stage >= 4) { + line(player, "!!1 lens mould??", line++) + } + + if (stage >= 6) { + line(player, "The professor was pleased to have all the pieces needed", line++, true) + line(player, "to fix the telescope. Apparently, the professor's last", line++, true) + line(player, "attempt at Crafting ended in disaster. So, he wants me to", line++, true) + line(player, "create the lens by using the molten glass with the mould.", line++, true) + line(player, "Fine by me!", line++, true) + } else if (stage >= 5) { + line(player, "The !!professor?? was pleased to have all the pieces needed", line++) + line(player, "to fix the telescope. Apparently, the professor's last", line++) + line(player, "attempt at Crafting ended in disaster. So, he wants me to", line++) + line(player, "create the !!lens?? by using the !!molten glass?? with the !!mould??.", line++) + line(player, "Fine by me!", line++) + } + + if (stage >= 100) { + line(player, "The professor has gone ahead to the Observatory. He", line++, true) + line(player, "wants me to meet him there by travelling through the", line++, true) + line(player, "dungeon below it.", line++, true) + } else if (stage >= 6) { + line(player, "The !!professor?? has gone ahead to the !!Observatory??. He", line++) + line(player, "wants me to meet him there by travelling through the", line++) + line(player, "!!dungeon?? below it.", line++) + } + if (stage >= 100) { + line++ + line(player,"QUEST COMPLETE!", line) + } + } + } + + override fun finish(player: Player) { + var ln = 10 + super.finish(player) + player.packetDispatch.sendString("You have completed the Observatory Quest!", 277, 4) + // This image is special since it isn't an item, but a standalone model. + player.packetDispatch.sendModelOnInterface(1174, 277, 5, 0) // Scenery 2210, Model 1174 + player.packetDispatch.sendAngleOnInterface(277, 5, 2040, 0, 1836) + + drawReward(player, "2 Quest Points", ln++) + drawReward(player, "2,250 Crafting XP", ln++) + drawReward(player, "A payment depending on", ln++) + drawReward(player, "which constellation you", ln++) + drawReward(player, "observed", ln++) + + rewardXP(player, Skills.CRAFTING, 2250.0) + } + + override fun updateVarps(player: Player) { + setVarp(player, observatoryVarp, getQuestStage(player, questName), true) + if(getQuestStage(player, questName) >= 6) { + setVarbit(player, telescopeVarbit, 1, true) + } else { + setVarbit(player, telescopeVarbit, 0, true) + } + if(getQuestStage(player, questName) >= 7) { + setVarp(player, observatoryVarp, 7, true) + } + } + + override fun reset(player : Player) { + removeAttribute(player, attributeKilledGuard) + removeAttribute(player, attributeUnlockedGate) + removeAttribute(player, attributeTelescopeStar) + removeAttribute(player, attributeReceivedWine) + removeAttribute(player, attributeReceivedMould) + removeAttribute(player, attributeRandomChest) + removeAttribute(player, attributeFinishedCutscene) + setVarbit(player, 3837, 0, true) + } + + override fun newInstance(`object`: Any?): Quest { + return this + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryQuestInterfaces.kt b/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryQuestInterfaces.kt new file mode 100644 index 000000000..a6627b98a --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryQuestInterfaces.kt @@ -0,0 +1,59 @@ +package content.region.kandarin.quest.observatoryquest + +import core.api.* +import core.game.interaction.InterfaceListener +import core.game.world.map.Location + +class ObservatoryQuestInterfaces : InterfaceListener { + + companion object { + + val buttonToNameMapping = mapOf( + 19 to "Aquarius", + 20 to "Aries", + 21 to "Cancer", + 22 to "Capricorn", + 23 to "Gemini", + 24 to "Leo", + 25 to "Libra", + 26 to "Pisces", + 27 to "Sagittarius", + 28 to "Scorpio", + 29 to "Taurus", + 30 to "Virgo", + ) + + val buttonToStarObjectMapping = mapOf( + 19 to 27064, + 20 to 27066, + 21 to 27067, + 22 to 27061, + 23 to 27068, + 24 to 27058, + 25 to 27057, + 26 to 27062, + 27 to 27056, + 28 to 27055, + 29 to 27059, + 30 to 27060, + ) + } + + override fun defineInterfaceListeners() { + on(ObservatoryQuest.starChartsInterface) { player, component, opcode, buttonID, slot, itemID -> + if(buttonToStarObjectMapping.contains(buttonID)){ + // 55 57 + player.packetDispatch.sendModelOnInterface(buttonToStarObjectMapping[buttonID]!!, ObservatoryQuest.starChartsInterface, 55, 0) + setInterfaceText(player, buttonToNameMapping[buttonID]!!, ObservatoryQuest.starChartsInterface, 57) + } + return@on true + } + on(ObservatoryQuest.dungeonWarning) { player, _, _, buttonID, _, _ -> + when (buttonID) { + 17 -> teleport(player, Location(2355, 9394)).also { closeInterface(player) } + 18 -> closeInterface(player) + } + return@on true + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryQuestListeners.kt b/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryQuestListeners.kt new file mode 100644 index 000000000..c718f9a10 --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryQuestListeners.kt @@ -0,0 +1,294 @@ +package content.region.kandarin.quest.observatoryquest + +import content.data.Quests +import content.region.kandarin.quest.observatoryquest.ObservatoryQuest.Companion.attributeFinishedCutscene +import content.region.kandarin.quest.observatoryquest.ObservatoryQuest.Companion.attributeRandomChest +import core.ServerConstants +import core.api.* +import core.game.dialogue.DialogueFile +import core.game.dialogue.FacialExpression +import core.game.global.action.DoorActionHandler +import core.game.interaction.InteractionListener +import core.game.interaction.QueueStrength +import core.game.node.entity.npc.NPC +import core.game.node.entity.skill.Skills +import core.game.world.map.Location +import core.tools.END_DIALOGUE +import core.tools.RandomFunction +import org.rs09.consts.Items +import org.rs09.consts.NPCs +import org.rs09.consts.Scenery + +class ObservatoryQuestListeners : InteractionListener { + + companion object { + val chestMap = mapOf( + Scenery.CHEST_2191 to Scenery.CHEST_2194, + Scenery.CHEST_25385 to Scenery.CHEST_25386, + Scenery.CHEST_25387 to Scenery.CHEST_25388, + Scenery.CHEST_25389 to Scenery.CHEST_25390, + Scenery.CHEST_25391 to Scenery.CHEST_25392 + ) + + val reverseChestMap = chestMap.map { (k, v) -> v to k }.toMap() + + /* Comments show only the default order BEFORE randomization. A randomized + number (0-10) is calculated the first time the player goes down the stairs, + and is then added to the map value modulo 11, rotating the map. + */ + + val chestLocations = mapOf( + Location(2333, 9405) to 0, // key + Location(2312, 9400) to 1, // spider + Location(2310, 9374) to 2, // spider + Location(2348, 9383) to 3, // empty + Location(2356, 9380) to 4, // spider + Location(2359, 9376) to 5, // empty + Location(2360, 9366) to 6, // empty + Location(2351, 9361) to 7, // spider + Location(2364, 9355) to 8, // antipoison + Location(2335, 9374) to 9, // spider + Location(2326, 9360) to 10) // empty + } + + override fun defineListeners() { + + on(Scenery.SIGNPOST_25397, SCENERY, "read") { player, _ -> + openDialogue(player, object : DialogueFile(){ + override fun handle(componentID: Int, buttonID: Int) { + val servername = ServerConstants.SERVER_NAME + when(stage){ + 0 -> sendDialogueLines(player, "~ The Observatory ~", "Step ahead to the reception if you wish to explore $servername's most", "magnificent invention.").also { + stage++ + } + 1 -> player(FacialExpression.NEUTRAL, "Magnificent invention? I've seen some pretty magnificent", "things in my time. It'll have to be pretty impressive.").also { stage = END_DIALOGUE } + } + } + }) + return@on true + } + + on(Scenery.ORRERY_25401, SCENERY, "view") { player, _ -> + sendChat(player, "Oooooh, bizarre!") + return@on true + } + + on(Scenery.STAIRS_25432, SCENERY, "climb-down") { player, _ -> + openInterface(player, 560) + if (getAttribute(player, attributeRandomChest, null) == null) { + setAttribute(player, attributeRandomChest, RandomFunction.random(11)) + } + return@on true + } + // http://youtu.be/Kg84MYXgo-M -> You can always go to the observatory, no matter which stage. + on(Scenery.STAIRS_25429, SCENERY, "climb up") { player, node -> + if (node.location == Location(2335, 9351)) { + if (!getAttribute(player, attributeFinishedCutscene, false) && getQuestStage(player, Quests.OBSERVATORY_QUEST) == 6) { + ObservatoryCutscene(player).start() + } + else teleport(player, Location(2439, 3164)) + } else { + teleport(player, Location(2457, 3186)) + } + return@on true + } + + on(Scenery.STAIRS_25434, SCENERY, "climb-down") { player, _ -> + teleport(player, Location(2335, 9350)) + return@on true + } + + on(Scenery.STAIRS_25431, SCENERY, "climb-up") { player, _ -> + teleport(player, Location(2443, 3160, 1)) + return@on true + } + + on(Scenery.STAIRS_25437, SCENERY, "climb-down") { player, _ -> + teleport(player, Location(2444, 3162, 0)) + return@on true + } + + // All chests + on(chestMap.keys.toIntArray(), SCENERY, "open") { player, node -> + animate(player, 536) + sendMessage(player, "You open the chest.") + chestMap[node.id]?.let { replaceScenery(node as core.game.node.scenery.Scenery, it, 240) } + return@on true + } + on(chestMap.values.toIntArray(), SCENERY, "close") { player, node -> + animate(player, 535) + reverseChestMap[node.id]?.let { replaceScenery(node as core.game.node.scenery.Scenery, it, -1) } + return@on true + } + + on(chestMap.values.toIntArray(), SCENERY, "search") { player, node -> + val chest = chestLocations[node.location]?.plus(getAttribute(player, attributeRandomChest, 0))?.mod(11) + when (chest) { + 0 -> { + if (inInventory(player, Items.GOBLIN_KITCHEN_KEY_601) || getQuestStage(player, Quests.OBSERVATORY_QUEST) != 4 || getAttribute(player, ObservatoryQuest.attributeUnlockedGate, false)) { + sendMessage(player, "You search the chest.") + sendMessage(player, "The chest is empty.") + } else { + lock(player,2) + queueScript(player, 2, QueueStrength.STRONG) { + if (inInventory(player, Items.GOBLIN_KITCHEN_KEY_601)) { + return@queueScript stopExecuting(player) + } + addItemOrDrop(player, Items.GOBLIN_KITCHEN_KEY_601) + sendItemDialogue(player, Items.GOBLIN_KITCHEN_KEY_601,"You find a kitchen key.") + return@queueScript stopExecuting(player) + } + } + animate(player, 537) + return@on true + } + 1, 2, 4, 7, 9 -> { + sendMessage(player, "You search the chest.") + if (findLocalNPC(player, NPCs.POISON_SPIDER_1009) != null) { + sendMessage(player, "The chest is empty.") + animate(player, 537) + return@on true + } + sendMessage(player, "The chest contains a poisonous spider.") + val npc = NPC(NPCs.POISON_SPIDER_1009) + npc.location = player.location + npc.init() + npc.isRespawn = false + npc.moveStep() + npc.face(player) + animate(player, 537) + return@on true + } + 8 -> { + sendMessage(player, "You search the chest.") + lock(player,2) + queueScript(player, 2, QueueStrength.STRONG) { + addItemOrDrop(player, Items.ANTIPOISON1_179) + sendMessage(player,"This chest contains some antipoison.") + return@queueScript stopExecuting(player) + } + animate(player, 537) + return@on true + } + else -> { + sendMessage(player, "You search the chest.") + sendMessage(player, "The chest is empty.") + animate(player, 537) + return@on true + } + } + } + + on(intArrayOf(Scenery.KITCHEN_GATE_2199, Scenery.KITCHEN_GATE_2200), SCENERY, "open") { player, node -> + if (getAttribute(player, ObservatoryQuest.attributeUnlockedGate, false)) { + DoorActionHandler.handleAutowalkDoor(player, node.asScenery()) + } else if (getAttribute(player, ObservatoryQuest.attributeKilledGuard, false)) { + if (removeItem(player, Items.GOBLIN_KITCHEN_KEY_601)) { + sendMessage(player, "The gate unlocks.") + sendMessage(player, "The key is useless now. You discard it.") + setAttribute(player, ObservatoryQuest.attributeUnlockedGate, true) + sendPlayerDialogue(player, "I had better be quick, there may be more guards about.") + DoorActionHandler.handleAutowalkDoor(player, node.asScenery()) + } else { + // http://youtu.be/ZkUF-0eonls + sendMessage(player, "The gate is locked.") + } + } else if (getQuestStage(player, Quests.OBSERVATORY_QUEST) >= 4){ + sendMessage(player, "If you open the gate, the guard will hear you. You need to get rid of him.") + } else sendMessage(player, "The gate is locked.") + return@on true + } + + on(NPCs.SLEEPING_GUARD_6122, NPC, "prod") { player, node -> + if (getQuestStage(player, Quests.OBSERVATORY_QUEST) < 4) { + return@on true + } + sendChat(node as NPC, "Oi, how dare you wake me up!") + transformNpc(node, NPCs.GOBLIN_GUARD_489, 400) + node.attack(player) + return@on true + } + + on(Scenery.GOBLIN_STOVE_25440, SCENERY, "inspect") { player, _ -> + openDialogue(player, object : DialogueFile(){ + override fun handle(componentID: Int, buttonID: Int) { + when(stage){ + 0 -> sendDialogueLines(player, "The goblins appear to have been using the lens mould to cook their", "stew!").also { stage++ } + 1 -> sendDialogueLines(player, "You shake out its contents and take it with you.").also { animate(player, 537); stage++ } + 2 -> end().also { + sendChat(player, "Euuuw, that smells awful!") + setVarbit(player, ObservatoryQuest.goblinStoveVarbit ,1) + addItemOrDrop(player, Items.LENS_MOULD_602) + } + } + } + }) + return@on true + } + + on(Scenery.GOBLIN_STOVE_25441, SCENERY, "inspect") { player, _ -> + if (!inInventory(player, Items.LENS_MOULD_602)) addItemOrDrop(player, Items.LENS_MOULD_602) + return@on true + } + + onUseWith(ITEM, Items.LENS_MOULD_602, Items.MOLTEN_GLASS_1775) { player, _, with -> + if (getStatLevel(player, Skills.CRAFTING) < 10) { + sendMessage(player, "You need a crafting level of 10 to do this.") + return@onUseWith true + } + + sendMessage(player, "You pour the molten glass into the mould.") + sendMessage(player, "You clasp it together.") + sendItemDialogue(player, Items.OBSERVATORY_LENS_603, "It has produced a small, convex glass disc.") + if (removeItem(player, with)) { + addItemOrDrop(player, Items.OBSERVATORY_LENS_603) + } + return@onUseWith true + } + + on(intArrayOf(Scenery.STAR_CHART_25578, Scenery.STAR_CHART_25579, Scenery.STAR_CHART_25580, Scenery.STAR_CHART_25581, Scenery.STAR_CHART_25582, Scenery.STAR_CHART_25583), SCENERY, "look-at") { player, _ -> + openInterface(player, ObservatoryQuest.starChartsInterface) + return@on true + } + + on(intArrayOf(Scenery.TELESCOPE_25438, Scenery.TELESCOPE_25439), SCENERY, "view") { player, _ -> + if (getQuestStage(player, Quests.OBSERVATORY_QUEST) >= 100) { + openDialogue(player, TelescopeDialogue(), NPC(NPCs.OBSERVATORY_PROFESSOR_488)) + } + else if (getQuestStage(player, Quests.OBSERVATORY_QUEST) >= 6) { + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == null) { + setAttribute(player, ObservatoryQuest.attributeTelescopeStar, (19..30).random()) + } + val randomStar = getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) + openInterface(player, ObservatoryQuest.telescopeInterface) + player.packetDispatch.sendModelOnInterface(ObservatoryQuestInterfaces.buttonToStarObjectMapping[randomStar]!!, ObservatoryQuest.telescopeInterface, 7, 0) + } else { + sendMessage(player, "The telescope is broken.") + } + return@on true + } + + on(intArrayOf(Scenery.DOOR_25526, Scenery.DOOR_25527), SCENERY, "open") { player, _ -> + sendMessage(player, "The door is locked.") + return@on true + } + + on(Scenery.GRAVE_OF_SCORPIUS_2211, SCENERY, "read") { player, _ -> + sendMessage(player, "Here lies Scorpius: Only those who have seen beyond the stars may seek his counsel.") + return@on true + } + } +} + +class TelescopeDialogue : DialogueFile() { + override fun handle(componentID: Int, buttonID: Int) { + when(stage){ + 0 -> npcl(FacialExpression.ASKING, "What do you see now?").also { stage++ } + 1 -> playerl("I can see a constellation through the telescope. It looks like Scorpio.").also { stage++ } + 2 -> npcl("Scorpio? Interesting. How very fitting.").also { stage++ } + 3 -> playerl(FacialExpression.ASKING, " What do you mean?").also { stage++ } + 4 -> npcl("Scorpius, the founder of all we know relating to astronomy. There's a book about him in the reception. Perhaps you should check it out, you might learn something.").also { stage++ } + 5 -> playerl("Then perhaps I shall.").also { stage = END_DIALOGUE } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/observatoryquest/PoisonSpiderBehavior.kt b/Server/src/main/content/region/kandarin/quest/observatoryquest/PoisonSpiderBehavior.kt new file mode 100644 index 000000000..dc4f0b74d --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/observatoryquest/PoisonSpiderBehavior.kt @@ -0,0 +1,20 @@ +package content.region.kandarin.quest.observatoryquest + +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.GameWorld +import org.rs09.consts.NPCs + +class PoisonSpiderBehavior : NPCBehavior(NPCs.POISON_SPIDER_1009) { + override fun onCreation(self: NPC) { + setAttribute(self, "despawn-time", GameWorld.ticks + 100) + } + + override fun tick(self: NPC): Boolean { + if (getAttribute(self, "despawn-time", 0) <= GameWorld.ticks && !self.inCombat()) + self.clear() + return true + } +} diff --git a/Server/src/main/content/region/kandarin/quest/observatoryquest/SpiritOfScorpiusDialogue.kt b/Server/src/main/content/region/kandarin/quest/observatoryquest/SpiritOfScorpiusDialogue.kt new file mode 100644 index 000000000..6d8fd5815 --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/observatoryquest/SpiritOfScorpiusDialogue.kt @@ -0,0 +1,110 @@ +package content.region.kandarin.quest.observatoryquest + +import content.data.Quests +import content.region.kandarin.quest.observatoryquest.ObservatoryQuest.Companion.attributeReceivedMould +import core.api.* +import core.game.dialogue.* +import core.game.node.entity.player.Player +import core.game.node.item.Item +import core.plugin.Initializable +import org.rs09.consts.Items +import org.rs09.consts.NPCs + +@Initializable +class SpiritOfScorpiusDialogue (player: Player? = null) : DialoguePlugin(player) { + override fun newInstance(player: Player): DialoguePlugin { + return SpiritOfScorpiusDialogue(player) + } + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + openDialogue(player, SpiritOfScorpiusDialogueFile(), npc) + return false + } + override fun getIds(): IntArray { + return intArrayOf(NPCs.SPIRIT_OF_SCORPIUS_492) + } +} + +class SpiritOfScorpiusDialogueFile : DialogueLabeller() { + override fun addConversation() { + assignToIds(NPCs.SPIRIT_OF_SCORPIUS_492) + + exec { player, npc -> + if (getQuestStage(player, Quests.OBSERVATORY_QUEST) == 100) { + if (!inEquipment(player, Items.GHOSTSPEAK_AMULET_552)) loadLabel(player, "noghostspeak") + else loadLabel(player, "observatoryqueststage100") + } else loadLabel(player, "observatoryquestincomplete") + } + + label("observatoryquestincomplete") + line("They seem to be ignoring you.") + + label("noghostspeak") + line("This powerful spirit seems capable of speaking to you", "even though you are not wearing an Amulet of Ghostspeak.") + exec { player, _ -> + loadLabel(player, "observatoryqueststage100") + } + + label("observatoryqueststage100") + npc("Who treads upon my grave?") + options( + DialogueOption("wisdom", "I seek your wisdom.") { player, _ -> + return@DialogueOption !getAttribute(player, attributeReceivedMould, false) + }, + DialogueOption("anothermould", "I need another unholy symbol mould.", "I need another mould for the unholy symbol.") { player, _ -> + return@DialogueOption getAttribute(player, attributeReceivedMould, false) + }, + DialogueOption("blessing", "I have come to seek your blessing."), + DialogueOption("killyou", "I have come to kill you.") + ) + + label("wisdom") + npc("Indeed, I feel you have beheld the far places in the heavens. My Lord instructs me to help you.") + item(Item(Items.UNHOLY_MOULD_1594), "An unholy mould appears in your inventory.") + exec { player, _ -> + addItemOrDrop(player, Items.UNHOLY_MOULD_1594) + setAttribute(player, attributeReceivedMould, true) + } + npc("Here is a mould to make a token for our Lord; a mould for the unholy symbol of Zamorak. Return to me when you desire my blessing.") + + label("anothermould") + exec { player, _ -> + if (inInventory(player, Items.UNHOLY_MOULD_1594)) { + loadLabel(player, "hasmould") + } + else { + addItemOrDrop(player, Items.UNHOLY_MOULD_1594) + loadLabel(player, "lostmould") + } + } + + label("hasmould") + npc("One you already have, another is not needed. Leave me be.") + + label("lostmould") + npc("A lost object is easy to replace. The loss of the affections of our Lord is impossible to forgive.") + + label("blessing") + exec { player, _ -> + if (inInventory(player, Items.UNPOWERED_SYMBOL_1722)) { + loadLabel(player, "canbless") + } + else loadLabel(player, "cannotbless") + } + + label("canbless") + npc("I see you have the unholy symbol of our Lord. I will bless it for you.") + line("The ghost mutters in a strange voice.", "The unholy symbol throbs with power.") + exec { player, _ -> + removeItem(player, Items.UNPOWERED_SYMBOL_1722) + addItemOrDrop(player, Items.UNHOLY_SYMBOL_1724) + } + npc("The symbol of our Lord has been blessed with power! My master calls.") + + + label("cannotbless") + npc("No blessing will be given to those who have no symbol of our Lord's love.") + + label("killyou") + npc("The might of mortals, to me, is as the dust to the sea.") + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/templeofikov/TempleOfIkovListeners.kt b/Server/src/main/content/region/kandarin/quest/templeofikov/TempleOfIkovListeners.kt index f513997e3..135e3dc92 100644 --- a/Server/src/main/content/region/kandarin/quest/templeofikov/TempleOfIkovListeners.kt +++ b/Server/src/main/content/region/kandarin/quest/templeofikov/TempleOfIkovListeners.kt @@ -1,7 +1,9 @@ package content.region.kandarin.quest.templeofikov +import content.data.LightSource import content.data.Quests import content.global.skill.agility.AgilityHandler +import content.global.skill.skillcapeperks.SkillcapePerks import core.api.* import core.game.global.action.DoorActionHandler import core.game.global.action.PickupHandler @@ -114,6 +116,40 @@ class TempleOfIkovListeners : InteractionListener { // https://www.youtube.com/watch?v=6ZGpJNeGLJ0 // sendDialogue("Hmm...bit dark down here! I'm not going to venture far!") + // Ikov dungeon top stairs to either boots of lightness or the dark room + on(Scenery.STAIRS_35121, SCENERY, "Climb-down") { player, node -> + if (LightSource.hasActiveLightSource(player) || SkillcapePerks.isActive(SkillcapePerks.CONSTANT_GLOW, player)) { // has light source + teleport(player, Location.create(2641, 9763, 0)) + } else { // doesn't have light source + teleport(player, Location.create(2641, 9740, 0)) + sendDialogueLines(player, "Hmm...bit dark down here! I'm not going to venture far!") + } + true + } + + // Ikov dungeon bottom stairs (lit) to top stairs + on(Scenery.STAIRS_96, SCENERY, "Climb-up") { player, node -> + teleport(player, Location.create(2649, 9804, 0)) + } + + // Ikov dungeon bottom stairs (dark) to top stairs + on(Scenery.STAIRS_33232, SCENERY, "Climb-up") { player, node -> + teleport(player, Location.create(2649, 9804, 0)) + } + + // don't let light extinguish or cape take off + val lightSourceProducts = LightSource.values().map { it.product.id }.toIntArray() + + on(lightSourceProducts, ITEM, "drop") { player, light -> + val active = LightSource.getActiveLightSource(player).product.id + if (player.location.isInRegion(10648) && light.id == active) { + sendMessage(player, "Dropping the " + LightSource.getActiveLightSource(player).product.name.lowercase() + " would leave you without a light source.") + return@on false + } + val removed = removeItem(player, light.id) + if (removed) produceGroundItem(player, light.id) + return@on true + } // B: Attach lever, authentic if you log out, the lever is lost, and you have to do that bridge again onUseWith(SCENERY, Items.LEVER_83, Scenery.LEVER_BRACKET_86) { player, used, with -> diff --git a/Server/src/main/content/region/misc/apeatoll/dialogue/marim/SolihibDialogue.kt b/Server/src/main/content/region/misc/apeatoll/dialogue/marim/SolihibDialogue.kt new file mode 100644 index 000000000..381e3e856 --- /dev/null +++ b/Server/src/main/content/region/misc/apeatoll/dialogue/marim/SolihibDialogue.kt @@ -0,0 +1,42 @@ +package content.region.misc.apeatoll.dialogue.marim + +import content.data.Quests +import core.api.hasRequirement +import core.api.openNpcShop +import core.game.dialogue.ChatAnim +import core.game.dialogue.DialogueLabeller +import core.game.dialogue.DialogueOption +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.npc.NPC +import org.rs09.consts.NPCs + +class SolihibDialogue : InteractionListener { + override fun defineListeners() { + on(NPCs.SOLIHIB_1433, IntType.NPC, "talk-to") { player, node -> + if (!hasRequirement(player, Quests.MONKEY_MADNESS)) return@on true + DialogueLabeller.open(player, SolihibDialogueLabellerFile(), node as NPC) + return@on true + } + on(NPCs.SOLIHIB_1433, IntType.NPC, "trade") { player, _ -> + if (!hasRequirement(player, Quests.MONKEY_MADNESS)) return@on true + openNpcShop(player, NPCs.SOLIHIB_1433) + return@on true + } + } + + class SolihibDialogueLabellerFile : DialogueLabeller() { + override fun addConversation() { + assignToIds(NPCs.SOLIHIB_1433) + + npc(ChatAnim.FRIENDLY, "Would you like to buy or sell some food?") + options( + DialogueOption("trade", "Yes, please."), + DialogueOption("nowhere", "No, thanks.") + ) + label("trade") + exec { player, _ -> openNpcShop(player, NPCs.SOLIHIB_1433) } + goto("nowhere") + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/BankerTutorDialogue.java b/Server/src/main/content/region/misc/tutisland/dialogue/BankerTutorDialogue.java deleted file mode 100644 index c6a121500..000000000 --- a/Server/src/main/content/region/misc/tutisland/dialogue/BankerTutorDialogue.java +++ /dev/null @@ -1,155 +0,0 @@ -package content.region.misc.tutisland.dialogue; - -import core.game.dialogue.DialoguePlugin; -import core.game.node.entity.npc.NPC; -import core.plugin.Initializable; -import core.game.node.entity.player.Player; - -/** - * Represents the banker tutor dialogue. - * @author 'Vexia - * @version 1.0 - */ -@Initializable -public final class BankerTutorDialogue extends DialoguePlugin { - - /** - * Constructs a new {@code BankerTutorDialogue} {@code Object}. - * @param player the player. - */ - public BankerTutorDialogue(final Player player) { - super(player); - } - - /** - * Constructs a new {@code BankerTutorDialogue} {@code Object}. - */ - public BankerTutorDialogue() { - /** - * empty. - */ - } - - @Override - public DialoguePlugin newInstance(Player player) { - return new BankerTutorDialogue(player); - } - - @Override - public boolean open(Object... args) { - npc = args[0] instanceof NPC ? (NPC) args[0] : null; - npc("Good day, would you like to access your bank account?"); - stage = 0; - return true; - } - - @Override - public boolean handle(int interfaceId, int buttonId) { - switch (stage) { - case 0: - options("How do I use the bank?", "I'd like to access my bank account please.", "I'd like to check my PIN settings."); - stage = 1; - break; - case 1: - switch (buttonId) { - case 1: - options("Using the bank itself.", "Using Bank deposit boxes.", "What's this PIN thing that people keep talking about?", "Goodbye."); - stage = 9; - break; - case 2: - end(); - player.getBank().open(); - break; - case 3: - end(); - player.getBankPinManager().openSettings(); - break; - } - break; - case 9: - switch (buttonId) { - case 1: - player("Using the bank itself. I'm not sure how....?"); - stage = 10; - break; - case 2: - player("Using Bank deposit boxes.... what are they?"); - stage = 20; - break; - case 3: - player("What's this PIN thing that people keep talking about?"); - stage = 30; - break; - case 4: - player("Goodbye."); - stage = 99; - break; - } - break; - case 10: - npc("Speak to any banker and ask to see your bank", "account. If you have set a PIN you will be asked for", "it, then all the belongings you have placed in the bank", "will appear in the window. To withdraw one item, left-"); - stage = 11; - break; - case 11: - npc("click on it once."); - stage = 12; - break; - case 12: - npc("To withdraw many, right-click on the item and select", "from the menu. The same for depositing, left-click on", "the item in your inventory to deposit it in the bank.", "Right-click on it to deposit many of the same items."); - stage = 13; - break; - case 13: - npc("To move things around in your bank: firstly select", "Swap or Insert as your default moving mode, you can", "find these buttons on the bank window itself. Then click", "and drag an item to where you want it to appear."); - stage = 14; - break; - case 14: - npc("You may withdraw 'notes' or 'certificates' when the", "items you are trying to withdraw do not stack in your", "inventory. This will only work for items which are", "tradeable."); - stage = 15; - break; - case 15: - npc("For instance, if you wanted to sell 100 logs to another", "player, they would not fit in one inventory and you", "would need to do multiple trades. Instead, click the", "Note button to do withdraw the logs as 'certs' or 'notes',"); - stage = 16; - break; - case 16: - npc("then withdraw the items you need."); - stage = 99; - break; - case 20: - npc("They look like grey pillars, there's one just over there,", "near the desk. Bank deposit boxes save so much time.", "If you're simply wanting to deposit a single item, 'Use'", "it on the deposit box."); - stage = 21; - break; - case 21: - npc("Otherwise, simply click once on the box and it will give", "you a choice of what to deposit in an interface very", "similar to the bank itself. Very quick for when you're", "simply fishing or mining etc."); - stage = 22; - break; - case 22: - end(); - break; - case 30: - npc("The PIN - Personal Identification Number - can be", "set on your bank account to protect the items there in", "case someone finds out your account password. It", "consists of four numbers that you remember and tell"); - stage = 31; - break; - case 31: - npc("no one."); - stage = 32; - break; - case 32: - npc("So if someone did manage to get your password they", "couldn't steal your items if they were in the bank."); - stage = 33; - break; - case 33: - end(); - break; - case 99: - end(); - break; - } - return true; - } - - @Override - public int[] getIds() { - return new int[] { 4907 }; - } - -} diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/RatPenDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/RatPenDialogue.kt new file mode 100644 index 000000000..3728d6d9c --- /dev/null +++ b/Server/src/main/content/region/misc/tutisland/dialogue/RatPenDialogue.kt @@ -0,0 +1,15 @@ +package content.region.misc.tutisland.dialogue + +import content.region.misc.tutisland.handlers.sendStageDialog +import core.game.dialogue.ChatAnim +import core.game.dialogue.DialogueLabeller + +/** + * Vannaka's angry dialogue when you try to enter his rat pen before you're supposed to + */ +class RatPenDialogue : DialogueLabeller() { + override fun addConversation() { + npc(ChatAnim.ANGRY, "Oi, get away from there!", "Don't enter my rat pen unless I say so!") + exec { player, _ -> sendStageDialog(player) } + } +} diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/SkipTutorialDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/SkipTutorialDialogue.kt index 3a16f44c9..f19f67a64 100644 --- a/Server/src/main/content/region/misc/tutisland/dialogue/SkipTutorialDialogue.kt +++ b/Server/src/main/content/region/misc/tutisland/dialogue/SkipTutorialDialogue.kt @@ -9,10 +9,11 @@ import core.game.world.map.Location import core.plugin.Initializable import org.rs09.consts.NPCs import content.region.misc.tutisland.handlers.TutorialStage +import core.game.component.Component.setUnclosable /** * Handles Skippy's skip tutorial dialogue - * @author Ceikry + * @author Ceikry, Player Name */ @Initializable class SkipTutorialDialogue(player: Player? = null) : DialoguePlugin(player) { @@ -21,13 +22,13 @@ class SkipTutorialDialogue(player: Player? = null) : DialoguePlugin(player) { } override fun open(vararg args: Any?): Boolean { - npcl(FacialExpression.FRIENDLY, "Hey, would you like to skip to the end? Choose wisely! This is the only time you get this choice.") + setUnclosable(player, npcl(FacialExpression.FRIENDLY, "Hey, would you like to skip to the end? Choose wisely! This is the only time you get this choice.")) return true } override fun handle(interfaceId: Int, buttonId: Int): Boolean { when(stage){ - 0 -> options("Yes, I'd like to skip the tutorial.", "No thanks.").also { stage++ } + 0 -> options("Yes, I'd like to skip the tutorial.", "No, thanks.").also { stage++ } 1 -> when(buttonId) { 1 -> { diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/SurvivalExpertDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/SurvivalExpertDialogue.kt index 21c914b98..4e0385178 100644 --- a/Server/src/main/content/region/misc/tutisland/dialogue/SurvivalExpertDialogue.kt +++ b/Server/src/main/content/region/misc/tutisland/dialogue/SurvivalExpertDialogue.kt @@ -1,119 +1,108 @@ package content.region.misc.tutisland.dialogue -import core.api.addItem +import core.api.addItemOrDrop import core.api.inInventory import core.api.setAttribute -import core.game.component.Component -import core.game.dialogue.DialoguePlugin -import core.game.dialogue.FacialExpression -import core.game.node.entity.npc.NPC -import core.game.node.entity.player.Player -import core.plugin.Initializable import org.rs09.consts.Items import org.rs09.consts.NPCs import content.region.misc.tutisland.handlers.TutorialStage +import content.region.misc.tutisland.handlers.sendStageDialog +import core.api.getAttribute +import core.api.inEquipment +import core.game.dialogue.ChatAnim +import core.game.dialogue.DialogueLabeller +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.npc.NPC +import core.game.node.item.Item -/** - * Handles the survival expert's dialogue - * @author Ceikry - */ -@Initializable -class SurvivalExpertDialogue(player: Player? = null) : DialoguePlugin(player) { - override fun newInstance(player: Player?): DialoguePlugin { - return SurvivalExpertDialogue(player) +class SurvivalExpertDialogue : InteractionListener { + override fun defineListeners() { + on(NPCs.SURVIVAL_EXPERT_943, IntType.NPC, "talk-to") { player, node -> + DialogueLabeller.open(player, SurvivalExpertDialogueFile(), node as NPC) + return@on true + } } +} - override fun open(vararg args: Any?): Boolean { - npc = args[0] as NPC - val tutStage = player?.getAttribute("tutorial:stage", 0) ?: 0 - when(tutStage) - { - 4 -> Component.setUnclosable( - player, - interpreter.sendDialogues( - npc, - FacialExpression.FRIENDLY, - "Hello there, newcomer. My name is Brynna. My job is", - "to teach you a few survival tips and tricks. First off", - "we're going to start with the most basic survival skill of", - "all: making a fire." - ) - ) +class SurvivalExpertDialogueFile : DialogueLabeller() { + override fun addConversation() { + assignToIds(NPCs.SURVIVAL_EXPERT_943) - 11 -> Component.setUnclosable( - player, - interpreter.sendDialogues( - npc, - FacialExpression.FRIENDLY, - "Well done! Next we need to get some food in our", - "bellies. We'll need something to cook. There are shrimp", - "in the pond there, so let's catch and cook some." - ) - ) - - 5, 14, 15 -> { - if(!inInventory(player, Items.BRONZE_AXE_1351)) - { - player.dialogueInterpreter.sendItemMessage(Items.BRONZE_AXE_1351, "The Survival Expert gives you a spare bronze axe.") - addItem(player, Items.BRONZE_AXE_1351) + exec { player, _ -> + when (val stage = getAttribute(player, "/save:tutorial:stage", 0)) { + 4 -> loadLabel(player, "hello") + 5, 6, 7, 8, 9, 10, 12, 13, 14 -> { + if (!inInventory(player, Items.BRONZE_AXE_1351) && !inEquipment(player, Items.BRONZE_AXE_1351)) { + loadLabel(player, "spare axe") + } + if (!inInventory(player, Items.TINDERBOX_590)) { + loadLabel(player, "spare tinderbox") + } + if (stage >= 11 && !inInventory(player, Items.SMALL_FISHING_NET_303)) { + loadLabel(player, "spare net") + } + loadLabel(player, "nowhere") } - if(!inInventory(player, Items.TINDERBOX_590)) - { - player.dialogueInterpreter.sendItemMessage(Items.TINDERBOX_590, "The Survival Expert gives you a spare tinderbox.") - addItem(player, Items.TINDERBOX_590) - } - return false + 11 -> loadLabel(player, "fishing") + else -> loadLabel(player, "nowhere") } } - return true - } - override fun handle(interfaceId: Int, buttonId: Int): Boolean { - when(player?.getAttribute("tutorial:stage", 0)) - { - 4 -> when(stage) - { - 0 -> { - Component.setUnclosable( - player, - interpreter.sendDoubleItemMessage( - Items.TINDERBOX_590, - Items.BRONZE_AXE_1351, - "The Survival Guide gives you a tinderbox and a bronze axe!" - ) - ) - addItem(player, Items.TINDERBOX_590) - addItem(player, Items.BRONZE_AXE_1351) - stage++ - } - 1 -> { - end() - setAttribute(player, "tutorial:stage", 5) - TutorialStage.load(player, 5) - } + label("hello") + npc(ChatAnim.FRIENDLY, "Hello there, newcomer. My name is Brynna. My job is to teach you a few survival tips and tricks. First off we're going to start with the most basic survival skill of all: making a fire.", unclosable = true) + exec { player, _ -> + addItemOrDrop(player, Items.TINDERBOX_590) + addItemOrDrop(player, Items.BRONZE_AXE_1351) + } + item(Item(Items.TINDERBOX_590), Item(Items.BRONZE_AXE_1351), "The Survival Guide gives you a tinderbox and a bronze", "axe!", unclosable = true) + exec { player, _ -> + setAttribute(player, "tutorial:stage", 5) + TutorialStage.load(player, 5) + } + goto("nowhere") + + label("fishing") + npc(ChatAnim.FRIENDLY, "Well done! Next we need to get some food in our bellies. We'll need something to cook. There are shrimp in the pond there, so let's catch and cook some.", unclosable = true) + exec { player, _ -> addItemOrDrop(player, Items.SMALL_FISHING_NET_303) } + item(Item(Items.SMALL_FISHING_NET_303), "The Survival Guide gives you a", "net!", unclosable = true) + exec { player, _ -> + setAttribute(player, "tutorial:stage", 12) + TutorialStage.load(player, 12) + } + goto("nowhere") + + label("spare axe") + exec { player, _ -> addItemOrDrop(player, Items.BRONZE_AXE_1351) } + item(Item(Items.BRONZE_AXE_1351), "The Survival Guide gives you a spare bronze axe.", unclosable = true) + exec { player, _ -> + if (!inInventory(player, Items.TINDERBOX_590)) { + loadLabel(player, "spare tinderbox") } - - 11 -> when(stage){ - 0 -> { - Component.setUnclosable( - player, - interpreter.sendItemMessage(303, "The Survival Guide gives you a net!") - ) - addItem(player, Items.SMALL_FISHING_NET_303) - stage++ - } - 1 -> { - end() - setAttribute(player, "tutorial:stage", 12) - TutorialStage.load(player, 12) - } + val stage = getAttribute(player, "/save:tutorial:stage", 0) + if (stage >= 11 && !inInventory(player, Items.SMALL_FISHING_NET_303)) { + loadLabel(player, "spare net") } } - return true - } + goto("nowhere") //closes the dialogue, letting the hook reopen the tutorial stage dialog as appropriate - override fun getIds(): IntArray { - return intArrayOf(NPCs.SURVIVAL_EXPERT_943) - } + label("spare tinderbox") + exec { player, _ -> addItemOrDrop(player, Items.TINDERBOX_590) } + item(Item(Items.TINDERBOX_590), "The Survival Guide gives you a spare tinderbox.", unclosable = true) + exec { player, _ -> + val stage = getAttribute(player, "/save:tutorial:stage", 0) + if (stage >= 11 && !inInventory(player, Items.SMALL_FISHING_NET_303)) { + loadLabel(player, "spare net") + } + } + goto("nowhere") -} \ No newline at end of file + label("spare net") + exec { player, _ -> addItemOrDrop(player, Items.SMALL_FISHING_NET_303) } + item(Item(Items.SMALL_FISHING_NET_303), "The Survival Guide gives you a spare net.", unclosable = true) + goto("nowhere") + + label("nowhere") + exec { player, _ -> sendStageDialog(player) } + } +} diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialCombatInstructorDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialCombatInstructorDialogue.kt index 3513aaeb8..1ab335e26 100644 --- a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialCombatInstructorDialogue.kt +++ b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialCombatInstructorDialogue.kt @@ -1,95 +1,106 @@ package content.region.misc.tutisland.dialogue -import core.api.* -import core.game.dialogue.DialoguePlugin -import core.game.dialogue.FacialExpression import core.game.node.entity.npc.NPC -import core.game.node.entity.player.Player -import core.plugin.Initializable import org.rs09.consts.Items import org.rs09.consts.NPCs import content.region.misc.tutisland.handlers.TutorialStage +import content.region.misc.tutisland.handlers.sendStageDialog +import core.api.* +import core.game.dialogue.ChatAnim +import core.game.dialogue.DialogueLabeller +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.player.Player +import core.game.node.item.Item -/** - * Handles the combat instructor's dialogue - * @author Ceikry - */ -@Initializable -class TutorialCombatInstructorDialogue(player: Player? = null) : DialoguePlugin(player) { - override fun newInstance(player: Player?): DialoguePlugin { - return TutorialCombatInstructorDialogue(player) - } - - override fun open(vararg args: Any?): Boolean { - npc = args[0] as NPC - - when(getAttribute(player, "tutorial:stage", 0)) - { - 44 -> playerl(FacialExpression.FRIENDLY, "Hi! My name's ${player.username}.") - 47 -> npcl(FacialExpression.FRIENDLY, "Very good, but that little butter knife isn't going to protect you much. Here, take these.") - 53 -> playerl(FacialExpression.FRIENDLY, "I did it! I killed a giant rat!") - 54 -> { - player.dialogueInterpreter.sendDoubleItemMessage(Items.SHORTBOW_841, Items.BRONZE_ARROW_882, "The Combat Guide gives you some bronze arrows and a shortbow!") - if(!inInventory(player, Items.SHORTBOW_841) && !inEquipment(player, Items.SHORTBOW_841)) - addItemOrDrop(player, Items.SHORTBOW_841) - if(!inInventory(player, Items.BRONZE_ARROW_882) && !inEquipment(player, Items.BRONZE_ARROW_882)) - addItemOrDrop(player, Items.BRONZE_ARROW_882, 30) - } +class CombatInstructorDialogue : InteractionListener { + override fun defineListeners() { + on(NPCs.COMBAT_INSTRUCTOR_944, IntType.NPC, "talk-to") { player, node -> + DialogueLabeller.open(player, CombatInstructorDialogueFile(), node as NPC) + return@on true } - return true } - - override fun handle(interfaceId: Int, buttonId: Int): Boolean { - when(getAttribute(player, "tutorial:stage", 0)) - { - 44 -> when(stage){ - 0 -> npcl(FacialExpression.ANGRY, "Do I look like I care? To me you're just another newcomer who thinks they're ready to fight.").also { stage++ } - 1 -> npcl(FacialExpression.FRIENDLY, "I'm Vannaka, the greatest swordsman alive.").also { stage++ } - 2 -> npcl(FacialExpression.FRIENDLY, "Let's get started by teaching you to wield a weapon.").also { stage++ } - 3 -> { - end() - setAttribute(player, "tutorial:stage", 45) - TutorialStage.load(player, 45) - } - } - - 47 -> when(stage){ - 0 -> { - addItemOrDrop(player, Items.BRONZE_SWORD_1277) - addItemOrDrop(player, Items.WOODEN_SHIELD_1171) - sendDoubleItemDialogue(player, Items.BRONZE_SWORD_1277, Items.WOODEN_SHIELD_1171, "The Combat Guide gives you a bronze sword and a wooden shield!") - stage++ - } - 1 -> { - end() - setAttribute(player, "tutorial:stage", 48) - TutorialStage.load(player, 48) - } - } - - 53 -> when(stage){ - 0 -> npcl(FacialExpression.FRIENDLY, "I saw, ${player.username}. You seem better at this than I thought. Now that you have grasped basic swordplay, let's move on.").also { stage++ } - 1 -> npcl(FacialExpression.FRIENDLY, "Let's try some ranged attacking, with this you can kill foes from a distance. Also, foes unable to reach you are as good as dead. You'll be able to attack the rats, without entering the pit.").also { stage++ } - 2 -> { - sendDoubleItemDialogue(player, Items.SHORTBOW_841, Items.BRONZE_ARROW_882, "The Combat Guide gives you some bronze arrows and a shortbow!") - if(!inInventory(player, Items.SHORTBOW_841) && !inEquipment(player, Items.SHORTBOW_841)) - addItem(player, Items.SHORTBOW_841) - if(!inInventory(player, Items.BRONZE_ARROW_882) && !inEquipment(player, Items.BRONZE_ARROW_882)) - addItem(player, Items.BRONZE_ARROW_882, 30) - stage++ - } - 3 -> { - end() - setAttribute(player, "tutorial:stage", 54) - TutorialStage.load(player, 54) - } - } - } - return true - } - - override fun getIds(): IntArray { - return intArrayOf(NPCs.COMBAT_INSTRUCTOR_944) - } - +} + +class CombatInstructorDialogueFile : DialogueLabeller() { + fun lostWeapon(player: Player, id: Int): Boolean { + return !inInventory(player, id) && !inEquipment(player, id) + } + + override fun addConversation() { + assignToIds(NPCs.COMBAT_INSTRUCTOR_944) + + exec { player, _ -> + when (getAttribute(player, "tutorial:stage", 0)) { + 44 -> loadLabel(player, "hello") + 47 -> loadLabel(player, "butter") + 48, 49, 50, 51, 52 -> loadLabel(player, if (lostWeapon(player, Items.BRONZE_SWORD_1277)) "lost sword" else if (lostWeapon(player, Items.WOODEN_SHIELD_1171)) "lost shield" else "nowhere") + 53 -> loadLabel(player, "killed rat") + 54 -> loadLabel(player, if (lostWeapon(player, Items.SHORTBOW_841)) "lost bow" else if (lostWeapon(player, Items.BRONZE_ARROW_882)) "lost arrows" else "nowhere") + else -> loadLabel(player, "nowhere") + } + } + + label("hello") + player(ChatAnim.FRIENDLY, "Hi! My name's ${player?.username}.", unclosable = true) + npc(ChatAnim.ANGRY, "Do I look like I care? To me you're just another newcomer who thinks they're ready to fight.", unclosable = true) + npc(ChatAnim.FRIENDLY, "I'm Vannaka, the greatest swordsman alive.", unclosable = true) + npc(ChatAnim.FRIENDLY, "Let's get started by teaching you to wield a weapon.", unclosable = true) + exec { player, _ -> + setAttribute(player, "tutorial:stage", 45) + TutorialStage.load(player, 45) + } + + label("butter") + npc(ChatAnim.FRIENDLY, "Very good, but that little butter knife isn't going to protect you much. Here, take these.", unclosable = true) + exec { player, _ -> + addItemOrDrop(player, Items.BRONZE_SWORD_1277) + addItemOrDrop(player, Items.WOODEN_SHIELD_1171) + } + item(Item(Items.BRONZE_SWORD_1277), Item(Items.WOODEN_SHIELD_1171), "The Combat Guide gives you a bronze sword and a", "wooden shield!", unclosable = true) + exec { player, _ -> + setAttribute(player, "tutorial:stage", 48) + TutorialStage.load(player, 48) + } + goto("nowhere") + + label("lost sword") + exec { player, _ -> addItemOrDrop(player, Items.BRONZE_SWORD_1277) } + item(Item(Items.BRONZE_SWORD_1277), "The Combat Guide gives you a spare sword.", unclosable = true) + exec { player, _ -> if (lostWeapon(player, Items.WOODEN_SHIELD_1171)) loadLabel(player, "lost shield") } + goto("nowhere") + + label("lost shield") + exec { player, _ -> addItemOrDrop(player, Items.WOODEN_SHIELD_1171) } + item(Item(Items.WOODEN_SHIELD_1171), "The Combat Guide gives you a spare shield.", unclosable = true) + goto("nowhere") + + label("killed rat") + player(ChatAnim.FRIENDLY, "I did it! I killed a giant rat!", unclosable = true) + npc(ChatAnim.FRIENDLY, "I saw, ${player?.username}. You seem better at this than I thought. Now that you have grasped basic swordplay, let's move on.", unclosable = true) + npc(ChatAnim.FRIENDLY, "Let's try some ranged attacking, with this you can kill foes from a distance. Also, foes unable to reach you are as good as dead. You'll be able to attack the rats, without entering the pit.", unclosable = true) + exec { player, _ -> + addItemOrDrop(player, Items.SHORTBOW_841) + addItemOrDrop(player, Items.BRONZE_ARROW_882, 30) + } + item(Item(Items.SHORTBOW_841), Item(Items.BRONZE_ARROW_882), "The Combat Guide gives you some bronze arrows and", "a shortbow!", unclosable = true) + exec { player, _ -> + setAttribute(player, "tutorial:stage", 54) + TutorialStage.load(player, 54) + } + goto("nowhere") + + label("lost bow") + exec { player, _ -> addItemOrDrop(player, Items.SHORTBOW_841) } + item(Item(Items.SHORTBOW_841), "The Combat Guide gives you a spare bow.", unclosable = true) + goto("nowhere") + + label("lost arrows") + exec { player, _ -> addItemOrDrop(player, Items.BRONZE_ARROW_882, 10) } + item(Item(Items.BRONZE_ARROW_882), "You receive some spare arrows.", unclosable = true) + goto("nowhere") + + label("nowhere") + exec { player, _ -> sendStageDialog(player) } + } } diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialFinanceAdvisorDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialFinanceAdvisorDialogue.kt index 305911ae5..d94bcd922 100644 --- a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialFinanceAdvisorDialogue.kt +++ b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialFinanceAdvisorDialogue.kt @@ -7,11 +7,12 @@ import core.game.node.entity.player.Player import core.plugin.Initializable import org.rs09.consts.NPCs import content.region.misc.tutisland.handlers.TutorialStage +import core.game.component.Component.setUnclosable import core.game.world.GameWorld.settings /** * Handles the finance tutor's dialogue - * @author Ceikry + * @author Ceikry, Player Name */ @Initializable class TutorialFinanceAdvisorDialogue(player: Player? = null) : core.game.dialogue.DialoguePlugin(player) { @@ -23,8 +24,8 @@ class TutorialFinanceAdvisorDialogue(player: Player? = null) : core.game.dialogu npc = args[0] as NPC when(getAttribute(player, "tutorial:stage", 0)) { - 58 -> playerl(core.game.dialogue.FacialExpression.FRIENDLY, "Hello, who are you?") - 59 -> npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Move along, now.").also { return false } + 58 -> setUnclosable(player, playerl(core.game.dialogue.FacialExpression.FRIENDLY, "Hello, who are you?")) + 59 -> setUnclosable(player, npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Move along, now.")) } return true } @@ -32,21 +33,22 @@ class TutorialFinanceAdvisorDialogue(player: Player? = null) : core.game.dialogu override fun handle(interfaceId: Int, buttonId: Int): Boolean { when(getAttribute(player, "tutorial:stage", 0)){ 58 -> when(stage++){ - 0 -> npcl(core.game.dialogue.FacialExpression.FRIENDLY, "I'm the Financial Advisor. I'm here to tell people how to make money.") - 1 -> playerl(core.game.dialogue.FacialExpression.FRIENDLY, "Okay. How can I make money then?") - 2 -> npcl(core.game.dialogue.FacialExpression.HALF_THINKING, "How you can make money? Quite.") - 3 -> npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Well there are three basic ways of making money here: combat, quests, and trading. I will talk you through each of them very quickly.") - 4 -> npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Let's start with combat as it is probably still fresh in your mind. Many enemies, both human and monster will drop items when they die.") - 5 -> npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Now, the next way to earn money quickly is by quests. Many people on " + settings!!.name + " have things they need doing, which they will reward you for.") - 6 -> npcl(core.game.dialogue.FacialExpression.FRIENDLY, "By getting a high level in skills such as Cooking, Mining, Smithing or Fishing, you can create or catch your own items and sell them for pure profit.") - 7 -> npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Lastly, we have jobs you can get from tutors in Lumbridge. These pay very handsomely early on!").also { stage++ } - 8 -> npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Well, that about covers it. Move along now.") + 0 -> setUnclosable(player, npcl(core.game.dialogue.FacialExpression.FRIENDLY, "I'm the Financial Advisor. I'm here to tell people how to make money.")) + 1 -> setUnclosable(player, playerl(core.game.dialogue.FacialExpression.FRIENDLY, "Okay. How can I make money then?")) + 2 -> setUnclosable(player, npcl(core.game.dialogue.FacialExpression.HALF_THINKING, "How you can make money? Quite.")) + 3 -> setUnclosable(player, npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Well there are three basic ways of making money here: combat, quests, and trading. I will talk you through each of them very quickly.")) + 4 -> setUnclosable(player, npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Let's start with combat as it is probably still fresh in your mind. Many enemies, both human and monster will drop items when they die.")) + 5 -> setUnclosable(player, npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Now, the next way to earn money quickly is by quests. Many people on " + settings!!.name + " have things they need doing, which they will reward you for.")) + 6 -> setUnclosable(player, npcl(core.game.dialogue.FacialExpression.FRIENDLY, "By getting a high level in skills such as Cooking, Mining, Smithing or Fishing, you can create or catch your own items and sell them for pure profit.")) + 7 -> setUnclosable(player, npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Lastly, we have jobs you can get from tutors in Lumbridge. These pay very handsomely early on!")).also { stage++ } + 8 -> setUnclosable(player, npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Well, that about covers it. Move along now.")) 9 -> { end() setAttribute(player, "tutorial:stage", 59) TutorialStage.load(player, 59) } } + 59 -> TutorialStage.load(player, 59) } return true } @@ -54,5 +56,4 @@ class TutorialFinanceAdvisorDialogue(player: Player? = null) : core.game.dialogu override fun getIds(): IntArray { return intArrayOf(NPCs.FINANCIAL_ADVISOR_947) } - } \ No newline at end of file diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMagicTutorDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMagicTutorDialogue.kt index 6c15f7449..68707766d 100644 --- a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMagicTutorDialogue.kt +++ b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMagicTutorDialogue.kt @@ -4,208 +4,226 @@ import content.global.handlers.iface.RulesAndInfo import content.region.misc.tutisland.handlers.* import core.ServerConstants import core.api.* +import core.game.dialogue.ChatAnim +import core.game.dialogue.DialogueLabeller +import core.game.dialogue.DialogueOption +import core.game.interaction.InteractionListener +import core.game.interaction.QueueStrength import core.game.node.entity.npc.NPC -import core.game.node.entity.player.Player import core.game.node.entity.player.link.IronmanMode import core.game.node.entity.player.link.TeleportManager import core.game.node.item.Item import core.game.world.GameWorld import core.game.world.map.Location import core.plugin.Initializable -import core.tools.END_DIALOGUE import core.worker.ManagementEvents import org.rs09.consts.Items import org.rs09.consts.NPCs import proto.management.JoinClanRequest -/** - * Handles the magic tutor's dialogue - * @author Ceikry - */ @Initializable -class TutorialMagicTutorDialogue(player: Player? = null) : core.game.dialogue.DialoguePlugin(player) { - private val STARTER_PACK = arrayOf( - Item(1351, 1), - Item(590, 1), - Item(303, 1), - Item(315, 1), - Item(1925, 1), - Item(1931, 1), - Item(2309, 1), - Item(1265, 1), - Item(1205, 1), - Item(1277, 1), - Item(1171, 1), - Item(841, 1), - Item(882, 25), - Item(556, 25), - Item(558, 15), - Item(555, 6), - Item(557, 4), - Item(559, 2) - ) - private val STARTER_BANK = arrayOf(Item(995, 25)) - - override fun newInstance(player: Player?): core.game.dialogue.DialoguePlugin { - return TutorialMagicTutorDialogue(player) +class TutorialMagicTutorDialogue : InteractionListener { + override fun defineListeners() { + on(NPCs.MAGIC_INSTRUCTOR_946, NPC, "talk-to") { player, _ -> + val stage = getAttribute(player, "tutorial:stage", 0) + if (stage == 70 && inInventory(player, Items.AIR_RUNE_556) && inInventory(player, Items.MIND_RUNE_558)) { + // Player should be killing chickens instead, and could be. Instead of opening the dialogue and doing nothing (which will make you lose the tutorial island dialog), do nothing at all + return@on true + } + openDialogue(player, TutorialMagicTutorDialogueFile(), NPC(NPCs.MAGIC_INSTRUCTOR_946)) + return@on true + } } +} - override fun open(vararg args: Any?): Boolean { - npc = args[0] as NPC - when(getAttribute(player, "tutorial:stage", 0)) - { - 67 -> playerl(core.game.dialogue.FacialExpression.FRIENDLY, "Hello.") - 69 -> npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Good. This is a list of your spells. Currently you can only cast one offensive spell called Wind Strike. Let's try it out on one of those chickens.") - 70 -> if(!inInventory(player, Items.AIR_RUNE_556) && !inInventory(player, Items.MIND_RUNE_558)) - { - player.dialogueInterpreter.sendDoubleItemMessage(Items.AIR_RUNE_556, Items.MIND_RUNE_558, "You receive some spare runes.") +class TutorialMagicTutorDialogueFile : DialogueLabeller() { + override fun addConversation() { + val STARTER_PACK = arrayOf( + Item(Items.BRONZE_AXE_1351), + Item(Items.TINDERBOX_590), + Item(Items.SMALL_FISHING_NET_303), + Item(Items.SHRIMPS_315), + Item(Items.BUCKET_1925), + Item(Items.EMPTY_POT_1931), + Item(Items.BREAD_2309), + Item(Items.BRONZE_PICKAXE_1265), + Item(Items.BRONZE_DAGGER_1205), + Item(Items.BRONZE_SWORD_1277), + Item(Items.WOODEN_SHIELD_1171), + Item(Items.SHORTBOW_841), + Item(Items.BRONZE_ARROW_882, 25), + Item(Items.AIR_RUNE_556, 25), + Item(Items.MIND_RUNE_558, 15), + Item(Items.WATER_RUNE_555, 6), + Item(Items.EARTH_RUNE_557, 4), + Item(Items.BODY_RUNE_559, 2) + ) + val STARTER_BANK = arrayOf(Item(Items.COINS_995, 25)) + + exec { player, _ -> + when (getAttribute(player, "tutorial:stage", 0)) { + 67 -> loadLabel(player, "hello") + 69 -> loadLabel(player, "spelllist") + 70 -> { + if (!inInventory(player, Items.AIR_RUNE_556, 1) || !inInventory(player, Items.MIND_RUNE_558, 1)) { + loadLabel(player, "givemorerunes") + } else { + goto("nowhere") + } + } + 71 -> loadLabel(player, "finishedtutorial") + else -> goto("nowhere") + } + } + + label("hello") + player(ChatAnim.FRIENDLY, "Hello.", unclosable = true) + npc(ChatAnim.FRIENDLY, "Good day, newcomer. My name is Terrova. I'm here to tell you about Magic. Let's start by opening your spell list.", unclosable = true) + exec { player, _ -> + setAttribute(player, "tutorial:stage", 68) + TutorialStage.load(player, 68) + } + + label("spelllist") + npc(ChatAnim.FRIENDLY, "Good. This is a list of your spells. Currently you can only cast one offensive spell called Wind Strike. Let's try it out on one of those chickens.", unclosable = true) + exec { player, _ -> + setAttribute(player, "tutorial:stage", 70) addItemOrDrop(player, Items.AIR_RUNE_556, 15) addItemOrDrop(player, Items.MIND_RUNE_558, 15) - return false } - 71 -> npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Alright, last thing. Are you interested in being an ironman or changing your experience rate?") - else -> return false - } - return true - } + item(Item(Items.AIR_RUNE_556), Item(Items.MIND_RUNE_558), "Terrova gives you 15 air runes and 15 mind runes!", unclosable = true) + exec { player, _ -> TutorialStage.load(player, 70) } - override fun handle(interfaceId: Int, buttonId: Int): Boolean { - when(getAttribute(player, "tutorial:stage", 0)) - { - 67 -> when(stage++){ - 0 -> npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Good day, newcomer. My name is Terrova. I'm here to tell you about Magic. Let's start by opening your spell list.") - 1 -> { - end() - setAttribute(player, "tutorial:stage", 68) - TutorialStage.load(player, 68) + label("givemorerunes") + exec { player, _ -> + addItemOrDrop(player, Items.AIR_RUNE_556, 5) + addItemOrDrop(player, Items.MIND_RUNE_558, 5) + } + item(Item(Items.AIR_RUNE_556), Item(Items.MIND_RUNE_558), "You receive some spare runes.", unclosable = true) + exec { player, _ -> TutorialStage.load(player, 70) } + + label("finishedtutorial") + exec { player, _ -> + if (ServerConstants.XP_RATES || ServerConstants.IRONMAN) { + loadLabel(player, "talk about inauthentic") + } else { + loadLabel(player, "leave") } } - 69 -> when(stage++){ - 0 -> { - sendDoubleItemDialogue(player, Items.AIR_RUNE_556, Items.MIND_RUNE_558, "Terrova gives you 15 air runes and 15 mind runes!") - addItemOrDrop(player, Items.AIR_RUNE_556, 5) - addItemOrDrop(player, Items.MIND_RUNE_558, 5) - } - 1 -> { - end() - setAttribute(player, "tutorial:stage", 70) - TutorialStage.load(player, 70) - } + + label("talk about inauthentic") + npc(ChatAnim.FRIENDLY, "Alright, last thing. Are you interested in our inauthentic ${ServerConstants.SERVER_NAME} features?", unclosable = true) + goto("inauthentic") + + label("inauthentic") + options( + DialogueOption("xprate","Change XP rate (current: ${player?.skills?.experienceMultiplier}x)", skipPlayer = true) { _, _ -> + return@DialogueOption ServerConstants.XP_RATES + }, + DialogueOption("ironman","Set ironman mode (current: ${player?.ironmanManager?.mode?.name?.toLowerCase()})", skipPlayer = true) { _, _ -> + return@DialogueOption ServerConstants.IRONMAN + }, + DialogueOption("leave","I'm ready now."), + unclosable = true) + + label("xprate") + options( + DialogueOption("1.0x","1.0x (default)", skipPlayer = true), + DialogueOption("2.5x","2.5x", skipPlayer = true), + DialogueOption("5.0x","5.0x", skipPlayer = true), + title = "Change XP rate (current: ${player?.skills?.experienceMultiplier}x)", + unclosable = true + ) + for (rate in doubleArrayOf(1.0, 2.5, 5.0)) { + label("${rate}x") + exec { player, _ -> player.skills.experienceMultiplier = rate } + manual(unclosable = true) { player, _ -> player.dialogueInterpreter.sendDialogue("You set your XP rate to: ${rate}x.") } + goto("inauthentic") } - 71 -> when(stage){ - 0 -> options("Set Ironman Mode (current: ${player.ironmanManager.mode.name})", "Change XP Rate (current: ${player.skills.experienceMultiplier}x)", "I'm ready now.").also { stage++ } - 1 -> when(buttonId){ - 1 -> options("None","Standard","Ultimate","Nevermind.").also { stage = 10 } - 2 -> options("1.0x","2.5x","5.0x").also { stage = 20 } - 3 -> npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Well, you're all finished here now. I'll give you a reasonable number of starting items when you leave.").also { stage = 30 } + + label("ironman") + options( + DialogueOption("NONE","None (default)", skipPlayer = true), + DialogueOption("STANDARD","Standard", skipPlayer = true), + DialogueOption("ULTIMATE","Ultimate (no bank)", skipPlayer = true), + title = "Change ironman mode (current: ${player?.ironmanManager?.mode?.name?.toLowerCase()}x)" + ) + for (mode in arrayOf(IronmanMode.NONE, IronmanMode.STANDARD, IronmanMode.ULTIMATE)) { + label(mode.name) + exec { player, _ -> player.ironmanManager.mode = mode } + manual(unclosable = true){ player, _ -> return@manual player.dialogueInterpreter.sendDialogue("You set your ironman mode to: ${mode.name.toLowerCase()}.") } + exec { player, _ -> loadLabel(player, if (player.ironmanManager.mode == IronmanMode.NONE) "inauthentic" else "ironwarning") } + } + + label("ironwarning") + manual(unclosable = true) { player, _ -> player.dialogueInterpreter.sendDialogue(*splitLines("WARNING: You have selected an ironman mode. This is an uncompromising mode that WILL completely restrict your ability to trade. This MAY leave you unable to complete certain content, including quests.")) } + goto("inauthentic") + + label("leave") + npc(ChatAnim.FRIENDLY, "Well, you're all finished here now. I'll give you a reasonable number of starting items when you leave.", unclosable = true) + options( + DialogueOption("leave:yes","Yes, I'm ready.","I'm ready to go now, thank you.", ChatAnim.FRIENDLY), + DialogueOption("nowhere","No, not yet.","I'm not quite ready to go yet, thank you.", ChatAnim.FRIENDLY), + title = "Leave Tutorial Island?", + unclosable = true + ) + + label("leave:yes") + manual { player, _ -> + setAttribute(player, "/save:tutorial:complete", true) + setVarbit(player, 3756, 0) + setVarp(player, 281, 1000, true) + teleport(player, Location.create(3233, 3230), TeleportManager.TeleportType.NORMAL) + closeOverlay(player) + + player.inventory.clear() + player.bank.clear() + player.equipment.clear() + player.interfaceManager.restoreTabs() + player.interfaceManager.setViewedTab(3) + player.inventory.add(*STARTER_PACK) + player.bank.add(*STARTER_BANK) + + TutorialStage.removeHintIcon(player) + player.unhook(TutorialKillReceiver) + player.unhook(TutorialFireReceiver) + player.unhook(TutorialResourceReceiver) + player.unhook(TutorialUseWithReceiver) + player.unhook(TutorialInteractionReceiver) + player.unhook(TutorialButtonReceiver) + player.unhook(TutorialDialogPreserver) + + if (GameWorld.settings!!.enable_default_clan) { + player.communication.currentClan = ServerConstants.SERVER_NAME.toLowerCase() + + val clanJoin = JoinClanRequest.newBuilder() + clanJoin.clanName = ServerConstants.SERVER_NAME.toLowerCase() + clanJoin.username = player.name + + ManagementEvents.publish(clanJoin.build()) } - 10 -> { - stage = 0 - if(buttonId < 5) - { - val mode = when (buttonId - 1) - { - 0 -> IronmanMode.NONE - 1 -> IronmanMode.STANDARD - 2 -> IronmanMode.ULTIMATE - else -> IronmanMode.NONE - } - if (mode != IronmanMode.NONE) stage = 11 - player.dialogueInterpreter.sendDialogue("You set your ironman mode to: ${mode.name}.") - player.ironmanManager.mode = mode - if (player.skills.experienceMultiplier == 10.0) player.skills.experienceMultiplier = 5.0 - } - else - { - handle(interfaceId, 0) - } - } - 11 -> player.dialogueInterpreter.sendPlainMessage(false, *splitLines("WARNING: You have selected an ironman mode. This is an uncompromising mode that WILL completely restrict your ability to trade. This MAY leave you unable to complete certain content, including quests.")).also { stage = 0 } - - 20 -> { - val rates = arrayOf(1.0,2.5,5.0) - val rate = rates[buttonId - 1] - if(rate == 10.0) { - player.dialogueInterpreter.sendDialogue("10.0x is no longer available!") - player.skills.experienceMultiplier = 5.0 - stage = 0 - return true - } - player.dialogueInterpreter.sendDialogue("You set your XP rate to: ${rate}x.") - player.skills.experienceMultiplier = rate - stage = 0 - } - - 30 -> player.dialogueInterpreter.sendOptions("Leave Tutorial Island?", "Yes, I'm ready.", "No, not yet.").also { stage++ } - 31 -> when(buttonId) - { - 1 -> playerl(core.game.dialogue.FacialExpression.FRIENDLY, "I'm ready to go now, thank you.").also { stage = 40 } - 2 -> playerl(core.game.dialogue.FacialExpression.FRIENDLY, "I'm not quite ready to go yet, thank you.").also { stage = END_DIALOGUE } - } - - 40 -> { - setAttribute(player, "/save:tutorial:complete", true) - setVarbit(player, 3756, 0) - setVarp(player, 281, 1000, true) - teleport(player, Location.create(3233, 3230), TeleportManager.TeleportType.NORMAL) - closeOverlay(player) - - player.inventory.clear() - player.bank.clear() - player.equipment.clear() - player.interfaceManager.restoreTabs() - player.interfaceManager.setViewedTab(3) - player.inventory.add(*STARTER_PACK) - player.bank.add(*STARTER_BANK) - - if(player.skills.experienceMultiplier == 10.0) - { - player.skills.experienceMultiplier = 5.0 - } - - //This overwrites the stuck dialogue after teleporting to Lumbridge for some reason - //Dialogue from 2007 or thereabouts - //Original is five lines, but if the same is done here it will break. Need to find another way of showing all this information. - interpreter.sendDialogue( - "Welcome to Lumbridge! To get more help, simply click on the", - "Lumbridge Guide or one of the Tutors - these can be found by looking", - "for the question mark icon on your mini-map. If you find you are lost", - "at any time, look for a signpost or use the Lumbridge Home Port Spell." - ) - stage = 12 - TutorialStage.removeHintIcon(player) - - player.unhook(TutorialKillReceiver) - player.unhook(TutorialFireReceiver) - player.unhook(TutorialResourceReceiver) - player.unhook(TutorialUseWithReceiver) - player.unhook(TutorialInteractionReceiver) - player.unhook(TutorialButtonReceiver) + // This shows the actual dialog, which is what this manual stage is for. + // Dialog is from 2007 or thereabouts. + // Original is five lines, but if the same is done here it will break. Need to find another way of showing all this information. + player.dialogueInterpreter.sendDialogue( + "Welcome to Lumbridge! To get more help, simply click on the", + "Lumbridge Guide or one of the Tutors - these can be found by looking", + "for the question mark icon on your mini-map. If you find you are lost", + "at any time, look for a signpost or use the Lumbridge Home Port Spell." + ) + if (ServerConstants.RULES_AND_INFO_ENABLED) { RulesAndInfo.openFor(player) - - if (GameWorld.settings!!.enable_default_clan) { - player.communication.currentClan = ServerConstants.SERVER_NAME.toLowerCase() - - val clanJoin = JoinClanRequest.newBuilder() - clanJoin.clanName = ServerConstants.SERVER_NAME.toLowerCase() - clanJoin.username = player.name - - ManagementEvents.publish(clanJoin.build()) + // The teleport finishing will release the player, so we need to relock them here + queueScript(player, 4, QueueStrength.SOFT) { _ -> + player.lock() + return@queueScript stopExecuting(player) } } - - 12 -> { - player.setAttribute("close_c_", true) - end() - } + return@manual null } - } - return true - } - override fun getIds(): IntArray { - return intArrayOf(NPCs.MAGIC_INSTRUCTOR_946) + label("nowhere") + exec { player, _ -> sendStageDialog(player) } } - } diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMasterChefDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMasterChefDialogue.kt index ad7d90bcb..89d985f29 100644 --- a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMasterChefDialogue.kt +++ b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMasterChefDialogue.kt @@ -1,7 +1,7 @@ package content.region.misc.tutisland.dialogue import core.api.* -import core.game.component.Component +import core.game.component.Component.setUnclosable import core.game.dialogue.DialoguePlugin import core.game.dialogue.FacialExpression import core.game.node.entity.npc.NPC @@ -25,32 +25,17 @@ class TutorialMasterChefDialogue(player: Player? = null) : DialoguePlugin(player npc = args[0] as NPC when(getAttribute(player, "tutorial:stage", 0)) { - 18 -> Component.setUnclosable( - player, - interpreter.sendDialogues( - npc, - FacialExpression.FRIENDLY, - "Ah! Welcome, newcomer. I am the Master Chef, Lev. It", - "is here I will teach you how to cook food truly fit for a", - "king." - ) - ) - - 19,20 -> { - if(!inInventory(player, Items.BREAD_DOUGH_2307)) - { - if(!inInventory(player, Items.BUCKET_OF_WATER_1929)) - { - sendItemDialogue(player, Items.BUCKET_OF_WATER_1929, "The Master Chef gives you another bucket of water.") + 18 -> setUnclosable(player, interpreter.sendDialogues(npc, FacialExpression.FRIENDLY, "Ah! Welcome, newcomer. I am the Master Chef, Lev. It", "is here I will teach you how to cook food truly fit for a", "king.")) + 19, 20 -> { + if (!inInventory(player, Items.BREAD_DOUGH_2307)) { + if (!inInventory(player, Items.BUCKET_OF_WATER_1929)) { + setUnclosable(player, player.dialogueInterpreter.sendItemMessage(Items.BUCKET_OF_WATER_1929, "The Master Chef gives you another bucket of water.")) addItemOrDrop(player, Items.BUCKET_OF_WATER_1929) - TutorialStage.load(player, 19) return false } - if(!inInventory(player, Items.POT_OF_FLOUR_1933)) - { - sendItemDialogue(player, Items.POT_OF_FLOUR_1933, "The Master Chef gives you another pot of flour.") + if (!inInventory(player, Items.POT_OF_FLOUR_1933)) { + setUnclosable(player, player.dialogueInterpreter.sendItemMessage(Items.POT_OF_FLOUR_1933, "The Master Chef gives you another pot of flour.")) addItemOrDrop(player, Items.POT_OF_FLOUR_1933) - TutorialStage.load(player, 19) return false } } @@ -61,54 +46,21 @@ class TutorialMasterChefDialogue(player: Player? = null) : DialoguePlugin(player } override fun handle(interfaceId: Int, buttonId: Int): Boolean { - when(getAttribute(player, "tutorial:stage", 0)) - { - 18 -> when(stage) - { - 0 -> Component.setUnclosable( - player, - interpreter.sendDialogues( - player, - FacialExpression.HALF_GUILTY, - "I already know how to cook. Brynna taught me just", - "now." - ) - ).also { stage++ } - 1 -> Component.setUnclosable( - player, - interpreter.sendDialogues( - npc, - FacialExpression.LAUGH, - "Hahahahahaha! You call THAT cooking? Some shrimp", - "on an open log fire? Oh, no, no no. I am going to", - "teach you the fine art of cooking bread." - ) - ).also { stage++ } - 2 -> Component.setUnclosable( - player, - interpreter.sendDialogues( - npc, - FacialExpression.FRIENDLY, - "And no fine meal is complete without good music, so", - "we'll cover that while you're here too." - ) - ).also { stage++ } + when (getAttribute(player, "tutorial:stage", 0)) { + 18 -> when(stage) { + 0 -> setUnclosable(player, interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "I already know how to cook. Brynna taught me just", "now.")).also { stage++ } + 1 -> setUnclosable(player, interpreter.sendDialogues(npc, FacialExpression.LAUGH, "Hahahahahaha! You call THAT cooking? Some shrimp", "on an open log fire? Oh, no, no no. I am going to", "teach you the fine art of cooking bread.")).also { stage++ } + 2 -> setUnclosable(player, interpreter.sendDialogues(npc, FacialExpression.FRIENDLY, "And no fine meal is complete without good music, so", "we'll cover that while you're here too.")).also { stage++ } 3 -> { - Component.setUnclosable( - player, - interpreter.sendDoubleItemMessage( - Items.BUCKET_OF_WATER_1929, - Items.POT_OF_FLOUR_1933, - "The Cooking Guide gives you a bucket of water and a pot of flour." - ) - ) + setUnclosable(player, interpreter.sendDoubleItemMessage(Items.BUCKET_OF_WATER_1929, Items.POT_OF_FLOUR_1933, "The Cooking Guide gives you a bucket of water and a","pot of flour.")) addItemOrDrop(player, Items.BUCKET_OF_WATER_1929) addItemOrDrop(player, Items.POT_OF_FLOUR_1933) stage++ + setAttribute(player, "tutorial:stage", 19) + TutorialStage.load(player, 19) } 4 -> { end() - setAttribute(player, "tutorial:stage", 19) TutorialStage.load(player, 19) } } diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMiningInstructorDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMiningInstructorDialogue.kt index 85a8577fa..fc1f96c81 100644 --- a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMiningInstructorDialogue.kt +++ b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMiningInstructorDialogue.kt @@ -1,104 +1,95 @@ package content.region.misc.tutisland.dialogue -import core.game.dialogue.DialoguePlugin -import core.game.dialogue.FacialExpression import core.game.node.entity.npc.NPC -import core.game.node.entity.player.Player -import core.plugin.Initializable import org.rs09.consts.Items import org.rs09.consts.NPCs import content.region.misc.tutisland.handlers.TutorialStage +import content.region.misc.tutisland.handlers.sendStageDialog import core.api.* +import core.game.dialogue.ChatAnim +import core.game.dialogue.DialogueLabeller +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.player.Player +import core.game.node.item.Item -/** - * Handles the mining tutor's dialogue - * @author Ceikry - */ -@Initializable -class TutorialMiningInstructorDialogue(player: Player? = null) : DialoguePlugin(player) { - override fun newInstance(player: Player?): DialoguePlugin { - return TutorialMiningInstructorDialogue(player) +class MiningInstructorDialogue : InteractionListener { + override fun defineListeners() { + on(NPCs.MINING_INSTRUCTOR_948, IntType.NPC, "talk-to") { player, node -> + DialogueLabeller.open(player, MiningInstructorDialogueFile(), node as NPC) + return@on true + } + } +} + +class MiningInstructorDialogueFile : DialogueLabeller() { + fun lostPickaxe(player: Player): Boolean { + return !inInventory(player, Items.BRONZE_PICKAXE_1265) && !inEquipment(player, Items.BRONZE_PICKAXE_1265) } - override fun open(vararg args: Any?): Boolean { - npc = args[0] as NPC - when(getAttribute(player, "tutorial:stage", 0)) { - 30 -> npcl(FacialExpression.FRIENDLY, "Hi there. You must be new around here. So what do I call you? 'Newcomer' seems so impersonal, and if we're going to be working together, I'd rather tell you by name.") - 34 -> playerl(FacialExpression.FRIENDLY, "I prospected both types of rock! One set contains tin and the other has copper ore inside.") - 35 -> { - if(!inInventory(player, Items.BRONZE_PICKAXE_1265)) { - addItemOrDrop(player, Items.BRONZE_PICKAXE_1265) - player.dialogueInterpreter.sendItemMessage(Items.BRONZE_PICKAXE_1265, "Dezzick gives you a bronze pickaxe!") - stage = 3 - } - else { - TutorialStage.load(player, 35) - } - } - 40 -> playerl(FacialExpression.ASKING, "How do I make a weapon out of this?") - 41 -> { - if(!inInventory(player, Items.HAMMER_2347)) { - addItemOrDrop(player, Items.HAMMER_2347) - player.dialogueInterpreter.sendItemMessage(Items.HAMMER_2347, "Dezzick gives you a hammer!") - stage = 3 - } - else - { - end() - TutorialStage.load(player, 41) - } + override fun addConversation() { + assignToIds(NPCs.MINING_INSTRUCTOR_948) + + exec { player, _ -> + when (getAttribute(player, "tutorial:stage", 0)) { + 30 -> loadLabel(player, "hello") + 34 -> loadLabel(player, "prospected") + 35, 36, 37, 38, 39 -> loadLabel(player, if (lostPickaxe(player)) "lost pickaxe" else "nowhere") + 40 -> loadLabel(player, "make wep") + 41, 42, 43, 44, 45, 46 -> loadLabel(player, if (lostPickaxe(player)) "lost pickaxe" else if (!inInventory(player, Items.HAMMER_2347)) "lost hammer" else "nowhere") + else -> loadLabel(player, "nowhere") } } - return true - } - - override fun handle(interfaceId: Int, buttonId: Int): Boolean { - when(getAttribute(player, "tutorial:stage", 0)) { - 30 -> when(stage) { - 0 -> playerl(FacialExpression.FRIENDLY, "You can call me ${player.username}.").also { stage++ } - 1 -> npcl(FacialExpression.FRIENDLY, "Ok then, ${player.username}. My name is Dezzick and I'm a miner by trade. Let's prospect some of these rocks.").also { stage++ } - 2 -> { - end() - setAttribute(player, "tutorial:stage", 31) - TutorialStage.load(player, 31) - } - } - - 34,35 -> when(stage) { - 0 -> npcl(FacialExpression.FRIENDLY, "Absolutely right, ${player.username}. These two ore types can be smelted together to make bronze.").also { stage++ } - 1 -> npcl(FacialExpression.FRIENDLY, "So now you know what ore is in the rocks over there, why don't you have a go at mining some tin and copper? Here, you'll need this to start with.").also { stage++ } - 2 -> { - addItem(player, Items.BRONZE_PICKAXE_1265) - player.dialogueInterpreter.sendItemMessage(Items.BRONZE_PICKAXE_1265, "Dezzick gives you a bronze pickaxe!") - stage++ - } - 3 -> { - end() - setAttribute(player, "tutorial:stage", 35) - TutorialStage.load(player, 35) - } - } - - 40,41 -> when(stage){ - 0 -> npcl(FacialExpression.FRIENDLY, "Okay, I'll show you how to make a dagger out of it. You'll be needing this..").also { stage++ } - 1 -> { - addItem(player, Items.HAMMER_2347) - player.dialogueInterpreter.sendItemMessage(Items.HAMMER_2347, "Drezzick gives you a hammer!") - stage++ - } - 2 -> { - end() - setAttribute(player, "tutorial:stage", 41) - TutorialStage.load(player, 41) - } - } + label("hello") + npc(ChatAnim.FRIENDLY, "Hi there. You must be new around here. So what do I call you? 'Newcomer' seems so impersonal, and if we're going to be working together, I'd rather tell you by name.", unclosable = true) + player(ChatAnim.FRIENDLY, "You can call me ${player?.username}.", unclosable = true) + npc(ChatAnim.FRIENDLY, "Ok then, ${player?.username}. My name is Dezzick and I'm a miner by trade. Let's prospect some of these rocks.", unclosable = true) + exec { player, _ -> + setAttribute(player, "tutorial:stage", 31) + TutorialStage.load(player, 31) } - return true - } + label("prospected") + player(ChatAnim.FRIENDLY, "I prospected both types of rock! One set contains tin and the other has copper ore inside.", unclosable = true) + npc(ChatAnim.FRIENDLY, "Absolutely right, ${player?.username}. These two ore types can be smelted together to make bronze.", unclosable = true) + npc(ChatAnim.FRIENDLY, "So now you know what ore is in the rocks over there, why don't you have a go at mining some tin and copper? Here, you'll need this to start with.", unclosable = true) + exec { player, _ -> + addItem(player, Items.BRONZE_PICKAXE_1265) + setAttribute(player, "tutorial:stage", 35) + TutorialStage.load(player, 35) + } + item(Item(Items.BRONZE_PICKAXE_1265), "Dezzick gives you a bronze pickaxe!", unclosable = true) + goto("nowhere") - override fun getIds(): IntArray { - return intArrayOf(NPCs.MINING_INSTRUCTOR_948) + label("make wep") + player(ChatAnim.ASKING, "How do I make a weapon out of this?", unclosable = true) + npc(ChatAnim.FRIENDLY, "Okay, I'll show you how to make a dagger out of it. You'll be needing this.", unclosable = true) + exec { player, _ -> + addItem(player, Items.HAMMER_2347) + setAttribute(player, "tutorial:stage", 41) + TutorialStage.load(player, 41) + } + item(Item(Items.HAMMER_2347), "Dezzick gives you a hammer!", unclosable = true) + goto("nowhere") + + label("lost pickaxe") + exec { player, _ -> addItem(player, Items.BRONZE_PICKAXE_1265) } + item(Item(Items.BRONZE_PICKAXE_1265), "Dezzick gives you a spare pickaxe.", unclosable = true) + exec { player, _ -> + val stage = getAttribute(player, "/save:tutorial:stage", 0) + if (stage >= 41 && !inInventory(player, Items.HAMMER_2347)) { + loadLabel(player, "lost hammer") + } + } + goto("nowhere") + + label("lost hammer") + exec { player, _ -> addItem(player, Items.HAMMER_2347) } + item(Item(Items.HAMMER_2347), "Dezzick gives you a spare hammer.", unclosable = true) + goto("nowhere") + + label("nowhere") + exec { player, _ -> sendStageDialog(player) } } } \ No newline at end of file diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialPrayerDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialPrayerDialogue.kt index 5278dc65d..3fb71bd16 100644 --- a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialPrayerDialogue.kt +++ b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialPrayerDialogue.kt @@ -9,6 +9,7 @@ import core.game.node.entity.player.Player import core.plugin.Initializable import org.rs09.consts.NPCs import content.region.misc.tutisland.handlers.TutorialStage +import core.game.component.Component.setUnclosable /** * Handles the prayer guide's dialogue @@ -24,9 +25,9 @@ class TutorialPrayerDialogue(player: Player? = null) : DialoguePlugin(player) { npc = args[0] as NPC when(getAttribute(player, "tutorial:stage", 0)) { - 60 -> npcl(FacialExpression.FRIENDLY, "Greetings! I'd just like to briefly go over two topics with you: Prayer, and Friend's.") - 62 -> npcl(FacialExpression.FRIENDLY, "Prayers have all sorts of wonderful benefits! From boosting defence and damage, to protecting you from outside damage, to saving items on death!") - 65 -> npcl(FacialExpression.FRIENDLY, "For your friend and ignore lists, it's quite simple really! Use your friend list to keep track of players who you like, and ignore those you don't!") + 60 -> setUnclosable(player, npcl(FacialExpression.FRIENDLY, "Greetings! I'd just like to briefly go over two topics with you: Prayer, and Friend's.")) + 62 -> setUnclosable(player, npcl(FacialExpression.FRIENDLY, "Prayers have all sorts of wonderful benefits! From boosting defence and damage, to protecting you from outside damage, to saving items on death!")) + 65 -> setUnclosable(player, npcl(FacialExpression.FRIENDLY, "For your friend and ignore lists, it's quite simple really! Use your friend list to keep track of players who you like, and ignore those you don't!")) } return true } @@ -35,8 +36,8 @@ class TutorialPrayerDialogue(player: Player? = null) : DialoguePlugin(player) { when(getAttribute(player, "tutorial:stage", 0)) { 60 -> when(stage++){ - 0 -> playerl(FacialExpression.FRIENDLY, "Alright, sounds fun!") - 1 -> npcl(FacialExpression.FRIENDLY, "Right, so first thing: Prayer. Prayer is trained by offering bones to the gods, and can grant you many boons!") + 0 -> setUnclosable(player, playerl(FacialExpression.FRIENDLY, "Alright, sounds fun!")) + 1 -> setUnclosable(player, npcl(FacialExpression.FRIENDLY, "Right, so first thing: Prayer. Prayer is trained by offering bones to the gods, and can grant you many boons!")) 2 -> { end() setAttribute(player, "tutorial:stage", 61) @@ -45,8 +46,8 @@ class TutorialPrayerDialogue(player: Player? = null) : DialoguePlugin(player) { } 62 -> when(stage++){ - 0 -> playerl(FacialExpression.AMAZED, "Very cool!") - 1 -> npcl(FacialExpression.FRIENDLY, "Next up, let's talk about friends.") + 0 -> setUnclosable(player, playerl(FacialExpression.AMAZED, "Very cool!")) + 1 -> setUnclosable(player, npcl(FacialExpression.FRIENDLY, "Next up, let's talk about friends.")) 2 -> { end() setAttribute(player, "tutorial:stage", 63) diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialQuestGuideDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialQuestGuideDialogue.kt index 015928854..716bc2cf4 100644 --- a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialQuestGuideDialogue.kt +++ b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialQuestGuideDialogue.kt @@ -12,6 +12,7 @@ import core.plugin.Initializable import org.rs09.consts.Components import org.rs09.consts.NPCs import content.region.misc.tutisland.handlers.TutorialStage +import core.game.component.Component.setUnclosable /** * Handles the quest guide's dialogue @@ -27,7 +28,7 @@ class TutorialQuestGuideDialogue(player: Player? = null) : DialoguePlugin(player npc = args[0] as NPC when(getAttribute(player, "tutorial:stage", 0)) { - 27 -> Component.setUnclosable( + 27 -> setUnclosable( player, interpreter.sendDialogues( npc, @@ -37,7 +38,7 @@ class TutorialQuestGuideDialogue(player: Player? = null) : DialoguePlugin(player ) ) - 28 -> Component.setUnclosable( + 28 -> setUnclosable( player, interpreter.sendDialogues( npc, @@ -57,7 +58,7 @@ class TutorialQuestGuideDialogue(player: Player? = null) : DialoguePlugin(player when(getAttribute(player, "tutorial:stage", 0)) { 27 -> { - Component.setUnclosable( + setUnclosable( player, interpreter.sendPlaneMessageWithBlueTitle( "Open the Quest Journal.", @@ -72,7 +73,7 @@ class TutorialQuestGuideDialogue(player: Player? = null) : DialoguePlugin(player } 28 -> when(stage) { - 0 -> Component.setUnclosable( + 0 -> setUnclosable( player, interpreter.sendDialogues( npc, @@ -83,7 +84,7 @@ class TutorialQuestGuideDialogue(player: Player? = null) : DialoguePlugin(player "to begin." ) ).also { stage++ } - 1 -> Component.setUnclosable( + 1 -> setUnclosable( player, interpreter.sendDialogues( npc, @@ -93,7 +94,7 @@ class TutorialQuestGuideDialogue(player: Player? = null) : DialoguePlugin(player "see marking my house." ) ).also { stage++ } - 2 -> Component.setUnclosable( + 2 -> setUnclosable( player, interpreter.sendDialogues( npc, @@ -118,5 +119,4 @@ class TutorialQuestGuideDialogue(player: Player? = null) : DialoguePlugin(player override fun getIds(): IntArray { return intArrayOf(NPCs.QUEST_GUIDE_949) } - } diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialRSGuideDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialRSGuideDialogue.kt index 4d7653695..b3611f89e 100644 --- a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialRSGuideDialogue.kt +++ b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialRSGuideDialogue.kt @@ -1,7 +1,6 @@ package content.region.misc.tutisland.dialogue import core.api.setAttribute -import core.game.component.Component import core.game.dialogue.DialoguePlugin import core.game.dialogue.FacialExpression import core.game.node.entity.npc.NPC @@ -9,10 +8,12 @@ import core.game.node.entity.player.Player import core.plugin.Initializable import org.rs09.consts.NPCs import content.region.misc.tutisland.handlers.TutorialStage +import core.game.component.Component.setUnclosable /** - * Handles the RuneSccape guide's dialogue + * Handles the 2009scape guide's dialogue * @author Ceikry + * @author Player Name */ @Initializable class TutorialRSGuideDialogue(player: Player? = null) : DialoguePlugin(player) { @@ -23,26 +24,12 @@ class TutorialRSGuideDialogue(player: Player? = null) : DialoguePlugin(player) { override fun open(vararg args: Any?): Boolean { npc = args[0] as NPC val tutStage = player?.getAttribute("tutorial:stage", 0) ?: 0 - if(tutStage < 2) { - end() - player.dialogueInterpreter.sendDialogues(npc,FacialExpression.HALF_GUILTY,"Greetings! Please follow the onscreen, instructions!") - return false - } else { - Component.setUnclosable( - player, - interpreter.sendDialogues( - npc, - FacialExpression.HALF_GUILTY, - "Greetings! Please follow the onscreen", - "instructions!" - ) - ) - } - - if(tutStage == 2) - { + if (tutStage < 2) { + setUnclosable(player, player.dialogueInterpreter.sendDialogues(npc,FacialExpression.HALF_GUILTY,"Greetings! Please follow the onscreen instructions!")) + stage = 99 + } else if (tutStage == 2) { player.lock() - Component.setUnclosable( + setUnclosable( player, interpreter.sendDialogues( npc, @@ -52,26 +39,17 @@ class TutorialRSGuideDialogue(player: Player? = null) : DialoguePlugin(player) { ) ) stage = 0 - return true - } - else - { - Component.setUnclosable( - player, - interpreter.sendDialogues( - npc, - FacialExpression.HALF_GUILTY, - "Please follow the onscreen instructions!" - ) - ) - return false + } else { + setUnclosable(player, player.dialogueInterpreter.sendDialogues(npc,FacialExpression.HALF_GUILTY,"Please follow the onscreen instructions!")) + stage = 99 } + return true } override fun handle(interfaceId: Int, buttonId: Int): Boolean { when(stage) { - 0 -> Component.setUnclosable( + 0 -> setUnclosable( player, interpreter.sendDialogues( npc, @@ -81,7 +59,7 @@ class TutorialRSGuideDialogue(player: Player? = null) : DialoguePlugin(player) { ) ).also { stage++ } - 1 -> Component.setUnclosable( + 1 -> setUnclosable( player, interpreter.sendDialogues( npc, @@ -92,7 +70,7 @@ class TutorialRSGuideDialogue(player: Player? = null) : DialoguePlugin(player) { ) ).also { stage++ } - 2 -> Component.setUnclosable( + 2 -> setUnclosable( player, interpreter.sendDialogues( npc, @@ -104,7 +82,7 @@ class TutorialRSGuideDialogue(player: Player? = null) : DialoguePlugin(player) { ) ).also { stage++ } - 3 -> Component.setUnclosable( + 3 -> setUnclosable( player, interpreter.sendDialogues( npc, @@ -114,7 +92,7 @@ class TutorialRSGuideDialogue(player: Player? = null) : DialoguePlugin(player) { ) ).also { stage++ } - 4 -> Component.setUnclosable( + 4 -> setUnclosable( player, interpreter.sendDialogues( npc, @@ -130,6 +108,11 @@ class TutorialRSGuideDialogue(player: Player? = null) : DialoguePlugin(player) { setAttribute(player, "tutorial:stage", 3) TutorialStage.load(player, 3) } + 99 -> { + end() + val tutStage = player?.getAttribute("tutorial:stage", 0) ?: 0 + TutorialStage.load(player, tutStage) + } } return true } diff --git a/Server/src/main/content/region/misc/tutisland/handlers/TutorialDialogs.kt b/Server/src/main/content/region/misc/tutisland/handlers/TutorialDialogs.kt new file mode 100644 index 000000000..78a493dcd --- /dev/null +++ b/Server/src/main/content/region/misc/tutisland/handlers/TutorialDialogs.kt @@ -0,0 +1,554 @@ +package content.region.misc.tutisland.handlers + +import core.api.getAttribute +import core.api.inInventory +import core.game.component.Component.setUnclosable +import core.game.event.DialogueCloseEvent +import core.game.event.EventHook +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import core.game.world.GameWorld.settings +import org.rs09.consts.Items +import kotlin.also + +fun sendStageDialog(player: Player, stage: Int) { + val message = when (stage) { + 0 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Getting started", + "To start the tutorial use your left mouse button to click on the", + "" + settings!!.name + " Guide in this room. He is indicated by a flashing", + "yellow arrow above his head. If you can't see him, use your", + "keyboard's arrow keys to rotate the view." + ) + 1 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "", + "", + "Game options", + "Please click on the flashing spanner icon found at the bottom", + "right of your screen. This will display your game options." + ) + 2 -> player.dialogueInterpreter.sendScrollMessageWithBlueTitle( + "Game Options", + "In the interface, you can now see a variety of options such as", + "screen brightness, sound and music volume and whether you", + "want to accept aid from other player's or not. Don't worry", + "about these too much for now; they will become easier as you", + "explore the game. Talk to the " + settings!!.name + " Guide to continue." + ) + 3 -> player.dialogueInterpreter.sendScrollMessageWithBlueTitle( + "Interacting with scenery", + "You can interact with many items of scenery by simply clicking", + "on them. Right clicking will also give more options. Feel free to", + "try it with the things in this room, then click on the door", + "indicated with the yellow arrow to go though to the next", + "instructor." + ) + 4 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Moving around", + "Follow the path to find the next instructor. Clicking on the", + "ground will walk you to that point. You can also navigate by", + "clicking on the minimap in the top-right corner of your screen.", + "Talk to Survival Expert by the pond to continue the tutorial." + ) + 5 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Viewing the items that you were given.", + "Click on the flashing backpack icon to the right-hand side of", + "the main window to view your inventory. Your inventory is a list", + "of everything you have in your backpack.", + "" + ) + 6 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Cut down a tree", + "You can click on the backpack icon at any time to view the", + "items that you currently have in your inventory. You will see", + "that you now have an axe in your inventory. Use this to get", + "some logs by clicking on one of the trees in the area." + ) + 7 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Please wait.", + "", + "Your character is now attempting to cut down the tree. Sit back", + "for a moment while " + (if (player.appearance.isMale) "he" else "she") + " does all the hard work.", + "" + ) + 8 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Making a fire", + "Well done! You managed to cut some logs from the tree! Next,", + "use the tinderbox in your inventory to light the logs.", + "First click on the tinderbox to 'use' it.", + "Then click on the logs in your inventory to light them." + ) + 9 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Please wait.", + "", + "Your character is now attempting to light the fire.", + "This should only take a few seconds.", + "" + ) + 10 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "You gained some experience.", + "", + "Click on the flashing bar graph icon near the inventory button", + "to see your skill state.", + "" + ) + 11 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Your skill stats", + "Here you will see how good your skills are. As you move your", + "mouse over any of the icons in this tab, the small yellow popup", + "box will show you the exact amount of experience you have", + "and how much is needed to get to the next level. Speak to the survival guide." + ) + 12 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Catch some shrimp", + "Click on the bubbling fishing spot, indicated by the flashing", + "arrow. Remember, you can check your inventory by clicking the", + "backpack icon.", + "" + ) + 13 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Please wait.", + "", + "This should only take a few seconds.", + "As you gain Fishing experience you'll find that there are many", + "types of fish and many ways to catch them." + ) + 14 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Cooking your shrimp", + "Now you have caught some shrimp, let's cook it. First light a", + "fire: chop down a tree and then use the tinderbox on the logs.", + "If you've lost your axe or tinderbox Brynna will give you", + "another." + ).also { + if (!inInventory(player, Items.RAW_SHRIMPS_317, 1)) { + setUnclosable( + player, + player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Cooking your shrimp", + "Now right click on the shrimp and select the use option. Next,", + "left click on the fire you just lit. If while doing this you look in", + "the top left of the screen, you will see the instruction that", + "you're giving your character." + ) + ) + } + } + 15 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Burning your shrimp", + "You have just burnt your first shrimp. This is normal. As you", + "get more experience in Cooking you will burn stuff less often.", + "Let's try cooking without burning it this time. First catch some", + "more shrimp, then use them on a fire." + ) + 16 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Well done, you've just cooked your first " + settings!!.name + " meal.", + "If you'd like a recap on anything you've learnt so far, speak to", + "the Survival Expert. You can now move on to the next", + "instructor. Click on the gate shown and follow the path.", + "Remember, you can move the camera with the arrow keys." + ) + 17 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Find your next instructor", + "Follow the path until you get to the door with the yellow arrow", + "above it. Click on the door to open it. Notice the mini map in the", + "top right; this shows a top down view of the area around you.", + "This can also be used for navigation." + ) + 18 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Find your next instructor", + "Talk to the chef indicated. He will teach you the more advanced", + "aspects of Cooking such as combining ingredients. He will also", + "teach you about your Music Player.", + "" + ) + 19 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Making dough", + "This is the base for many of the meals. To make dough we must", + "mix flour and water. First, right click the bucket of water and", + "select use, then left click on the pot of flour.", + "" + ) + 20 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Cooking dough", + "Now you have made dough, you can cook it. To cook the dough,", + "use it with the range shown by the arrow. If you lose your", + "dough, talk to Lev - he will give you more ingredients.", + "" + ) + 21 -> player.dialogueInterpreter.sendScrollMessageWithBlueTitle( + "Cooking dough", + "Well done! Your first loaf of bread. As you gain experience in", + "Cooking, you will be able to make other things like pies, cakes", + "and even kebabs. Now you've got the hang of cooking, let's", + "move on. Click on the flashing icon in the bottom right to see", + "the flashing icon in the bottom right to see the Music Player." + ) + 22 -> player.dialogueInterpreter.sendScrollMessageWithBlueTitle( + "The Music Player", + "From this interface you can control the music that is played.", + "As you explore the world and complete quests, more of the", + "tunes will become unlocked. Once you've examined this menu,", + "use the next door to continue. If you need a recap on anything", + "you've learnt so far, speak to the Master Chef." + ) + 23 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Emotes", + "", + "Now how about showing some feelings? You will see a flashing", + "icon in the shape of a person. Click on that to access your", + "emotes." + ) + 24 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Emotes", + "For those situations where words don't quite describe how you feel try", + "an emote. Go ahead try one out! You might notice that some of the", + "emotes are grey and cannot be used now. Don't worry! As you", + "progress further into the game you'll gain access to all sorts of things." + ) + 25 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Running", + "", + "It's only a short distance to the next guide.", + "Why not try running there? To do this, click on the run icon", + "next to the minimap." + ) + 26 -> player.dialogueInterpreter.sendScrollMessageWithBlueTitle( + "Run to the next guide", + "Now that you have the run button turned on, follow the path", + "until you come to the end. You may notice that the number on", + "the button goes down. This is your run energy. If your run", + "energy reaches zero, you'll stop running. Click on the door to", + "pass through it." + ) + 27 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Talk with the Quest Guide.", + "", + "He will tell you all about quests.", + "", + "" + ) + 28 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Your Quest Journal", + "", + "This is your Quest Journal, a list of all the quests in the game.", + "Talk to the Quest Guide again for an explanation.", + "" + ) + 29 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "", + "Moving on", + "It's time to enter some caves. Click on the ladder to go down to", + "the next area.", + "" + ) + 30 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Mining and Smithing", + "", + "Next let's get you a weapon, or more to the point, you can", + "make your first weapon yourself. Don't panic, the Mining", + "Instructor will help you. Talk to him and he'll tell you all about it." + ) + 31 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Prospecting", + "To prospect a mineable rock, just right click it and select the", + "'prospect rock' option. This will tell you the type of ore you can", + "mine from it. Try it now on one of the rocks indicated.", + "" + ) + 32 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Please wait.", + "", + "Your character is now attempting to prospect the rock. This", + "should only take a few seconds.", + "" + ) + 33 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "It's tin.", + "", + "So now you know there's tin in the grey rocks, try prospecting the", + "brown ones next.", + "" + ) + 34 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "It's copper.", + "", + "Talk to the Mining Instructor to find out about these types of", + "ore and how you can mine them.", + "He'll even give you the required tools.", + ) + 35 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Mining", + "", + "It's quite simple really. All you need to do is right click on the", + "rock and select 'mine' You can only mine when you have a", + "pickaxe. So give it a try: first mine one tin ore.", + ) + 36 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Please wait.", + "", + "Your character is now attempting to mine the rock.", + "This should only take a few seconds.", + "" + ) + 37 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Mining", + "", + "Now you have some tin ore you just need some copper ore,", + "then you'll have all you need to create a bronze bar. As you", + "did before right click on the copper rock and select 'mine'." + ) + 38 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Smelting", + "You should now have both some copper and tin ore. So let's", + "smelt them to make a bronze bar. To do this, right click on", + "either tin or copper ore and select use then left click on the", + "furnace. Try it now." + ) + 39, 40 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "You've made a bronze bar!", + "", + "Speak to the Mining Instructor and he'll show you how to make", + "it into a weapon.", + "" + ) + 41 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Smithing a dagger", + "To smith you'll need a hammer - like the one you were given by", + "Dezzick - access to an anvil like the one with the arrow over it", + "and enough metal bars to make what you are trying to smith.", + "To start the process, use the bar on one of the anvils." + ) + 42 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Smithing a dagger.", + "Now you have the Smithing menu open, you will see a list of all", + "the things you can make. Only the dagger can be made at your", + "skill level; this is shown by the white text under it. You'll need", + "to select the dagger to continue." + ) + 43 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "You've finished in this area.", + "", + "So let's move on. Go through the gates shown by the arrow.", + "Remember, you may need to move the camera to see your", + "surroundings. Speak to the guide for a recap at any time.", + ) + 44 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Combat", + "", + "In this area you will find out about combat with swords and", + "bows. Speak to the guide and he will tell you all about it.", + "" + ) + 45 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Wielding weapons", + "", + "You now have access to a new interface. Click on the flashing", + "icon of a man, the one to the right of your backpack icon.", + "" + ) + 46 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "This is your worn inventory.", + "From here you can see what items you have equipped. Let's", + "get one of those slots filled, go back to your inventory and", + "right click your dagger, select wield from the menu.", + "" + ) + 47 -> player.dialogueInterpreter.sendScrollMessageWithBlueTitle( + "You're now holding your dagger.", + "Clothes, armour, weapons and many other items are equipped", + "like this. You can unequip items by clicking on the item in the", + "worn equipment. You can close this window by clicking on the", + "small 'x' in the top-right hand corner. Speak to the Combat", + "Instructor." + ) + 48 -> player.dialogueInterpreter.sendScrollMessageWithBlueTitle( + "Unequipping items.", + "In your worn inventory panel, right click on the dagger and", + "select the remove option from the drop down list. After you've", + "unequipped the dagger, wield the sword and shield. As you", + "pass the mouse over an item you will see its name appear at", + "the top left of the screen." + ) + 49 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Combat interface.", + "", + "Click on the flashing crossed swords icon to see the combat", + "interface.", + "" + ) + 50 -> player.dialogueInterpreter.sendScrollMessageWithBlueTitle( + "This is your combat interface.", + "From this interface you can select the type of attack your", + "character will use. Different monsters have different", + "weaknesses. If you hover your mouse over the buttons, you", + "will see the type of XP you will receive when using each type of", + "attack. Now you have the tools needed for battle why not slay", + "some rats. Click on the gates indicated to continue." + ) + 51 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Attacking", + "To attack the rat, click it and select the attack option. You", + "will then walk over to it and start hitting it.", + "", + "" + ) + 52 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Sit back and watch.", + "While you are fighting you will see a bar over your head. The", + "bar shows how much health you have left. Your opponent will", + "have one too. You will continue to attack the rat until it's dead", + "or you do something else." + ) + 53 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Well done, you've made your first kill!", + "", + "Pass through the gate and talk to the Combat Instructor; he", + "will give you your next task.", + "" + ) + 54 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Rat ranging", + "Now you have a bow and some arrows. Before you can use", + "them you'll need to equip them. Once equipped with the", + "ranging gear try killing another rat. Remember: to attack, right", + "click on the monster and select attack." + ) + 55 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Moving on.", + "You have completed the tasks here. To move on, click on the", + "ladder shown. If you need to go over any of what you learnt", + "here, just talk to the Combat Instructor and he'll tell you what", + "he can." + ) + 56 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Banking.", + "Follow the path and you will come to the front of a building.", + "This is the 'Bank of " + settings!!.name + "' where you can store all your", + "most valued items. To open your bank box just right click on an", + "open booth indicated and select 'use'." + ) + 57 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "This is your bank box.", + "You can store stuff here for safekeeping. If you die, anything", + "in your bank will be saved. To deposit something, right click it", + "and select 'Deposit-1'. Once you've had a good look, close the", + "window and move on through the door indicated." + ) + 58 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Financial advice", + "", + "The guide here will tell you all about making cash. Just click on", + "him to hear what he's got to say.", + "" + ) + 59 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "", + "", + "Continue through the next door.", + "", + "" + ) + 60 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Prayer", + "Follow the path to the chapel and enter it.", + "Once inside talk to the monk. He'll tell you all about the Prayer", + "skill.", + "" + ) + 61 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Your Prayer List", + "", + "Click on the flashing icon to open the Prayer List.", + "", + "" + ) + 62 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "", + "Your Prayer List", + "", + "Talk with Brother Brace and he'll tell you all about prayers.", + "" + ) + 63 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "", + "Friends list", + "You should now see another new icon. Click on the flashing", + "smiling face to open your Friend List.", + "" + ) + 64 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "This is your Friends List.", + "", + "This will be explained by Brother Brace shortly, but first click", + "on the other flashing face in the interface.", + "" + ) + 65 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "This is your Ignore List.", + "The two lists, Friends and Ignore - can be very helpful for", + "keeping track of when your friends are online or for blocking", + "messages from people you simply don't like. Speak with", + "Brother Brace and he will tell you more." + ) + 66 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "", + "Your final instructor!", + "You're almost finished on tutorial island. Pass through the", + "door to find the path leading to your final instructor.", + "" + ) + 67 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Your final instructor!", + "Just follow the path to the Wizard's house, where you will be", + "shown how to cast spells. Just talk with the mage indicated to", + "find out more.", + "" + ) + 68 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Open up your final tab.", + "", + "Open up the Magic Spellbook tab by clicking on the flashing", + "icon next to the Prayer List tab you just learned about.", + "" + ) + 69 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "", + "This is your spell list.", + "", + "Ask the mage about it.", + "" + ) + 70 -> player.dialogueInterpreter.sendScrollMessageWithBlueTitle( + "Cast Wind Strike at a chicken.", + "Now you have the runes you should see the Wind Strike icon at the", + "top-left of your spellbook, second in from the left. Walk over", + "to the caged chickens, click the Wind Strike icon and then", + "select one of the chickens to cast it on. It may take several", + "tries." + ) + 71 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "You have almost completed the tutorial!", + "", + "All you need to do now is teleport to the mainland. Just speak", + "with Terrova and he'll tell you how to do that.", + "" + ) + else -> null + } + setUnclosable(player, message ?: return) +} + +fun sendStageDialog(entity: Entity) { + val player = entity as Player + val stage = getAttribute(player, "tutorial:stage", 0) + sendStageDialog(player, stage) +} + +// Because we don't have proper interface stacking, the above unclosable dialogs can still be closed, if a new dialog +// opens up over it and that dialog *is* closable. This is the case for npc dialogs and the smelting interface. Fake the +// authentic behavior by making sure we reopen our unclosable dialog when this happens. +object TutorialDialogPreserver : EventHook { + override fun process(entity: Entity, event: DialogueCloseEvent) { + sendStageDialog(entity) + } +} diff --git a/Server/src/main/content/region/misc/tutisland/handlers/TutorialEventReceivers.kt b/Server/src/main/content/region/misc/tutisland/handlers/TutorialEventReceivers.kt index 655c16d1c..c65191bb1 100644 --- a/Server/src/main/content/region/misc/tutisland/handlers/TutorialEventReceivers.kt +++ b/Server/src/main/content/region/misc/tutisland/handlers/TutorialEventReceivers.kt @@ -7,13 +7,30 @@ import core.game.node.entity.player.Player import content.global.skill.fishing.FishingSpot import content.global.skill.gather.mining.MiningNode import content.global.skill.gather.woodcutting.WoodcuttingNode +import core.api.animate +import core.api.delayScript +import core.api.forceWalk +import core.api.inInventory +import core.api.lock +import core.api.lockInteractions +import core.api.playAudio +import core.api.queueScript +import core.api.replaceSlot +import core.api.stopExecuting import core.game.event.* +import core.game.interaction.QueueStrength +import core.game.node.entity.impl.Animator +import core.game.node.item.Item +import core.game.world.map.Location +import core.game.world.update.flag.context.Animation import org.rs09.consts.Items import org.rs09.consts.NPCs +import org.rs09.consts.Scenery +import org.rs09.consts.Sounds /** * Event receivers for tutorial island - * @author Ceikry + * @author Ceikry, Player Name */ object TutorialButtonReceiver : EventHook { @@ -77,7 +94,7 @@ object TutorialButtonReceiver : EventHook } //Open equipment tab SD:548,42 HD:746,45 - 45 -> if((event.iface == 548 && event.buttonId == 42) || (event.iface == 746 && event.buttonId == 45)){ + 45, 46 -> if((event.iface == 548 && event.buttonId == 42) || (event.iface == 746 && event.buttonId == 45)){ setAttribute(entity, "tutorial:stage", 46) TutorialStage.load(entity, 46) } @@ -136,18 +153,6 @@ object TutorialInteractionReceiver : EventHook TutorialStage.load(entity, 13) } - //Prospect rock - Tin - 31 -> if(MiningNode.forId(event.target.id)?.identifier?.equals(2.toByte()) == true && event.option == "prospect"){ - setAttribute(entity, "tutorial:stage", 32) - TutorialStage.load(entity, 32) - } - - //Prospect rock- Copper - 33 -> if(MiningNode.forId(event.target.id)?.identifier?.equals(1.toByte()) == true && event.option == "prospect"){ - setAttribute(entity, "tutorial:stage", 34) - TutorialStage.load(entity, 34) - } - //Mine rock - Tin 35 -> if(MiningNode.forId(event.target.id)?.identifier?.equals(2.toByte()) == true && event.option == "mine"){ setAttribute(entity, "tutorial:stage", 36) @@ -211,30 +216,12 @@ object TutorialResourceReceiver : EventHook TutorialStage.load(entity, 14) } - //Cook a shrimp - 14,15 -> if(event.itemId == Items.BURNT_SHRIMP_7954) - { - setAttribute(entity, "tutorial:stage", 15) - TutorialStage.load(entity, 15) - } - else if(event.itemId == Items.SHRIMPS_315) - { - setAttribute(entity, "tutorial:stage", 16) - TutorialStage.load(entity, 16) - } - //Make some bread dough 19 -> if(event.itemId == Items.BREAD_DOUGH_2307) { setAttribute(entity, "tutorial:stage", 20) TutorialStage.load(entity, 20) } - //Bake some bread - 20 -> if(event.itemId == Items.BREAD_2309 || event.itemId == Items.BURNT_BREAD_2311) { - setAttribute(entity, "tutorial:stage", 21) - TutorialStage.load(entity, 21) - } - //Mine some tin ore 36 -> if(event.itemId == Items.TIN_ORE_438){ setAttribute(entity, "tutorial:stage", 37) diff --git a/Server/src/main/content/region/misc/tutisland/handlers/TutorialFurnaceListener.kt b/Server/src/main/content/region/misc/tutisland/handlers/TutorialFurnaceListener.kt deleted file mode 100644 index b53335a9d..000000000 --- a/Server/src/main/content/region/misc/tutisland/handlers/TutorialFurnaceListener.kt +++ /dev/null @@ -1,51 +0,0 @@ -package content.region.misc.tutisland.handlers - -import core.api.* -import core.game.event.ResourceProducedEvent -import core.game.node.entity.skill.Skills -import content.global.skill.smithing.smelting.Bar -import core.game.system.task.Pulse -import core.game.world.update.flag.context.Animation -import org.rs09.consts.Items -import org.rs09.consts.Scenery -import core.game.interaction.IntType -import core.game.interaction.InteractionListener - -/** - * Listener for tutorial island furnace - * @author Byte - */ -class TutorialFurnaceListener : InteractionListener { - - companion object { - private val ANIMATION = Animation(833) - - private val ORES = intArrayOf( - Items.TIN_ORE_438, - Items.COPPER_ORE_436 - ) - } - - override fun defineListeners() { - onUseWith(IntType.SCENERY, ORES, Scenery.FURNACE_3044) { player, _, _ -> - if (!inInventory(player, Items.TIN_ORE_438) || !inInventory(player, Items.COPPER_ORE_436)) { - return@onUseWith true - } - - animate(player, ANIMATION) - submitIndividualPulse(player, object: Pulse(2) { - override fun pulse(): Boolean { - if (removeItem(player, Items.TIN_ORE_438) && removeItem(player, Items.COPPER_ORE_436)) { - addItem(player, Items.BRONZE_BAR_2349) - rewardXP(player, Skills.SMITHING, Bar.BRONZE.experience) - player.dispatch(ResourceProducedEvent(Items.BRONZE_BAR_2349, 1, player, Items.COPPER_ORE_436)) - return true - } - return false - } - }) - - return@onUseWith true - } - } -} diff --git a/Server/src/main/content/region/misc/tutisland/handlers/TutorialListeners.kt b/Server/src/main/content/region/misc/tutisland/handlers/TutorialListeners.kt index c7dd26f50..5a143acfb 100644 --- a/Server/src/main/content/region/misc/tutisland/handlers/TutorialListeners.kt +++ b/Server/src/main/content/region/misc/tutisland/handlers/TutorialListeners.kt @@ -1,13 +1,25 @@ package content.region.misc.tutisland.handlers +import content.global.skill.smithing.smelting.Bar +import content.region.misc.tutisland.dialogue.RatPenDialogue import core.api.* +import core.game.event.ResourceProducedEvent import core.game.node.scenery.Scenery import core.game.system.task.Pulse import core.game.world.map.Location import org.rs09.consts.NPCs import core.game.interaction.InteractionListener import core.game.interaction.IntType +import core.game.interaction.QueueStrength +import core.game.node.entity.impl.Animator +import core.game.node.entity.npc.NPC +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item import core.game.world.repository.Repository +import core.game.world.update.flag.context.Animation +import org.rs09.consts.Items +import org.rs09.consts.Sounds /** * Handles tutorial-specific node interactions @@ -16,10 +28,13 @@ import core.game.world.repository.Repository class TutorialListeners : InteractionListener { val GUIDE_HOUSE_DOOR = 3014 val COOKS_DOOR = 3017 + val RANGE = 3039 val COOKS_EXIT = 3018 val QUEST_ENTER = 3019 val QUEST_LADDER = 3029 val QUEST_EXIT_LADDER = 3028 + val TIN_ROCK = 3043 + val COPPER_ROCK = 3042 val COMBAT_EXIT = 3030 val BANK_EXIT = 3024 val FINANCE_EXIT = 3025 @@ -27,6 +42,7 @@ class TutorialListeners : InteractionListener { val FIRST_GATE = intArrayOf(3015,3016) val COMBAT_GATES = intArrayOf(3020,3021) val RAT_GATES = intArrayOf(3022, 3023) + val FURNACE = 3044 override fun defineListeners() { on(GUIDE_HOUSE_DOOR, IntType.SCENERY, "open"){ player, door -> @@ -60,6 +76,36 @@ class TutorialListeners : InteractionListener { return@on true } + fun cookBread(player: Player, dough: Item): Boolean { + if (getAttribute(player, "tutorial:stage", 0) < 20) { + return true + } + // Need to reinvent the wheel of cooking. Yes, I do. On tutorial island, we don't want the default stuff like asking the player what dough they want to make. + queueScript(player, 0, QueueStrength.WEAK) { stage -> + if (stage == 0) { + val RANGE_ANIMATION = Animation(883, Animator.Priority.HIGH) + lock(player, RANGE_ANIMATION.duration) + lockInteractions(player, RANGE_ANIMATION.duration) + animate(player, RANGE_ANIMATION) + playAudio(player, Sounds.FRY_2577) + return@queueScript delayScript(player, RANGE_ANIMATION.duration) + } else { + replaceSlot(player, dough.slot, Item(Items.BREAD_2309), dough) + setAttribute(player, "tutorial:stage", 21) + TutorialStage.load(player, 21) + return@queueScript stopExecuting(player) + } + } + return true + } + on(RANGE, IntType.SCENERY, "use") { player, _ -> + val dough = player.inventory.get(Item(Items.BREAD_DOUGH_2307)) ?: return@on true + return@on cookBread(player, dough) + } + onUseWith(IntType.SCENERY, Items.BREAD_DOUGH_2307, RANGE) { player, dough, _ -> + return@onUseWith cookBread(player, dough as Item) + } + on(COOKS_EXIT, IntType.SCENERY, "open"){ player, door -> if(getAttribute(player, "tutorial:stage", 0) != 22) return@on true @@ -105,23 +151,41 @@ class TutorialListeners : InteractionListener { return@on true } - on(COMBAT_GATES, IntType.SCENERY, "open"){ player, gate -> - if(getAttribute(player, "tutorial:stage", 0) != 43) + on(TIN_ROCK, IntType.SCENERY, "prospect") { player, _ -> + if (getAttribute(player, "tutorial:stage", 0) != 31) { return@on true + } + setAttribute(player, "tutorial:stage", 32) + TutorialStage.load(player, 32) + return@on true + } + on(COPPER_ROCK, IntType.SCENERY, "prospect") { player, _ -> + if (getAttribute(player, "tutorial:stage", 0) != 33) { + return@on true + } + setAttribute(player, "tutorial:stage", 34) + TutorialStage.load(player, 34) + return@on true + } - setAttribute(player, "tutorial:stage", 44) - TutorialStage.load(player, 44) + on(COMBAT_GATES, IntType.SCENERY, "open"){ player, gate -> + if (getAttribute(player, "tutorial:stage", 0) < 43) { + return@on true + } + if (getAttribute(player, "tutorial:stage", 0) == 43) { + setAttribute(player, "tutorial:stage", 44) + TutorialStage.load(player, 44) + } core.game.global.action.DoorActionHandler.handleAutowalkDoor(player, gate as Scenery) } on(RAT_GATES, IntType.SCENERY, "open") { player, gate -> val stage = getAttribute(player, "tutorial:stage", 0) - if(stage !in 50..53){ - player.dialogueInterpreter.sendDialogues(NPCs.COMBAT_INSTRUCTOR_944, core.game.dialogue.FacialExpression.ANGRY, "Oi, get away from there!","Don't enter my rat pen unless I say so!") + if (stage !in 50..53) { + openDialogue(player, RatPenDialogue(), NPC(NPCs.COMBAT_INSTRUCTOR_944)) return@on true } - - if(stage == 50) { + if (stage == 50) { setAttribute(player, "tutorial:stage", 51) TutorialStage.load(player, 51) } @@ -130,11 +194,14 @@ class TutorialListeners : InteractionListener { } on(COMBAT_EXIT, IntType.SCENERY, "climb-up") { player, ladder -> - if(getAttribute(player, "tutorial:stage", 0) != 55) + val stage = getAttribute(player, "tutorial:stage", 0) + if (stage < 55) { return@on true - - setAttribute(player, "tutorial:stage", 56) - TutorialStage.load(player, 56) + } + if (stage == 55) { + setAttribute(player, "tutorial:stage", 56) + TutorialStage.load(player, 56) + } core.game.global.action.ClimbActionHandler.climbLadder(player, ladder.asScenery(), "climb-up") } @@ -165,5 +232,28 @@ class TutorialListeners : InteractionListener { core.game.global.action.DoorActionHandler.handleAutowalkDoor(player, door as Scenery) } + fun smeltBronzeBar(player: Player): Boolean { + if (getAttribute(player, "tutorial:stage", 0) < 38) { + return true + } + if (!inInventory(player, Items.COPPER_ORE_436) || !inInventory(player, Items.TIN_ORE_438)) { + return true + } + animate(player, 833) + queueScript(player, 2, QueueStrength.WEAK) { + if (removeItem(player, Items.COPPER_ORE_436) && removeItem(player, Items.TIN_ORE_438)) { + addItem(player, Items.BRONZE_BAR_2349) + rewardXP(player, Skills.SMITHING, Bar.BRONZE.experience) + player.dispatch(ResourceProducedEvent(Items.BRONZE_BAR_2349, 1, player, Items.COPPER_ORE_436)) + TutorialStage.load(player, 39) + } + return@queueScript stopExecuting(player) + } + return true + } + on(FURNACE, IntType.SCENERY, "use") { player, _ -> smeltBronzeBar(player) } + for (item in arrayOf(Items.COPPER_ORE_436, Items.TIN_ORE_438)) { + onUseWith(IntType.SCENERY, item, FURNACE) { player, _, _ -> smeltBronzeBar(player) } + } } -} \ No newline at end of file +} diff --git a/Server/src/main/content/region/misc/tutisland/handlers/TutorialStage.kt b/Server/src/main/content/region/misc/tutisland/handlers/TutorialStage.kt index 3ab07f9ac..50eeddb00 100644 --- a/Server/src/main/content/region/misc/tutisland/handlers/TutorialStage.kt +++ b/Server/src/main/content/region/misc/tutisland/handlers/TutorialStage.kt @@ -12,13 +12,13 @@ import core.game.world.map.Location import org.rs09.consts.Components import core.api.Event import core.game.world.GameWorld.Pulser -import core.game.world.GameWorld.settings import core.game.world.repository.Repository -import org.rs09.consts.Items +import org.rs09.consts.NPCs /** * Loads stage-relevant tutorial data * @author Ceikry + * @author Player Name */ object TutorialStage { /** @@ -26,28 +26,30 @@ object TutorialStage { * @param player the player to perform the actions on * @param stage the stage to load */ - fun load(player: Player, stage: Int, login: Boolean = false){ - if(login) - { + fun load(player: Player, stage: Int, login: Boolean = false) { + if (login) { player.hook(Event.ButtonClicked, TutorialButtonReceiver) player.hook(Event.Interacted, TutorialInteractionReceiver) player.hook(Event.ResourceProduced, TutorialResourceReceiver) player.hook(Event.UsedWith, TutorialUseWithReceiver) player.hook(Event.FireLit, TutorialFireReceiver) player.hook(Event.NPCKilled, TutorialKillReceiver) + player.hook(Event.DialogueClosed, TutorialDialogPreserver) openOverlay(player, Components.TUTORIAL_PROGRESS_371) player.packetDispatch.sendInterfaceConfig(371, 4, true) } - updateProgressBar(player) - when(stage) - { + when(stage) { 0 -> { lock(player, 10) teleport(player, Location.create(3094, 3107, 0)) hideTabs(player, login) CharacterDesign.open(player) + // We have two dialogs in this stage. This is awkward, but not a problem. + // The first dialog is impossible to close in any way, so we can send it here manually. + // The second dialog could be lost by e.g. talking to an npc, so this dialog gets implemented in + // TutorialDialogs.kt, which has the hook for restoring it if it does get lost. Component.setUnclosable( player, player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( @@ -59,380 +61,133 @@ object TutorialStage { ) ).also { runTask(player, 10) { - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Getting started", - "To start the tutorial use your left mouse button to click on the", - "" + settings!!.name + " Guide in this room. He is indicated by a flashing", - "yellow arrow above his head. If you can't see him, use your", - "keyboard's arrow keys to rotate the view." - ) - ) + sendStageDialog(player, stage) } } } - - 1 -> { hideTabs(player, login) player.interfaceManager.openTab(Component(Components.OPTIONS_261)) setVarbit(player, 3756, 12) removeHintIcon(player) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "", - "", - "Game options", - "Please click on the flashing spanner icon found at the bottom", - "right of your screen. This will display your game options." - ) - ) + sendStageDialog(player, stage) } - 2 -> { setVarbit(player, 3756, 0) hideTabs(player, login) - registerHintIcon(player, Repository.findNPC(945)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendScrollMessageWithBlueTitle( - "Game Options", - "In the interface, you can now see a variety of options such as", - "screen brightness, sound and music volume and whether you", - "want to accept aid from other player's or not. Don't worry", - "about these too much for now; they will become easier as you", - "explore the game. Talk to the " + settings!!.name + " Guide to continue." - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.RUNESCAPE_GUIDE_945)!!) + sendStageDialog(player, stage) } - 3 -> { hideTabs(player, login) removeHintIcon(player) registerHintIcon(player, Location.create(3098, 3107, 0), 125) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendScrollMessageWithBlueTitle( - "Interacting with scenery", - "You can interact with many items of scenery by simply clicking", - "on them. Right clicking will also give more options. Feel free to", - "try it with the things in this room, then click on the door", - "indicated with the yellow arrow to go though to the next", - "instructor." - ) - ) + sendStageDialog(player, stage) } - 4 -> { hideTabs(player, login) removeHintIcon(player) - registerHintIcon(player, Repository.findNPC(943)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Moving around", - "Follow the path to find the next instructor. Clicking on the", - "ground will walk you to that point. You can also navigate by", - "clicking on the minimap in the top-right corner of your screen.", - "Talk to Survival Expert by the pond to continue the tutorial." - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.SURVIVAL_EXPERT_943)!!) + sendStageDialog(player, stage) } - 5 -> { hideTabs(player, login) player.interfaceManager.openTab(Component(Components.INVENTORY_149)) setVarbit(player, 3756, 4) removeHintIcon(player) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Viewing the items that you were given.", - "Click on the flashing backpack icon to the right-hand side of", - "the main window to view your inventory. Your inventory is a list", - "of everything you have in your backpack.", - "" - ) - ) + sendStageDialog(player, stage) } - 6 -> { hideTabs(player, login) setVarbit(player, 3756, 4) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Cut down a tree", - "You can click on the backpack icon at any time to view the", - "items that you currently have in your inventory. You will see", - "that you now have an axe in your inventory. Use this to get", - "some logs by clicking on one of the trees in the area." - ) - ) + sendStageDialog(player, stage) } - 7 -> { hideTabs(player, login) removeHintIcon(player) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Please wait.", - "", - "Your character is now attempting to cut down the tree. Sit back", - "for a moment while " + (if (player.appearance.isMale) "he" else "she") + " does all the hard work.", - "" - ) - ) + sendStageDialog(player, stage) } - 8 -> { hideTabs(player, login) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Making a fire", - "Well done! You managed to cut some logs from the tree! Next,", - "use the tinderbox in your inventory to light the logs.", - "First click on the tinderbox to 'use' it.", - "Then click on the logs in your inventory to light them." - ) - ) + sendStageDialog(player, stage) } - 9 -> { hideTabs(player, login) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Please wait.", - "", - "Your character is now attempting to light the fire.", - "This should only take a few seconds.", - "" - ) - ) + sendStageDialog(player, stage) } - 10 -> { hideTabs(player, login) player.interfaceManager.openTab(Component(Components.STATS_320)) setVarbit(player, 3756, 2) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "You gained some experience.", - "", - "Click on the flashing bar graph icon near the inventory button", - "to see your skill state.", - "" - ) - ) + sendStageDialog(player, stage) } - 11 -> { hideTabs(player, login) setVarbit(player, 3756, 2) - registerHintIcon(player, Repository.findNPC(943)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Your skill stats", - "Here you will see how good your skills are. As you move your", - "mouse over any of the icons in this tab, the small yellow popup", - "box will show you the exact amount of experience you have", - "and how much is needed to get to the next level. Speak to the survival guide." - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.SURVIVAL_EXPERT_943)!!) + sendStageDialog(player, stage) } - 12 -> { hideTabs(player, login) setVarp(player, 406, 2) removeHintIcon(player) - registerHintIcon(player, Repository.findNPC(952)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Catch some shrimp", - "Click on the bubbling fishing spot, indicated by the flashing", - "arrow. Remember, you can check your inventory by clicking the", - "backpack icon.", - "" - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.FISHING_SPOT_952)!!) + sendStageDialog(player, stage) } - 13 -> { hideTabs(player, login) removeHintIcon(player) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Please wait.", - "", - "This should only take a few seconds.", - "As you gain Fishing experience you'll find that there are many", - "types of fish and many ways to catch them." - ) - ) + sendStageDialog(player, stage) } - 14 -> { hideTabs(player, login) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Cooking your shrimp", - "Now you have caught some shrimp, let's cook it. First light a", - "fire: chop down a tree and then use the tinderbox on the logs.", - "If you've lost your axe or tinderbox Brynna will give you", - "another." - ).also { - if (!inInventory(player, Items.RAW_SHRIMPS_317, 1)) { - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Cooking your shrimp", - "Now right click on the shrimp and select the use option. Next,", - "left click on the fire you just lit. If while doing this you look in", - "the top left of the screen, you will see the instruction that", - "you're giving your character." - ) - ) - } - } - ) + sendStageDialog(player, stage) } - 15 -> { hideTabs(player, login) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Burning your shrimp", - "You have just burnt your first shrimp. This is normal. As you", - "get more experience in Cooking you will burn stuff less often.", - "Let's try cooking without burning it this time. First catch some", - "more shrimp, then use them on a fire." - ) - ) + sendStageDialog(player, stage) } - 16 -> { hideTabs(player, login) + removeHintIcon(player) registerHintIcon(player, Location.create(3089, 3091, 0), 75) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Well done, you've just cooked your first " + settings!!.name + " meal.", - "If you'd like a recap on anything you've learnt so far, speak to", - "the Survival Expert. You can now move on to the next", - "instructor. Click on the gate shown and follow the path.", - "Remember, you can move the camera with the arrow keys." - ) - ) + sendStageDialog(player, stage) } - 17 -> { hideTabs(player, login) removeHintIcon(player) - registerHintIcon(player, Location.create(3078, 3084, 0), 125) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Find your next instructor", - "Follow the path until you get to the door with the yellow arrow", - "above it. Click on the door to open it. Notice the mini map in the", - "top right; this shows a top down view of the area around you.", - "This can also be used for navigation." - ) - ) + registerHintIcon(player, Location.create(3079, 3084, 0), 125) + sendStageDialog(player, stage) } - 18 -> { hideTabs(player, login) removeHintIcon(player) - registerHintIcon(player, Repository.findNPC(942)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Find your next instructor", - "Talk to the chef indicated. He will teach you the more advanced", - "aspects of Cooking such as combining ingredients. He will also", - "teach you about your Music Player.", - "" - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.MASTER_CHEF_942)!!) + sendStageDialog(player, stage) } - 19 -> { hideTabs(player, login) removeHintIcon(player) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Making dough", - "This is the base for many of the meals. To make dough we must", - "mix flour and water. First, right click the bucket of water and", - "select use, then left click on the pot of flour.", - "" - ) - ) + sendStageDialog(player, stage) } - 20 -> { hideTabs(player, login) registerHintIcon(player, Location.create(3076, 3081, 0), 75) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Cooking dough", - "Now you have made dough, you can cook it. To cook the dough,", - "use it with the range shown by the arrow. If you lose your", - "dough, talk to Lev - he will give you more ingredients.", - "" - ) - ) + sendStageDialog(player, stage) } - 21 -> { hideTabs(player, login) removeHintIcon(player) player.interfaceManager.openTab(Component(Components.MUSIC_V3_187)) setVarbit(player, 3756, 14) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendScrollMessageWithBlueTitle( - "Cooking dough", - "Well done! Your first loaf of bread. As you gain experience in", - "Cooking, you will be able to make other things like pies, cakes", - "and even kebabs. Now you've got the hang of cooking, let's", - "move on. Click on the flashing icon in the bottom right to see", - "the flashing icon in the bottom right to see the Music Player." - ) - ) + sendStageDialog(player, stage) } - 22 -> { hideTabs(player, login) setVarbit(player, 3756, 0) - registerHintIcon(player, Location.create(3072, 3090, 0), 125) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendScrollMessageWithBlueTitle( - "The Music Player", - "From this interface you can control the music that is played.", - "As you explore the world and complete quests, more of the", - "tunes will become unlocked. Once you've examined this menu,", - "use the next door to continue. If you need a recap on anything", - "you've learnt so far, speak to the Master Chef." - ) - ) + registerHintIcon(player, Location.create(3073, 3090, 0), 125) + sendStageDialog(player, stage) } - 23 -> { hideTabs(player, login) setVarbit(player, 3756, 13) @@ -440,159 +195,59 @@ object TutorialStage { player.interfaceManager.openTab(Component(Components.EMOTES_464)) stopWalk(player) player.locks.lockMovement(100000) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Emotes", - "", - "Now how about showing some feelings? You will see a flashing", - "icon in the shape of a person. Click on that to access your", - "emotes." - ) - ) + sendStageDialog(player, stage) } - 24 -> { hideTabs(player, login) setVarbit(player, 3756, 0) player.locks.lockMovement(100000) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Emotes", - "For those situations where words don't quite describe how you feel try", - "an emote. Go ahead try one out! You might notice that some of the", - "emotes are grey and cannot be used now. Don't worry! As you", - "progress further into the game you'll gain access to all sorts of things." - ) - ) + sendStageDialog(player, stage) } - 25 -> { hideTabs(player, login) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Running", - "", - "It's only a short distance to the next guide.", - "Why not try running there? To do this, click on the run icon", - "next to the minimap." - ) - ) + sendStageDialog(player, stage) } - 26 -> { hideTabs(player, login) - registerHintIcon(player, Repository.findNPC(949)!!) + registerHintIcon(player, Repository.findNPC(NPCs.QUEST_GUIDE_949)!!) player.locks.unlockMovement() - Component.setUnclosable( - player, - player.dialogueInterpreter.sendScrollMessageWithBlueTitle( - "Run to the next guide", - "Now that you have the run button turned on, follow the path", - "until you come to the end. You may notice that the number on", - "the button goes down. This is your run energy. If your run", - "energy reaches zero, you'll stop running. Click on the door to", - "pass through it." - ) - ) + sendStageDialog(player, stage) } - 27 -> { hideTabs(player, login) removeHintIcon(player) - registerHintIcon(player, Repository.findNPC(949)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Talk with the Quest Guide.", - "", - "He will tell you all about quests.", - "", - "" - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.QUEST_GUIDE_949)!!) + sendStageDialog(player, stage) } - 28 -> { hideTabs(player, login) setVarbit(player, 3756, 0) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Your Quest Journal", - "", - "This is your Quest Journal, a list of all the quests in the game.", - "Talk to the Quest Guide again for an explanation.", - "" - ) - ) + sendStageDialog(player, stage) } - 29 -> { hideTabs(player, login) removeHintIcon(player) setVarbit(player, 3756, 0) registerHintIcon(player, Location.create(3088, 3119, 0), 15) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "", - "Moving on", - "It's time to enter some caves. Click on the ladder to go down to", - "the next area.", - "" - ) - ) + sendStageDialog(player, stage) } - 30 -> { hideTabs(player, login) removeHintIcon(player) setVarbit(player, 3756, 0) - registerHintIcon(player, Repository.findNPC(948)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Mining and Smithing", - "", - "Next let's get you a weapon, or more to the point, you can", - "make your first weapon yourself. Don't panic, the Mining", - "Instructor will help you. Talk to him and he'll tell you all about it." - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.MINING_INSTRUCTOR_948)!!) + sendStageDialog(player, stage) } - 31 -> { hideTabs(player, login) removeHintIcon(player) registerHintIcon(player, Location.create(3076, 9504, 0), 50) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Prospecting", - "To prospect a mineable rock, just right click it and select the", - "'prospect rock' option. This will tell you the type of ore you can", - " mine from it. Try it now on one of the rocks indicated.", - "" - ) - ) + sendStageDialog(player, stage) } - 32 -> { hideTabs(player, login) removeHintIcon(player) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Please wait.", - "", - "Your character is now attempting to prospect the rock. This", - "should only take a few seconds.", - "" - ) - ) + sendStageDialog(player, stage) Pulser.submit(object : Pulse(3) { override fun pulse(): Boolean { setAttribute(player, "tutorial:stage", 33) @@ -601,196 +256,79 @@ object TutorialStage { } }) } - 33 -> { hideTabs(player, login) removeHintIcon(player) registerHintIcon(player, Location.create(3086, 9501, 0), 50) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "It's tin.", - "", - "So now you know there's tin in the grey rocks, try prospecting the", - "brown ones next.", - "" - ) - ) + sendStageDialog(player, stage) } - 34 -> { hideTabs(player, login) removeHintIcon(player) - registerHintIcon(player, Repository.findNPC(948)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "It's copper.", - "", - "Talk to the Mining Instructor to find out about these types of", - "ore and how you can mine them.", - "He'll even give you the required tools.", - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.MINING_INSTRUCTOR_948)!!) + sendStageDialog(player, stage) } - 35 -> { hideTabs(player, login) removeHintIcon(player) registerHintIcon(player, Location.create(3076, 9504), 50) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Mining", - "", - "It's quite simple really. All you need to do is right click on the", - "rock and select 'mine' You can only mine when you have a", - "pickaxe. So give it a try: first mine one tin ore.", - ) - ) + sendStageDialog(player, stage) } - 36 -> { hideTabs(player, login) removeHintIcon(player) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Please wait.", - "", - "Your character is now attempting to mine the rock.", - "This should only take a few seconds.", - "" - ) - ) + sendStageDialog(player, stage) } - 37 -> { hideTabs(player, login) registerHintIcon(player, Location.create(3086, 9501), 50) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Mining", - "", - "Now you have some tin ore you just need some copper ore,", - "then you'll have all you need to create a bronze bar. As you", - "did before right click on the copper rock and select 'mine'." - ) - ) + sendStageDialog(player, stage) } - 38 -> { hideTabs(player, login) removeHintIcon(player) registerHintIcon(player, Location.create(3079, 9496), 75) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Smelting", - "You should now have both some copper and tin ore. So let's", - "smelt them to make a bronze bar. To do this, right click on", - "either tin or copper ore and select use then left click on the", - "furnace. Try it now." - ) - ) + sendStageDialog(player, stage) + } + 39 -> { + sendStageDialog(player, stage) } - - //39 -> {} - 40 -> { hideTabs(player, login) removeHintIcon(player) - registerHintIcon(player, Repository.findNPC(948)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "You've made a bronze bar!", - "", - "Speak to the Mining Instructor and he'll show you how to make", - "it into a weapon.", - "" - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.MINING_INSTRUCTOR_948)!!) + sendStageDialog(player, stage) } - 41 -> { hideTabs(player, login) removeHintIcon(player) registerHintIcon(player, Location.create(3083, 9499), 50) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Smithing a dagger", - "To smith you'll need a hammer - like the one you were given by", - "Dezzick - access to an anvil like the one with the arrow over it", - "and enough metal bars to make what you are trying to smith.", - "To start the process, use the bar on one of the anvils." - ) - ) + sendStageDialog(player, stage) } - 42 -> { hideTabs(player, login) removeHintIcon(player) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Smithing a dagger.", - "Now you have the Smithing menu open, you will see a list of all", - "the things you can make. Only the dagger can be made at your", - "skill level; this is shown by the white text under it. You'll need", - "to select the dagger to continue." - ) - ) + sendStageDialog(player, stage) } - 43 -> { hideTabs(player, login) registerHintIcon(player, Location.create(3095, 9502), 75) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "You've finished in this area.", - "", - "So let's move on. Go through the gates shown by the arrow.", - "Remember, you may need to move the camera to see your", - "surroundings. Speak to the guide for a recap at any time.", - ) - ) + sendStageDialog(player, stage) } - 44 -> { hideTabs(player, login) removeHintIcon(player) - registerHintIcon(player, Repository.findNPC(944)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Combat", - "", - "In this area you will find out about combat with swords and", - "bows. Speak to the guide and he will tell you all about it.", - "" - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.COMBAT_INSTRUCTOR_944)!!) + sendStageDialog(player, stage) } - 45 -> { hideTabs(player, login) removeHintIcon(player) runTask(player, 10) { - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Wielding weapons", - "", - "You now have access to a new interface. Click on the flashing", - "icon of a man, the one to the right of your backpack icon.", - "" - ) - ) + // this part needs sendStageDialog because you could just be logging in here + sendStageDialog(player, stage) }.also { + // for this part, you are locked into the interface so we don't need sendStageDialog here hideTabs(player, login) removeHintIcon(player) player.interfaceManager.openTab(Component(Components.WORNITEMS_387)) @@ -807,429 +345,174 @@ object TutorialStage { ) } } - 46 -> { hideTabs(player, login) setVarbit(player, 3756, 0) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "This is your worn inventory.", - "From here you can see what items you have equipped. Let's", - "get one of those slots filled, go back to your inventory and", - "right click your dagger, select wield from the menu.", - "" - ) - ) + sendStageDialog(player, stage) } - 47 -> { hideTabs(player, login) removeHintIcon(player) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendScrollMessageWithBlueTitle( - "You're now holding your dagger.", - "Clothes, armour, weapons and many other items are equipped", - "like this. You can unequip items by clicking on the item in the", - "worn equipment. You can close this window by clicking on the", - "small 'x' in the top-right hand corner. Speak to the Combat", - "Instructor." - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.COMBAT_INSTRUCTOR_944)!!) + sendStageDialog(player, stage) } - 48 -> { hideTabs(player, login) removeHintIcon(player) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendScrollMessageWithBlueTitle( - "Unequipping items.", - "In your worn inventory panel, right click on the dagger and", - "select the remove option from the drop down list. After you've", - "unequipped the dagger, wield the sword and shield. As you", - "pass the mouse over an item you will see its name appear at", - "the top left of the screen." - ) - ) + sendStageDialog(player, stage) } - 49 -> { hideTabs(player, login) setVarbit(player, 3756, 1) var wepInter = player.getExtension(WeaponInterface::class.java) - if(wepInter == null) - { + if (wepInter == null) { wepInter = WeaponInterface(player) player.addExtension(WeaponInterface::class.java, wepInter) } player.interfaceManager.openTab(wepInter) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Combat interface.", - "", - "Click on the flashing crossed swords icon to see the combat", - "interface.", - "" - ) - ) + sendStageDialog(player, stage) } - 50 -> { hideTabs(player, login) setVarbit(player, 3756, 0) - registerHintIcon(player, Location.create(3110,9518,0), 75) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendScrollMessageWithBlueTitle( - "This is your combat interface.", - "From this interface you can select the type of attack your", - "character will use. Different monsters have different", - "weaknesses. If you hover your mouse over the buttons, you", - "will see the type of XP you will receive when using each type of", - "attack. Now you have the tools needed for battle why not slay", - "some rats. Click on the gates indicated to continue." - ) - ) + registerHintIcon(player, Location.create(3111,9518,0), 75) + sendStageDialog(player, stage) } - 51 -> { hideTabs(player, login) removeHintIcon(player) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Attacking", - "To attack the rat, click it and select the attack option. You", - "will then walk over to it and start hitting it.", - "", - "" - ) - ) + //FIXME: add a hint arrow over the rat closest to you that is not in combat with somebody else. https://www.youtube.com/watch?v=FGQ2BZrJIug. The below should work but doesn't. + registerHintIcon(player, Repository.findNPC(NPCs.GIANT_RAT_86)!!) + sendStageDialog(player, stage) } - 52 -> { hideTabs(player, login) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Sit back and watch.", - "While you are fighting you will see a bar over your head. The", - "bar shows how much health you have left. Your opponent will", - "have one too. You will continue to attack the rat until it's dead", - "or you do something else." - ) - ) + //FIXME: add a hint arrow over the rat you're in combat with (also in the ranging part btw). https://www.youtube.com/watch?v=FGQ2BZrJIug + sendStageDialog(player, stage) } - 53 -> { hideTabs(player, login) removeHintIcon(player) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Well done, you've made your first kill!", - "", - "Pass through the gate and talk to the Combat Instructor; he", - "will give you your next task.", - "" - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.COMBAT_INSTRUCTOR_944)!!) + sendStageDialog(player, stage) } - 54 -> { hideTabs(player, login) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Rat ranging", - "Now you have a bow and some arrows. Before you can use", - "them you'll need to equip them. Once equipped with the", - "ranging gear try killing another rat. Remember: to attack, right", - "click on the monster and select attack." - ) - ) + sendStageDialog(player, stage) } - 55 -> { hideTabs(player, login) removeHintIcon(player) registerHintIcon(player, Location.create(3111,9526), 50) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Moving on.", - "You have completed the tasks here. To move on, click on the", - "ladder shown. If you need to go over any of what you learnt", - "here, just talk to the Combat Instructor and he'll tell you what", - "he can." - ) - ) + sendStageDialog(player, stage) } - 56 -> { hideTabs(player, login) removeHintIcon(player) registerHintIcon(player, Location.create(3122,3124), 50) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Banking.", - "Follow the path and you will come to the front of a building.", - "This is the 'Bank of " + settings!!.name + "' where you can store all your", - "most valued items. To open your bank box just right click on an", - "open booth indicated and select 'use'." - ) - ) + sendStageDialog(player, stage) } - 57 -> { hideTabs(player, login) removeHintIcon(player) registerHintIcon(player, Location.create(3125, 3124), 75) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "This is your bank box.", - "You can store stuff here for safekeeping. If you die, anything", - "in your bank will be saved. To deposit something, right click it", - "and select 'Deposit-1'. Once you've had a good look, close the", - "window and move on through the door indicated." - ) - ) + sendStageDialog(player, stage) } - 58 -> { hideTabs(player, login) removeHintIcon(player) - registerHintIcon(player, Repository.findNPC(947)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Financial advice", - "", - "The guide here will tell you all about making cash. Just click on", - "him to hear what he's got to say.", - "" - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.FINANCIAL_ADVISOR_947)!!) + sendStageDialog(player, stage) } - 59 -> { hideTabs(player, login) removeHintIcon(player) registerHintIcon(player, Location.create(3130, 3124, 0), 75) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "", - "", - "Continue through the next door.", - "", - "" - ) - ) + sendStageDialog(player, stage) } - 60 -> { hideTabs(player, login) removeHintIcon(player) - registerHintIcon(player, Repository.findNPC(954)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Prayer", - "Follow the path to the chapel and enter it.", - "Once inside talk to the monk. He'll tell you all about the Prayer", - "skill.", - "" - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.BROTHER_BRACE_954)!!) + sendStageDialog(player, stage) } - 61 -> { hideTabs(player, login) removeHintIcon(player) player.interfaceManager.openTab(Component(Components.PRAYER_271)) setVarbit(player, 3756, 6) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Your Prayer List", - "", - "Click on the flashing icon to open the Prayer List.", - "", - "" - ) - ) + sendStageDialog(player, stage) } - 62 -> { hideTabs(player, login) - registerHintIcon(player, Repository.findNPC(954)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "", - "Your Prayer List", - "", - "Talk with Brother Brace and he'll tell you all about prayers.", - "" - ) - ) + removeHintIcon(player) + registerHintIcon(player, Repository.findNPC(NPCs.BROTHER_BRACE_954)!!) + sendStageDialog(player, stage) } - 63 -> { hideTabs(player, login) removeHintIcon(player) player.interfaceManager.openTab(Component(Components.FRIENDS2_550)) setVarbit(player, 3756, 9) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "", - "Friends list", - "You should now see another new icon. Click on the flashing", - "smiling face to open your Friend List.", - "" - ) - ) + sendStageDialog(player, stage) } - 64 -> { hideTabs(player, login) setVarbit(player, 3756, 10) player.interfaceManager.openTab(Component(Components.IGNORE2_551)) player.interfaceManager.openTab(Component(Components.CLANJOIN_589)) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "This is your Friends List.", - "", - "This will be explained by Brother Brace shortly, but first click", - "on the other flashing face in the interface.", - "" - ) - ) + sendStageDialog(player, stage) } - 65 -> { hideTabs(player, login) setVarbit(player, 3756, 0) - registerHintIcon(player, Repository.findNPC(945)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "This is your Ignore List.", - "The two lists, Friends and Ignore - can be very helpful for", - "keeping track of when your friends are online or for blocking", - "messages from people you simply don't like. Speak with", - "Brother Brace and he will tell you more." - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.BROTHER_BRACE_954)!!) + sendStageDialog(player, stage) } - 66 -> { hideTabs(player, login) removeHintIcon(player) registerHintIcon(player, Location.create(3122,3102), 75) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "", - "Your final instructor!", - "You're almost finished on tutorial island. Pass through the", - "door to find the path leading to your final instructor.", - "" - ) - ) + sendStageDialog(player, stage) } - 67 -> { hideTabs(player, login) removeHintIcon(player) - registerHintIcon(player, Repository.findNPC(946)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Your final instructor!", - "Just follow the path to the Wizard's house, where you will be", - "shown how to cast spells. Just talk with the mage indicated to", - "find out more.", - "" - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.MAGIC_INSTRUCTOR_946)!!) + sendStageDialog(player, stage) } - 68 -> { hideTabs(player, login) removeHintIcon(player) player.interfaceManager.openTab(Component(player.spellBookManager.spellBook)) setVarbit(player, 3756, 7) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Open up your final tab.", - "", - "Open up the Magic Spellbook tab by clicking on the flashing", - "icon next to the Prayer List tab you just learned about.", - "" - ) - ) + sendStageDialog(player, stage) } - 69 -> { hideTabs(player, login) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "", - "This is your spell list.", - "", - "Ask the mage about it.", - "" - ) - ) + removeHintIcon(player) + setVarbit(player, 3756, 0) + registerHintIcon(player, Repository.findNPC(NPCs.MAGIC_INSTRUCTOR_946)!!) + sendStageDialog(player, stage) } - 70 -> { hideTabs(player, login) - registerHintIcon(player, Repository.findNPC(41)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendScrollMessageWithBlueTitle( - "Cast Wind Strike at a chicken.", - "Now you have the runes you should see the Wind Strike icon at the", - "top-left of your spellbook, second in from the left. Walk over", - "to the caged chickens, click the Wind Strike icon and then", - "select one of the chickens to cast it on. It may take several", - "tries." - ) - ) + removeHintIcon(player) + //FIXME: as with the rats, the below should work, but doesn't + registerHintIcon(player, Repository.findNPC(NPCs.CHICKEN_41)!!) + sendStageDialog(player, stage) } - 71 -> { removeHintIcon(player) player.interfaceManager.restoreTabs() - registerHintIcon(player, Repository.findNPC(946)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "You have almost completed the tutorial!", - "", - "All you need to do now is teleport to the mainland. Just speak", - "with Terrova and he'll tell you how to do that.", - "" - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.MAGIC_INSTRUCTOR_946)!!) + sendStageDialog(player, stage) } } } @JvmStatic - public fun hideTabs(player: Player, login: Boolean) + fun hideTabs(player: Player, login: Boolean) { val stage = getAttribute(player, "tutorial:stage", 0) if(login && player.interfaceManager.tabs.isNotEmpty()) @@ -1249,10 +532,9 @@ object TutorialStage { player.interfaceManager.openTab(Component(Components.QUESTJOURNAL_V2_274)) if(stage > 45) player.interfaceManager.openTab(Component(Components.WORNITEMS_387)) - if(stage > 49){ + if(stage > 46){ var wepInter = player.getExtension(WeaponInterface::class.java) - if(wepInter == null) - { + if (wepInter == null) { wepInter = WeaponInterface(player) player.addExtension(WeaponInterface::class.java, wepInter) } diff --git a/Server/src/main/content/region/misthalin/barbvillage/stronghold/playersafety/StrongHoldOfPlayerSafetyListener.kt b/Server/src/main/content/region/misthalin/barbvillage/stronghold/playersafety/StrongHoldOfPlayerSafetyListener.kt index 0b4134a0e..8c6a1ca73 100644 --- a/Server/src/main/content/region/misthalin/barbvillage/stronghold/playersafety/StrongHoldOfPlayerSafetyListener.kt +++ b/Server/src/main/content/region/misthalin/barbvillage/stronghold/playersafety/StrongHoldOfPlayerSafetyListener.kt @@ -246,7 +246,7 @@ package content.region.misthalin.barbvillage.stronghold.playersafety addItem(player, Items.SAFETY_GLOVES_12629) sendItemDialogue( player, Items.SAFETY_GLOVES_12629, - "You open the chest to find a pair of safety gloves. " + "You open the chest to find a pair of safety gloves." ) } } diff --git a/Server/src/main/content/region/misthalin/lumbridge/dialogue/BobDialogue.java b/Server/src/main/content/region/misthalin/lumbridge/dialogue/BobDialogue.java deleted file mode 100644 index e98e13693..000000000 --- a/Server/src/main/content/region/misthalin/lumbridge/dialogue/BobDialogue.java +++ /dev/null @@ -1,569 +0,0 @@ -package content.region.misthalin.lumbridge.dialogue; - -import core.cache.def.impl.ItemDefinition; -import core.game.dialogue.DialoguePlugin; -import core.game.dialogue.FacialExpression; -import content.data.RepairItem; -import core.game.node.entity.npc.NPC; -import core.game.node.entity.player.Player; -import core.game.node.entity.player.link.diary.AchievementDiary; -import core.game.node.entity.player.link.diary.DiaryType; -import core.game.node.item.Item; -import content.global.handlers.item.equipment.BarrowsEquipmentRegister; - -/** - * Represents the dialogue plugin used for the bob npc who repairs items. - * @author 'Vexia - * @version 1.0 - */ -public final class BobDialogue extends DialoguePlugin { - - /** - * Represents the item id being repaired. - */ - private int itemId = 0; - - /** - * Represents the item being repaired. - */ - private Item item; - - /** - * Represents the item repairing. - */ - private static RepairItem repairitem = null; - - /** - * The achievement diary. - */ - private final int level = 1; - - /** - * Constructs a new {@code BobDialogue} {@code Object}. - */ - public BobDialogue() { - /** - * empty. - */ - } - - /** - * Constructs a new {@code BobDialogue} {@code Object}. - * @param player the player. - */ - public BobDialogue(Player player) { - super(player); - } - - @Override - public boolean handle(int interfaceId, int buttonId) { - switch (stage) { - case 754: - options("Yes, please.", "No, thank you."); - stage = 755; - break; - case 755: - switch (buttonId) { - case 1: - player("Yes, please."); - stage = 757; - break; - case 2: - player("No, thank you."); - stage = 756; - break; - } - break; - case 756: - end(); - break; - case 757: - end(); - if (repairitem != null) { - if (!player.getInventory().contains(995, repairitem.getCost())) { - end(); - player.getPacketDispatch().sendMessage("You don't have enough to pay him."); - break; - } - if (!player.getInventory().contains(itemId, 1)) { - end(); - return true; - } - player.getInventory().remove(new Item(995, repairitem.getCost())); - if (player.getInventory().remove(new Item(itemId, 1))) { - player.getInventory().add(repairitem.getProduct()); - } - String cost = "free"; - if (repairitem.getCost() != 0) { - cost = repairitem.getCost() + "gp"; - } - player.getPacketDispatch().sendMessage("Bob fixes your " + ItemDefinition.forId(itemId).getName().toLowerCase().replace("broken", "").trim() + " for " + cost + "."); - } - if (repairitem == null) { - String cost = "free"; - String type = BarrowsEquipment.formatedName(itemId); - String single = BarrowsEquipment.getSingleName(type); - String equipment = BarrowsEquipment.getEquipmentType(type); - String newString = type.toLowerCase().replace(single, "").trim().replace("'s", ""); - StringBuilder newewString = new StringBuilder(); - newewString.append(newString).append(" " + equipment); - final BarrowsEquipment.BarrowsFullEquipment fullequip = BarrowsEquipment.BarrowsFullEquipment.forName(newewString.toString()); - if (BarrowsEquipment.getFormatedCost(equipment, item) != 0) { - cost = String.valueOf(BarrowsEquipment.getFormatedCost(equipment, item) + "gp"); - } - if (!player.getInventory().contains(995, BarrowsEquipment.getFormatedCost(equipment, item))) { - end(); - player.getPacketDispatch().sendMessage("You don't have enough to pay him."); - break; - } - if (fullequip == null || fullequip.getFull() == null) { - player.getPacketDispatch().sendMessage("Report this to an administrator!"); - return true; - } - if (!player.getInventory().contains(itemId, 1)) { - end(); - return true; - } - player.getInventory().remove(new Item(995, BarrowsEquipment.getFormatedCost(equipment, item))); - if (player.getInventory().remove(new Item(itemId, 1))) { - player.getInventory().add(fullequip.getFull()); - } - player.getPacketDispatch().sendMessage("Bob fixes your " + equipment + " for " + cost + "."); - } - break; - case 678: - end(); - break; - case 0: - switch (buttonId) { - case 1: - player("Give me a quest!"); - stage = -5; - break; - case 2: - player("Have you anything to sell?"); - stage = 10; - break; - case 3: - player("Can you repair my items for me?"); - stage = 20; - break; - case 4: - player("I'd like to talk about Achievement Diaries."); - stage = 30; - break; - } - break; - case -5: - interpreter.sendDialogues(npc, FacialExpression.FURIOUS, "Get yer own!"); - stage = -4; - break; - case -4: - end(); - break; - case 10: - npc("Yes! I buy and sell axes! Take your pick (or axe)!"); - stage = 11; - break; - case 11: - end(); - npc.openShop(player); - break; - case 20: - npc("Of course I'll repair it, though the materials may cost", "you. Just hand me the item and I'll have a look."); - stage = 21; - break; - case 21: - end(); - break; - case 30: - if (AchievementDiary.canClaimLevelRewards(player, DiaryType.LUMBRIDGE, level)) { - player("I've done all the medium tasks in my Lumbridge", "Achievement Diary."); - stage = 150; - break; - } - if (AchievementDiary.canReplaceReward(player, DiaryType.LUMBRIDGE, level)) { - player("I've seemed to have lost my explorer's ring..."); - stage = 160; - break; - } - options("What is the Achievement Diary?", "What are the rewards?", "How do I claim the rewards?", "See you later."); - stage = 31; - break; - case 31: - switch (buttonId) { - case 1: - player("What is the Achievement Diary?"); - stage = 110; - break; - case 2: - player("What are the rewards?"); - stage = 120; - break; - case 3: - player("How do I claim the rewards?"); - stage = 130; - break; - case 4: - player("See you later!"); - stage = 140; - break; - } - break; - case 110: - npc("Ah, well, it's a diary that helps you keep track of", "particular achievements you've made in the world of", "Gielinor. In Lumbridge and Draynor I can help you", "discover some very useful things indeed."); - stage++; - break; - case 111: - npc("Eventually with enough exploration you will be", "rewarded for your explorative efforts."); - stage++; - break; - case 112: - npc("You can access your Achievement Diary by going to", "the Quest Journal. When you've opened the Quest", "Journal click on the green star icon on the top right", "hand corner. This will open the diary."); - stage = 30; - break; - case 120: - npc("Ah, well there are different rewards for each", "Achievement Diary. For completing the Lumbridge and", "Draynor diary you are presented with an explorer's", "ring."); - stage++; - break; - case 121: - npc("This ring will become increasingly useful with each", "section of the diary that you complete."); - stage = 30; - break; - case 130: - npc("You need to complete the tasks so that they're all ticked", "off, then you can claim your reward. Most of them are", "straightforward although you might find some required", "quests to be started, if not finished."); - stage++; - break; - case 131: - npc("To claim the explorer's ring speak to Explorer Jack", " in Lumbridge, Ned in Draynor Village or myself."); - stage = 30; - break; - case 140: - end(); - break; - case 150: - npc("Yes I see that, you'll be wanting your", "reward then I assume?"); - stage++; - break; - case 151: - player("Yes please."); - stage++; - break; - case 152: - AchievementDiary.flagRewarded(player, DiaryType.LUMBRIDGE, level); - npc("This ring is a representation of the adventures you", "went on to complete your tasks."); - stage ++; - break; - case 153: - player("Wow, thanks!"); - stage = 30; - break; - case 160: - AchievementDiary.grantReplacement(player, DiaryType.LUMBRIDGE, level); - npc("You better be more careful this time."); - stage = -1; - break; - } - - return true; - } - - @Override - public DialoguePlugin newInstance(Player player) { - return new BobDialogue(player); - } - - @Override - public boolean open(Object... args) { - npc = (NPC) args[0]; - boolean repair = false; - boolean wrong = false; - if (npc.getId() == 3797 && args.length == 1) { - player("Can you repair my items for me?"); - stage = 20; - return true; - } - if (args.length == 1) { - options("Give me a quest!", "Have you anything to sell?", "Can you repair my items for me?", "Talk about Achievement Diaries"); - stage = 0; - return true; - } - if (args[1] != null) { - repair = (boolean) args[1]; - } - if (args[2] != null) { - wrong = (boolean) args[2]; - } - if (args[3] != null) { - repairitem = RepairItem.forId((int) args[3]); - itemId = (int) args[3]; - } - if (args[4] != null) { - item = (Item) args[4]; - } - if (repair && !wrong) { - String cost = "free"; - if (repairitem != null) { - if (repairitem.getCost() != 0) { - cost = String.valueOf(repairitem.getCost() + "gp"); - } - } else { - String type = BarrowsEquipment.formatedName(itemId); - String single = BarrowsEquipment.getSingleName(type); - String equipment = BarrowsEquipment.getEquipmentType(type); - String newString = type.toLowerCase().replace(single, "").trim().replace("'s", ""); - StringBuilder newewString = new StringBuilder(); - newewString.append(newString).append(" " + equipment); - if (BarrowsEquipment.getFormatedCost(equipment, item) != 0) { - cost = String.valueOf(BarrowsEquipment.getFormatedCost(equipment, item) + "gp"); - } - } - npc("Quite badly damaged, but easy to repair. Would you", "like me to repair it for " + cost + "?"); - stage = 754; - return true; - } - if (repair == true && wrong == true) { - npc("Sorry friend, but I can't do anything with that."); - stage = 678; - return true; - } - return true; - } - - @Override - public int[] getIds() { - return new int[] { 519, 3797 }; - } - - /** - * Barrows equipment information. - * @author 'Vexia - */ - public static class BarrowsEquipment { - - /** - * Represents the base names. - */ - private String[] base = new String[] { "dharok", "verac", "ahrim", "torag", "guthan" }; - - /** - * The weapon names. - */ - private static final String[] weapon_names = new String[] { "flail", "greataxe", "spear", "x-bow", "hammer", "hammers", "staff" }; - - /** - * The weapon body names. - */ - private static final String[] body_names = new String[] { "top", "platebody", "body" }; - - /** - * The helm names. - */ - private static final String[] helm_names = new String[] { "hood", "helm", "coif" }; - - /** - * The leg names. - */ - private static final String[] leg_names = new String[] { "skirt", "legs", "plateskirt", "platelegs" }; - - /** - * The prices. - */ - private static final Object[][] prices = new Object[][] { { "weapon", 100 }, { "body", 90 }, { "legs", 80 }, { "helm", 60 } }; - - /** - * Represents the items. - */ - private static final Object[][] ITEMS = { { 4856, "Ahrim's hood" }, { 4857, "Ahrim's hood" }, { 4858, "Ahrim's hood" }, { 4859, "Ahrim's hood" }, { 4860, "Ahrim's hood" }, { 4862, "Ahrim's staff" }, { 4863, "Ahrim's staff" }, { 4864, "Ahrim's staff" }, { 4865, "Ahrim's staff" }, { 4866, "Ahrim's staff" }, { 4868, "Ahrim's top" }, { 4869, "Ahrim's top" }, { 4870, "Ahrim's top" }, { 4871, "Ahrim's top" }, { 4872, "Ahrim's top" }, { 4874, "Ahrim's skirt" }, { 4875, "Ahrim's skirt" }, { 4876, "Ahrim's skirt" }, { 4877, "Ahrim's skirt" }, { 4878, "Ahrim's skirt" }, { 4880, "Dharok's helm" }, { 4881, "Dharok's helm" }, { 4882, "Dharok's helm" }, { 4883, "Dharok's helm" }, { 4884, "Dharok's helm" }, { 4886, "Dharok's greataxe" }, { 4887, "Dharok's greataxe" }, { 4888, "Dharok's greataxe" }, { 4889, "Dharok's greataxe" }, { 4890, "Dharok's greataxe" }, { 4892, "Dharok's platebody" }, { 4893, "Dharok's platebody" }, { 4894, "Dharok's platebody" }, { 4895, "Dharok's platebody" }, { 4896, "Dharok's platebody" }, { 4898, "Dharok's platelegs" }, { 4899, "Dharok's platelegs" }, { 4900, "Dharok's platelegs" }, { 4901, "Dharok's platelegs" }, { 4902, "Dharok's platelegs" }, { 4904, "Guthan's helm" }, { 4905, "Guthan's helm" }, { 4906, "Guthan's helm" }, { 4907, "Guthan's helm" }, { 4908, "Guthan's helm" }, { 4910, "Guthan's spear" }, { 4911, "Guthan's spear" }, { 4912, "Guthan's spear" }, { 4913, "Guthan's spear" }, { 4914, "Guthan's spear" }, { 4916, "Guthan's body" }, { 4917, "Guthan's body" }, { 4918, "Guthan's body" }, { 4919, "Guthan's body" }, { 4920, "Guthan's body" }, { 4922, "Guthan's skirt" }, { 4923, "Guthan's skirt" }, { 4924, "Guthan's skirt" }, { 4925, "Guthan's skirt" }, { 4926, "Guthan's skirt" }, { 4928, "Karil's coif" }, { 4929, "Karil's coif" }, { 4930, "Karil's coif" }, { 4931, "Karil's coif" }, { 4932, "Karil's coif" }, { 4934, "Karil's x-bow" }, { 4935, "Karil's x-bow" }, { 4936, "Karil's x-bow" }, { 4937, "Karil's x-bow" }, { 4938, "Karil's x-bow" }, { 4940, "Karil's top" }, { 4941, "Karil's top" }, { 4942, "Karil's top" }, { 4943, "Karil's top" }, { 4944, "Karil's top" }, { 4946, "Karil's skirt" }, { 4947, "Karil's skirt" }, { 4948, "Karil's skirt" }, { 4949, "Karil's skirt" }, { 4950, "Karil's skirt" }, { 4952, "Torag's helm" }, { 4953, "Torag's helm" }, { 4954, "Torag's helm" }, { 4955, "Torag's helm" }, { 4956, "Torag's helm" }, { 4958, "Torag's hammers" }, { 4959, "Torag's hammers" }, { 4960, "Torag's hammers" }, { 4961, "Torag's hammers" }, { 4962, "Torag's hammers" }, { 4964, "Torag's body" }, { 4965, "Torag's body" }, { 4966, "Torag's body" }, { 4967, "Torag's body" }, { 4968, "Torag's body" }, { 4970, "Torag's legs" }, { 4971, "Torag's legs" }, { 4972, "Torag's legs" }, { 4973, "Torag's legs" }, { 4974, "Torag's legs" }, { 4976, "Verac's helm" }, { 4977, "Verac's helm" }, { 4978, "Verac's helm" }, { 4979, "Verac's helm" }, { 4980, "Verac's helm" }, { 4982, "Verac's flail" }, { 4983, "Verac's flail" }, { 4984, "Verac's flail" }, { 4985, "Verac's flail" }, { 4986, "Verac's flail" }, { 4988, "Verac's top" }, { 4989, "Verac's top" }, { 4990, "Verac's top" }, { 4991, "Verac's top" }, { 4992, "Verac's top" }, { 4994, "Verac's skirt" }, { 4995, "Verac's skirt" }, { 4996, "Verac's skirt" }, { 4997, "Verac's skirt" }, { 4998, "Verac's skirt" } }; - - /** - * Gets the cost. - * @param name the name. - * @return the price. - */ - public static int getFormatedCost(String name, Item item) { - int ticks = BarrowsEquipmentRegister.TICKS; - int[] degrades = new int[] { 100, 75, 50, 25, 0 }; - for (int i = 0; i < prices.length; i++) { - String check = (String) prices[i][0]; - if (check.equals(name)) { - int degrade = 0; - for (int d : degrades) { - if (item.getName().contains(String.valueOf(d))) { - degrade = d; - break; - } - } - degrade -= 25 - (25 * ((double)item.getCharge() / (double)ticks)); - int max = (int) prices[i][1] * 1000; - return (int) (max - (max * (degrade * 0.01))); - } - } - return 0; - } - - /** - * Gets the cost of the item type. - * @param name the name. - * @return the return type. - */ - public static int getCost(String name) { - for (int i = 0; i < prices.length; i++) { - String check = (String) prices[i][0]; - if (check.equals(name)) { - return (int) prices[i][1]; - } - } - return 0; - } - - /** - * Represents if an item is a barrows item. - * @param id the id. - * @return {@code True} if so. - */ - public static boolean isBarrowsItem(int id) { - for (int i = 0; i < ITEMS.length; i++) { - if ((int) ITEMS[i][0] == id) { - return true; - } - } - return false; - } - - /** - * Gets the formatted name. - * @param id the id. - * @return the name. - */ - public static String formatedName(int id) { - for (int i = 0; i < ITEMS.length; i++) { - if ((int) ITEMS[i][0] == id) { - return (String) ITEMS[i][1]; - } - } - return null; - } - - /** - * Gets the equipment type. - * @param name the name. - * @return the type. - */ - public static String getEquipmentType(String name) { - name = name.toLowerCase().replace("verac's", "").replace("karil's", "").replace("dharok's", "").replace("torag's", "").replace("guthan's", "").replace("ahrim's", "").trim(); - for (int i = 0; i < weapon_names.length; i++) { - if (weapon_names[i].contains(name)) { - return "weapon"; - } - } - for (int k = 0; k < body_names.length; k++) { - if (body_names[k].contains(name)) { - return "body"; - } - } - for (int z = 0; z < leg_names.length; z++) { - if (leg_names[z].contains(name)) { - return "legs"; - } - } - for (int q = 0; q < helm_names.length; q++) { - if (helm_names[q].contains(name)) { - return "helm"; - } - } - return null; - } - - /** - * Method used t get its single name. - * @param name the name. - * @return the name. - */ - public static String getSingleName(String name) { - name = name.toLowerCase().replace("verac's", "").replace("karil's", "").replace("dharok's", "").replace("torag's", "").replace("guthan's", "").replace("ahrim's", "").trim(); - for (int i = 0; i < weapon_names.length; i++) { - if (weapon_names[i].contains(name)) { - return weapon_names[i]; - } - } - for (int k = 0; k < body_names.length; k++) { - if (body_names[k].contains(name)) { - return body_names[k]; - } - } - for (int z = 0; z < leg_names.length; z++) { - if (leg_names[z].contains(name)) { - return leg_names[z]; - } - } - for (int q = 0; q < helm_names.length; q++) { - if (helm_names[q].contains(name)) { - return helm_names[q]; - } - } - return null; - } - - /** - * Gets the bases. - * @return the base. - */ - public String[] getBase() { - return base; - } - - /** - * Represents the multiple full barrows equipment items. - * @author 'Vexia - * @version 1.0 - */ - public enum BarrowsFullEquipment { - VERAC_LEGS(new Item(4759, 1)), VERAC_TOP(new Item(4757, 1)), VERAC_WEAPON(new Item(4755, 1)), VERAC_HELM(new Item(4753, 1)), TORAG_LEGS(new Item(4751, 1)), TORAG_BODY(new Item(4749, 1)), TORAG_HELM(new Item(4745, 1)), TORAG_WEAPON(new Item(4747, 1)), KARIL_HELM(new Item(4732, 1)), KARIL_WEAPON(new Item(4734, 1)), KARIL_BODY(new Item(4736, 1)), KARIL_LEGS(new Item(4738, 1)), GUTHAN_HELM(new Item(4724, 1)), GUTHAN_BODY(new Item(4728, 1)), GUTHAN_LEGS(new Item(4730, 1)), GUTHAN_WEAPON(new Item(4726, 1)), DHAROK_HELM(new Item(4716, 1)), DHAROK_BODY(new Item(4720, 1)), DHAROK_LEGS(new Item(4722, 1)), DHAROK_WEAPON(new Item(4718, 1)), AHRIM_HELM(new Item(4708, 1)), AHRIM_BODY(new Item(4712, 1)), AHRIM_LEGS(new Item(4714, 1)), AHRIM_WEAPON(new Item(4710, 1)); - - /** - * Constructs a new {@code BarrowsEquipment} {@code Object}. - * @param full the full item. - */ - BarrowsFullEquipment(Item full) { - this.full = full; - } - - /** - * Represents the full item. - */ - private final Item full; - - /** - * for name - * @param name thename. - * @return the equipment. - */ - public static BarrowsFullEquipment forName(String name) { - if (name.equals("guthan body body")) { - name = "guthan body"; - } else if (name.equals("torag body body")) { - name = "torag body"; - } else if (name.equals("verac body")) { - name = "verac top"; - } - for (BarrowsFullEquipment barrow : BarrowsFullEquipment.values()) { - if (barrow.name().toLowerCase().replace("_", " ").trim().equalsIgnoreCase(name)) { - return barrow; - } - } - return null; - } - - /** - * Gets the full. - * @return The full. - */ - public Item getFull() { - return full; - } - } - - } - -} diff --git a/Server/src/main/content/region/misthalin/lumbridge/dialogue/BobDialogue.kt b/Server/src/main/content/region/misthalin/lumbridge/dialogue/BobDialogue.kt new file mode 100644 index 000000000..c759ef0b2 --- /dev/null +++ b/Server/src/main/content/region/misthalin/lumbridge/dialogue/BobDialogue.kt @@ -0,0 +1,266 @@ +package content.region.misthalin.lumbridge.dialogue + +import content.data.RepairItem +import content.global.handlers.item.equipment.BarrowsEquipment +import core.cache.def.impl.ItemDefinition +import core.game.dialogue.DialoguePlugin +import core.game.dialogue.FacialExpression +import core.game.node.entity.npc.NPC +import core.game.node.entity.player.Player +import core.game.node.entity.player.link.diary.AchievementDiary +import core.game.node.entity.player.link.diary.DiaryType +import core.game.node.item.Item +import core.plugin.Initializable + +/** + * Represents the dialogue plugin used for the bob npc who repairs items. + * @author 'Vexia + * @author Damighty - Kotlin conversion + * @version 1.0 + */ +@Initializable +class BobDialogue(player: Player? = null) : DialoguePlugin(player) { + + /** + * Represents the item id being repaired. + */ + private var itemId = 0 + + /** + * Represents the item being repaired. + */ + private lateinit var item: Item + + /** + * Represents the item repairing. + */ + private var repairItem: RepairItem? = null + + /** + * The achievement diary. + */ + private val level = 1 + + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + when (stage) { + 754 -> { + options("Yes, please.", "No, thank you.") + stage = 755 + } + 755 -> when (buttonId) { + 1 -> { + player("Yes, please.") + stage = 757 + } + 2 -> { + player("No, thank you.") + stage = 756 + } + } + 756 -> end() + 757 -> { + end() + val repairItemDef = RepairItem.forId(itemId) + + if (repairItemDef != null) { + // Standard repairable items + if (!player.inventory.contains(995, repairItemDef.cost)) { + player.packetDispatch.sendMessage("You don't have enough to pay him.") + return true + } + if (player.inventory.remove(Item(itemId, 1))) { + player.inventory.remove(Item(995, repairItemDef.cost)) + player.inventory.add(repairItemDef.product) + val costText = if (repairItemDef.cost > 0) "${repairItemDef.cost}gp" else "free" + player.packetDispatch.sendMessage("Bob fixes your ${item.name.lowercase().replace("broken", "").trim()} for $costText.") + } + } else { + // Barrows items + val barrowsDef = BarrowsEquipment.getDefinition(itemId) ?: return true + val repairCost = BarrowsEquipment.getRepairCost(item) + + if (!player.inventory.contains(995, repairCost)) { + player.packetDispatch.sendMessage("You don't have enough to pay him.") + return true + } + if (player.inventory.remove(Item(itemId, 1))) { + player.inventory.remove(Item(995, repairCost)) + player.inventory.add(Item(barrowsDef.repairedId)) + val costText = if (repairCost > 0) "${repairCost}gp" else "free" + player.packetDispatch.sendMessage("Bob fixes your ${barrowsDef.itemName.lowercase()} for $costText.") + } + } + return true + } + 678 -> end() + 0 -> when (buttonId) { + 1 -> { + player("Give me a quest!") + stage = -5 + } + 2 -> { + player("Have you anything to sell?") + stage = 10 + } + 3 -> { + player("Can you repair my items for me?") + stage = 20 + } + 4 -> { + player("I'd like to talk about Achievement Diaries.") + stage = 30 + } + } + -5 -> { + interpreter.sendDialogues(npc, FacialExpression.FURIOUS, "Get yer own!") + stage = -4 + } + -4 -> end() + 10 -> { + npc("Yes! I buy and sell axes! Take your pick (or axe)!") + stage = 11 + } + 11 -> { + end() + npc.openShop(player) + } + 20 -> { + npc("Of course I'll repair it, though the materials may cost", "you. Just hand me the item and I'll have a look.") + stage = 21 + } + 21 -> end() + 30 -> { + if (AchievementDiary.canClaimLevelRewards(player, DiaryType.LUMBRIDGE, level)) { + player("I've done all the medium tasks in my Lumbridge", "Achievement Diary.") + stage = 150 + } else if (AchievementDiary.canReplaceReward(player, DiaryType.LUMBRIDGE, level)) { + player("I've seemed to have lost my explorer's ring...") + stage = 160 + } else { + options("What is the Achievement Diary?", "What are the rewards?", "How do I claim the rewards?", "See you later.") + stage = 31 + } + } + 31 -> when (buttonId) { + 1 -> { + player("What is the Achievement Diary?") + stage = 110 + } + 2 -> { + player("What are the rewards?") + stage = 120 + } + 3 -> { + player("How do I claim the rewards?") + stage = 130 + } + 4 -> { + player("See you later!") + stage = 140 + } + } + 110 -> { + npc("Ah, well, it's a diary that helps you keep track of", "particular achievements you've made in the world of", "Gielinor. In Lumbridge and Draynor I can help you", "discover some very useful things indeed.") + stage++ + } + 111 -> { + npc("Eventually with enough exploration you will be", "rewarded for your explorative efforts.") + stage++ + } + 112 -> { + npc("You can access your Achievement Diary by going to", "the Quest Journal. When you've opened the Quest", "Journal click on the green star icon on the top right", "hand corner. This will open the diary.") + stage = 30 + } + 120 -> { + npc("Ah, well there are different rewards for each", "Achievement Diary. For completing the Lumbridge and", "Draynor diary you are presented with an explorer's", "ring.") + stage++ + } + 121 -> { + npc("This ring will become increasingly useful with each", "section of the diary that you complete.") + stage = 30 + } + 130 -> { + npc("You need to complete the tasks so that they're all ticked", "off, then you can claim your reward. Most of them are", "straightforward although you might find some required", "quests to be started, if not finished.") + stage++ + } + 131 -> { + npc("To claim the explorer's ring speak to Explorer Jack", " in Lumbridge, Ned in Draynor Village or myself.") + stage = 30 + } + 140 -> end() + 150 -> { + npc("Yes I see that, you'll be wanting your", "reward then I assume?") + stage++ + } + 151 -> { + player("Yes please.") + stage++ + } + 152 -> { + AchievementDiary.flagRewarded(player, DiaryType.LUMBRIDGE, level) + npc("This ring is a representation of the adventures you", "went on to complete your tasks.") + stage++ + } + 153 -> { + player("Wow, thanks!") + stage = 30 + } + 160 -> { + AchievementDiary.grantReplacement(player, DiaryType.LUMBRIDGE, level) + npc("You better be more careful this time.") + stage = -1 + } + } + return true + } + + override fun newInstance(player: Player): DialoguePlugin { + return BobDialogue(player) + } + + override fun open(vararg args: Any): Boolean { + npc = args[0] as NPC + var repair = false + var wrong = false + + if (npc.id == 3797 && args.size == 1) { + player("Can you repair my items for me?") + stage = 20 + return true + } + + if (args.size == 1) { + options("Give me a quest!", "Have you anything to sell?", "Can you repair my items for me?", "Talk about Achievement Diaries") + stage = 0 + return true + } + + if (args.size > 1) repair = args[1] as Boolean + if (args.size > 2) wrong = args[2] as Boolean + if (args.size > 3) { + repairItem = RepairItem.forId(args[3] as Int) + itemId = args[3] as Int + } + if (args.size > 4) item = args[4] as Item + + if (repair && !wrong) { + val cost = RepairItem.forId(itemId)?.cost ?: BarrowsEquipment.getRepairCost(item) + val costText = if (cost > 0) "${cost}gp" else "free" + npc("Quite badly damaged, but easy to repair. Would you", "like me to repair it for $costText?") + stage = 754 + return true + } + + if (repair && wrong) { + npc("Sorry friend, but I can't do anything with that.") + stage = 678 + return true + } + + return true + } + + override fun getIds(): IntArray { + return intArrayOf(519, 3797) + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/misthalin/lumbridge/handlers/BobRepairItem.java b/Server/src/main/content/region/misthalin/lumbridge/handlers/BobRepairItem.java deleted file mode 100644 index 12025ef46..000000000 --- a/Server/src/main/content/region/misthalin/lumbridge/handlers/BobRepairItem.java +++ /dev/null @@ -1,48 +0,0 @@ -package content.region.misthalin.lumbridge.handlers; - -import content.region.misthalin.lumbridge.dialogue.BobDialogue; -import content.data.RepairItem; -import core.game.interaction.NodeUsageEvent; -import core.game.interaction.UseWithHandler; -import core.game.node.entity.npc.NPC; -import core.game.node.entity.player.Player; -import core.plugin.Plugin; -import core.plugin.Initializable; -import core.plugin.ClassScanner; - -/** - * Represents the plugin used to handle an item being used on bob. - * @author 'Vexia - * @version 1.0 - */ -@Initializable -public final class BobRepairItem extends UseWithHandler { - - /** - * Constructs a new {@code BobRepairItem} {@code Object}. - */ - public BobRepairItem() { - super(494, 468, 496, 470, 498, 472, 500, 502, 474, 504, 476, 506, 478, 6741, 4856, 4857, 4858, 4859, 4860, 4862, 4863, 4864, 4865, 4866, 4868, 4869, 4870, 4871, 4872, 4874, 4875, 4876, 4877, 4878, 4880, 4881, 4882, 4883, 4884, 4886, 4887, 4888, 4889, 4890, 4892, 4893, 4894, 4895, 4896, 4898, 4899, 4900, 4901, 4902, 4904, 4905, 4906, 4907, 4908, 4910, 4911, 4912, 4913, 4914, 4916, 4917, 4918, 4919, 4920, 4922, 4923, 4924, 4925, 4926, 4928, 4929, 4930, 4931, 4932, 4934, 4935, 4936, 4937, 4938, 4940, 4941, 4942, 4943, 4944, 4946, 4947, 4948, 4949, 4950, 4952, 4953, 4954, 4955, 4956, 4958, 4959, 4960, 4961, 4962, 4964, 4965, 4966, 4967, 4968, 4970, 4971, 4972, 4973, 4974, 4976, 4977, 4978, 4979, 4980, 4982, 4983, 4984, 4985, 4986, 4988, 4989, 4990, 4991, 4992, 4994, 4995, 4996, 4997, 4998); - } - - @Override - public Plugin newInstance(Object arg) throws Throwable { - addHandler(519, NPC_TYPE, this); - addHandler(3797, NPC_TYPE, this); - ClassScanner.definePlugin(new BobDialogue()); - return this; - } - - @Override - public boolean handle(NodeUsageEvent event) { - final Player player = event.getPlayer(); - final RepairItem repair = RepairItem.forId(event.getUsedItem().getId()); - if (repair == null && !BobDialogue.BarrowsEquipment.isBarrowsItem(event.getUsedItem().getId())) { - player.getDialogueInterpreter().open(519, ((NPC) event.getUsedWith()), true, true, null); - return true; - } - player.getDialogueInterpreter().open(519, ((NPC) event.getUsedWith()), true, false, event.getUsedItem().getId(), event.getUsedItem()); - return true; - } - -} diff --git a/Server/src/main/content/region/misthalin/lumbridge/handlers/BobRepairItem.kt b/Server/src/main/content/region/misthalin/lumbridge/handlers/BobRepairItem.kt new file mode 100644 index 000000000..d939865f1 --- /dev/null +++ b/Server/src/main/content/region/misthalin/lumbridge/handlers/BobRepairItem.kt @@ -0,0 +1,47 @@ +package content.region.misthalin.lumbridge.handlers + +import content.global.handlers.item.equipment.BarrowsEquipment +import content.data.RepairItem +import core.game.interaction.NodeUsageEvent +import core.game.interaction.UseWithHandler +import core.game.node.entity.npc.NPC +import core.plugin.Plugin +import core.plugin.Initializable + +private val ALL_REPAIRABLE_ITEM_IDS = (RepairItem.repairableItemIds + BarrowsEquipment.getAllRepairableBarrowsIds()).toIntArray() + +/** + * Handles items being used on Bob for repair services. + * @author 'Vexia + * @author Damighty - Kotlin conversion + */ +@Initializable +class BobRepairItem : UseWithHandler(*ALL_REPAIRABLE_ITEM_IDS) { + + override fun newInstance(arg: Any?): Plugin { + addHandler(519, NPC_TYPE, this) + addHandler(3797, NPC_TYPE, this) + return this + } + + override fun handle(event: NodeUsageEvent): Boolean { + val player = event.player + val item = event.usedItem + val npc = event.usedWith as NPC + + val isBarrowsItem = BarrowsEquipment.isBarrowsItem(item.id) + + if (isBarrowsItem) { + if (BarrowsEquipment.isFullyRepaired(item.id)) { + player.dialogueInterpreter.open(519, npc, true, true, item.id) + return true + } + } else if (RepairItem.forId(item.id) == null) { + player.dialogueInterpreter.open(519, npc, true, true) + return true + } + + player.dialogueInterpreter.open(519, npc, true, false, item.id, item) + return true + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/misthalin/lumbridge/quest/lostcity/DramenTreeListener.kt b/Server/src/main/content/region/misthalin/lumbridge/quest/lostcity/DramenTreeListener.kt deleted file mode 100644 index 679e28bde..000000000 --- a/Server/src/main/content/region/misthalin/lumbridge/quest/lostcity/DramenTreeListener.kt +++ /dev/null @@ -1,47 +0,0 @@ -package content.region.misthalin.lumbridge.quest.lostcity - -import core.game.node.scenery.Scenery -import content.data.skill.SkillingTool -import content.global.skill.gather.woodcutting.WoodcuttingSkillPulse -import core.game.world.map.Location -import org.rs09.consts.NPCs -import org.rs09.consts.Scenery as Sceneries -import core.game.interaction.InteractionListener -import core.api.getQuestStage -import core.api.sendMessage -import core.game.interaction.IntType -import content.data.Quests - -class DramenTreeListener : InteractionListener { - - override fun defineListeners() { - - on(Sceneries.DRAMEN_TREE_1292, IntType.SCENERY, "chop down"){ player, node -> - val questStage = getQuestStage(player,Quests.LOST_CITY) - if (SkillingTool.getHatchet(player) == null) { - sendMessage(player,"You do not have an axe which you have the level to use.") - return@on true - } - if (questStage < 20) { - return@on true - } - if (questStage == 20) { - if (player.getAttribute("treeSpawned", false)) { - return@on true - } - val spirit = TreeSpiritNPC(NPCs.TREE_SPIRIT_655, Location(2862, 9734, 0)) - spirit.target = player - spirit.init() - spirit.attack(player) - player.setAttribute("treeSpawned", true) - spirit.sendChat("You must defeat me before touching the tree!") - return@on true - } - - player.pulseManager.run(WoodcuttingSkillPulse(player, node as Scenery)) - return@on true - } - - } - -} diff --git a/Server/src/main/content/region/misthalin/lumbridge/quest/lostcity/LostCity.kt b/Server/src/main/content/region/misthalin/lumbridge/quest/lostcity/LostCity.kt index 744ef3ca1..89f48fe42 100644 --- a/Server/src/main/content/region/misthalin/lumbridge/quest/lostcity/LostCity.kt +++ b/Server/src/main/content/region/misthalin/lumbridge/quest/lostcity/LostCity.kt @@ -1,22 +1,29 @@ package content.region.misthalin.lumbridge.quest.lostcity +import core.api.Event +import core.api.LoginListener +import core.api.clearScripts +import core.api.getQuestStage +import core.game.event.EventHook +import core.game.event.InteractionEvent +import core.game.node.entity.Entity 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.game.node.item.Item +import core.game.world.map.Location import core.plugin.Initializable import org.rs09.consts.Items import content.data.Quests +import org.rs09.consts.NPCs +import org.rs09.consts.Scenery /** * LostCity class for the Lost City quest - * @author lila - * @author Vexia - * @author Aero + * @author lila, Vexia, Aero, Player Name */ @Initializable -class LostCity : Quest(Quests.LOST_CITY, 83, 82, 3, 147, 0, 1, 6) { - +class LostCity : Quest(Quests.LOST_CITY, 83, 82, 3, 147, 0, 1, 6), LoginListener { class SkillRequirement(val skill: Int?, val level: Int?) val requirements = arrayListOf() @@ -67,9 +74,45 @@ class LostCity : Quest(Quests.LOST_CITY, 83, 82, 3, 147, 0, 1, 6) { line(player, BLUE + "and be able to defeat a " + RED + "Level 101 Spirit without weapons", line++) } + private val DramenTreeHook = object : EventHook { + override fun process(entity: Entity, event: InteractionEvent) { + if (event.target.id == Scenery.DRAMEN_TREE_1292 && event.option == "chop down") { + val player = entity as Player + val questStage = getQuestStage(player, Quests.LOST_CITY) + if (questStage == 20) { + if (player.getAttribute("treeSpawned", false)) { + return + } + val spirit = TreeSpiritNPC(NPCs.TREE_SPIRIT_655, Location(2862, 9734, 0)) + spirit.target = player + spirit.init() + spirit.attack(player) + player.setAttribute("treeSpawned", true) + spirit.sendChat("You must defeat me before touching the tree!") + } + } + } + } + + override fun setStage(player: Player, stage: Int) { + super.setStage(player, stage) + if (stage <= 20) { + player.hook(Event.Interacted, DramenTreeHook) + } + if (stage == 21) { + player.unhook(DramenTreeHook) + } + } + + override fun login(player: Player) { + if (getQuestStage(player, Quests.LOST_CITY) <= 20) { + player.hook(Event.Interacted, DramenTreeHook) + } + } + override fun newInstance(`object`: Any?): Quest { requirements.add(SkillRequirement(Skills.WOODCUTTING, 36)) requirements.add(SkillRequirement(Skills.CRAFTING, 31)) return this } -} \ No newline at end of file +} diff --git a/Server/src/main/content/region/morytania/phas/dialogue/GhostDiscipleDialogue.java b/Server/src/main/content/region/morytania/phas/dialogue/GhostDiscipleDialogue.java index 23d8093fe..40f395cf4 100644 --- a/Server/src/main/content/region/morytania/phas/dialogue/GhostDiscipleDialogue.java +++ b/Server/src/main/content/region/morytania/phas/dialogue/GhostDiscipleDialogue.java @@ -82,13 +82,13 @@ public final class GhostDiscipleDialogue extends DialoguePlugin { stage++; break; case 1: - options("What is the Ectofunuts?", "Where do I get ectoplasm from?", "How do I grind bones?", "How do I receive Ectotokens?", "Thanks for your time."); + options("What is the Ectofuntus?", "Where do I get ectoplasm from?", "How do I grind bones?", "How do I receive Ectotokens?", "Thanks for your time."); stage++; break; case 2: switch (buttonId) { case 1: - player("What is the Ectofunuts?"); + player("What is the Ectofuntus?"); stage = 10; break; case 2: diff --git a/Server/src/main/content/region/morytania/quest/creatureoffenkenstrain/BookcaseDialogueFile.kt b/Server/src/main/content/region/morytania/quest/creatureoffenkenstrain/BookcaseDialogueFile.kt index f188633d9..7a07c5a44 100644 --- a/Server/src/main/content/region/morytania/quest/creatureoffenkenstrain/BookcaseDialogueFile.kt +++ b/Server/src/main/content/region/morytania/quest/creatureoffenkenstrain/BookcaseDialogueFile.kt @@ -68,7 +68,7 @@ class BookcaseEastDialogueFile : DialogueFile() { class ChimneySweepingOnABudgetBook { companion object { - private val TITLE = "Chimney Sweeping on a Budget " + private val TITLE = "Chimney Sweeping on a Budget" val CONTENTS = arrayOf( PageSet( Page( @@ -103,4 +103,4 @@ class ChimneySweepingOnABudgetBook { return true } } -} \ No newline at end of file +} diff --git a/Server/src/main/core/ServerConstants.kt b/Server/src/main/core/ServerConstants.kt index d67997daf..2959ccb0c 100644 --- a/Server/src/main/core/ServerConstants.kt +++ b/Server/src/main/core/ServerConstants.kt @@ -161,7 +161,7 @@ class ServerConstants { var LOG_CUTSCENE = true @JvmField - var RULES_AND_INFO_ENABLED = true + var RULES_AND_INFO_ENABLED = false @JvmField var WATCHDOG_ENABLED = true @@ -292,13 +292,16 @@ class ServerConstants { var DRAGON_AXE_USE_OSRS_SPEC = false @JvmField - var ENABLE_GLOBALCHAT = false + var ENABLE_GLOBAL_CHAT = false @JvmField var MAX_PATHFIND_DISTANCE = 25 @JvmField - var IRONMAN_ICONS = false + var XP_RATES = false + + @JvmField + var IRONMAN = false @JvmField var PLAYER_STOCK_CLEAR_INTERVAL = 1 @@ -310,19 +313,19 @@ class ServerConstants { var BOTSTOCK_LIMIT = 5000 @JvmField - var BETTER_AGILITY_PYRAMID_GP = true + var BETTER_AGILITY_PYRAMID_GP = false @JvmField - var BETTER_DFS = true + var BETTER_DFS = false @JvmField - var NEW_PLAYER_ANNOUNCEMENT = true + var NEW_PLAYER_ANNOUNCEMENT = false @JvmField var INAUTHENTIC_CANDLELIGHT_RANDOM = false @JvmField - var HOLIDAY_EVENT_RANDOMS = true + var HOLIDAY_EVENT_RANDOMS = false @JvmField var FORCE_HALLOWEEN_EVENTS = false @@ -334,11 +337,29 @@ class ServerConstants { var FORCE_EASTER_EVENTS = false @JvmField - var RUNECRAFTING_FORMULA_REVISION = 581 + var RUNECRAFTING_FORMULA_REVISION = 530 @JvmField var ENHANCED_DEEP_WILDERNESS = false + @JvmField + var WILDERNESS_EXCLUSIVE_LOOT = false + + @JvmField + var SHOOTING_STAR_RING = false + + @JvmField + var RING_OF_WEALTH_TELEPORT = false + + @JvmField + var SECOND_BANK = false + + @JvmField + var PLAYER_COMMANDS = false + + @JvmField + var BOOSTED_TRAWLER_REWARDS = false + @JvmField var STARTUP_MOMENT = Calendar.getInstance() diff --git a/Server/src/main/core/api/ContentAPI.kt b/Server/src/main/core/api/ContentAPI.kt index 615e51996..8185d5079 100644 --- a/Server/src/main/core/api/ContentAPI.kt +++ b/Server/src/main/core/api/ContentAPI.kt @@ -6,6 +6,7 @@ import content.data.consumables.* import content.data.skill.SkillingTool import content.global.handlers.iface.ge.StockMarket import content.global.skill.slayer.SlayerEquipmentFlags +import content.global.skill.slayer.SlayerFlags import content.global.skill.slayer.SlayerManager import content.global.skill.slayer.Tasks import content.global.skill.summoning.familiar.BurdenBeast @@ -343,6 +344,24 @@ fun removeItem(player: Player, item: T, container: Container = Container.INV } } +/** + * Remove items in a players inventory + * Checks that the player has enough first + * @param player + * @param items the items to remove + */ +fun removeItemsIfPlayerHasEnough(player: Player, vararg items: Item): Boolean { + for (item in items) { + if (amountInInventory(player, item.id) >= 1) continue + return false + } + for (item in items) { + if (removeItem(player, item)) continue + return false + } + return true +} + /** * Add an item to the given player's given container * @param player the player whose container to modify @@ -1842,8 +1861,8 @@ fun sendItemDialogue(player: Player, item: Any, message: String) { */ fun sendDoubleItemDialogue(player: Player, item1: Any, item2: Any, message: String) { when (item1) { - is Item -> player.dialogueInterpreter.sendDoubleItemMessage(item1, item2 as Item, *splitLines(message)) - is Int -> player.dialogueInterpreter.sendDoubleItemMessage(item1, item2 as Int, *splitLines(message)) + is Item -> player.dialogueInterpreter.sendDoubleItemMessage(item1, item2 as Item, *splitLines(message)) + is Int -> player.dialogueInterpreter.sendDoubleItemMessage(item1, item2 as Int, *splitLines(message)) } } @@ -1948,12 +1967,16 @@ fun setQuestStage(player: Player, quest: Quests, stage: Int) { } /** - * Check if a quest is in progress + * Check if a quests' stage for a given player is (inclusively) between the provided start and end stage */ fun isQuestInProgress(player: Player, quest: Quests, startStage: Int, endStage: Int): Boolean { return player.questRepository.getStage(quest) in startStage..endStage } +fun isQuestStarted(player: Player, quest: Quests): Boolean { + return getQuestStage(player, quest) > 0 +} + /** * Check if a quest is complete */ @@ -2083,7 +2106,7 @@ fun runcs2 (player: Player, scriptId: Int, vararg arguments: Any) { * @param callback a callback to handle the selection. The parameters passed to the callback are the slot in the inventory of the selected item, and the 0-9 index of the option clicked. **/ @JvmOverloads -fun sendItemSelect (player: Player, vararg options: String, keepAlive: Boolean = false, callback: (slot: Int, optionIndex: Int) -> Unit) { +fun sendItemSelect(player: Player, vararg options: String, keepAlive: Boolean = false, callback: (slot: Int, optionIndex: Int) -> Unit) { player.interfaceManager.openSingleTab(Component(12)) val scriptArgs = arrayOf ((12 shl 16) + 18, 93, 4, 7, 0, -1, "", "", "", "", "", "", "", "", "") for (i in 0 until min(9, options.size)) @@ -2454,7 +2477,11 @@ fun getSlayerTip(player: Player): Array { * @return the task flags as Int. */ fun getSlayerTaskFlags(player: Player): Int { - return SlayerManager.getInstance(player).flags.taskFlags + return getSlayerFlags(player).taskFlags +} + +fun getSlayerFlags(player: Player): SlayerFlags { + return SlayerManager.getInstance(player).flags } /** @@ -2982,10 +3009,14 @@ fun queueScript(entity: Entity, delay: Int = 1, strength: QueueStrength = QueueS * @param entity the entity whose clock we are updating * @param clock the clock we are updating. Please use [core.game.interaction.Clocks] for this argument. * @param ticks the number of ticks to delay by + * @param delayScript whether to delay the script by the number of ticks passed in. * @return always returns false so this can be used as a script return value. **/ -fun delayClock(entity: Entity, clock: Int, ticks: Int) : Boolean { +fun delayClock(entity: Entity, clock: Int, ticks: Int, delayScript: Boolean = false) : Boolean { entity.clocks[clock] = getWorldTicks() + ticks + if (delayScript) { + return delayScript(entity, ticks) + } return false } diff --git a/Server/src/main/core/api/utils/Permadeath.kt b/Server/src/main/core/api/utils/Permadeath.kt index c7e5069f4..a49d575cf 100644 --- a/Server/src/main/core/api/utils/Permadeath.kt +++ b/Server/src/main/core/api/utils/Permadeath.kt @@ -3,6 +3,7 @@ package core.api.utils import content.global.skill.construction.HouseLocation import content.minigame.blastfurnace.BFPlayerState import content.minigame.blastfurnace.BlastFurnace +import core.ServerConstants import core.api.isUsingSecondaryBankAccount import core.api.teleport import core.api.toggleBankAccount @@ -91,6 +92,9 @@ fun permadeath(target: Player) { } } + // Xp rate + target.skills.experienceMultiplier = 1.0 + // Ironman data target.ironmanManager.mode = IronmanMode.NONE diff --git a/Server/src/main/core/cache/def/impl/ItemDefinition.java b/Server/src/main/core/cache/def/impl/ItemDefinition.java index 13eb0940e..b7c17e5ec 100644 --- a/Server/src/main/core/cache/def/impl/ItemDefinition.java +++ b/Server/src/main/core/cache/def/impl/ItemDefinition.java @@ -1,6 +1,5 @@ package core.cache.def.impl; -import content.global.skill.summoning.familiar.BurdenBeast; import core.api.EquipmentSlot; import core.cache.Cache; import core.cache.def.Definition; @@ -605,12 +604,11 @@ public class ItemDefinition extends Definition { * @return {@code True} if so. */ public static boolean canEnterEntrana(Player player) { - Container[] containers; - if (player.getFamiliarManager().hasFamiliar() && player.getFamiliarManager().getFamiliar().isBurdenBeast()) { - containers = new Container[] { player.getInventory(), player.getEquipment(), ((BurdenBeast) player.getFamiliarManager().getFamiliar()).getContainer() }; - } else { - containers = new Container[] { player.getInventory(), player.getEquipment() }; + Container[] containers = new Container[] { player.getInventory(), player.getEquipment() }; + if (player.getFamiliarManager().hasFamiliar() && !player.getFamiliarManager().hasPet()) { + return false; } + for (Container c : containers) { for (Item i : c.toArray()) { if (i == null) { @@ -650,6 +648,7 @@ public class ItemDefinition extends Definition { Items.BOOK_OF_BALANCE_3844, Items.DAMAGED_BOOK_3843, Items.WIZARD_BOOTS_2579, + Items.COMBAT_BRACELET_11126, Items.COMBAT_BRACELET1_11124, Items.COMBAT_BRACELET2_11122, Items.COMBAT_BRACELET3_11120, @@ -665,10 +664,28 @@ public class ItemDefinition extends Definition { Items.HAM_HOOD_4302, Items.HAM_CLOAK_4304, Items.HAM_LOGO_4306, - Items.GLOVES_4308, - Items.BOOTS_4310, Items.ZAMORAK_ROBE_1033, - Items.ZAMORAK_ROBE_1035 + Items.ZAMORAK_ROBE_1035, + Items.PRIEST_GOWN_426, + Items.PRIEST_GOWN_428, + Items.DRUIDS_ROBE_538, + Items.DRUIDS_ROBE_540, + Items.FLOWERS_2460, + Items.FLOWERS_2462, + Items.FLOWERS_2464, + Items.FLOWERS_2466, + Items.FLOWERS_2468, + Items.FLOWERS_2470, + Items.FLOWERS_2472, + Items.FLOWERS_2474, + Items.FLOWERS_2476, + Items.FIXED_DEVICE_6082, + Items.GHOSTLY_BOOTS_6106, + Items.GHOSTLY_ROBE_6107, + Items.GHOSTLY_ROBE_6108, + Items.GHOSTLY_HOOD_6109, + Items.GHOSTLY_GLOVES_6110, + Items.GHOSTLY_CLOAK_6111 )); private static final HashSet entranaBannedItems = new HashSet(Arrays.asList( /**Items.BUTTERFLY_NET_10010, easing the restriction until barehanded implementation**/ @@ -677,15 +694,10 @@ public class ItemDefinition extends Definition { Items.CANNON_BASE_6, Items.CANNON_STAND_8, Items.CANNON_FURNACE_12, - Items.COOKING_GAUNTLETS_775, - Items.CHAOS_GAUNTLETS_777, - Items.GOLDSMITH_GAUNTLETS_776, - Items.KARAMJA_GLOVES_1_11136, - Items.KARAMJA_GLOVES_2_11138, - Items.KARAMJA_GLOVES_3_11140, - Items.VYREWATCH_TOP_9634, - Items.VYREWATCH_LEGS_9636, - Items.VYREWATCH_SHOES_9638 + Items.ZAMORAK_STOLE_10474, + Items.EXPLORERS_RING_1_13560, + Items.EXPLORERS_RING_2_13561, + Items.EXPLORERS_RING_3_13562 )); @@ -700,11 +712,11 @@ public class ItemDefinition extends Definition { if (entranaBannedItems.contains(getId())) { return false; } - if (equipSlot(getId()) == EquipmentSlot.AMMO) { + if (equipSlot(getId()) == EquipmentSlot.AMMO || equipSlot(getId()) == EquipmentSlot.NECK || equipSlot(getId()) == EquipmentSlot.RING) { return true; } - if (getName().toLowerCase().startsWith("ring") || getName().toLowerCase().startsWith("amulet")) { - return true; + if (hasAction("summon")) { + return false; } int[] bonuses = getConfiguration(ItemConfigParser.BONUS); return bonuses == null || Arrays.stream(bonuses).allMatch(x -> x == 0); diff --git a/Server/src/main/core/game/activity/Cutscene.kt b/Server/src/main/core/game/activity/Cutscene.kt index 5cf9584ec..a75dcb4b5 100644 --- a/Server/src/main/core/game/activity/Cutscene.kt +++ b/Server/src/main/core/game/activity/Cutscene.kt @@ -166,6 +166,18 @@ abstract class Cutscene(val player: Player) { player.dialogueInterpreter.addAction {_,_ -> onContinue.invoke()} } + /** + * Sends a non-NPC dialogue to the player, which updates the cutscene stage by default when continued + * @param message the message to send + * @param onContinue (optional) a method that runs when the dialogue is "continued." Increments the cutscene stage by default. + */ + fun dialogueLinesUpdate(vararg message: String, onContinue: () -> Unit = {incrementStage()}) + { + logCutscene("Sending standard dialogue lines update.") + sendDialogueLines(player, *message) + player.dialogueInterpreter.addAction {_,_ -> onContinue.invoke()} + } + /** * Sends a player dialogue, which updates the cutscene stage by default when continued * @param expression the FacialExpression to use diff --git a/Server/src/main/core/game/container/impl/BankContainer.java b/Server/src/main/core/game/container/impl/BankContainer.java index 2b1640293..5f8a51215 100644 --- a/Server/src/main/core/game/container/impl/BankContainer.java +++ b/Server/src/main/core/game/container/impl/BankContainer.java @@ -153,17 +153,16 @@ public final class BankContainer extends Container { BankContainer.this.close(); return true; }); - refresh(listener); player.getInterfaceManager().openSingleTab(new Component(763)); - player.getInventory().getListeners().add(player.getBank().listener); + refresh(listener); player.getInventory().refresh(); - setVarp(player, 1249, lastAmountX); + player.getInventory().getListeners().add(player.getBank().listener); + setVarp(player, 1249, lastAmountX); player.getPacketDispatch().sendIfaceSettings(1278, 73, 762, 0, SIZE); int settings = new IfaceSettingsBuilder().enableOptions(new IntRange(0,5)).enableExamine().enableSlotSwitch().build(); player.getPacketDispatch().sendIfaceSettings(settings, 0, 763, 0, 27); player.getPacketDispatch().sendRunScript(1451, ""); open = true; - } /** @@ -281,7 +280,7 @@ public final class BankContainer extends Container { */ public void updateLastAmountX(int amount) { this.lastAmountX = amount; - setVarp(player, 1249, amount); + setVarp(player, 1249, amount); } /** @@ -414,7 +413,7 @@ public final class BankContainer extends Container { * @return If items have to be noted {@code true}. */ public boolean isNoteItems() { - return getVarbit(player, Vars.VARBIT_IFACE_BANK_NOTE_MODE) == 1; + return getVarbit(player, Vars.VARBIT_IFACE_BANK_NOTE_MODE) == 1; } /** @@ -422,7 +421,7 @@ public final class BankContainer extends Container { * @param noteItems If items have to be noted {@code true}. */ public void setNoteItems(boolean noteItems) { - setVarbit(player, Vars.VARBIT_IFACE_BANK_NOTE_MODE, noteItems ? 1 : 0, true); + setVarbit(player, Vars.VARBIT_IFACE_BANK_NOTE_MODE, noteItems ? 1 : 0, true); } /** @@ -456,7 +455,7 @@ public final class BankContainer extends Container { * @param insertItems The insert items value. */ public void setInsertItems(boolean insertItems) { - setVarbit(player, Vars.VARBIT_IFACE_BANK_INSERT_MODE, insertItems ? 1 : 0, true); + setVarbit(player, Vars.VARBIT_IFACE_BANK_INSERT_MODE, insertItems ? 1 : 0, true); } /** @@ -464,7 +463,7 @@ public final class BankContainer extends Container { * @return {@code True} if inserting items mode is enabled. */ public boolean isInsertItems() { - return getVarbit(player, Vars.VARBIT_IFACE_BANK_INSERT_MODE) == 1; + return getVarbit(player, Vars.VARBIT_IFACE_BANK_INSERT_MODE) == 1; } /** @@ -498,22 +497,22 @@ public final class BankContainer extends Container { public void update(Container c, ContainerEvent event) { if (c instanceof BankContainer) { PacketRepository.send(ContainerPacket.class, new ContainerContext(player, 762, 64000, 95, event.getItems(), false, event.getSlots())); + player.getBank().setTabConfigurations(); + player.getBank().sendBankSpace(); } else { PacketRepository.send(ContainerPacket.class, new ContainerContext(player, 763, 64000, 93, event.getItems(), false, event.getSlots())); } - player.getBank().setTabConfigurations(); - player.getBank().sendBankSpace(); } @Override public void refresh(Container c) { if (c instanceof BankContainer) { PacketRepository.send(ContainerPacket.class, new ContainerContext(player, 762, 64000, 95, c.toArray(), c.capacity(), false)); + player.getBank().setTabConfigurations(); + player.getBank().sendBankSpace(); } else { PacketRepository.send(ContainerPacket.class, new ContainerContext(player, 763, 64000, 93, c.toArray(), 28, false)); } - player.getBank().setTabConfigurations(); - player.getBank().sendBankSpace(); } } } diff --git a/Server/src/main/core/game/dialogue/DialogueLabeller.kt b/Server/src/main/core/game/dialogue/DialogueLabeller.kt index 6a48b86ae..8259474a0 100644 --- a/Server/src/main/core/game/dialogue/DialogueLabeller.kt +++ b/Server/src/main/core/game/dialogue/DialogueLabeller.kt @@ -1,9 +1,15 @@ package core.game.dialogue +import core.api.Event import core.api.InputType import core.api.face import core.api.openDialogue import core.api.splitLines +import core.game.component.Component +import core.game.component.Component.setUnclosable +import core.game.event.DialogueCloseEvent +import core.game.event.EventHook +import core.game.node.entity.Entity import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.node.item.Item @@ -73,11 +79,14 @@ abstract class DialogueLabeller : DialogueFile() { /** Implement this function instead of overriding handle. */ abstract fun addConversation() - /** Helper function to create an individual stage for each of the dialogue stages. */ - private fun assignIndividualStage(callback: () -> Unit) { + /** Helper functions to create an individual stage for each of the dialogue stages. */ + private fun assignIndividualStage(callback: () -> Component?, unclosable: Boolean) { if (startingStage == null) { startingStage = 0 } if (stage == dialogueCounter && jumpTo == null) { // Run this stage when the stage equals to the dialogueCounter of this dialogue - callback() // CALLBACK FUNCTION + val component = callback() // CALLBACK FUNCTION + if (unclosable && component != null) { + setUnclosable(player, component) + } super.stage++ // Increment the stage to the next stage (only applies after a pass) stageHit = true // Flag that the stage was hit, so that it doesn't close the dialogue } @@ -131,13 +140,13 @@ abstract class DialogueLabeller : DialogueFile() { } /** Manual stage. For custom creation of an individual stage. Must call interpreter in some form. **/ - fun manual(callback: (player: Player, npc: NPC) -> Unit) { - assignIndividualStage { callback(player!!, npc!!) } + fun manual(unclosable: Boolean = false, callback: (player: Player, npc: NPC) -> Component?) { + assignIndividualStage({ return@assignIndividualStage callback(player!!, npc!!) }, unclosable) } /** Dialogue player/playerl. Shows player chathead with text. **/ - fun player(chatAnim: ChatAnim = ChatAnim.NEUTRAL, vararg messages: String) { - assignIndividualStage { interpreter!!.sendDialogues(player, chatAnim, *formatMessages(messages)) } + fun player(chatAnim: ChatAnim = ChatAnim.NEUTRAL, vararg messages: String, unclosable: Boolean = false) { + assignIndividualStage({ return@assignIndividualStage interpreter!!.sendDialogues(player, chatAnim, *formatMessages(messages)) }, unclosable) } /** Dialogue player/playerl. Shows player chathead with text. **/ fun player(vararg messages: String) { player(ChatAnim.NEUTRAL, *messages) } @@ -147,16 +156,17 @@ abstract class DialogueLabeller : DialogueFile() { fun playerl(vararg messages: String) { throw Exception("Deprecated DialogueLabel: Use player() instead.") } /** Dialogue npc/npcl. Shows npc chathead with text. **/ - fun npc(chatAnim: ChatAnim = ChatAnim.NEUTRAL, vararg messages: String) { - assignIndividualStage { interpreter!!.sendDialogues(npc, chatAnim, *formatMessages(messages)) } + fun npc(chatAnim: ChatAnim = ChatAnim.NEUTRAL, vararg messages: String, unclosable: Boolean = false) { + val callback = callback@{ return@callback interpreter!!.sendDialogues(npc, chatAnim, *formatMessages(messages)) } + assignIndividualStage(callback, unclosable) } /** Dialogue npc/npcl. Shows npcId chathead with text. **/ - fun npc(chatAnim: ChatAnim = ChatAnim.NEUTRAL, npcId: Int = npc!!.id, vararg messages: String) { - assignIndividualStage { interpreter!!.sendDialogues(NPC(npcId), chatAnim, *formatMessages(messages)) } + fun npc(chatAnim: ChatAnim = ChatAnim.NEUTRAL, npcId: Int = npc!!.id, vararg messages: String, unclosable: Boolean = false) { + assignIndividualStage({ return@assignIndividualStage interpreter!!.sendDialogues(NPC(npcId), chatAnim, *formatMessages(messages)) }, unclosable) } /** Dialogue npc/npcl. Shows npcId chathead with text. **/ - fun npc(npcId: Int = npc!!.id, vararg messages: String) { - assignIndividualStage { interpreter!!.sendDialogues(NPC(npcId), ChatAnim.NEUTRAL, *formatMessages(messages)) } + fun npc(npcId: Int = npc!!.id, vararg messages: String, unclosable: Boolean = false) { + assignIndividualStage({ return@assignIndividualStage interpreter!!.sendDialogues(NPC(npcId), ChatAnim.NEUTRAL, *formatMessages(messages)) }, unclosable) } /** Dialogue npc/npcl. Shows npc chathead with text. **/ fun npc(vararg messages: String) { npc(ChatAnim.NEUTRAL, *messages) } @@ -166,40 +176,45 @@ abstract class DialogueLabeller : DialogueFile() { fun npcl(vararg messages: String) { throw Exception("Deprecated DialogueLabel: Use npc() instead.") } /** Dialogue item/iteml. Shows item with text. **/ - fun item(item: Item, vararg messages: String, message: String = "") { - assignIndividualStage { interpreter!!.sendItemMessage(item, *formatMessages(messages, message)) } + fun item(item: Item, vararg messages: String, message: String = "", unclosable: Boolean = false) { + val callback = callback@{ return@callback interpreter!!.sendItemMessage(item, *formatMessages(messages, message)) } + assignIndividualStage(callback, unclosable) } @Deprecated("Use item() instead.", ReplaceWith("item(item, *messages)")) fun iteml(item: Item, vararg messages: String) { throw Exception("Deprecated DialogueLabel: Use item() instead.") } /** Dialogue overloaded doubleItem/doubleIteml. Shows two items with text. **/ - fun item(item: Item, item2: Item, vararg messages: String, message: String = "") { - assignIndividualStage { interpreter!!.sendDoubleItemMessage(item, item2, formatMessages(messages, message).joinToString(" ")) } + fun item(item: Item, item2: Item, vararg messages: String, message: String = "", unclosable: Boolean = false) { + assignIndividualStage({ return@assignIndividualStage interpreter!!.sendDoubleItemMessage(item, item2, *formatMessages(messages, message)) }, unclosable) } /** Dialogue line/linel. Simply shows text. **/ - fun line(vararg messages: String) { - assignIndividualStage { interpreter!!.sendDialogue(*messages) } + fun line(vararg messages: String, unclosable: Boolean = false) { + val callback = callback@{ return@callback interpreter!!.sendDialogue(*messages) } + assignIndividualStage(callback, unclosable) } @Deprecated("Use line() instead.", ReplaceWith("line(*messages)")) fun linel(vararg messages: String) { throw Exception("Deprecated DialogueLabel: Use line() instead.") } /** Dialogue option. Shows the option dialogue with choices for the user to select. **/ - fun options(vararg options: DialogueOption, title: String = "Select an Option") { + fun options(vararg options: DialogueOption, title: String = "Select an Option", unclosable: Boolean = false) { // Filter out options that aren't shown. val filteredOptions = options.filter{ if (it.optionIf != null) { it.optionIf.invoke(player!!, npc!!) } else { true } } // Stage Part 1: Options List Dialogue - assignIndividualStage { interpreter!!.sendOptions(title, *filteredOptions.map{ it.option }.toTypedArray()) } + val callback = callback@{ return@callback interpreter!!.sendOptions(title, *filteredOptions.map{ it.option }.toTypedArray()) } + assignIndividualStage(callback, unclosable) // Stage Part 2: Show spoken text. - var opt = if (buttonID != null && buttonID in 1..filteredOptions.size) { filteredOptions[buttonID!! - 1] } else { null } - assignIndividualStage { + val opt = if (buttonID != null && buttonID in 1..filteredOptions.size) { filteredOptions[buttonID!! - 1] } else { null } + assignIndividualStage({ + var component: Component? = null if (opt?.skipPlayer == true) { jumpTo = stage + 1 } else { - interpreter!!.sendDialogues(player, opt?.expression ?: ChatAnim.NEUTRAL, *(splitLines(opt?.spokenText ?: " "))) + component = interpreter!!.sendDialogues(player, opt?.expression ?: ChatAnim.NEUTRAL, *(splitLines(opt?.spokenText ?: " "))) } optButton = buttonID // transfer the buttonID to a temp memory for the next stage - } + return@assignIndividualStage component + }, unclosable) // Stage Part 3: Jump To goto if (stage == dialogueCounter && optButton != null && optButton in 1..filteredOptions.size) { jumpTo = labelStageMap[filteredOptions[optButton!! - 1].goto] @@ -211,7 +226,7 @@ abstract class DialogueLabeller : DialogueFile() { /** Dialogue input. Shows the input dialogue with an input box for the user to type in. Read [optInput] for the value. **/ fun input(type: InputType, prompt: String = "Enter the amount") { - assignIndividualStage { + assignIndividualStage({ // These are similar to calling sendInputDialogue when (type) { InputType.AMOUNT -> interpreter!!.sendInput(true, prompt) @@ -230,14 +245,31 @@ abstract class DialogueLabeller : DialogueFile() { interpreter!!.handle(player!!.interfaceManager.chatbox.id, 2) } player!!.setAttribute("input-type", type) - } + return@assignIndividualStage null + }, false) } /** Dialogue input. Shows the input dialogue with an input box for the user to type in. Read [optInput] in an [exec] function for the value. **/ fun input(numeric: Boolean, prompt: String = "Enter the amount") { input( if (numeric) { InputType.NUMERIC } else { InputType.STRING_SHORT }, prompt) } + /** Runs arbitrary code when the dialogue closes, once. **/ + fun afterClose(callback: (player: Player) -> Unit) { + val hook = object : EventHook { + override fun process(entity: Entity, event: DialogueCloseEvent) { + val you = entity as Player + you.unhook(this) + callback(you) + } + } + player!!.hook(Event.DialogueClosed, hook) + } + /** Calls another dialogue file. Always use this to open another dialogue file instead of calling openDialogue() in exec{} due to interfaces clashing. **/ fun open(player: Player, dialogue: Any, vararg args: Any) { - assignIndividualStage { core.api.openDialogue(player, dialogue, *args) } + val callback = callback@{ + core.api.openDialogue(player, dialogue, *args) + return@callback null + } + assignIndividualStage(callback, false) } /** WARNING: DIALOGUE LABELLER WILL BREAK IN CERTAIN FUNCTIONS. USE open() instead. */ @@ -269,7 +301,10 @@ abstract class DialogueLabeller : DialogueFile() { break } } - if (!stageHit) { end() } // If a dialogue stage is not hit, end the dialogues. + // If a dialogue stage is not hit, end the dialogue. + if (!stageHit) { + end() + } } } diff --git a/Server/src/main/core/game/dialogue/DialoguePlugin.java b/Server/src/main/core/game/dialogue/DialoguePlugin.java index 635b41c2b..18d17dd37 100644 --- a/Server/src/main/core/game/dialogue/DialoguePlugin.java +++ b/Server/src/main/core/game/dialogue/DialoguePlugin.java @@ -276,8 +276,8 @@ public abstract class DialoguePlugin implements Plugin { * Method used to send options. * @param options the options. */ - public void options(final String... options) { - interpreter.sendOptions("Select an Option", options); + public Component options(final String... options) { + return interpreter.sendOptions("Select an Option", options); } /** diff --git a/Server/src/main/core/game/dialogue/SkillDialogueHandler.kt b/Server/src/main/core/game/dialogue/SkillDialogueHandler.kt index 58e692b23..acc07905c 100644 --- a/Server/src/main/core/game/dialogue/SkillDialogueHandler.kt +++ b/Server/src/main/core/game/dialogue/SkillDialogueHandler.kt @@ -19,25 +19,11 @@ open class SkillDialogueHandler( /** * Represents the skill dialogue type. */ - val type: SkillDialogue?, vararg data: Any) { - /** - * Gets the player. - * @return The player. - */ - - /** - * Gets the type. - * @return The type. - */ - - /** - * Gets the passed data. - * @return the data. - */ - /** - * Represents the object data passed through. - */ - val data: Array + val type: SkillDialogue?, + /** + * The items that can be created through this dialog. + */ + vararg val items: Item) { /** * Method used to open a skill dialogue. @@ -70,7 +56,7 @@ open class SkillDialogueHandler( * @return the amount. */ open fun getAll(index: Int): Int { - return player.inventory.getAmount(data[0] as Item) + return player.inventory.getAmount(items[0]) } /** @@ -107,7 +93,7 @@ open class SkillDialogueHandler( private val length: Int) { ONE_OPTION(309, 5, 1) { override fun display(player: Player, handler: SkillDialogueHandler) { - val item = handler.data[0] as Item + val item = handler.items[0] player.packetDispatch.sendString("



" + item.name, 309, 6) player.packetDispatch.sendItemZoomOnInterface(item.id, 160, 309, 2) PacketRepository.send(RepositionChild::class.java, ChildPositionContext(player, 309, 6, 60, 20)) @@ -125,7 +111,7 @@ open class SkillDialogueHandler( }, MAKE_SET_ONE_OPTION(582, 4, 1) { override fun display(player: Player, handler: SkillDialogueHandler) { - val item = handler.data[0] as Item + val item = handler.items[0] // Send item + item name to interface player.packetDispatch.sendItemZoomOnInterface(item.id, 160, 582, 2) @@ -163,8 +149,8 @@ open class SkillDialogueHandler( override fun display(player: Player, handler: SkillDialogueHandler) { var item: Item player.interfaceManager.openChatbox(306) - for (i in handler.data.indices) { - item = handler.data[i] as Item + for (i in handler.items.indices) { + item = handler.items[i] player.packetDispatch.sendString("



" + handler.getName(item), 303, 7 + i) player.packetDispatch.sendItemZoomOnInterface(item.id, 160, 303, 2 + i) } @@ -190,11 +176,11 @@ open class SkillDialogueHandler( }, THREE_OPTION(304, 8, 3) { override fun display(player: Player, handler: SkillDialogueHandler) { - var item: Item? = null + var item: Item? for (i in 0..2) { - item = handler.data[i] as Item + item = handler.items[i] player.packetDispatch.sendItemZoomOnInterface(item.id, 135, 304, 2 + i) - player.packetDispatch.sendString("



" + item!!.name, 304, 304 - 296 + i * 4) + player.packetDispatch.sendString("



" + item.name, 304, 304 - 296 + i * 4) } } @@ -219,10 +205,10 @@ open class SkillDialogueHandler( }, FOUR_OPTION(305, 9, 4) { override fun display(player: Player, handler: SkillDialogueHandler) { - var item: Item? = null + var item: Item? for (i in 0..3) { - item = handler.data[i] as Item - player.packetDispatch.sendItemZoomOnInterface(item!!.id, 135, 305, 2 + i) + item = handler.items[i] + player.packetDispatch.sendItemZoomOnInterface(item.id, 135, 305, 2 + i) player.packetDispatch.sendString("



" + item.name, 305, 305 - 296 + i * 4) } } @@ -255,8 +241,8 @@ open class SkillDialogueHandler( override fun display(player: Player, handler: SkillDialogueHandler) { var item: Item player.interfaceManager.openChatbox(306) - for (i in handler.data.indices) { - item = handler.data[i] as Item + for (i in handler.items.indices) { + item = handler.items[i] player.packetDispatch.sendString("



" + handler.getName(item), 306, 10 + 4 * i) player.packetDispatch.sendItemZoomOnInterface(item.id, 160, 306, 2 + i) PacketRepository.send(RepositionChild::class.java, ChildPositionContext(player, 306, 2 + i, positions[i][0], positions[i][1])) @@ -361,14 +347,4 @@ open class SkillDialogueHandler( */ const val SKILL_DIALOGUE = 3 shl 16 } - - /** - * Constructs a new `SkillDialogueHandler` `Object`. - * @param player the player. - * @param type the type. - * @param data the data. - */ - init { - this.data = data as Array - } } \ No newline at end of file diff --git a/Server/src/main/core/game/ge/GrandExchange.kt b/Server/src/main/core/game/ge/GrandExchange.kt index 9fba63030..a7ead4d1c 100644 --- a/Server/src/main/core/game/ge/GrandExchange.kt +++ b/Server/src/main/core/game/ge/GrandExchange.kt @@ -6,14 +6,11 @@ import core.cache.def.impl.ItemDefinition import core.game.node.entity.player.Player import core.game.node.entity.player.info.PlayerDetails import core.game.system.command.Privilege -import core.game.system.config.ItemConfigParser import core.game.system.task.Pulse import core.game.world.GameWorld import core.game.world.repository.Repository import core.tools.Log import core.tools.SystemLogger -import core.tools.colorize -import org.rs09.consts.Sounds import java.lang.Integer.max import java.util.concurrent.LinkedBlockingDeque @@ -266,9 +263,6 @@ class GrandExchange : StartupListener, Commands { seller.completedAmount += amount buyer.completedAmount += amount - if(seller.amountLeft < 1 && seller.player != null) - playAudio(seller.player!!, Sounds.GE_COLLECT_COINS_4042) - seller.addWithdrawItem(995, amount * if(sellerBias) buyer.offeredValue else seller.offeredValue) buyer.addWithdrawItem(seller.itemID, amount) @@ -298,12 +292,13 @@ class GrandExchange : StartupListener, Commands { } */ - seller.update() - val sellerPlayer = Repository.uid_map[seller.playerUID] - sellerPlayer?.let { GrandExchangeRecords.getInstance(sellerPlayer).visualizeRecords() } - buyer.update() - val buyerPlayer = Repository.uid_map[buyer.playerUID] - buyerPlayer?.let { GrandExchangeRecords.getInstance(buyerPlayer).visualizeRecords() } + for (entity in arrayOf(buyer, seller)) { + entity.update() + val player = Repository.uid_map[entity.playerUID] ?: continue + val records = GrandExchangeRecords.getInstance(player) + records.visualizeRecords() + records.updateNotification = true + } } private fun canUpdatePriceIndex(seller: GrandExchangeOffer, buyer: GrandExchangeOffer): Boolean { diff --git a/Server/src/main/core/game/ge/GrandExchangeRecords.kt b/Server/src/main/core/game/ge/GrandExchangeRecords.kt index 63aa20848..e970c01bb 100644 --- a/Server/src/main/core/game/ge/GrandExchangeRecords.kt +++ b/Server/src/main/core/game/ge/GrandExchangeRecords.kt @@ -22,6 +22,7 @@ import java.util.* class GrandExchangeRecords(private val player: Player? = null) : PersistPlayer, LoginListener { var history = arrayOfNulls(5) val offerRecords = arrayOfNulls(6) + var updateNotification = false override fun login(player: Player) { val instance = GrandExchangeRecords(player) diff --git a/Server/src/main/core/game/ge/GrandExchangeTimer.kt b/Server/src/main/core/game/ge/GrandExchangeTimer.kt new file mode 100644 index 000000000..2e68ca5f4 --- /dev/null +++ b/Server/src/main/core/game/ge/GrandExchangeTimer.kt @@ -0,0 +1,24 @@ +package core.game.ge + +import core.api.hasAwaitingGrandExchangeCollections +import core.api.playJingle +import core.api.sendMessage +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import core.game.system.timer.RSTimer + +class GrandExchangeTimer : RSTimer(500, "GE periodic poll", isSoft = true, isAuto = true) { + override fun run(entity: Entity) : Boolean { + if (entity !is Player) return false + val player = entity + val records = GrandExchangeRecords.getInstance(player) + if (records.updateNotification) { + records.updateNotification = false + if (hasAwaitingGrandExchangeCollections(player)) { + sendMessage(player, "One or more of your Grand Exchange offers have been updated.") + playJingle(player, 284) + } + } + return true + } +} diff --git a/Server/src/main/core/game/interaction/InteractionListeners.kt b/Server/src/main/core/game/interaction/InteractionListeners.kt index c28113500..e3cab3e34 100644 --- a/Server/src/main/core/game/interaction/InteractionListeners.kt +++ b/Server/src/main/core/game/interaction/InteractionListeners.kt @@ -164,6 +164,7 @@ object InteractionListeners { @JvmStatic fun run(id: Int, player: Player, node: Node, isEquip: Boolean): Boolean{ + player.scripts.removeWeakScripts() player.dispatch(InteractionEvent(node, if(isEquip) "equip" else "unequip")) if(isEquip){ return equipListeners["equip:$id"]?.invoke(player,node) ?: true @@ -174,6 +175,7 @@ object InteractionListeners { @JvmStatic fun run(used: Node, with: Node, type: IntType, player: Player): Boolean{ + player.scripts.removeWeakScripts() val flag = when(type){ IntType.NPC, IntType.PLAYER -> DestinationFlag.ENTITY IntType.SCENERY -> DestinationFlag.OBJECT @@ -224,6 +226,8 @@ object InteractionListeners { @JvmStatic fun run(id: Int, type: IntType, option: String, player: Player, node: Node): Boolean{ + player.scripts.removeWeakScripts() + val flag = when(type){ IntType.PLAYER -> DestinationFlag.ENTITY IntType.GROUNDITEM -> DestinationFlag.ITEM diff --git a/Server/src/main/core/game/interaction/MovementPulse.java b/Server/src/main/core/game/interaction/MovementPulse.java index 04c9d2a8d..3eb7c43bd 100644 --- a/Server/src/main/core/game/interaction/MovementPulse.java +++ b/Server/src/main/core/game/interaction/MovementPulse.java @@ -11,6 +11,7 @@ import core.game.world.GameWorld; import core.game.world.map.Direction; import core.game.world.map.Location; import core.game.world.map.Point; +import core.game.world.map.RegionManager; import core.game.world.map.path.Path; import core.game.world.map.path.Pathfinder; import core.net.packet.PacketRepository; @@ -380,44 +381,49 @@ public abstract class MovementPulse extends Pulse { return canMove; } - private Location checkForEntityPathInterrupt(Location loc) { - Location ml = mover.getLocation(); - Location dl = destination.getLocation(); - // Lead the target if they're walking/running, unless they're already within interaction range - if(loc != null && destination instanceof Entity) { - WalkingQueue wq = ((Entity)destination).getWalkingQueue(); - if(wq.hasPath()) { - Point[] points = wq.getQueue().toArray(new Point[0]); - if(points.length > 0) { - Point p = points[0]; - Point predictiveIntersection = null; - for(int i=0; i 0) { + Point p = points[0]; + Point predictiveIntersection = null; + for (int i = 0; i < points.length; i++) { + Location closestBorder = getClosestBorderToPoint(points[i], loc.getZ()); - int moverDist = Math.max(Math.abs(ml.getX() - closestBorder.getX()), Math.abs(ml.getY() - closestBorder.getY())); - float movementRatio = moverDist / (float) ((i + 1) / (mover.getWalkingQueue().isRunning() ? 2 : 1)); - if (predictiveIntersection == null && movementRatio <= 1.0) { //try to predict an intersection point on the path if possible - predictiveIntersection = points[i]; - break; - } + if (!RegionManager.isTeleportPermitted(closestBorder)) { // A nasty hack to discard invalid intersection points + continue; + } + int moverDist = Math.max(Math.abs(ml.getX() - closestBorder.getX()), Math.abs(ml.getY() - closestBorder.getY())); + float movementRatio = moverDist / (float) ((i + 1) / (mover.getWalkingQueue().isRunning() ? 2 : 1)); + if (predictiveIntersection == null && movementRatio <= 1.0) { //try to predict an intersection point on the path if possible + predictiveIntersection = points[i]; + break; + } + // Otherwise, we target the farthest point along target's planned movement that's within 1 tick's running, + // this ensures the player will run to catch up to the target if able. + if (moverDist <= 2) { + p = points[i]; + } + } + if (predictiveIntersection != null) + p = predictiveIntersection; - // Otherwise, we target the farthest point along target's planned movement that's within 1 tick's running, - // this ensures the player will run to catch up to the target if able. - if(moverDist <= 2) { - p = points[i]; - } - } + Location endLoc = getClosestBorderToPoint(p, loc.getZ()); - if (predictiveIntersection != null) - p = predictiveIntersection; - - Location endLoc = getClosestBorderToPoint(p, loc.getZ()); - return endLoc; - } - } - } - return loc; - } + if (!RegionManager.isTeleportPermitted(endLoc)) { // Basically a prayer + return loc; + } + return endLoc; + } + } + } + return loc; + } private Location getClosestBorderToPoint (Point p, int plane) { Vector pathDiff = Vector.betweenLocs (destination.getLocation(), Location.create(p.getX(), p.getY(), plane)); diff --git a/Server/src/main/core/game/interaction/ScriptProcessor.kt b/Server/src/main/core/game/interaction/ScriptProcessor.kt index 52c86b1e8..2c638f35f 100644 --- a/Server/src/main/core/game/interaction/ScriptProcessor.kt +++ b/Server/src/main/core/game/interaction/ScriptProcessor.kt @@ -1,6 +1,7 @@ package core.game.interaction import core.api.* +import core.game.bots.AIPlayer import core.game.node.Node import core.game.node.entity.Entity import core.game.node.entity.npc.NPC @@ -10,10 +11,10 @@ import core.game.node.scenery.Scenery import core.game.world.GameWorld import core.game.world.map.Location import core.game.world.map.path.Pathfinder -import core.game.bots.AIPlayer import core.tools.Log +import java.io.PrintWriter +import java.io.StringWriter import java.lang.Integer.max -import java.io.* class ScriptProcessor(val entity: Entity) { private var apScript: Script<*>? = null @@ -90,11 +91,8 @@ class ScriptProcessor(val entity: Entity) { } fun processQueue() : Boolean { - var strongInQueue = false - var softInQueue = false var anyExecuted = false - strongInQueue = hasTypeInQueue(QueueStrength.STRONG) - softInQueue = hasTypeInQueue(QueueStrength.SOFT) + val strongInQueue = hasTypeInQueue(QueueStrength.STRONG) if (strongInQueue) { if (entity is Player) { diff --git a/Server/src/main/core/game/node/entity/Entity.java b/Server/src/main/core/game/node/entity/Entity.java index b566cccb0..9699be83d 100644 --- a/Server/src/main/core/game/node/entity/Entity.java +++ b/Server/src/main/core/game/node/entity/Entity.java @@ -78,7 +78,7 @@ public abstract class Entity extends Node { /** * The pulse manager. */ - private final PulseManager pulseManager = new PulseManager(); + private final PulseManager pulseManager = new PulseManager(this); /** * The impact handler. diff --git a/Server/src/main/core/game/node/entity/combat/graves/Grave.kt b/Server/src/main/core/game/node/entity/combat/graves/Grave.kt index 2118ab7dc..e330489a9 100644 --- a/Server/src/main/core/game/node/entity/combat/graves/Grave.kt +++ b/Server/src/main/core/game/node/entity/combat/graves/Grave.kt @@ -1,5 +1,6 @@ package core.game.node.entity.combat.graves +import content.global.handlers.item.equipment.BarrowsEquipment import core.api.clearHintIcon import core.api.registerHintIcon import core.api.sendMessage @@ -65,7 +66,11 @@ class Grave : AbstractNPC { continue } - val finalItem = GraveController.checkTransform(item) + val finalItem = if (BarrowsEquipment.isBarrowsItem(item.id) && !BarrowsEquipment.isFullyRepaired(item.id)) { + BarrowsEquipment.graveDeathDurabilityReduction(item) ?: item + } else { + GraveController.checkTransform(item) + } val gi = GroundItemManager.create(finalItem, this.location, player) gi.isRemainPrivate = true diff --git a/Server/src/main/core/game/node/entity/impl/Animator.java b/Server/src/main/core/game/node/entity/impl/Animator.java index 3afafb41b..430c4d551 100644 --- a/Server/src/main/core/game/node/entity/impl/Animator.java +++ b/Server/src/main/core/game/node/entity/impl/Animator.java @@ -2,7 +2,6 @@ package core.game.node.entity.impl; import core.game.interaction.Clocks; import core.game.node.entity.Entity; -import core.game.node.entity.npc.NPC; import core.game.world.GameWorld; import core.game.world.update.flag.context.Animation; import core.game.world.update.flag.context.Graphics; diff --git a/Server/src/main/core/game/node/entity/impl/PulseManager.java b/Server/src/main/core/game/node/entity/impl/PulseManager.java index 74a78e7cc..6f93e1250 100644 --- a/Server/src/main/core/game/node/entity/impl/PulseManager.java +++ b/Server/src/main/core/game/node/entity/impl/PulseManager.java @@ -17,6 +17,12 @@ import java.util.HashMap; */ public final class PulseManager { + private final Entity entity; + + public PulseManager(Entity entity) { + this.entity = entity; + } + /** * The movement pulse. */ @@ -61,6 +67,9 @@ public final class PulseManager { } public void clear() { + entity.scripts.removeWeakScripts(); + entity.scripts.removeNormalScripts(); + currentPulses.forEach((type, pulse) -> { if (type != PulseType.STRONG && pulse != null) pulse.stop(); }); @@ -70,6 +79,9 @@ public final class PulseManager { * Clears the pulses. */ public boolean clear(PulseType pulseType) { + entity.scripts.removeWeakScripts(); + entity.scripts.removeNormalScripts(); + Pulse pulse = currentPulses.get(pulseType); if (pulse != null) { diff --git a/Server/src/main/core/game/node/entity/npc/NPC.java b/Server/src/main/core/game/node/entity/npc/NPC.java index 8c2600642..2ce5481cc 100644 --- a/Server/src/main/core/game/node/entity/npc/NPC.java +++ b/Server/src/main/core/game/node/entity/npc/NPC.java @@ -476,7 +476,6 @@ public class NPC extends Entity { public boolean pulse() { getProperties().getCombatPulse().stop(); getLocks().unlockMovement(); - fullRestore(); getImpactHandler().setDisabledTicks(0); removeAttribute("return-to-spawn"); removeAttribute("return-to-spawn-pulse"); diff --git a/Server/src/main/core/game/node/entity/player/Player.java b/Server/src/main/core/game/node/entity/player/Player.java index 1592d0b0e..dfe357ff0 100644 --- a/Server/src/main/core/game/node/entity/player/Player.java +++ b/Server/src/main/core/game/node/entity/player/Player.java @@ -1,12 +1,11 @@ package core.game.node.entity.player; +import content.global.handlers.item.equipment.BarrowsEquipment; import content.global.handlers.item.equipment.special.SalamanderSwingHandler; import content.global.skill.runecrafting.PouchManager; import core.api.ContentAPIKt; -import core.api.EquipmentSlot; import core.game.component.Component; import core.game.container.Container; -import core.game.container.ContainerType; import core.game.container.impl.BankContainer; import core.game.container.impl.EquipmentContainer; import core.game.container.impl.InventoryListener; @@ -467,15 +466,13 @@ public class Player extends Entity { // Check if the player is on the map, runs only every 6 seconds for performance reasons. // This is only a sanity check to detect improper usage of the 'original-loc' attribute, hence only do this work if the attribute is set. - // Only runs when the player is not movement/interaction-locked, so that original-loc does not get wiped e.g. in the middle of the player teleporting to their POH. - if (GameWorld.getTicks() % 10 == 0 && !getLocks().isMovementLocked() && !getLocks().isInteractionLocked()) { - if (ContentAPIKt.getAttribute(this, "/save:original-loc", null) != null) { - int rid = location.getRegionId(); - Region r = RegionManager.forId(rid); - if (!(r instanceof DynamicRegion) && !getZoneMonitor().isRestricted(ZoneRestriction.OFF_MAP)) { - log(this.getClass(), Log.ERR, "Player " + getUsername() + " has the original-loc attribute set but isn't actually off-map! This indicates a bug in the code that set that attribute. The original-loc is " + getAttribute("/save:original-loc") + ", the current region is " + rid + ". Good luck debugging!"); - ContentAPIKt.removeAttribute(this, "original-loc"); - } + // Does not run if the player is currently in the middle of a kidnap sequence (to avoid false positives while the teleport is taking place). + if (GameWorld.getTicks() % 10 == 0 && ContentAPIKt.getAttribute(this, "/save:original-loc", null) != null && !ContentAPIKt.getAttribute(this, "kidnapped-by-random", false)) { + int rid = location.getRegionId(); + Region r = RegionManager.forId(rid); + if (!(r instanceof DynamicRegion) && !getZoneMonitor().isRestricted(ZoneRestriction.OFF_MAP)) { + log(this.getClass(), Log.ERR, "Player " + getUsername() + " has the original-loc attribute set but isn't actually off-map! This indicates a bug in the code that set that attribute. The original-loc is " + getAttribute("/save:original-loc") + ", the current region is " + rid + ". Good luck debugging!"); + ContentAPIKt.removeAttribute(this, "original-loc"); } } } @@ -617,13 +614,8 @@ public class Player extends Entity { if (this.isArtificial() && killer instanceof NPC) { return; } - if (killer instanceof Player && killer.getName() != getName()) { // the latter happens if you died via typeless damage from an external cause, e.g. bugs in a dark cave without a light source - long unixSeconds = System.currentTimeMillis() / 1000L; - if (unixSeconds - killer.getAttribute("/save:last-murder-news", 0L) >= 300) { - Item wep = getItemFromEquipment((Player) killer, EquipmentSlot.WEAPON); - sendNews(killer.getUsername() + " has murdered " + getUsername() + " with " + (wep == null ? "their fists." : (StringUtils.isPlusN(wep.getName()) ? "an " : "a ") + wep.getName())); - killer.setAttribute("/save:last-murder-news", unixSeconds); - } + if (killer instanceof Player && !Objects.equals(killer.getName(), getName())) { // the latter happens if you died via typeless damage from an external cause, e.g. bugs in a dark cave without a light source + ContentAPIKt.sendMessage((Player) killer, "You have defeated " + getUsername() + "."); } getPacketDispatch().sendMessage("Oh dear, you are dead!"); incrementAttribute("/save:"+STATS_BASE+":"+STATS_DEATHS); @@ -661,11 +653,13 @@ public class Player extends Entity { if (item == null) continue; if (killer instanceof Player) itemsLost.append(getItemName(item.getId())).append("(").append(item.getAmount()).append("), "); + if (GraveController.shouldCrumble(item.getId())) continue; if (GraveController.shouldRelease(item.getId())) continue; - if (!item.getDefinition().isTradeable()) { + + if (!BarrowsEquipment.isBarrowsItem(item.getId()) && !item.getDefinition().isTradeable()) { if (killer instanceof Player) { int value = item.getDefinition().getAlchemyValue(true); if (getStatLevel(killer, Skills.MAGIC) < 55) value /= 2; @@ -673,7 +667,14 @@ public class Player extends Entity { continue; } else stayPrivate = true; } - item = GraveController.checkTransform(item); + if (BarrowsEquipment.isBarrowsItem(item.getId())) { + if (!BarrowsEquipment.isBroken(item.getId())) { + int brokenItemId = Objects.requireNonNull(BarrowsEquipment.getDefinition(item.getId())).getBrokenId(); + item = new Item(brokenItemId, item.getAmount()); + } + } else { + item = GraveController.checkTransform(item); + } GroundItem gi = GroundItemManager.create(item, location, killer instanceof Player ? (Player) killer : this); gi.setRemainPrivate(stayPrivate); } diff --git a/Server/src/main/core/game/node/entity/player/info/Rights.java b/Server/src/main/core/game/node/entity/player/info/Rights.java index c50de0e95..95009c23a 100644 --- a/Server/src/main/core/game/node/entity/player/info/Rights.java +++ b/Server/src/main/core/game/node/entity/player/info/Rights.java @@ -25,11 +25,11 @@ public enum Rights { if (c != Rights.REGULAR_PLAYER && c != null) { return c.toInteger(); } - if (ServerConstants.IRONMAN_ICONS) { - if (player.getIronmanManager().isIronman()) { - return player.getIronmanManager().getMode().getIcon(); - } - } + if (ServerConstants.IRONMAN) { + if (player.getIronmanManager().isIronman()) { + return player.getIronmanManager().getMode().getIcon(); + } + } return 0; } diff --git a/Server/src/main/core/game/node/entity/player/info/login/LoginParser.kt b/Server/src/main/core/game/node/entity/player/info/login/LoginParser.kt index 75eb6e4c6..649db34a5 100644 --- a/Server/src/main/core/game/node/entity/player/info/login/LoginParser.kt +++ b/Server/src/main/core/game/node/entity/player/info/login/LoginParser.kt @@ -41,6 +41,7 @@ class LoginParser(val details: PlayerDetails) { loginListeners.forEach(Consumer { listener: LoginListener -> listener.login(player) }) //Run our login hooks parser.runContentHooks() //Run our saved-content-parsing hooks player.details.session.setObject(player) + player.getDetails().accountInfo.lastUsedIp = player.getDetails().getIpAddress() if (reconnect) { reconnect(player) } else { diff --git a/Server/src/main/core/game/node/entity/player/info/login/PlayerSaveParser.kt b/Server/src/main/core/game/node/entity/player/info/login/PlayerSaveParser.kt index 03fc75d67..028e8121d 100644 --- a/Server/src/main/core/game/node/entity/player/info/login/PlayerSaveParser.kt +++ b/Server/src/main/core/game/node/entity/player/info/login/PlayerSaveParser.kt @@ -330,9 +330,6 @@ class PlayerSaveParser(val player: Player) { player.skills.parse(skillData) player.skills.experienceGained = saveFile!!["totalEXP"].toString().toDouble() player.skills.experienceMultiplier = saveFile!!["exp_multiplier"].toString().toDouble() - if (GameWorld.settings?.default_xp_rate != 5.0) { - player.skills.experienceMultiplier = GameWorld.settings?.default_xp_rate!! - } val divisor: Double if(player.skills.experienceMultiplier >= 10 && !player.attributes.containsKey("permadeath")){ //exclude permadeath HCIMs from XP squish divisor = player.skills.experienceMultiplier / 5.0 diff --git a/Server/src/main/core/game/node/entity/player/info/login/PlayerSaver.kt b/Server/src/main/core/game/node/entity/player/info/login/PlayerSaver.kt index 11c15b4d8..470818396 100644 --- a/Server/src/main/core/game/node/entity/player/info/login/PlayerSaver.kt +++ b/Server/src/main/core/game/node/entity/player/info/login/PlayerSaver.kt @@ -322,7 +322,6 @@ class PlayerSaver (val player: Player){ fun saveGlobalData(root: JSONObject){ val globalData = JSONObject() - globalData.put("tutorialStage",player.savedData.globalData.tutorialStage.toString()) globalData.put("homeTeleportDelay",player.savedData.globalData.homeTeleportDelay.toString()) globalData.put("lumbridgeRope",player.savedData.globalData.hasTiedLumbridgeRope()) globalData.put("apprentice",player.savedData.globalData.hasSpokenToApprentice()) diff --git a/Server/src/main/core/game/node/entity/player/link/GlobalData.java b/Server/src/main/core/game/node/entity/player/link/GlobalData.java index 29878c2f6..62873b245 100644 --- a/Server/src/main/core/game/node/entity/player/link/GlobalData.java +++ b/Server/src/main/core/game/node/entity/player/link/GlobalData.java @@ -10,12 +10,6 @@ import org.json.simple.JSONObject; * @author 'Vexia */ public final class GlobalData { - - /** - * Represents the tutorial stage. - */ - private int tutorialStage; - /** * Represents the home teleport delay. */ @@ -298,7 +292,6 @@ public final class GlobalData { private boolean macroDisabled = false; public void parse(JSONObject data){ - tutorialStage = Integer.parseInt( data.get("tutorialStage").toString()); homeTeleportDelay = Long.parseLong(data.get("homeTeleportDelay").toString()); lumbridgeRope = (boolean) data.get("lumbridgeRope"); apprentice = (boolean) data.get("apprentice"); @@ -467,22 +460,6 @@ public final class GlobalData { playerTestStage = stage; } - /** - * Gets the tutorialStage. - * @return The tutorialStage. - */ - public int getTutorialStage() { - return tutorialStage; - } - - /** - * Sets the tutorialStage. - * @param tutorialStage The tutorialStage to set. - */ - public void setTutorialStage(int tutorialStage) { - this.tutorialStage = tutorialStage; - } - /** * Gets the homeTeleportDelay. * @return The homeTeleportDelay. diff --git a/Server/src/main/core/game/node/entity/player/link/IronmanMode.java b/Server/src/main/core/game/node/entity/player/link/IronmanMode.java index 2573b3fe4..3aa5e2a7a 100644 --- a/Server/src/main/core/game/node/entity/player/link/IronmanMode.java +++ b/Server/src/main/core/game/node/entity/player/link/IronmanMode.java @@ -5,7 +5,7 @@ package core.game.node.entity.player.link; * @author Vexia */ public enum IronmanMode { - // HARDCORE_DEAD has to be before Ultimate so that it does not adopt it's restrictions (on the basis of >= in IronmanManager.java?) + // HARDCORE_DEAD has to be before Ultimate so that it does not adopt its restrictions (on the basis of >= in IronmanManager.java) NONE(-1), STANDARD(5), HARDCORE(6), ULTIMATE(7); /** diff --git a/Server/src/main/core/game/node/entity/player/link/TeleportManager.java b/Server/src/main/core/game/node/entity/player/link/TeleportManager.java index 140ecf405..3da78d55b 100644 --- a/Server/src/main/core/game/node/entity/player/link/TeleportManager.java +++ b/Server/src/main/core/game/node/entity/player/link/TeleportManager.java @@ -26,16 +26,6 @@ public class TeleportManager { */ public static final int WILDY_TELEPORT = 1 << 16 | 8; - /** - * The animations used in the home teleport. - */ - private final static int[] HOME_ANIMATIONS = {1722, 1723, 1724, 1725, 2798, 2799, 2800, 3195, 4643, 4645, 4646, 4847, 4848, 4849, 4850, 4851, 4852, 65535}; - - /** - * The graphics used in the home teleport. - */ - private final static int[] HOME_GRAPHICS = {775, 800, 801, 802, 803, 804, 1703, 1704, 1705, 1706, 1707, 1708, 1709, 1710, 1711, 1712, 1713, 65535}; - /** * The entity being handled. */ @@ -62,7 +52,6 @@ public class TeleportManager { */ public TeleportManager(Entity entity) { this.entity = entity; - lastTeleport = TeleportType.HOME.getPulse(entity, ServerConstants.HOME_LOCATION); } /** @@ -116,16 +105,11 @@ public class TeleportManager { } this.teleportType = teleportType; entity.getWalkingQueue().reset(); - lastTeleport = currentTeleport; currentTeleport = type.getPulse(entity, location); entity.getPulseManager().clear(); - if (type == TeleportType.HOME) { - entity.getPulseManager().run(type.getPulse(entity, location)); - } else { - entity.lock(12); - entity.getImpactHandler().setDisabledTicks(teleportType == -1 ? 5 : 12); - GameWorld.getPulser().submit(currentTeleport); - } + entity.lock(12); + entity.getImpactHandler().setDisabledTicks(teleportType == -1 ? 5 : 12); + GameWorld.getPulser().submit(currentTeleport); if (entity instanceof Player) { ((Player) entity).getInterfaceManager().close(); } @@ -135,7 +119,7 @@ public class TeleportManager { /** * Fires a random event. * @param entity The entity teleporting. - * @param location The destination lcoation. + * @param location The destination location. */ public static void fireRandom(Entity entity, Location location) { if (entity instanceof Player && entity.getTeleporter().getTeleportType() == 0) { @@ -143,22 +127,6 @@ public class TeleportManager { } } - /** - * Get the home teleport audio based on tick count. - * @param count - */ - private static int getAudio(int count){ - switch(count){ - case 0: - return 193; - case 4: - return 194; - case 11: - return 195; - } - return -1; - } - /** * Gets the entity. * @return the Entity @@ -167,14 +135,6 @@ public class TeleportManager { return entity; } - /** - * Gets the last teleport pulse. - * @return the Pulse - */ - public final Pulse getLastTeleport() { - return lastTeleport; - } - /** * Gets the current teleport pulse. * @return the Pulse @@ -183,7 +143,6 @@ public class TeleportManager { return currentTeleport; } - /** * Represents a NodeType for Teleporter * @author SonicForce41 @@ -330,57 +289,6 @@ public class TeleportManager { }; } }, - HOME(new TeleportSettings(4847, 4857, 800, 804)) { - @Override - public Pulse getPulse(final Entity entity, final Location location) { - return new TeleportPulse(entity) { - int count; - Player player; - - @Override - public boolean pulse() { - switch (count) { - case 18: - player.getProperties().setTeleportLocation(location); - return true; - default: - playGlobalAudio(entity.getLocation(), getAudio(count)); - player.getPacketDispatch().sendGraphic(HOME_GRAPHICS[count]); - player.getPacketDispatch().sendAnimation(HOME_ANIMATIONS[count]); - break; - } - count++; - return false; - } - @Override - public void start() { - player = ((Player) entity); - /*if (player.getSavedData().getGlobalData().getHomeTeleportDelay() > System.currentTimeMillis() && !player.isDonator()) { - long milliseconds = player.getSavedData().getGlobalData().getHomeTeleportDelay() - System.currentTimeMillis(); - int minutes = (int) Math.round(milliseconds / 120000); - if (minutes > 15) { - player.getSavedData().getGlobalData().setHomeTeleportDelay(System.currentTimeMillis() + 1200000); - milliseconds = player.getSavedData().getGlobalData().getHomeTeleportDelay() - System.currentTimeMillis(); - minutes = (int) Math.round(milliseconds / 120000); - } - if (minutes != 0) { - player.getPacketDispatch().sendMessage("You need to wait another " + minutes + " " + (minutes == 1 ? "minute" : "minutes") + " to cast this spell."); - stop(); - return; - } - }*/ - super.start(); - } - - @Override - public void stop() { - super.stop(); - entity.getAnimator().forceAnimation(new Animation(-1)); - player.graphics(new Graphics(-1)); - } - }; - } - }, OBELISK(new TeleportSettings(8939, 8941, 661, -1)) { @Override public Pulse getPulse(final Entity entity, final Location location) { diff --git a/Server/src/main/core/game/node/entity/skill/Skills.java b/Server/src/main/core/game/node/entity/skill/Skills.java index 19debcc0e..c545ed949 100644 --- a/Server/src/main/core/game/node/entity/skill/Skills.java +++ b/Server/src/main/core/game/node/entity/skill/Skills.java @@ -41,7 +41,7 @@ public final class Skills { /** * Represents the constant modifier of experience. */ - public double experienceMultiplier = 5.0; + public double experienceMultiplier = 1.0; /** * The maximum experience multiplier. @@ -363,9 +363,6 @@ public final class Skills { int staticLevel = getStaticLevel(i); setLevel(i, staticLevel); } - if (entity instanceof Player) { - playAudio(entity.asPlayer(), Sounds.PRAYER_RECHARGE_2674); - } rechargePrayerPoints(); } @@ -397,6 +394,7 @@ public final class Skills { } public void correct(double divisor){ + // XP squish for legacy x20 accounts for(int i = 0; i < staticLevels.length; i++){ experience[i] /= divisor; staticLevels[i] = getStaticLevelByExperience(i); diff --git a/Server/src/main/core/game/shops/Shop.kt b/Server/src/main/core/game/shops/Shop.kt index d19a4c2ec..1d041cf8a 100644 --- a/Server/src/main/core/game/shops/Shop.kt +++ b/Server/src/main/core/game/shops/Shop.kt @@ -167,7 +167,12 @@ class Shop(val title: String, val stock: Array, val general: Boolean = Items.TOKKUL_6529 -> item.definition.getConfiguration(ItemConfigParser.TOKKUL_PRICE, 1) Items.ARCHERY_TICKET_1464 -> item.definition.getConfiguration(ItemConfigParser.ARCHERY_TICKET_PRICE, 1) Items.CASTLE_WARS_TICKET_4067 -> item.definition.getConfiguration(ItemConfigParser.CASTLE_WARS_TICKET_PRICE, 1) - else -> getGPCost(Item(item.id, 1), if (isMainStock) stock[item.slot].amount else playerStock[slot].amount, if (isMainStock) item.amount else playerStock[slot].amount) + else -> { + val fixedPrice = fixedPriceItems[item.id] + fixedPrice ?: getGPCost(Item(item.id, 1), + if (isMainStock) stock[item.slot].amount else playerStock[slot].amount, + if (isMainStock) item.amount else playerStock[slot].amount) + } } return Item(currency, price) @@ -210,13 +215,16 @@ class Shop(val title: String, val stock: Array, val general: Boolean = } } - val price = when(currency) - { - Items.TOKKUL_6529 -> (item.definition.getConfiguration(ItemConfigParser.TOKKUL_PRICE, 1) / 10.0).toInt() // selling items authentically return 10x less tokkul (floored/truncated) than the item's shop price - Items.ARCHERY_TICKET_1464 -> item.definition.getConfiguration(ItemConfigParser.ARCHERY_TICKET_PRICE, 1) - Items.CASTLE_WARS_TICKET_4067 -> item.definition.getConfiguration(ItemConfigParser.CASTLE_WARS_TICKET_PRICE, 1) - else -> getGPSell(Item(shopItemId, 1), stockAmt, currentAmt) - } + val price = when (currency) { + // selling items authentically return 10x less tokkul (floored/truncated) than the item's shop price + Items.TOKKUL_6529 -> (item.definition.getConfiguration(ItemConfigParser.TOKKUL_PRICE, 1) / 10.0).toInt() + Items.ARCHERY_TICKET_1464 -> item.definition.getConfiguration(ItemConfigParser.ARCHERY_TICKET_PRICE, 1) + Items.CASTLE_WARS_TICKET_4067 -> item.definition.getConfiguration(ItemConfigParser.CASTLE_WARS_TICKET_PRICE, 1) + else -> { + val fixedPrice = fixedPriceItems[shopItemId] + fixedPrice ?: getGPSell(Item(shopItemId, 1), stockAmt, currentAmt) + } + } if(!general && stockAmt == 0 && shopSlot == -1) { @@ -293,10 +301,17 @@ class Shop(val title: String, val stock: Array, val general: Boolean = if(cost.id == -1) sendMessage(player, "This shop cannot sell that item.").also { return TransactionStatus.Failure("Shop cannot sell this item") } if(currency == Items.COINS_995){ - var amt = item.amount - var inStockAmt = inStock.amount - while(amt-- > 1) - cost.amount += getGPCost(Item(item.id, 1), if (isMainStock) stock[slot].amount else playerStock[slot].amount, --inStockAmt) + val fixedPrice = fixedPriceItems[item.id] + if (fixedPrice != null) { + // Fixed price items: simple multiplication + cost.amount = fixedPrice * item.amount + } else { + // Dynamic pricing: calculate cost for each item as stock depletes + var amt = item.amount + var inStockAmt = inStock.amount + while(amt-- > 1) + cost.amount += getGPCost(Item(item.id, 1), if (isMainStock) stock[slot].amount else playerStock[slot].amount, --inStockAmt) + } } else { cost.amount = cost.amount * item.amount } @@ -379,7 +394,10 @@ class Shop(val title: String, val stock: Array, val general: Boolean = return TransactionStatus.Failure("Attempt to sell to full shop.") } - if(currency == Items.COINS_995 && item.amount > 1){ + val fixedPrice = fixedPriceItems[id] + if (fixedPrice != null) { + profit.amount = fixedPrice * item.amount + } else if(currency == Items.COINS_995 && item.amount > 1){ var amt = item.amount var inStockAmt = container!![shopSlot]?.amount ?: playerStock.getAmount(id) while(amt-- > 1) @@ -446,11 +464,17 @@ class Shop(val title: String, val stock: Array, val general: Boolean = return Pair(isPlayerStock, shopSlot) } - companion object { - //General stores globally share player stock (weird quirk, right?) - val generalPlayerStock = Container(40, ContainerType.SHOP) - val listenerInstances = HashMap() - } + companion object { + //General stores globally share player stock (weird quirk, right?) + val generalPlayerStock = Container(40, ContainerType.SHOP) + val listenerInstances = HashMap() + + // Items with fixed prices that don't fluctuate based on stock (both buy & sell price) + val fixedPriceItems = mapOf( + Items.SPIRIT_SHARDS_12183 to 25, + Items.POUCH_12155 to 1 + ) + } sealed class TransactionStatus { class Success : TransactionStatus() diff --git a/Server/src/main/core/game/shops/Shops.kt b/Server/src/main/core/game/shops/Shops.kt index 0a649a891..b87f82d21 100644 --- a/Server/src/main/core/game/shops/Shops.kt +++ b/Server/src/main/core/game/shops/Shops.kt @@ -68,7 +68,8 @@ class Shops : StartupListener, TickListener, InteractionListener, InterfaceListe return@map }} } else { - items.add(ShopItem(item, amount.toInt(), tokens.getOrNull(2)?.toIntOrNull() ?: 100)) + val restockRate = tokens.getOrNull(2)?.toIntOrNull() ?: 100 + items.add(ShopItem(item, amount.toInt(), restockRate)) idsInStock[item] = true } } catch (e: Exception) { diff --git a/Server/src/main/core/game/system/command/Command.kt b/Server/src/main/core/game/system/command/Command.kt index fdbc56d90..8fc40e222 100644 --- a/Server/src/main/core/game/system/command/Command.kt +++ b/Server/src/main/core/game/system/command/Command.kt @@ -2,6 +2,7 @@ package core.game.system.command import core.game.node.entity.player.Player import core.ServerConstants +import core.game.node.entity.player.info.Rights import core.game.world.GameWorld import kotlin.collections.ArrayList @@ -12,7 +13,10 @@ import kotlin.collections.ArrayList class Command(val name: String, val privilege: Privilege, val usage: String = "UNDOCUMENTED", val description: String = "UNDOCUMENTED", val handle: (Player, Array) -> Unit) { fun attemptHandling(player: Player, args: Array?){ args ?: return - if(player.rights.ordinal >= privilege.ordinal || GameWorld.settings?.isDevMode == true || ServerConstants.I_AM_A_CHEATER){ + val hasRights = player.rights == Rights.ADMINISTRATOR || (ServerConstants.PLAYER_COMMANDS && player.rights.ordinal >= privilege.ordinal) + val isDev = GameWorld.settings?.isDevMode == true + val isCheater = ServerConstants.I_AM_A_CHEATER + if (hasRights || isDev || isCheater) { handle(player,args) } } diff --git a/Server/src/main/core/game/system/command/sets/AnimationCommandSet.kt b/Server/src/main/core/game/system/command/sets/AnimationCommandSet.kt index e1de944d1..d06258333 100644 --- a/Server/src/main/core/game/system/command/sets/AnimationCommandSet.kt +++ b/Server/src/main/core/game/system/command/sets/AnimationCommandSet.kt @@ -1,5 +1,10 @@ package core.game.system.command.sets +import core.api.animate +import core.api.delayScript +import core.api.queueScript +import core.api.stopExecuting +import core.game.interaction.QueueStrength import core.game.node.entity.npc.NPC import core.game.system.task.Pulse import core.game.world.update.flag.context.Animation @@ -27,23 +32,26 @@ class AnimationCommandSet : CommandSet(Privilege.ADMIN) { player.animate(animation) } - /** - * Force the player to play animation - */ - define("anims", Privilege.ADMIN, "::anim Animation ID", "Plays the animation with the given ID."){ player, args -> + define("anims", Privilege.ADMIN, "::anims <(opt) Duration Per Animation>", "Plays animations from the From ID to the To ID, with a delay of 3 between animations, unless specified otherwise"){ player, args -> + if (args.size < 3) { - reject(player, "Syntax error: ::anim ") + reject(player, "Syntax error: ::anims <(opt) Duration Per Animation>") } - val animation = args[1].toInt() + val animationFrom = args[1].toInt() val animationTo = args[2].toInt() - GameWorld.Pulser.submit(object : Pulse(3, player) { - var someId = animation - override fun pulse(): Boolean { - player.animate(Animation.create(someId)) - someId += 1 - return someId >= animationTo + val animationDelay = (args.getOrNull(3) ?: "3").toInt() + + queueScript(player, 1, QueueStrength.STRONG) { stage: Int -> + val animationId = animationFrom + stage + animate(player, animationId, true) + notify(player, "Playing animation $animationId") + + if (animationId == animationTo) { + return@queueScript stopExecuting(player) } - }) + + return@queueScript delayScript(player, animationDelay) + } } /** diff --git a/Server/src/main/core/game/system/config/ServerConfigParser.kt b/Server/src/main/core/game/system/config/ServerConfigParser.kt index 72df173ff..273b36531 100644 --- a/Server/src/main/core/game/system/config/ServerConfigParser.kt +++ b/Server/src/main/core/game/system/config/ServerConfigParser.kt @@ -76,8 +76,6 @@ object ServerConfigParser { isQuickChat = false, isLootshare = false, msAddress = data.getString("server.msip"), - default_xp_rate = data.getDouble("world.default_xp_rate"), - allow_slayer_reroll = data.getBoolean("world.allow_slayer_reroll"), enable_default_clan = data.getBoolean("world.enable_default_clan"), enable_bots = data.getBoolean("world.enable_bots"), autostock_ge = data.getBoolean("world.autostock_ge"), @@ -148,25 +146,32 @@ object ServerConfigParser { ServerConstants.NOAUTH_DEFAULT_ADMIN = data.getBoolean("server.noauth_default_admin", false) ServerConstants.DRAGON_AXE_USE_OSRS_SPEC = data.getBoolean("world.dragon_axe_use_osrs_spec", false) ServerConstants.DISCORD_OPENRSC_HOOK = data.getString("integrations.openrsc_integration_webhook", "") - ServerConstants.ENABLE_GLOBALCHAT = data.getBoolean("world.enable_globalchat", true) + ServerConstants.ENABLE_GLOBAL_CHAT = data.getBoolean("world.enable_global_chat", false) ServerConstants.MAX_PATHFIND_DISTANCE = data.getLong("server.max_pathfind_dist", 25L).toInt() - ServerConstants.IRONMAN_ICONS = data.getBoolean("world.ironman_icons", false) + ServerConstants.XP_RATES = data.getBoolean("world.xp_rates", false) + ServerConstants.IRONMAN = data.getBoolean("world.ironman", false) ServerConstants.PLAYER_STOCK_CLEAR_INTERVAL = data.getLong("world.playerstock_clear_mins", 180L).toInt() ServerConstants.PLAYER_STOCK_RECIRCULATE = data.getBoolean("world.playerstock_bot_offers", true) ServerConstants.BOTSTOCK_LIMIT = data.getLong("world.botstock_limit", 5000L).toInt() - ServerConstants.BETTER_AGILITY_PYRAMID_GP = data.getBoolean("world.better_agility_pyramid_gp", true) + ServerConstants.BETTER_AGILITY_PYRAMID_GP = data.getBoolean("world.better_agility_pyramid_gp", false) ServerConstants.GRAFANA_PATH = data.getPath("integrations.grafana_log_path") ServerConstants.GRAFANA_LOGGING = data.getBoolean("integrations.grafana_logging", false) ServerConstants.GRAFANA_TTL_DAYS = data.getLong("integrations.grafana_log_ttl_days", 7L).toInt() - ServerConstants.BETTER_DFS = data.getBoolean("world.better_dfs", true) - ServerConstants.NEW_PLAYER_ANNOUNCEMENT = data.getBoolean("world.new_player_announcement", true) + ServerConstants.BETTER_DFS = data.getBoolean("world.better_dfs", false) + ServerConstants.NEW_PLAYER_ANNOUNCEMENT = data.getBoolean("world.new_player_announcement", false) ServerConstants.INAUTHENTIC_CANDLELIGHT_RANDOM = data.getBoolean("world.inauthentic_candlelight_random", false) - ServerConstants.HOLIDAY_EVENT_RANDOMS = data.getBoolean("world.holiday_event_randoms", true) + ServerConstants.HOLIDAY_EVENT_RANDOMS = data.getBoolean("world.holiday_event_randoms", false) ServerConstants.FORCE_HALLOWEEN_EVENTS = data.getBoolean("world.force_halloween_randoms", false) ServerConstants.FORCE_CHRISTMAS_EVENTS = data.getBoolean("world.force_christmas_randoms", false) ServerConstants.FORCE_EASTER_EVENTS = data.getBoolean("world.force_easter_randoms", false) - ServerConstants.RUNECRAFTING_FORMULA_REVISION = data.getLong("world.runecrafting_formula_revision", 581).toInt() + ServerConstants.RUNECRAFTING_FORMULA_REVISION = data.getLong("world.runecrafting_formula_revision", 530).toInt() ServerConstants.ENHANCED_DEEP_WILDERNESS = data.getBoolean("world.enhanced_deep_wilderness", false) + ServerConstants.WILDERNESS_EXCLUSIVE_LOOT = data.getBoolean("world.wilderness_exclusive_loot", false) + ServerConstants.SHOOTING_STAR_RING = data.getBoolean("world.shooting_star_ring", false) + ServerConstants.RING_OF_WEALTH_TELEPORT = data.getBoolean("world.ring_of_wealth_teleport", false) + ServerConstants.SECOND_BANK = data.getBoolean("world.second_bank", false) + ServerConstants.PLAYER_COMMANDS = data.getBoolean("world.player_commands", false) + ServerConstants.BOOSTED_TRAWLER_REWARDS = data.getBoolean("world.boosted_trawler_rewards", false) ServerConstants.CONNECTIVITY_CHECK_URL = data.getString("server.connectivity_check_url", "https://google.com,https://2009scape.org") ServerConstants.CONNECTIVITY_TIMEOUT = data.getLong("server.connectivity_timeout", 500L).toInt() diff --git a/Server/src/main/core/game/system/timer/impl/Frozen.kt b/Server/src/main/core/game/system/timer/impl/Frozen.kt index 97fd002f8..23a2fa7e5 100644 --- a/Server/src/main/core/game/system/timer/impl/Frozen.kt +++ b/Server/src/main/core/game/system/timer/impl/Frozen.kt @@ -1,47 +1,50 @@ -package core.game.system.timer.impl - -import core.game.system.timer.* -import core.api.* -import core.game.node.entity.Entity -import core.game.node.entity.player.Player -import core.game.world.repository.Repository -import org.json.simple.* - -class Frozen : PersistTimer (1, "frozen", flags = arrayOf(TimerFlag.ClearOnDeath)) { - var shouldApplyImmunity = false - - override fun save (root: JSONObject, entity: Entity) { - root["ticksLeft"] = (nextExecution - getWorldTicks()).toString() - root["applyImmunity"] = shouldApplyImmunity - } - - override fun parse (root: JSONObject, entity: Entity) { - runInterval = root["ticksLeft"].toString().toInt() - shouldApplyImmunity = root["applyImmunity"] as? Boolean ?: false - } - - override fun beforeRegister (entity: Entity) { - if (hasTimerActive(entity)) { - removeTimer(entity, this) - return - } - if (hasTimerActive(entity)) { - removeTimer(entity, this) - return - } - } - - override fun run (entity: Entity) : Boolean { - if (shouldApplyImmunity) { - registerTimer (entity, spawnTimer(7)) - } else (entity as? Player)?.debug ("Can't apply immunity") - return false - } - - override fun getTimer (vararg args: Any) : RSTimer { - val inst = Frozen() - inst.runInterval = args.getOrNull(0) as? Int ?: 10 - inst.shouldApplyImmunity = args.getOrNull(1) as? Boolean ?: false - return inst - } -} +package core.game.system.timer.impl + +import core.game.system.timer.* +import core.api.* +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import core.game.world.repository.Repository +import org.json.simple.* + +class Frozen : PersistTimer (1, "frozen", flags = arrayOf(TimerFlag.ClearOnDeath)) { + var shouldApplyImmunity = false + + override fun save (root: JSONObject, entity: Entity) { + root["ticksLeft"] = (nextExecution - getWorldTicks()).toString() + root["applyImmunity"] = shouldApplyImmunity + } + + override fun parse (root: JSONObject, entity: Entity) { + runInterval = root["ticksLeft"].toString().toInt() + shouldApplyImmunity = root["applyImmunity"] as? Boolean ?: false + } + + override fun beforeRegister (entity: Entity) { + if (hasTimerActive(entity)) { + removeTimer(entity, this) + return + } + if (hasTimerActive(entity)) { + removeTimer(entity, this) + return + } + if (entity is Player) { + sendMessage(entity as Player, "You have been frozen!") + } + } + + override fun run (entity: Entity) : Boolean { + if (shouldApplyImmunity) { + registerTimer (entity, spawnTimer(7)) + } else (entity as? Player)?.debug ("Can't apply immunity") + return false + } + + override fun getTimer (vararg args: Any) : RSTimer { + val inst = Frozen() + inst.runInterval = args.getOrNull(0) as? Int ?: 10 + inst.shouldApplyImmunity = args.getOrNull(1) as? Boolean ?: false + return inst + } +} diff --git a/Server/src/main/core/game/system/timer/impl/SkillRestore.kt b/Server/src/main/core/game/system/timer/impl/SkillRestore.kt index b6b456618..f14d46abe 100644 --- a/Server/src/main/core/game/system/timer/impl/SkillRestore.kt +++ b/Server/src/main/core/game/system/timer/impl/SkillRestore.kt @@ -48,7 +48,7 @@ class SkillRestore : RSTimer (1, "skillrestore", isAuto = true, isSoft = true) { (entity as? Player)?.debug("Registered skill restoration timer.") } - private fun getHealAmount (entity: Entity) : Int { + fun getHealAmount (entity: Entity) : Int { if (entity !is Player) return 1 val gloves = getItemFromEquipment (entity, EquipmentSlot.HANDS) diff --git a/Server/src/main/core/game/world/GameSettings.kt b/Server/src/main/core/game/world/GameSettings.kt index 7c8c6e9d5..e113dbbe3 100644 --- a/Server/src/main/core/game/world/GameSettings.kt +++ b/Server/src/main/core/game/world/GameSettings.kt @@ -69,8 +69,6 @@ class GameSettings * The address of the Management server. */ var msAddress: String, - var default_xp_rate: Double, - var allow_slayer_reroll: Boolean, var enable_default_clan: Boolean, var enable_bots: Boolean, var autostock_ge: Boolean, @@ -126,8 +124,6 @@ class GameSettings val activity = data["activity"].toString() val pvpWorld = data["pvpWorld"] as Boolean val msip = data["msip"].toString() - val default_xp_rate = data["default_xp_rate"].toString().toDouble() - val allow_slayer_reroll = data["allow_slayer_reroll"] as Boolean val enable_default_clan = data["enable_default_clan"] as Boolean val enable_bots = data["enable_bots"] as Boolean val autostock_ge = data["autostock_ge"] as Boolean @@ -157,8 +153,6 @@ class GameSettings false, false, msip, - default_xp_rate, - allow_slayer_reroll, enable_default_clan, enable_bots, autostock_ge, diff --git a/Server/src/main/core/game/world/map/zone/impl/WildernessZone.java b/Server/src/main/core/game/world/map/zone/impl/WildernessZone.java index c06ba08ce..623f2f080 100644 --- a/Server/src/main/core/game/world/map/zone/impl/WildernessZone.java +++ b/Server/src/main/core/game/world/map/zone/impl/WildernessZone.java @@ -108,7 +108,8 @@ public final class WildernessZone extends MapZone { byte glove = (byte) RandomFunction.random(1, 14); Item reward = new Item(BrawlingGloves.forIndicator(glove).getId()); GroundItemManager.create(reward, e.asNpc().getDropLocation(), killer.asPlayer()); - Repository.sendNews(killer.getUsername() + " has received " + reward.getName().toLowerCase() + " from a " + e.asNpc().getName() + "!"); + String npcString = e.getId() == NPCs.CHAOS_ELEMENTAL_3200 ? "the Chaos Elemental" : ("a " + e.asNpc().getName().toLowerCase()); + Repository.sendNews(killer.getUsername() + " has received " + reward.getName().toLowerCase() + " from " + npcString + "!"); } for (int j : PVP_GEAR) { boolean chance = RandomFunction.roll(pvpGearRate); @@ -120,7 +121,8 @@ public final class WildernessZone extends MapZone { reward = new Item(j); } GroundItemManager.create(reward, ((NPC) e).getDropLocation(), killer.asPlayer()); - Repository.sendNews(killer.asPlayer().getUsername() + " has received a " + reward.getName() + " from a " + e.asNpc().getName() + "!"); + String npcString = e.getId() == NPCs.CHAOS_ELEMENTAL_3200 ? "the Chaos Elemental" : ("a " + e.asNpc().getName().toLowerCase()); + Repository.sendNews(killer.asPlayer().getUsername() + " has received a " + reward.getName() + " from " + npcString + "!"); } } } @@ -153,17 +155,6 @@ public final class WildernessZone extends MapZone { p.getSkullManager().setWilderness(true); p.getSkullManager().setLevel(getWilderness(p)); } - for (int i = 0; i < 7; i++) { - if (i == 5 || i == 3) { - continue; - } - if(p.getAttributes().containsKey("overload") || p.getSkills().getLevel(i) > 118){ - if (p.getSkills().getLevel(i) > p.getSkills().getStaticLevel(i)) { - p.getSkills().setLevel(i, p.getSkills().getStaticLevel(i)); - p.removeAttribute("overload"); - } - } - } if (p.getFamiliarManager().hasFamiliar() && !p.getFamiliarManager().hasPet()) { Familiar familiar = p.getFamiliarManager().getFamiliar(); familiar.transform(); diff --git a/Server/src/main/core/game/world/repository/Repository.kt b/Server/src/main/core/game/world/repository/Repository.kt index 575bcb0f2..81d44d5f4 100644 --- a/Server/src/main/core/game/world/repository/Repository.kt +++ b/Server/src/main/core/game/world/repository/Repository.kt @@ -6,6 +6,7 @@ import core.game.node.entity.player.Player import core.game.world.map.Location import core.game.world.map.RegionManager import core.ServerConstants +import core.api.sendMessage import core.game.world.update.UpdateSequence import java.util.* import java.util.concurrent.CopyOnWriteArrayList @@ -55,20 +56,7 @@ object Repository { */ @JvmStatic val disconnectionQueue = DisconnectionQueue() - /** - * Sends a market update message to all players. - * @param string The string. - * @param color The color. - */ - @JvmOverloads - fun sendMarketUpdate(string: String, icon: Int = 12, color: String = "") { - val players: Array = playerNames.values.toTypedArray() - val size = players.size - for (i in 0 until size) { - val player = players[i] as Player ?: continue - player.sendMessage("" + color + "Market Update: " + string) - } - } + /** * Send a news message to all players. * @param string The string. @@ -76,11 +64,12 @@ object Repository { */ @JvmStatic fun sendNews(string: String, icon: Int = 12, color: String = "CC6600") { + if (!ServerConstants.ENABLE_GLOBAL_CHAT) return val players: Array = playerNames.values.toTypedArray() val size = players.size for (i in 0 until size) { val player = players[i] as Player ?: continue - player.sendMessage("News: $string") + sendMessage(player, "News: $string") } } diff --git a/Server/src/main/core/game/worldevents/holiday/halloween/randoms/SpiderHolidayRandomNPC.kt b/Server/src/main/core/game/worldevents/holiday/halloween/randoms/SpiderHolidayRandomNPC.kt index dea7ff063..037c9ba10 100644 --- a/Server/src/main/core/game/worldevents/holiday/halloween/randoms/SpiderHolidayRandomNPC.kt +++ b/Server/src/main/core/game/worldevents/holiday/halloween/randoms/SpiderHolidayRandomNPC.kt @@ -15,7 +15,7 @@ class SpiderHolidayRandomNPC() : HolidayRandomEventNPC(61) { this.behavior = SpiderHolidayRandomBehavior() playGlobalAudio(this.location, Sounds.SPIDER_4375) var stomped = false - queueScript(this,4, QueueStrength.SOFT) { stage: Int -> + queueScript(this, 4, QueueStrength.SOFT) { stage: Int -> when (stage) { 0 -> { sendChat(player, "Eww a spider!") @@ -32,7 +32,7 @@ class SpiderHolidayRandomNPC() : HolidayRandomEventNPC(61) { } 2 -> { if (stomped) { - impact(this, 1, ImpactHandler.HitsplatType.NORMAL) + impact(this, 2, ImpactHandler.HitsplatType.NORMAL) } else { sendMessage(player, "The spider runs away.") playGlobalAudio(this.location, Sounds.SPIDER_4375) @@ -47,4 +47,4 @@ class SpiderHolidayRandomNPC() : HolidayRandomEventNPC(61) { override fun talkTo(npc: NPC) { } -} \ No newline at end of file +} diff --git a/Server/src/main/core/net/packet/PacketProcessor.kt b/Server/src/main/core/net/packet/PacketProcessor.kt index d6028d648..d76b82db8 100644 --- a/Server/src/main/core/net/packet/PacketProcessor.kt +++ b/Server/src/main/core/net/packet/PacketProcessor.kt @@ -188,7 +188,7 @@ object PacketProcessor { offer.itemID = pkt.itemId offer.sell = false if (!PriceIndex.canTrade(pkt.itemId)) { - sendMessage(pkt.player, "That item is blacklisted from the grand exchange.") + sendMessage(pkt.player, "That item is blacklisted from the Grand Exchange.") return } offer.player = pkt.player @@ -208,13 +208,14 @@ object PacketProcessor { if (pkt.player.details.isMuted) pkt.player.sendMessage("You have been muted due to breaking a rule.") else { - if (ServerConstants.ENABLE_GLOBALCHAT && pkt.message.startsWith("//")) { + if (ServerConstants.ENABLE_GLOBAL_CHAT && pkt.message.startsWith("//")) { if (getAttribute(pkt.player, GlobalChat.ATTR_GLOBAL_MUTE, false)) return val messages = splitChatMessage(pkt.message.substring(2), pkt.player.name.length + 3, false) for (message in messages) { if (message.isNotBlank()) + PlayerMonitor.logChat(pkt.player, "global", message) GlobalChat.process(pkt.player.username, message, Rights.getChatIcon(pkt.player)) } return @@ -228,6 +229,7 @@ object PacketProcessor { builder.clanName = pkt.player.communication.clan.owner.lowercase().replace(" ", "_") builder.message = message builder.rank = Rights.getChatIcon(pkt.player) + PlayerMonitor.logChat(pkt.player, "clan", message) ManagementEvents.publish(builder.build()) } return diff --git a/Server/src/test/resources/test.conf b/Server/src/test/resources/test.conf index fd8697901..b143db9e0 100644 --- a/Server/src/test/resources/test.conf +++ b/Server/src/test/resources/test.conf @@ -27,8 +27,6 @@ members = true #activity as displayed on the world list activity = "2009Scape Classic." pvp = false -default_xp_rate = 5.0 -allow_slayer_reroll = false #enables a default clan for players to join automatically. Should be an account with the same name as @name, with a clan set up already. enable_default_clan = true enable_bots = true @@ -49,6 +47,7 @@ max_adv_bots = 100 enable_doubling_money_scammers = true wild_pvp_enabled = false jad_practice_enabled = false +enable_global_chat = false enable_castle_wars = false personalized_shops = false diff --git a/Server/worldprops/default.conf b/Server/worldprops/default.conf index 511b7e9ce..fcb90f72b 100644 --- a/Server/worldprops/default.conf +++ b/Server/worldprops/default.conf @@ -58,8 +58,6 @@ members = true #activity as displayed on the world list activity = "2009Scape Classic." pvp = false -default_xp_rate = 5.0 -allow_slayer_reroll = false #enables a default clan for players to join automatically. Should be an account with the same name as @name, with a clan set up already. enable_default_clan = true enable_bots = true @@ -72,44 +70,61 @@ new_player_location = "2524,5002,0" #the location of home teleport home_location = "3222,3218,0" autostock_ge = false -allow_token_purchase = true -skillcape_perks = true +allow_token_purchase = false +skillcape_perks = false increased_door_time = false enable_botting = false max_adv_bots = 100 enable_doubling_money_scammers = true -wild_pvp_enabled = true -jad_practice_enabled = true +wild_pvp_enabled = false +jad_practice_enabled = false +enable_global_chat = false #minimum HA value for announcements of bots selling on ge ge_announcement_limit = 500 enable_castle_wars = false -personalized_shops = true +personalized_shops = false bots_influence_ge_price = true #verbose cutscene logging (for cutscenes in the new system) verbose_cutscene = false #show the rules the first time a player logs in -show_rules = true +show_rules = false #the number of revenants active at a time revenant_population = 30 #enable auto-buy/auto-sell on the GE. i_want_to_cheat = false #better agility pyramid gp reward (gp reward = 1000 + ((agility level / 99) * 9000)) -better_agility_pyramid_gp = true +better_agility_pyramid_gp = false #better dragonfire shield attack (30 second cooldown instead of 2 minutes) -better_dfs = true +better_dfs = false #new player announcement -new_player_announcement = true +new_player_announcement = false #enables inauthentic candlelight random event (adds an additional normal random event) -inauthentic_candlelight_random = true +inauthentic_candlelight_random = false #enables holiday random events (no effect on normal random events) -holiday_event_randoms = true +holiday_event_randoms = false #force holiday randoms (can only force one at a time) force_halloween_randoms = false force_christmas_randoms = false #runecrafting formula revision (573 introduced probabilistic multiple runes, 581 extrapolated probabilistic runes past 99) -runecrafting_formula_revision = 581 -#enable the enhanced deep wilderness, where the area past the members' fence applies a red skull that boosts brawler/pvp drop rates -enhanced_deep_wilderness = true +runecrafting_formula_revision = 530 +#enables the enhanced deep wilderness, where the area past the members' fence applies a red skull that boosts brawler/pvp drop rates +enhanced_deep_wilderness = false +#enables wilderness-exclusive loot, i.e. brawling gloves and PvP gear, from revenants and the Chaos Elemental +wilderness_exclusive_loot = false +#enables the xp rates option on tutorial island +xp_rates = false +#enables the ironman option on tutorial island and the inauthentic game protocol addition to transmit chat icons for ironmen +ironman = false +#enables the custom-content ancient blueprint and ring of the star sprite +shooting_star_ring = false +#enables the inauthentic teleport option on the ring of wealth +ring_of_wealth_teleport = false +#enables second bank +second_bank = false +#enables inauthentic but non-dangerous commands for regular players +player_commands = false +#enables boosted rewards from fishing trawler consisting of key halves and pirate outfit pieces +boosted_trawler_rewards = false [paths] #path to the data folder, which contains the cache subfolder and such