Merge branch 2009scape:master into master

This commit is contained in:
Tooze 2026-04-01 22:48:30 +00:00
commit aa489af87a
78 changed files with 2920 additions and 794 deletions

View file

@ -11,7 +11,6 @@
<ObjectAction mode="remove" id="356" x="2804" y="3428" z="0"/>
<ObjectAction mode="add" id="356" x="2804" y="3428" z="0"/>
<ObjectAction mode="add" id="2783" x="3229" y="3254" z="0"/>
<ObjectAction mode="remove" id="20228" x="2537" y="3573" z="0"/>
<ObjectAction mode="add" id="27663" x="2537" y="3573" z="0" direction="w"/>

View file

@ -343,7 +343,7 @@
},
{
"id": "2054",
"replaceId": "24375",
"replaceId": "2055",
"fence": "false",
"metal": "false"
},
@ -419,18 +419,6 @@
"fence": "false",
"metal": "false"
},
{
"id": "2391",
"replaceId": "1561",
"fence": "false",
"metal": "true"
},
{
"id": "2392",
"replaceId": "1561",
"fence": "false",
"metal": "true"
},
{
"id": "2397",
"replaceId": "24383",
@ -2169,7 +2157,7 @@
},
{
"id": "24376",
"replaceId": "24377",
"replaceId": "24375",
"fence": "false",
"metal": "false"
},

View file

@ -141,7 +141,7 @@
},
{
"item_id": "401",
"loc_data": "{1,2959,2959,0,160}-{1,2943,2950,0,160}-{1,2866,2976,0,160}-{1,2854,2977,0,160}-{1,2839,2975,0,160}-{1,2757,2947,0,160}-{1,2755,2952,0,160}-{1,2786,2960,0,160}-{1,2782,2987,0,160}-{1,2771,2995,0,160}-{1,2761,3000,0,160}-{1,3829,3053,0,160}-{1,3813,3066,0,160}-{1,3814,3064,0,160}-{1,3808,3060,0,160}-{1,2945,3068,0,160}-{1,2975,3013,0,160}-{1,2955,3010,0,160}-{1,2755,3008,0,160}-{1,2754,3017,0,160}-{1,2756,3060,0,160}-{1,2755,3070,0,160}-{1,2896,3119,0,160}-{1,2914,3111,0,160}-{1,2926,3110,0,160}-{1,2939,3102,0,160}-{1,2938,3073,0,160}-{1,2756,3074,0,160}-{1,2806,3128,0,160}-{1,2764,3131,0,160}-{1,2753,3125,0,160}-{1,2757,3109,0,160}-{1,2864,3195,0,160}-{1,2810,3387,0,160}-{1,2809,3388,0,160}-{1,2809,3385,0,160}-{1,2807,3386,0,160}-{1,2804,3385,0,160}-{1,2807,3384,0,160}-{1,2805,3384,0,160}-{1,2802,3382,0,160}-{1,2805,3381,0,160}-{1,2804,3380,0,160}-{1,2693,3727,0,160}-{1,2698,3729,0,160}-{1,2700,3731,0,160}-{1,2708,3728,0,160}-{1,2725,3731,0,160}-{1,2721,3730,0,160}-{1,2719,3733,0,160}-{1,2714,3733,0,160}-{1,2712,3732,0,160}-"
"loc_data": "{1,2959,2959,0,160}-{1,2866,2976,0,160}-{1,2854,2977,0,160}-{1,2839,2975,0,160}-{1,2943,2950,0,160}-{1,2757,2947,0,160}-{1,2755,2952,0,160}-{1,2786,2960,0,160}-{1,2782,2987,0,160}-{1,2771,2995,0,160}-{1,2761,3000,0,160}-{1,2945,3068,0,160}-{1,2975,3013,0,160}-{1,2955,3010,0,160}-{1,3829,3053,0,160}-{1,3813,3066,0,160}-{1,3814,3064,0,160}-{1,3808,3060,0,160}-{1,2755,3008,0,160}-{1,2754,3017,0,160}-{1,2756,3060,0,160}-{1,2755,3070,0,160}-{1,2896,3119,0,160}-{1,2914,3111,0,160}-{1,2926,3110,0,160}-{1,2939,3102,0,160}-{1,2938,3073,0,160}-{1,2756,3074,0,160}-{1,2806,3128,0,160}-{1,2764,3131,0,160}-{1,2753,3125,0,160}-{1,2757,3109,0,160}-{1,2864,3195,0,160}-{1,2810,3387,0,160}-{1,2809,3388,0,160}-{1,2809,3385,0,160}-{1,2807,3386,0,160}-{1,2804,3385,0,160}-{1,2807,3384,0,160}-{1,2805,3384,0,160}-{1,2802,3382,0,160}-{1,2805,3381,0,160}-{1,2804,3380,0,160}-{1,2693,3727,0,160}-{1,2698,3729,0,160}-{1,2700,3731,0,160}-{1,2708,3728,0,160}-{1,2725,3731,0,160}-{1,2721,3730,0,160}-{1,2719,3733,0,160}-{1,2714,3733,0,160}-{1,2712,3732,0,160}-"
},
{
"item_id": "444",
@ -369,7 +369,7 @@
},
{
"item_id": "1573",
"loc_data": "{1,2559,2975,0,10}-{1,2560,2974,0,10}-{1,2561,2973,0,10}-{1,2561,2976,0,10}-{1,2740,3637,0,30}-{1,2736,3638,0,30}-{1,2735,3636,0,30}-{1,2743,3636,0,30}-{1,2739,3634,0,30}-{1,2743,3640,0,30}-{1,2741,3639,0,30}-{1,2736,3641,0,30}-{1,2738,3641,0,30}-{1,2734,3640,0,30}-{1,2738,3636,0,30}-"
"loc_data": "{1,2560,2974,0,10}-{1,2561,2973,0,10}-{1,2561,2976,0,10}-{1,2559,2975,0,10}-{1,2740,3637,0,30}-{1,2736,3638,0,30}-{1,2735,3636,0,30}-{1,2743,3636,0,30}-{1,2739,3634,0,30}-{1,2743,3640,0,30}-{1,2741,3639,0,30}-{1,2736,3641,0,30}-{1,2738,3641,0,30}-{1,2734,3640,0,30}-{1,2738,3636,0,30}-"
},
{
"item_id": "1590",
@ -469,7 +469,7 @@
},
{
"item_id": "1931",
"loc_data": "{1,3209,3214,0,80}-{1,3166,3310,0,90}-{1,3144,3449,2,90}-"
"loc_data": "{1,3209,3214,0,80}-{1,3166,3310,0,90}-{1,3144,3449,2,90}-{1,2918,10191,1,90}-"
},
{
"item_id": "1935",

View file

@ -17702,7 +17702,7 @@
{
"shop_price": "350",
"ge_buy_limit": "5000",
"examine": "A plain gold amulet.",
"examine": "It needs a string so I can wear it.",
"grand_exchange_price": "172",
"durability": null,
"name": "Gold amulet",
@ -17722,7 +17722,7 @@
{
"shop_price": "810",
"ge_buy_limit": "5000",
"examine": "I wonder if I can get this enchanted.",
"examine": "It needs a string so I can wear it.",
"grand_exchange_price": "473",
"durability": null,
"name": "Sapphire amulet",
@ -17742,7 +17742,7 @@
{
"shop_price": "1147",
"ge_buy_limit": "5000",
"examine": "I wonder if I can get this enchanted.",
"examine": "It needs a string so I can wear it.",
"grand_exchange_price": "695",
"durability": null,
"name": "Emerald amulet",
@ -17762,7 +17762,7 @@
{
"shop_price": "2025",
"ge_buy_limit": "5000",
"examine": "I wonder if I can get this enchanted.",
"examine": "It needs a string so I can wear it.",
"grand_exchange_price": "1268",
"durability": null,
"name": "Ruby amulet",
@ -17783,7 +17783,7 @@
{
"shop_price": "3307",
"ge_buy_limit": "5000",
"examine": "I wonder if I can get this enchanted.",
"examine": "It needs a string so I can wear it.",
"grand_exchange_price": "3359",
"durability": null,
"name": "Diamond amulet",
@ -17802,7 +17802,7 @@
},
{
"ge_buy_limit": "100",
"examine": "I wonder if I can get this enchanted.",
"examine": "It needs a string so I can wear it.",
"grand_exchange_price": "26900",
"durability": null,
"name": "Dragonstone ammy",
@ -48457,14 +48457,14 @@
},
{
"shop_price": "200",
"examine": "An acorn has been sown and watered in this plant pot.",
"examine": "An acorn has been sown in this plant pot.",
"durability": null,
"name": "Oak seedling",
"archery_ticket_price": "0",
"id": "5358"
},
{
"examine": "A willow tree seed has been sown and watered in this plant pot",
"examine": "A willow tree seed has been sown in this plant pot.",
"durability": null,
"name": "Willow seedling",
"weight": "0.9",
@ -48472,7 +48472,7 @@
"id": "5359"
},
{
"examine": "A maple tree has been sown (and watered) in this plant pot.",
"examine": "A maple tree seed has been sown in this plant pot.",
"durability": null,
"name": "Maple seedling",
"weight": "0.9",
@ -48480,7 +48480,7 @@
"id": "5360"
},
{
"examine": "A yew seed has been sown in the plant pot",
"examine": "A yew tree seed has been sown in this plant pot.",
"durability": null,
"name": "Yew seedling",
"weight": "1",
@ -48488,7 +48488,7 @@
"id": "5361"
},
{
"examine": "A magic tree seed has been sown and watered in this plant pot.",
"examine": "A magic tree seed has been sown in this plant pot.",
"durability": null,
"name": "Magic seedling",
"weight": "0.9",
@ -48496,7 +48496,7 @@
"id": "5362"
},
{
"examine": "A spirit seedling is growing in this pot. It has been sown and watered.",
"examine": "A spirit tree seed has been sown in this plant pot.",
"durability": null,
"name": "Spirit seedling",
"archery_ticket_price": "0",
@ -48511,7 +48511,7 @@
"id": "5364"
},
{
"examine": "A willow tree seed has been sown and watered in this plant pot",
"examine": "A willow tree seed has been sown and watered in this plant pot.",
"durability": null,
"name": "Willow seedling",
"weight": "0.9",
@ -48519,7 +48519,7 @@
"id": "5365"
},
{
"examine": "A maple tree has been sown (and watered) in this plant pot.",
"examine": "A maple tree seed has been sown and watered in this plant pot.",
"durability": null,
"name": "Maple seedling",
"weight": "0.9",
@ -48527,7 +48527,7 @@
"id": "5366"
},
{
"examine": "A yew seed has been sown in the plant pot",
"examine": "A yew tree seed has been sown and watered in this plant pot.",
"durability": null,
"name": "Yew seedling",
"weight": "1",
@ -48543,7 +48543,7 @@
"id": "5368"
},
{
"examine": "A spirit seedling is growing in this pot. It has been sown and watered.",
"examine": "A spirit tree seed has been sown and watered in this plant pot.",
"durability": null,
"name": "Spirit seedling",
"archery_ticket_price": "0",
@ -49497,7 +49497,7 @@
"id": "5479"
},
{
"examine": "An apple tree has been sown (and watered) in this plant pot.",
"examine": "An apple tree seed has been sown in this plant pot.",
"durability": null,
"name": "Apple seedling",
"weight": "0.9",
@ -49505,7 +49505,7 @@
"id": "5480"
},
{
"examine": "A banana tree seed has been sown and watered in this plant pot.",
"examine": "A banana tree seed has been sown in this plant pot.",
"durability": null,
"name": "Banana seedling",
"weight": "0.9",
@ -49513,14 +49513,14 @@
"id": "5481"
},
{
"examine": "An orange tree seed has been sown (and watered)in this plant pot.",
"examine": "An orange tree seed has been sown in this plant pot.",
"durability": null,
"name": "Orange seedling",
"archery_ticket_price": "0",
"id": "5482"
},
{
"examine": "A curry seed has been sown and watered in this plant pot.",
"examine": "A curry tree seed has been sown in this plant pot.",
"durability": null,
"name": "Curry seedling",
"weight": "0.9",
@ -49528,7 +49528,7 @@
"id": "5483"
},
{
"examine": "A pineapple tree has been sown (and watered) in this plant pot.",
"examine": "A pineapple plant seed has been sown in this plant pot.",
"durability": null,
"name": "Pineapple seedling",
"weight": "0.9",
@ -49536,7 +49536,7 @@
"id": "5484"
},
{
"examine": "This needs watering before it will grow.",
"examine": "A papaya tree seed has been sown in this plant pot.",
"durability": null,
"name": "Papaya seedling",
"weight": "0.9",
@ -49544,7 +49544,7 @@
"id": "5485"
},
{
"examine": "This sapling is ready to be replanted in a fruit tree patch.",
"examine": "A palm tree seed has been sown in this plant pot.",
"durability": null,
"name": "Palm seedling",
"weight": "0.66",
@ -49552,7 +49552,7 @@
"id": "5486"
},
{
"examine": "A calquat tree has been sown (and watered) in this plant pot.",
"examine": "A calquat tree seed has been sown in this plant pot.",
"durability": null,
"name": "Calquat seedling",
"weight": "0.9",
@ -49560,7 +49560,7 @@
"id": "5487"
},
{
"examine": "An apple tree has been sown (and watered) in this plant pot.",
"examine": "An apple tree seed has been sown and watered in this plant pot.",
"durability": null,
"name": "Apple seedling",
"weight": "0.9",
@ -49576,14 +49576,14 @@
"id": "5489"
},
{
"examine": "An orange tree seed has been sown (and watered)in this plant pot.",
"examine": "An orange tree seed has been sown and watered in this plant pot.",
"durability": null,
"name": "Orange seedling",
"archery_ticket_price": "0",
"id": "5490"
},
{
"examine": "A curry seed has been sown and watered in this plant pot.",
"examine": "A curry tree seed has been sown and watered in this plant pot.",
"durability": null,
"name": "Curry seedling",
"weight": "0.9",
@ -49591,7 +49591,7 @@
"id": "5491"
},
{
"examine": "A pineapple tree has been sown (and watered) in this plant pot.",
"examine": "A pineapple plant seed has been sown and watered in this plant pot.",
"durability": null,
"name": "Pineapple seedling",
"weight": "0.9",
@ -49599,7 +49599,7 @@
"id": "5492"
},
{
"examine": "This needs watering before it will grow.",
"examine": "A papaya tree seed has been sown and watered in this plant pot.",
"durability": null,
"name": "Papaya seedling",
"weight": "0.9",
@ -49607,7 +49607,7 @@
"id": "5493"
},
{
"examine": "This sapling is ready to be replanted in a fruit tree patch.",
"examine": "A palm tree seed has been sown and watered in this plant pot.",
"durability": null,
"name": "Palm seedling",
"weight": "0.66",
@ -49615,7 +49615,7 @@
"id": "5494"
},
{
"examine": "A calquat tree has been sown (and watered) in this plant pot.",
"examine": "A calquat tree seed has been sown and watered in this plant pot.",
"durability": null,
"name": "Calquat seedling",
"weight": "0.9",
@ -51571,9 +51571,9 @@
"equip_audio": "2248",
"render_anim": "2584",
"equipment_slot": "3",
"attack_anims": "400,400,401,400",
"grand_exchange_price": "483",
"attack_audios": "2517,2517,2500,2517",
"attack_anims": "400,400,401,400",
"name": "Iron dagger(p+)",
"tradeable": "true",
"archery_ticket_price": "0",
@ -51600,9 +51600,9 @@
"equip_audio": "2248",
"render_anim": "2584",
"equipment_slot": "3",
"attack_anims": "400,400,401,400",
"grand_exchange_price": "350",
"attack_audios": "2517,2517,2500,2517",
"attack_anims": "400,400,401,400",
"name": "Bronze dagger(p+)",
"tradeable": "true",
"archery_ticket_price": "0",
@ -51630,9 +51630,9 @@
"equip_audio": "2248",
"render_anim": "2584",
"equipment_slot": "3",
"attack_anims": "400,400,401,400",
"grand_exchange_price": "575",
"attack_audios": "2517,2517,2500,2517",
"attack_anims": "400,400,401,400",
"name": "Steel dagger(p+)",
"tradeable": "true",
"archery_ticket_price": "0",
@ -51660,9 +51660,9 @@
"equip_audio": "2248",
"render_anim": "2584",
"equipment_slot": "3",
"attack_anims": "400,400,401,400",
"grand_exchange_price": "538",
"attack_audios": "2517,2517,2500,2517",
"attack_anims": "400,400,401,400",
"name": "Mithril dagger(p+)",
"tradeable": "true",
"archery_ticket_price": "0",
@ -51686,9 +51686,9 @@
"equip_audio": "2248",
"render_anim": "2584",
"equipment_slot": "3",
"attack_anims": "400,400,401,400",
"grand_exchange_price": "800",
"attack_audios": "2517,2517,2500,2517",
"attack_anims": "400,400,401,400",
"name": "Adamant dagger(p+)",
"tradeable": "true",
"archery_ticket_price": "0",
@ -51783,9 +51783,9 @@
"equip_audio": "2248",
"render_anim": "2584",
"equipment_slot": "3",
"attack_anims": "400,400,401,400",
"grand_exchange_price": "664",
"attack_audios": "2517,2517,2500,2517",
"attack_anims": "400,400,401,400",
"name": "Black dagger(p+)",
"tradeable": "true",
"archery_ticket_price": "0",
@ -51831,9 +51831,9 @@
"equip_audio": "2248",
"render_anim": "2584",
"equipment_slot": "3",
"attack_anims": "400,400,401,400",
"grand_exchange_price": "4128",
"attack_audios": "2517,2517,2500,2517",
"attack_anims": "400,400,401,400",
"name": "Iron dagger(p++)",
"tradeable": "true",
"archery_ticket_price": "0",
@ -51860,9 +51860,9 @@
"equip_audio": "2248",
"render_anim": "2584",
"equipment_slot": "3",
"attack_anims": "400,400,401,400",
"grand_exchange_price": "5088",
"attack_audios": "2517,2517,2500,2517",
"attack_anims": "400,400,401,400",
"name": "Br'ze dagger(p++)",
"tradeable": "true",
"archery_ticket_price": "0",
@ -51890,9 +51890,9 @@
"equip_audio": "2248",
"render_anim": "2584",
"equipment_slot": "3",
"attack_anims": "400,400,401,400",
"grand_exchange_price": "4729",
"attack_audios": "2517,2517,2500,2517",
"attack_anims": "400,400,401,400",
"name": "Steel dagger(p++)",
"tradeable": "true",
"archery_ticket_price": "0",
@ -51920,9 +51920,9 @@
"equip_audio": "2248",
"render_anim": "2584",
"equipment_slot": "3",
"attack_anims": "400,400,401,400",
"grand_exchange_price": "3476",
"attack_audios": "2517,2517,2500,2517",
"attack_anims": "400,400,401,400",
"name": "Mithril dagger(p++)",
"tradeable": "true",
"archery_ticket_price": "0",
@ -51946,9 +51946,9 @@
"equip_audio": "2248",
"render_anim": "2584",
"equipment_slot": "3",
"attack_anims": "400,400,401,400",
"grand_exchange_price": "4612",
"attack_audios": "2517,2517,2500,2517",
"attack_anims": "400,400,401,400",
"name": "Adamant dagger(p++)",
"tradeable": "true",
"archery_ticket_price": "0",
@ -51976,10 +51976,10 @@
"equip_audio": "2248",
"render_anim": "2584",
"equipment_slot": "3",
"attack_anims": "400,400,401,400",
"lendable": "true",
"grand_exchange_price": "5061",
"attack_audios": "2517,2517,2500,2517",
"attack_anims": "400,400,401,400",
"name": "Rune dagger(p++)",
"tradeable": "true",
"archery_ticket_price": "0",
@ -52043,9 +52043,9 @@
"equip_audio": "2248",
"render_anim": "2584",
"equipment_slot": "3",
"attack_anims": "400,400,401,400",
"grand_exchange_price": "4921",
"attack_audios": "2517,2517,2500,2517",
"attack_anims": "400,400,401,400",
"name": "Black dagger(p++)",
"tradeable": "true",
"archery_ticket_price": "0",
@ -52751,7 +52751,7 @@
},
{
"ge_buy_limit": "100",
"examine": "This looks a good deal stronger than normal dwarven stout.",
"examine": "This looks a good deal stronger than normal Dwarven Stout.",
"grand_exchange_price": "56400",
"durability": null,
"name": "Dwarven stout(m)",
@ -53779,7 +53779,7 @@
},
{
"ge_buy_limit": "100",
"examine": "This looks a good deal stronger than normal dwarven stout.",
"examine": "This looks a good deal stronger than normal Dwarven Stout.",
"grand_exchange_price": "44600",
"durability": null,
"name": "Dwarven stout(m1)",
@ -53798,7 +53798,7 @@
},
{
"ge_buy_limit": "100",
"examine": "This looks a good deal stronger than normal dwarven stout.",
"examine": "This looks a good deal stronger than normal Dwarven Stout.",
"grand_exchange_price": "68900",
"durability": null,
"name": "Dwarven stout(m2)",
@ -53817,7 +53817,7 @@
},
{
"ge_buy_limit": "100",
"examine": "This looks a good deal stronger than normal dwarven stout.",
"examine": "This looks a good deal stronger than normal Dwarven Stout.",
"grand_exchange_price": "98800",
"durability": null,
"name": "Dwarven stout(m3)",
@ -53836,7 +53836,7 @@
},
{
"ge_buy_limit": "100",
"examine": "This looks a good deal stronger than normal dwarven stout.",
"examine": "This looks a good deal stronger than normal Dwarven Stout.",
"grand_exchange_price": "237400",
"durability": null,
"name": "Dwarven stout(m4)",
@ -60136,7 +60136,7 @@
"id": "6578"
},
{
"examine": "I wonder if I can get this enchanted.",
"examine": "It needs a string so I can wear it.",
"grand_exchange_price": "9058124",
"durability": null,
"name": "Onyx amulet",
@ -60318,9 +60318,9 @@
"equip_audio": "2248",
"render_anim": "2584",
"equipment_slot": "3",
"attack_anims": "400,400,401,400",
"grand_exchange_price": "497",
"attack_audios": "2517,2517,2500,2517",
"attack_anims": "400,400,401,400",
"name": "White dagger(p)",
"tradeable": "true",
"archery_ticket_price": "0",
@ -60348,9 +60348,9 @@
"equip_audio": "2248",
"render_anim": "2584",
"equipment_slot": "3",
"attack_anims": "400,400,401,400",
"grand_exchange_price": "982",
"attack_audios": "2517,2517,2500,2517",
"attack_anims": "400,400,401,400",
"name": "White dagger(p+)",
"tradeable": "true",
"archery_ticket_price": "0",
@ -60378,9 +60378,9 @@
"equip_audio": "2248",
"render_anim": "2584",
"equipment_slot": "3",
"attack_anims": "400,400,401,400",
"grand_exchange_price": "8422",
"attack_audios": "2517,2517,2500,2517",
"attack_anims": "400,400,401,400",
"name": "White dagger(p++)",
"tradeable": "true",
"archery_ticket_price": "0",

View file

@ -44609,7 +44609,9 @@
"strength_level": "1",
"id": "5079",
"range_level": "1",
"attack_level": "1"
"attack_level": "1",
"death_animation": "5183",
"examine": "Handle with care."
},
{
"examine": "It looks fluffy and cute; it's probably deadly.",
@ -59144,7 +59146,7 @@
"magic_animation": "7515",
"death_animation": "7461",
"name": "Revenant hellhound",
"defence_level": "1",
"defence_level": "50",
"safespot": null,
"lifepoints": "80",
"strength_level": "50",
@ -59990,7 +59992,7 @@
"magic_animation": "7515",
"death_animation": "7461",
"name": "Revenant hellhound",
"defence_level": "1",
"defence_level": "50",
"safespot": null,
"lifepoints": "80",
"strength_level": "50",
@ -76113,6 +76115,7 @@
"id": "2308"
},
{
"examine": "In his former life an expert brewer.",
"name": "Metarialus",
"id": "2322"
},

View file

@ -5547,6 +5547,10 @@
"npc_id": "2321",
"loc_data": "{2915,10194,1,1,0}-"
},
{
"npc_id": "2322",
"loc_data": "{3679,9957,0,1,1}-"
},
{
"npc_id": "2323",
"loc_data": "{3056,3306,0,1,1}-"

View file

@ -19168,87 +19168,87 @@
"ids": "13614"
},
{
"examine": "A gateway to Varrock",
"examine": "A gateway to Varrock.",
"ids": "13615"
},
{
"examine": "A gateway to Lumbridge",
"examine": "A gateway to Lumbridge.",
"ids": "13616"
},
{
"examine": "A gateway to Falador",
"examine": "A gateway to Falador.",
"ids": "13617"
},
{
"examine": "A gateway to Camelot",
"examine": "A gateway to Camelot.",
"ids": "13618"
},
{
"examine": "A gateway to Ardougne",
"examine": "A gateway to Ardougne.",
"ids": "13619"
},
{
"examine": "A gateway to the Yanille watchtower",
"examine": "A gateway to the Yanille watchtower.",
"ids": "13620"
},
{
"examine": "A gateway to Trollheim",
"examine": "A gateway to Kharyrll.",
"ids": "13621"
},
{
"examine": "A gateway to Varrock",
"examine": "A gateway to Varrock.",
"ids": "13622"
},
{
"examine": "A gateway to Lumbridge",
"examine": "A gateway to Lumbridge.",
"ids": "13623"
},
{
"examine": "A gateway to Falador",
"examine": "A gateway to Falador.",
"ids": "13624"
},
{
"examine": "A gateway to Camelot",
"examine": "A gateway to Camelot.",
"ids": "13625"
},
{
"examine": "A gateway to Ardougne",
"examine": "A gateway to Ardougne.",
"ids": "13626"
},
{
"examine": "A gateway to the Yanille watchtower",
"examine": "A gateway to the Yanille watchtower.",
"ids": "13627"
},
{
"examine": "A gateway to Trollheim",
"examine": "A gateway to Kharyrll.",
"ids": "13628"
},
{
"examine": "A gateway to Varrock",
"examine": "A gateway to Varrock.",
"ids": "13629"
},
{
"examine": "A gateway to Lumbridge",
"examine": "A gateway to Lumbridge.",
"ids": "13630"
},
{
"examine": "A gateway to Falador",
"examine": "A gateway to Falador.",
"ids": "13631"
},
{
"examine": "A gateway to Camelot",
"examine": "A gateway to Camelot.",
"ids": "13632"
},
{
"examine": "A gateway to Ardougne",
"examine": "A gateway to Ardougne.",
"ids": "13633"
},
{
"examine": "A gateway to the Yanille watchtower",
"examine": "A gateway to the Yanille watchtower.",
"ids": "13634"
},
{
"examine": "A gateway to Trollheim",
"examine": "A gateway to Kharyrll.",
"ids": "13635"
},
{

View file

@ -11,11 +11,13 @@ import core.game.node.entity.player.Player
import core.game.node.entity.player.link.TeleportManager
import core.game.node.item.Item
import core.game.system.task.Pulse
import core.game.world.GameWorld
import core.game.world.map.Location
import core.game.world.update.flag.context.Animation
import core.game.world.update.flag.context.Graphics
import org.rs09.consts.Items
import core.game.world.GameWorld.Pulser
import core.tools.Log
import org.rs09.consts.Sounds
import java.util.*
@ -73,17 +75,17 @@ enum class EnchantedJewellery(
),
AMULET_OF_GLORY(
arrayOf(
"Edgeville.",
"Karamja.",
"Draynor Village.",
"Al-Kharid.",
"Nowhere."
"Edgeville",
"Karamja",
"Draynor Village",
"Al Kharid",
"Nowhere"
),
arrayOf(
Location.create(3087, 3495, 0),
Location.create(2919, 3175, 0),
Location.create(3104, 3249, 0),
Location.create(3304, 3124, 0)
Location.create(3292, 3175, 0)
),
Items.AMULET_OF_GLORY4_1712,
Items.AMULET_OF_GLORY3_1710,
@ -209,7 +211,7 @@ enum class EnchantedJewellery(
constructor(options: Array<String>, locations: Array<Location>, vararg ids: Int) : this(options, locations, false, *ids)
/**
* Method used when the player "Use"s the jewellery piece.
* Method used when the player clicks one of the options in the jewellery piece's options dialog.
* @param player the player.
* @param item the used jewellery item.
* @param buttonID the button id.
@ -222,7 +224,7 @@ enum class EnchantedJewellery(
}
return
}
attemptTeleport(player, item, buttonID, isEquipped)
attemptTeleport(player, item, buttonID, true, isEquipped)
}
/**
@ -230,43 +232,69 @@ enum class EnchantedJewellery(
* @param player the player.
* @param item the used jewellery item.
* @param buttonID the button id.
* @param replace Whether to attempt replacing the item with its lower-charge version. Always true unless you generated a fake item in code.
* @param isEquipped If the player is operating.
*/
fun attemptTeleport(player: Player, item: Item, buttonID: Int, isEquipped: Boolean): Boolean {
fun attemptTeleport(player: Player, item: Item, buttonID: Int, replace: Boolean, isEquipped: Boolean): Boolean {
val itemIndex = getItemIndex(item)
val nextJewellery = Item(getNext(itemIndex))
if (!canTeleport(player, nextJewellery)) {
return false
}
Pulser.submit(object : Pulse(0) {
private var count = 0
private var location = getLocation(buttonID)
override fun pulse(): Boolean {
when (count) {
0 -> {
lock(player,4)
visualize(player, ANIMATION, GRAPHICS)
playGlobalAudio(player.location, Sounds.TELEPORT_ALL_200)
player.impactHandler.disabledTicks = 4
closeInterface(player)
val location = getLocation(buttonID)
player.scripts.delay = GameWorld.ticks + 4
queueScript(player, 0, QueueStrength.SOFT) { stage ->
when (stage) {
0 -> {
lock(player, 4)
visualize(player, ANIMATION, GRAPHICS)
playGlobalAudio(player.location, Sounds.TELEPORT_ALL_200)
player.impactHandler.disabledTicks = 4
closeInterface(player)
return@queueScript delayScript(player, 3)
}
1 -> {
teleport(player, location)
resetAnimator(player)
unlock(player)
player.dispatch(TeleportEvent(TeleportManager.TeleportType.NORMAL, TeleportMethod.JEWELRY, item, location))
if (!replace) {
return@queueScript stopExecuting(player)
}
3 -> {
teleport(player,location)
resetAnimator(player)
if (isLastItemIndex(itemIndex)) {
if (isCrumble) crumbleJewellery(player, item, isEquipped)
} else {
replaceJewellery(player, item, nextJewellery, isEquipped)
if (isLastItemIndex(itemIndex)) {
if (isSlayerRing(item)) {
if (isEquipped) {
// Remove it now, but only addItemOrDrop the gem after the teleport has procced
if (removeItem(player, item, Container.EQUIPMENT)) {
return@queueScript delayScript(player, 1)
}
log(this.javaClass, Log.ERR, "Error replacing slayer ring with enchanted gem (removeItem from equipment)")
}
// Not equipped -> can replace fluently
if (replaceSlot(player, item.slot, Item(Items.ENCHANTED_GEM_4155), item) == item) {
return@queueScript stopExecuting(player)
}
log(this.javaClass, Log.ERR, "Error replacing slayer ring with enchanted gem (replaceSlot)")
}
unlock(player)
player.dispatch(TeleportEvent(TeleportManager.TeleportType.NORMAL, TeleportMethod.JEWELRY, item, location))
return true
// Not slayer ring
if (isCrumble) {
val removeFrom = if (isEquipped) Container.EQUIPMENT else Container.INVENTORY
if (removeItem(player, item, removeFrom)) {
return@queueScript stopExecuting(player)
}
log(this.javaClass, Log.ERR, "Error crumbling jewelry")
}
} else {
replaceJewellery(player, item, nextJewellery, isEquipped)
}
}
count += 1
return false
2 -> {
addItemOrDrop(player, Items.ENCHANTED_GEM_4155)
sendMessage(player, "Your Ring of Slaying reverts back into a regular enchanted gem.")
}
}
})
return@queueScript stopExecuting(player)
}
return true
}
@ -278,21 +306,6 @@ enum class EnchantedJewellery(
}
}
private fun crumbleJewellery(player: Player, item: Item, isEquipped: Boolean) {
if (isEquipped) {
removeItem(player, item, Container.EQUIPMENT)
} else {
removeItem(player, item)
}
if (isSlayerRing(item)) {
queueScript(player, 1, QueueStrength.SOFT) {
addItemOrDrop(player, Items.ENCHANTED_GEM_4155)
sendMessage(player, "Your Ring of Slaying reverts back into a regular enchanted gem.")
return@queueScript stopExecuting(player)
}
}
}
private fun isSlayerRing(item: Item): Boolean {
return (item.id in RING_OF_SLAYING.ids)
}

View file

@ -203,8 +203,8 @@ public enum Consumables {
ASGARNIAN_ALE(new Drink(new int[] {1905, 1919}, new MultiEffect(new HealingEffect(2), new SkillEffect(Skills.STRENGTH, 2, 0), new SkillEffect(Skills.ATTACK, -4, 0)), "You drink the ale. You feel slightly reinvigorated...", "...and slightly dizzy too.")),
ASGARNIAN_ALE_POH(new Drink(new int[] {7744, 7742}, new MultiEffect(new HealingEffect(2), new SkillEffect(Skills.STRENGTH, 2, 0), new SkillEffect(Skills.ATTACK, -4, 0)), "You drink the ale. You feel slightly reinvigorated...", "...and slightly dizzy too.")),
ASGARNIAN_ALE_KEG(new Drink(new int[] {5785, 5783, 5781, 5779, 5769}, new MultiEffect(new SkillEffect(Skills.STRENGTH, 2, 0), new SkillEffect(Skills.ATTACK, -4, 0)), new Animation(2289), "You drink the ale. You feel slightly reinvigorated...", "...and slightly dizzy too.")),
ASGARNIAN_ALE_M(new Drink(new int[] {5739, 1919}, new MultiEffect(new SkillEffect(Skills.STRENGTH, 3, 0), new SkillEffect(Skills.ATTACK, -6, 0)))),
ASGARNIAN_ALE_M_KEG(new Drink(new int[] {5865, 5863, 5861, 5859, 5769}, new MultiEffect(new SkillEffect(Skills.STRENGTH, 3, 0), new SkillEffect(Skills.ATTACK, -6, 0)), new Animation(2289))),
ASGARNIAN_ALE_M(new Drink(new int[] {5739, 1919}, new MultiEffect(new SkillEffect(Skills.STRENGTH, 3, 0), new SkillEffect(Skills.ATTACK, -6, 0)), "You drink the ale. You feel slightly reinvigorated...", "...and slightly dizzy too.")),
ASGARNIAN_ALE_M_KEG(new Drink(new int[] {5865, 5863, 5861, 5859, 5769}, new MultiEffect(new SkillEffect(Skills.STRENGTH, 3, 0), new SkillEffect(Skills.ATTACK, -6, 0)), new Animation(2289), "You drink the ale. You feel slightly reinvigorated...", "...and slightly dizzy too.")),
AXEMANS_FOLLY(new Drink(new int[] {5751, 1919}, new MultiEffect(new SkillEffect(Skills.WOODCUTTING, 1, 0), new HealingEffect(1), new SkillEffect(Skills.STRENGTH, -3, 0), new SkillEffect(Skills.ATTACK, -3, 0)))),
AXEMANS_FOLLY_KEG(new Drink(new int[] {5825, 5823, 5821, 5819, 5769}, new MultiEffect(new SkillEffect(Skills.WOODCUTTING, 1, 0), new HealingEffect(1), new SkillEffect(Skills.STRENGTH, -3, 0), new SkillEffect(Skills.ATTACK, -3, 0)), new Animation(2289))),
AXEMANS_FOLLY_M(new Drink(new int[] {5753, 1919}, new MultiEffect(new SkillEffect(Skills.WOODCUTTING, 2, 0), new HealingEffect(2), new SkillEffect(Skills.STRENGTH, -4, 0), new SkillEffect(Skills.ATTACK, -4, 0)))),
@ -231,24 +231,26 @@ public enum Consumables {
DRAGON_BITTER_M_KEG(new Drink(new int[] {5889, 5887, 5885, 5883, 5769}, new MultiEffect(new HealingEffect(2), new SkillEffect(Skills.STRENGTH, 3, 0), new SkillEffect(Skills.ATTACK, -6, 0)), new Animation(2289))),
DWARVEN_STOUT(new Drink(new int[] {1913, 1919}, new MultiEffect(new SkillEffect(Skills.MINING, 1, 0), new SkillEffect(Skills.SMITHING, 1 ,0), new SkillEffect(Skills.ATTACK, -2, 0), new SkillEffect(Skills.STRENGTH, -2, 0), new SkillEffect(Skills.DEFENCE, -2, 0), new HealingEffect(1)), "You drink the Dwarven Stout. It tastes foul.", "It tastes pretty strong too.")),
DWARVEN_STOUT_KEG(new Drink(new int[] {5777, 5775, 5773, 5771, 5769}, new MultiEffect(new SkillEffect(Skills.MINING, 1, 0), new SkillEffect(Skills.SMITHING, 1 ,0), new SkillEffect(Skills.ATTACK, -2, 0), new SkillEffect(Skills.STRENGTH, -2, 0), new SkillEffect(Skills.DEFENCE, -2, 0), new HealingEffect(1)), new Animation(2289), "You drink the Dwarven Stout. It tastes foul.", "It tastes pretty strong too.")),
DWARVEN_STOUT_M(new Drink(new int[] {5747, 1919}, new MultiEffect(new SkillEffect(Skills.MINING, 2, 0), new SkillEffect(Skills.SMITHING, 2 ,0), new SkillEffect(Skills.ATTACK, -7, 0), new SkillEffect(Skills.STRENGTH, -7, 0), new SkillEffect(Skills.DEFENCE, -7, 0), new HealingEffect(1)))),
DWARVEN_STOUT_M_KEG(new Drink(new int[] {5857, 5855, 5853, 5851, 5769}, new MultiEffect(new SkillEffect(Skills.MINING, 2, 0), new SkillEffect(Skills.SMITHING, 2 ,0), new SkillEffect(Skills.ATTACK, -7, 0), new SkillEffect(Skills.STRENGTH, -7, 0), new SkillEffect(Skills.DEFENCE, -7, 0), new HealingEffect(1)), new Animation(2289))),
DWARVEN_STOUT_M(new Drink(new int[] {5747, 1919}, new MultiEffect(new SkillEffect(Skills.MINING, 2, 0), new SkillEffect(Skills.SMITHING, 2 ,0), new SkillEffect(Skills.ATTACK, -7, 0), new SkillEffect(Skills.STRENGTH, -7, 0), new SkillEffect(Skills.DEFENCE, -7, 0), new HealingEffect(1)), "You drink the Dwarven Stout. It tastes foul.", "It tastes pretty strong too.")),
DWARVEN_STOUT_M_KEG(new Drink(new int[] {5857, 5855, 5853, 5851, 5769}, new MultiEffect(new SkillEffect(Skills.MINING, 2, 0), new SkillEffect(Skills.SMITHING, 2 ,0), new SkillEffect(Skills.ATTACK, -7, 0), new SkillEffect(Skills.STRENGTH, -7, 0), new SkillEffect(Skills.DEFENCE, -7, 0), new HealingEffect(1)), new Animation(2289), "You drink the Dwarven Stout. It tastes foul.", "It tastes pretty strong too.")),
GREENMANS_ALE(new Drink(new int[] {1909, 1919}, new MultiEffect(new HealingEffect(1), new SkillEffect(Skills.HERBLORE, 1, 0), new SkillEffect(Skills.ATTACK, -3, 0), new SkillEffect(Skills.STRENGTH, -3, 0), new SkillEffect(Skills.DEFENCE, -3, 0)))),
GREENMANS_ALE_POH(new Drink(new int[] {7746, 7742}, new MultiEffect(new HealingEffect(1), new SkillEffect(Skills.HERBLORE, 1, 0), new SkillEffect(Skills.ATTACK, -3, 0), new SkillEffect(Skills.STRENGTH, -3, 0), new SkillEffect(Skills.DEFENCE, -3, 0)))),
GREENMANS_ALE_KEG(new Drink(new int[] {5793, 5791, 5789, 5787, 5769}, new MultiEffect(new HealingEffect(1), new SkillEffect(Skills.HERBLORE, 1, 0), new SkillEffect(Skills.ATTACK, -3, 0), new SkillEffect(Skills.STRENGTH, -3, 0), new SkillEffect(Skills.DEFENCE, -3, 0)), new Animation(2289))),
GREENMANS_ALE_M(new Drink(new int[] {5743, 1919}, new MultiEffect(new HealingEffect(1), new SkillEffect(Skills.HERBLORE, 2, 0), new SkillEffect(Skills.ATTACK, -2, 0), new SkillEffect(Skills.STRENGTH, -2, 0)))),
GREENMANS_ALE_M_KEG(new Drink(new int[] {5873, 5871, 5869, 5867, 5769}, new MultiEffect(new HealingEffect(1), new SkillEffect(Skills.HERBLORE, 2, 0), new SkillEffect(Skills.ATTACK, -2, 0), new SkillEffect(Skills.STRENGTH, -2, 0)), new Animation(2289))),
GROG(new Drink(new int[] {1915, 1919}, new MultiEffect(new SkillEffect(Skills.STRENGTH, 3, 0), new SkillEffect(Skills.ATTACK, -6, 0)))),
MOONLIGHT_MEAD(new Drink(new int[] {2955, 1919}, new HealingEffect(4), "It tastes like something just died in your mouth.")),
MOONLIGHT_MEAD_KEG(new Drink(new int[] {5817, 5815, 5813, 5811, 5769}, new HealingEffect(4), new Animation(2289), "It tastes like something just died in your mouth.")),
MOONLIGHT_MEAD_M(new Drink(new int[] {5749, 1919}, new HealingEffect(6))),
MOONLIGHT_MEAD_M_KEG(new Drink(new int[] {5897, 5895, 5893, 5891, 5769}, new HealingEffect(6), new Animation(2289))),
MOONLIGHT_MEAD(new Drink(new int[] {2955, 1919}, new HealingEffect(5), "It tastes like something just died in your mouth.")),
MOONLIGHT_MEAD_KEG(new Drink(new int[] {5817, 5815, 5813, 5811, 5769}, new HealingEffect(5), new Animation(2289), "It tastes like something just died in your mouth.")),
MOONLIGHT_MEAD_M(new Drink(new int[] {5749, 1919}, new HealingEffect(4), "It tastes like something just died in your mouth.")),
MOONLIGHT_MEAD_M_KEG(new Drink(new int[] {5897, 5895, 5893, 5891, 5769}, new HealingEffect(4), new Animation(2289), "It tastes like something just died in your mouth.")),
SLAYERS_RESPITE(new Drink(new int[] {5759, 1919}, new MultiEffect(new HealingEffect(1), new SkillEffect(Skills.SLAYER, 2, 0), new SkillEffect(Skills.ATTACK, -2, 0), new SkillEffect(Skills.STRENGTH, -2, 0)))),
SLAYERS_RESPITE_KEG(new Drink(new int[] {5841, 5839, 5837, 5835, 5769}, new MultiEffect(new HealingEffect(1), new SkillEffect(Skills.SLAYER, 2, 0), new SkillEffect(Skills.ATTACK, -2, 0), new SkillEffect(Skills.STRENGTH, -2, 0)), new Animation(2289))),
SLAYERS_RESPITE_M(new Drink(new int[] {5761, 1919}, new MultiEffect(new HealingEffect(1), new SkillEffect(Skills.SLAYER, 4, 0), new SkillEffect(Skills.ATTACK, -2, 0), new SkillEffect(Skills.STRENGTH, -2, 0)))),
SLAYERS_RESPITE_M_KEG(new Drink(new int[] {5841, 5839, 5837, 5835, 5769}, new MultiEffect(new HealingEffect(1), new SkillEffect(Skills.SLAYER, 4, 0), new SkillEffect(Skills.ATTACK, -2, 0), new SkillEffect(Skills.STRENGTH, -2, 0)), new Animation(2289))),
WIZARDS_MIND_BOMB(new Drink(new int[] {1907, 1919}, new WizardsMindBombEffect(), "You drink the Wizard's Mind Bomb.", "You feel very strange.")),
MATURE_WMB(new Drink(new int[] {5741, 1919}, new MatureWmbEffect())),
WIZARDS_MIND_BOMB_KEG(new Drink(new int[] {5801, 5799, 5797, 5795, 5769}, new WizardsMindBombEffect(), new Animation(2289), "You drink the Wizard's Mind Bomb.", "You feel very strange.")),
MATURE_WMB(new Drink(new int[] {5741, 1919}, new MatureWmbEffect(), "You drink the mature Wizard's Mind Bomb.", "You feel very strange.")),
MATURE_WMB_KEG(new Drink(new int[] {5881, 5879, 5877, 5875, 5769}, new MatureWmbEffect(), new Animation(2289), "You drink the mature Wizard's Mind Bomb.", "You feel very strange.")),
/** Cocktails */
FRUIT_BLAST(new Drink(new int[] {2084, 2026}, new HealingEffect(9))),

View file

@ -49,7 +49,7 @@ class ShootingStar(var level: ShootingStarType = ShootingStarType.values().rando
"South-west Varrock mine" to Location.create(3176, 3362, 0), // South-west Varrock mine / Champion's Guild mine
"Varrock east bank" to Location.create(3259, 3407, 0), // Varrock east bank / Rune shop
"Lumbridge Swamp south-east mine" to Location.create(3227, 3150, 0), // Lumbridge Swamp south-east mine
//"Burgh de Rott bank" to Location.create(3500, 3219, 0), // Burgh de Rott bank (requires quest to enter)
"Burgh de Rott bank" to Location.create(3500, 3219, 0), // Burgh de Rott bank (requires quest to enter)
"Canifis Bank" to Location.create(3504, 3487, 0), // Canifis bank
"Mos Le'Harmless bank" to Location.create(3687, 2969, 0), // Mos Le'Harmless bank (requires quest to enter but is currently accessible for Slayer)
"Gnome stronghold Bank" to Location.create(2460, 3432, 0), // Gnome stronghold bank

View file

@ -97,7 +97,7 @@ class ShootingStarPlugin : LoginListener, InteractionListener, TickListener, Com
fun teleportToStar(player: Player) {
val condition: (p: Player) -> Boolean = when (star.location.toLowerCase()) {
"canifis bank" -> {p -> requireQuest(p, Quests.PRIEST_IN_PERIL, "to access this.")}
//"burgh de rott bank" -> {p -> hasRequirement(p, Quests.IN_AID_OF_THE_MYREQUE)} //disabled: crash
"burgh de rott bank" -> {p -> hasRequirement(p, Quests.IN_AID_OF_THE_MYREQUE)}
"crafting guild" -> {p -> hasLevelStat(p, Skills.CRAFTING, 40)}
"lletya bank" -> {p -> hasRequirement(p, Quests.MOURNINGS_END_PART_I)}
"jatizso mine" -> {p -> hasRequirement(p, Quests.THE_FREMENNIK_ISLES)}
@ -120,7 +120,7 @@ class ShootingStarPlugin : LoginListener, InteractionListener, TickListener, Com
1 -> dialogue("WARNING: The star is located in the wilderness.").also { stage++ }
2 -> player.dialogueInterpreter.sendOptions("Teleport to the star?", "Yes", "No").also { stage++ }
3 -> when (buttonID) {
1 -> end().also { teleportToStar(player) }
1 -> teleportToStar(player)
2 -> end()
}
}

View file

@ -41,6 +41,7 @@ class EmptyOptionListener : InteractionListener {
BUCKET_OF_COMPOST(Items.COMPOST_6032, Items.BUCKET_1925, "You empty the bucket of compost."),
BUCKET_OF_SUPERCOMPOST(Items.SUPERCOMPOST_6034, Items.BUCKET_1925, "You empty the bucket of supercompost."),
BUCKET_OF_SLIME(Items.BUCKET_OF_SLIME_4286, Items.BUCKET_1925, "You empty the contents of the bucket on the floor.", Sounds.LIQUID_2401),
APPLE_MUSH(Items.APPLE_MUSH_5992,Items.BUCKET_1925, "You empty the bucket of apple mush."),
VIAL_OF_WATER(Items.VIAL_OF_WATER_227, Items.VIAL_229, "You empty the vial.", Sounds.LIQUID_2401),
BOWL_OF_WATER(Items.BOWL_OF_WATER_1921, Items.BOWL_1923, "You empty the contents of the bowl onto the floor.", Sounds.LIQUID_2401),
BOWL_OF_HOT_WATER(Items.BOWL_OF_HOT_WATER_4456, Items.BOWL_1923, "You empty the contents of the bowl onto the floor.", Sounds.LIQUID_2401),

View file

@ -50,15 +50,15 @@ class EnchantedJewelleryListener : InteractionListener {
jewellery.use(player, item, 0, isEquipped)
return
}
openDialogue(player,EnchantedJewelleryDialogueFile(jewellery,item,isEquipped))
openDialogue(player,EnchantedJewelleryDialogueFile(jewellery, item, true, isEquipped))
}
}
class EnchantedJewelleryDialogueFile(val jewellery: EnchantedJewellery, val item: Item, val isEquipped: Boolean) : DialogueFile() {
class EnchantedJewelleryDialogueFile(val jewellery: EnchantedJewellery, val item: Item, val replace: Boolean, val isEquipped: Boolean) : DialogueFile() {
override fun handle(componentID: Int, buttonID: Int) {
when (stage) {
START_DIALOGUE -> {
interpreter!!.sendOptions("Where would you like to go?", *jewellery.options)
interpreter!!.sendOptions("Where would you like to teleport to?", *jewellery.options)
stage++
}
1 -> end().also { jewellery.use(player!!, item, buttonID - 1, isEquipped) }

View file

@ -0,0 +1,19 @@
package content.global.handlers.item.withitem
import core.api.addItem
import core.api.animate
import core.api.removeItem
import core.game.interaction.InteractionListener
import org.rs09.consts.Items
class CalquatKeg : InteractionListener{
override fun defineListeners() {
onUseWith(ITEM, Items.CALQUAT_FRUIT_5980, Items.KNIFE_946) { player, _, _ ->
if (removeItem(player, Items.CALQUAT_FRUIT_5980)){
animate(player,2290)
addItem(player, Items.CALQUAT_KEG_5769)
}
return@onUseWith true
}
}
}

View file

@ -0,0 +1,52 @@
package content.global.handlers.npc
import core.api.*
import core.api.playGlobalAudio
import core.api.sendGraphics
import core.game.node.entity.Entity
import core.game.node.entity.npc.NPCBehavior
import org.rs09.consts.NPCs
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import core.tools.RandomFunction
import core.game.world.update.flag.context.Graphics
import core.game.node.entity.combat.ImpactHandler.HitsplatType;
private val chinchompaIDs = intArrayOf(
NPCs.CHINCHOMPA_5079,
NPCs.CARNIVOROUS_CHINCHOMPA_5080
)
/**
* Minimal evasive approach to the Chinchompas exploding on death cause by combat and not by trapping.
*/
class ChinchompaDeathListener: NPCBehavior(*chinchompaIDs) {
override fun onDeathFinished(self: NPC, killer: Entity) {
// If it 'died' from getting trapped an attribute is set of 'hunter'.
if (!self.attributes.containsKey("hunter") && killer is Player) {
val explosionLoc = self.location
val validExplosionDmgTiles = explosionLoc.surroundingTiles
val chinExplosionGFX = Graphics(157, 96)
sendGraphics(chinExplosionGFX, explosionLoc)
playGlobalAudio(explosionLoc, 158)
if (validExplosionDmgTiles.contains(killer.location)) {
//As weird this looks, it does allow to have a hit from 0 to 2
val hitAmount = RandomFunction.random(0, 3)
impact(killer, hitAmount, HitsplatType.NORMAL)
}
}
}
/**
* At content.global.skill.hunter.TrapSetting
* sets an attribute of 'hunter' during it's getCatchPulse for reward rolling, but it never cleans it up after.
* These NPCs is a member of NPC class although exists a HunterNPC class, but it's mainly used for imps.
* Logically you'd do this after death finishes, but it would interfere with the reward rolls and I fear it may affect other hunter NPCs.
*/
override fun onRespawn(self: NPC) {
self.removeAttribute("hunter")
}
}

View file

@ -1,6 +1,7 @@
package content.global.handlers.npc
import content.data.consumables.Consumables
import content.global.skill.cooking.fermenting.CalquatDecant
import core.api.*
import core.game.consumable.Potion
import core.game.dialogue.DialogueFile
@ -14,23 +15,39 @@ class DecantListener : InteractionListener {
companion object {
val potions = Consumables.potions.toIntArray()
val decantables = potions + CalquatDecant.allKegIds
}
override fun defineListeners() {
on(IntType.NPC,"decant"){ player, node ->
val (toRemove, toAdd) = decantContainer(player.inventory)
for (item in toRemove)
removeItem(player, item)
for (item in toAdd)
// Decant potions
val (potionRemove, potionAdd) = decantContainer(player.inventory)
for (item in potionRemove)
if (!removeItem(player, item)) { return@on false }
for (item in potionAdd)
addItem(player, item.id, item.amount)
// Decant kegs
val (kegRemove, kegAdd) = CalquatDecant.decantCalquat(player.inventory)
for (item in kegRemove)
if (!removeItem(player, item)) { return@on false }
for (item in kegAdd)
addItem(player, item.id, item.amount)
player.dialogueInterpreter.open(DecantingDialogue(),node.asNpc())
return@on true
}
onUseWith(IntType.ITEM, potions, *potions) { player, used, with ->
onUseWith(IntType.ITEM, decantables, *decantables) { player, used, with ->
if (used !is Item) return@onUseWith false
if (with !is Item) return@onUseWith false
// Check if these are kegs - handle with CalquatDecant
if (CalquatDecant.isKeg(used.id) || CalquatDecant.isKeg(with.id)) {
return@onUseWith CalquatDecant.combineKegs(player, used, with)
}
// Otherwise handle as potions
// Verify these are both the same potion types
val potionUsed = Consumables.getConsumableById(used.id)?.consumable as? Potion ?: return@onUseWith false
val potionWith = Consumables.getConsumableById(with.id)?.consumable as? Potion ?: return@onUseWith false
@ -83,6 +100,57 @@ class DecantListener : InteractionListener {
return@onUseWith true
}
// Decant keg into beer glass
onUseWith(IntType.ITEM, Items.BEER_GLASS_1919, *CalquatDecant.allKegIds) { player, used, with ->
if (used !is Item || with !is Item) return@onUseWith false
// Determine which is the glass and which is the keg
val (glass, keg) = if (used.id == Items.BEER_GLASS_1919) {
used to with
} else {
with to used
}
return@onUseWith CalquatDecant.pourIntoGlass(player, glass, keg)
}
// Barbarian decanting: use a 4-dose potion and an empty vial together to make two 2-dose potions
onUseWith(IntType.ITEM, potions, Items.VIAL_229) { player, used, with ->
// Check if player has started barbarian herblore training
if (getAttribute(player, "/save:barbtraining:herblore", 0) == 0) {
return@onUseWith false
}
if (used !is Item) return@onUseWith false
if (with !is Item) return@onUseWith false
// Determine which item is the potion and which is the vial
val (potionItem) = if (used.id == Items.VIAL_229) {
Pair(with, used)
} else {
Pair(used, with)
}
// Check if the potion is 4 doses
val potion = Consumables.getConsumableById(potionItem.id)?.consumable as? Potion ?: return@onUseWith false
val dose = potion.getDose(potionItem)
if (dose != 4) {
return@onUseWith false
}
// Get the 2-dose version of this potion by index
val twoDoseId = potion.ids[2]
// Replace ingredients with products
replaceSlot(player, with.slot, Item(twoDoseId))
replaceSlot(player, used.slot, Item(twoDoseId))
playAudio(player, 2401)
return@onUseWith true
}
}
internal class DecantingDialogue : DialogueFile(){

View file

@ -90,8 +90,6 @@ public final class BuildingUtils {
for (int menuIndex = 0; menuIndex < 7; menuIndex++) {
int itemsStringOffset = 97 + (menuIndex * 5);
//97 +
if (menuIndex >= hotspot.getDecorations().length || (hotspot.getDecorations()[menuIndex] != null && hotspot.getDecorations()[menuIndex].isInvisibleNode())) {
for (int j = 0; j < 5; j++) {
player.getPacketDispatch().sendString("", 396, itemsStringOffset + j);
@ -104,43 +102,24 @@ public final class BuildingUtils {
Decoration decoration = hotspot.getDecorations()[menuIndex];
items[BUILD_INDEXES[menuIndex]] = new Item(decoration.getInterfaceItem());
player.getPacketDispatch().sendString(ItemDefinition.forId(decoration.getInterfaceItem()).getName(), 396, itemsStringOffset);
boolean hasRequirements = player.getSkills().getLevel(Skills.CONSTRUCTION) >= decoration.getLevel();
boolean hasRequirements = player.getSkills().getLevel(Skills.CONSTRUCTION) >= decoration.getLevel(); //nails are checked in buildDecoration
Item[] reqItems = decoration.getItems();
String[] reqsText = decoration.getReqsText();
for (int j = 0; j < 4; j++) {
if (j >= decoration.getItems().length) {
if (j == decoration.getItems().length && decoration.getNailAmount() > 0) {
player.getPacketDispatch().sendString("Nails: " + decoration.getNailAmount(), 396, (itemsStringOffset + 1) + j);
} else {
player.getPacketDispatch().sendString("", 396, (itemsStringOffset + 1) + j);
}
} else {
Item item = decoration.getItems()[j];
if (!player.getInventory().containsItem(item)) {
hasRequirements = false;
}
String s = item.getName() + ": " + item.getAmount();
/*if (j > 1 && (decoration == Decoration.RUNE_CASE1 || decoration == Decoration.RUNE_CASE2)) {
if (j == 3) {
offset--;
item = decoration.getItems()[++j];
s = item.getName() + ": " + item.getAmount();
}
item = decoration.getItems()[j + 1];
s += ", " + item.getName() + ": " + item.getAmount();
player.getPacketDispatch().sendString(s, 396, 15 + offset + j);
continue;
}*/
player.getPacketDispatch().sendString(s, 396, (itemsStringOffset + 1) + j);
if (j < reqItems.length && !player.getInventory().containsItem(reqItems[j])) {
hasRequirements = false;
}
player.getPacketDispatch().sendString(reqsText[j], 396, (itemsStringOffset + 1) + j);
}
if (hasRequirements) {
c261Value += (1 << (menuIndex + 1));
}
setVarp(player, 1485 + menuIndex, hasRequirements || player.isStaff() ? 1 : 0);
setVarp(player, 1485 + menuIndex, hasRequirements || player.isStaff() ? 1 : 0);
player.getPacketDispatch().sendString("Level " + decoration.getLevel(), 396, 140 + menuIndex);
//player.getPacketDispatch().sendItemZoomOnInterface(items[i].protocol(), 50000, 396, 49 + i);
}
setVarp(player, 261, c261Value);
setVarp(player, 261, c261Value);
PacketRepository.send(ContainerPacket.class, new ContainerContext(player, 396, 132, 8, items, false));
}

View file

@ -454,7 +454,7 @@ public enum Decoration {
* Quest hall decorations.
*/
ANTIDRAGON_SHIELD(13522, 8282, 47, 280, new Item[] { new Item(Items.TEAK_PLANK_8780, 3), new Item(Items.ANTI_DRAGON_SHIELD_1540) }, new Item[] { new Item(Items.ANTI_DRAGON_SHIELD_1540) }),
AMULET_OF_GLORY (13523, 8283, 47, 290, new Item[] { new Item(Items.TEAK_PLANK_8780, 3), new Item(Items.AMULET_OF_GLORY_1704) }, new Item[] { new Item(Items.AMULET_OF_GLORY_1704) }),
AMULET_OF_GLORY (13523, 8283, 47, 290, new Item[] { new Item(Items.TEAK_PLANK_8780, 3), new Item(Items.AMULET_OF_GLORY_1704) }, new Item[] { new Item(Items.AMULET_OF_GLORY_1704) }, new String[] { "Teak plank: 3", "Amulet of Glory", "(uncharged): 1" }),
CAPE_OF_LEGENDS (13524, 8284, 47, 300, new Item[] { new Item(Items.TEAK_PLANK_8780, 3), new Item(Items.CAPE_OF_LEGENDS_1052) }, new Item[] { new Item(Items.CAPE_OF_LEGENDS_1052) }),
KING_ARTHUR (13510, 8285, 35, 211, new Item[] { new Item(Items.TEAK_PLANK_8780, 3), new Item(Items.ARTHUR_PORTRAIT_7995) }),
ELENA (13511, 8286, 35, 211, new Item[] { new Item(Items.TEAK_PLANK_8780, 3), new Item(Items.ELENA_PORTRAIT_7996) }),
@ -727,23 +727,42 @@ public enum Decoration {
private final int[] objectIds;
/**
* If this node should be invisible to user build options
* If this node should be invisible to user build options.
*/
private boolean invisibleNode;
/**
* Constructs a new object, no items, no tools, no refund items.
* The requirements text to show for this node.
*/
private String[] reqsText = {"", "", "", ""};
private String[] generateDefaultReqsText(Item[] items, int nailAmount) {
String[] ret = {"", "", "", ""};
for (int i = 0; i < 4; i++) {
if (i >= items.length) {
if (i == items.length && nailAmount > 0) {
ret[i] = "Nails: " + nailAmount;
}
} else {
ret[i] = items[i].getName() + ": " + items[i].getAmount();
}
}
return ret;
}
/**
* Constructs a new object, default items, default tools, default refund items, default requirements text.
* @param objectId The object id.
* @param interfaceItem The item id for the building interface.
* @param level The level required.
* @param experience The experience gained.
*/
Decoration(int objectId, int interfaceItem, int level, int experience) {
this(objectId, interfaceItem, level, experience, new int[] { Items.HAMMER_2347, Items.SAW_8794 }, new Item[] {}, new Item[] {});
this(objectId, interfaceItem, level, experience, new int[] { Items.HAMMER_2347, Items.SAW_8794 }, new Item[] {}, new Item[] {}, new String[] {});
}
/**
* Constructs a new object, no tools, no refund items.
* Constructs a new object, default tools, default refund items, default requirements text.
* @param objectId The object id.
* @param interfaceItem The item id for the building interface.
* @param level The level required.
@ -751,11 +770,12 @@ public enum Decoration {
* @param items The items required.
*/
Decoration(int objectId, int interfaceItem, int level, int experience, Item[] items) {
this(objectId, interfaceItem, level, experience, new int[] { Items.HAMMER_2347, Items.SAW_8794 }, items, new Item[] {});
this(objectId, interfaceItem, level, experience, new int[] { Items.HAMMER_2347, Items.SAW_8794 }, items, new Item[] {}, new String[] {});
this.reqsText = generateDefaultReqsText(items, getNailAmount());
}
/**
* Constructs a new object, no refund items.
* Constructs a new object, default refund items, default requirements text.
* @param objectId The object id.
* @param interfaceItem The item id for the building interface.
* @param level The level required.
@ -764,7 +784,8 @@ public enum Decoration {
* @param items The items required.
*/
Decoration(int objectId, int interfaceItem, int level, int experience, int[] tools, Item[] items) {
this(objectId, interfaceItem, level, experience, tools, items, new Item[] {});
this(objectId, interfaceItem, level, experience, tools, items, new Item[] {}, new String[] {});
this.reqsText = generateDefaultReqsText(items, getNailAmount());
}
/**
@ -777,7 +798,21 @@ public enum Decoration {
* @param refundItems The items to be refunded when the item is removed.
*/
Decoration(int objectId, int interfaceItem, int level, int experience, Item[] items, Item[] refundItems) {
this(objectId, interfaceItem, level, experience, new int[] { Items.HAMMER_2347, Items.SAW_8794 }, items, refundItems);
this(objectId, interfaceItem, level, experience, new int[] { Items.HAMMER_2347, Items.SAW_8794 }, items, refundItems, new String[] {});
this.reqsText = generateDefaultReqsText(items, getNailAmount());
}
/**
* Constructs a new object, default tools.
* @param objectId The object id.
* @param interfaceItem The item id for the building interface.
* @param level The level required.
* @param experience The experience gained.
* @param items The items required.
* @param refundItems The items to be refunded when the item is removed.
*/
Decoration(int objectId, int interfaceItem, int level, int experience, Item[] items, Item[] refundItems, String[] reqsText) {
this(objectId, interfaceItem, level, experience, new int[] { Items.HAMMER_2347, Items.SAW_8794 }, items, refundItems, reqsText);
}
/**
@ -789,8 +824,9 @@ public enum Decoration {
* @param tools The tools needed.
* @param items The items required.
* @param refundItems The items to be refunded when the item is removed.
* @param reqsText The requirements text to be shown in the interface.
*/
Decoration(int objectId, int interfaceItem, int level, int experience, int[] tools, Item[] items, Item[] refundItems) {
Decoration(int objectId, int interfaceItem, int level, int experience, int[] tools, Item[] items, Item[] refundItems, String[] reqsText) {
this.objectId = objectId;
this.objectIds = null;
this.interfaceItem = interfaceItem;
@ -799,6 +835,9 @@ public enum Decoration {
this.tools = tools;
this.items = items;
this.refundItems = refundItems;
if (reqsText.length > 0) {
System.arraycopy(reqsText, 0, this.reqsText, 0, reqsText.length);
}
}
/**
@ -822,18 +861,6 @@ public enum Decoration {
Decoration(int[] objectIds, int interfaceItem, int level, int experience, Item[] items) {
this(objectIds, interfaceItem, level, experience, new int[] { Items.HAMMER_2347, Items.SAW_8794 }, items, new Item[] {});
}
/**
* Constructs a new object no refund items.
* @param objectIds The object id.
* @param interfaceItem The item id for the building interface.
* @param level The level required.
* @param experience The experience gained.
* @param tools The tools needed.
* @param items The items required.
*/
Decoration(int[] objectIds, int interfaceItem, int level, int experience, int[] tools, Item[] items) {
this(objectIds, interfaceItem, level, experience, tools, items, new Item[] {});
}
/**
* Constructs a new object.
@ -1000,4 +1027,12 @@ public enum Decoration {
public boolean isInvisibleNode() {
return invisibleNode;
}
/**
* Gets the requirements text.
* @return The requirements text.
*/
public String[] getReqsText() {
return reqsText;
}
}

View file

@ -1,6 +1,7 @@
package content.global.skill.construction.decoration.portalchamber;
import content.data.Quests;
import content.global.skill.construction.Hotspot;
import content.global.skill.runecrafting.Rune;
import core.cache.def.impl.SceneryDefinition;
@ -18,6 +19,7 @@ import core.plugin.Plugin;
import core.plugin.ClassScanner;
import static content.region.kandarin.ardougne.quest.plaguecity.PlagueCityListeners.ARDOUGNE_TELE_ATTRIBUTE;
import static core.api.ContentAPIKt.*;
/**
* PortalChamberPlugin
@ -89,15 +91,14 @@ public class PortalChamberPlugin extends OptionHandler {
}
for (Locations l : Locations.values()) {
if (l.name().contains(identifier)) {
if (l == Locations.ARDOUGNE){
if (!player.getAttribute(ARDOUGNE_TELE_ATTRIBUTE, false)) {
player.sendMessage("You do not have the requirements to direct the portal there");
return;
}
if ((l == Locations.ARDOUGNE && !getAttribute(player, ARDOUGNE_TELE_ATTRIBUTE, false)) ||
(l == Locations.KHARYRLL && !isQuestComplete(player, Quests.DESERT_TREASURE))) {
sendMessage(player, "You do not have the requirements to direct the portal there.");
return;
}
Item[] runes = l.runes;
if (!player.getInventory().containsItems(runes)) {
player.sendMessage("You do not have the required runes to build this portal");
sendMessage(player, "You do not have the required runes to build this portal.");
return;
}
player.getInventory().remove(runes);

View file

@ -1,53 +1,53 @@
package content.global.skill.construction.decoration.questhall
import content.data.EnchantedJewellery
import content.global.handlers.item.EnchantedJewelleryListener.EnchantedJewelleryDialogueFile
import core.api.delayScript
import core.api.lock
import core.api.openDialogue
import core.api.playGlobalAudio
import core.api.queueScript
import core.api.resetAnimator
import core.api.stopExecuting
import core.api.teleport
import core.api.visualize
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.interaction.QueueStrength
import core.game.node.Node
import core.game.node.entity.player.Player
import core.game.node.item.Item
import core.game.node.scenery.Scenery
import core.game.system.task.Pulse
import core.game.world.map.Location
import core.game.world.update.flag.context.Animation
import core.game.world.update.flag.context.Graphics
import org.rs09.consts.Items
import org.rs09.consts.Sounds
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
class MountedGlory : InteractionListener {
val MOUNTED_GLORY = 13523
val TELEPORTS = arrayOf(
Location.create(3087, 3495, 0),
Location.create(2919, 3175, 0),
Location.create(3104, 3249, 0),
Location.create(3304, 3124, 0)
)
val TELEPORTS = EnchantedJewellery.AMULET_OF_GLORY.locations
override fun defineListeners() {
on(MOUNTED_GLORY, IntType.SCENERY, "Edgeville") { player, `object` ->
mountedGloryAction(player, `object`, 0)
return@on true
}
on(MOUNTED_GLORY, IntType.SCENERY, "Karamja") { player, `object` ->
mountedGloryAction(player, `object`, 1)
return@on true
}
on(MOUNTED_GLORY, IntType.SCENERY, "Draynor Village") { player, `object` ->
mountedGloryAction(player, `object`, 2)
return@on true
}
on(MOUNTED_GLORY, IntType.SCENERY, "Al Kharid") { player, `object` ->
mountedGloryAction(player, `object`, 3)
return@on true
}
on(MOUNTED_GLORY, IntType.SCENERY, "Rub") { player, _ ->
openDialogue(player,EnchantedJewelleryDialogueFile(EnchantedJewellery.AMULET_OF_GLORY, Item(Items.AMULET_OF_GLORY4_1712), false, false))
return@on true
}
}
private fun mountedGloryAction(player : Player, `object` : Node, int : Int) {
if (player.houseManager.isBuildingMode) {
@ -57,21 +57,16 @@ class MountedGlory : InteractionListener {
if (!player.zoneMonitor.teleport(1, Item(Items.AMULET_OF_GLORY_1704))) {
return
}
Executors.newSingleThreadScheduledExecutor().schedule({
player.pulseManager.run(object : Pulse() {
var counter = 0
override fun pulse(): Boolean {
when (counter++) {
1 -> {
player.lock(5)
player.visualize(Animation(714), Graphics(308, 100, 50))
playGlobalAudio(player.location, Sounds.TELEPORT_ALL_200)
}
4 -> player.animator.reset().also { teleport(player, TELEPORTS[int]) }
}
return false
queueScript(player, 0, QueueStrength.SOFT) { stage ->
if (stage == 0) {
lock(player, 4)
visualize(player, Animation(714), Graphics(308, 100, 50))
playGlobalAudio(player.location, Sounds.TELEPORT_ALL_200)
return@queueScript delayScript(player, 3)
}
})
}, 0, TimeUnit.SECONDS)
resetAnimator(player)
teleport(player, TELEPORTS[int])
return@queueScript stopExecuting(player)
}
}
}

View file

@ -1,98 +0,0 @@
package content.global.skill.construction.decoration.questhall;
import core.cache.def.impl.SceneryDefinition;
import core.game.dialogue.DialogueAction;
import core.game.dialogue.DialogueInterpreter;
import core.game.interaction.OptionHandler;
import core.game.node.Node;
import core.game.node.entity.player.Player;
import core.game.system.task.Pulse;
import core.game.world.GameWorld;
import core.game.world.map.Location;
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.playGlobalAudio;
/**
* Handles the POH Mounted Glory.
* @author Splinter
*/
@Initializable
public class MountedGloryPlugin extends OptionHandler {
/**
* Represents the teleport animation.
*/
private static final Animation ANIMATION = new Animation(714);
/**
* Represents the graphics to use.
*/
private static final Graphics GRAPHICS = new Graphics(308, 100, 50);
@Override
public Plugin<Object> newInstance(Object arg) throws Throwable {
SceneryDefinition.forId(13523).getHandlers().put("option:rub", this);
return this;
}
@Override
public boolean handle(Player player, Node node, String option) {
DialogueInterpreter interpreter = player.getDialogueInterpreter();
interpreter.sendOptions("Select a location.", "Edgeville", "Karamja", "Draynor Village", "Al-Kharid", "Nowhere.");
interpreter.addAction(new DialogueAction() {
@Override
public void handle(Player player, int buttonId) {
switch (buttonId) {
case 2:
teleport(player, Location.create(3087, 3495, 0));
break;
case 3:
teleport(player, Location.create(2919, 3175, 0));
break;
case 4:
teleport(player, Location.create(3081, 3250, 0));
break;
case 5:
teleport(player, Location.create(3304, 3124, 0));
break;
}
}
});
return true;
}
/**
* Method used to teleport to a location.
* @param player the player.
* @param location the location.
*/
private boolean teleport(final Player player, final Location location) {
if (player.isTeleBlocked()) {
player.sendMessage("A magical force has stopped you from teleporting.");
return false;
}
player.lock();
player.visualize(ANIMATION, GRAPHICS);
playGlobalAudio(player.getLocation(), Sounds.TELEPORT_ALL_200);
player.getImpactHandler().setDisabledTicks(4);
GameWorld.getPulser().submit(new Pulse(4, player) {
@Override
public boolean pulse() {
player.unlock();
player.getProperties().setTeleportLocation(location);
player.getAnimator().reset();
return true;
}
});
return true;
}
}

View file

@ -94,6 +94,7 @@ public enum CookableItems {
RAW_OOMLIE(Items.RAW_OOMLIE_2337, 0, Items.BURNT_OOMLIE_2426, 50, 0, 0, 0, 0, 0), // always burns
OOMLIE_WRAP(Items.COOKED_OOMLIE_WRAP_2343, Items.WRAPPED_OOMLIE_2341, Items.BURNT_OOMLIE_WRAP_2345, 50, 30, 106, 450, 112, 476),
SEAWEED(Items.SEAWEED_401, 0, Items.SODA_ASH_1781, 0, 0, 0, 0, 0, 0),
BARLEY(Items.BARLEY_6006, 0, Items.BARLEY_MALT_6008, 0, 0, 0, 0, 0, 0),
/**
* Sinew gets overridden by BEEF in this enum, due to values being looked up by the items RAW id.

View file

@ -32,6 +32,7 @@ class CookingRewrite : InteractionListener {
list.add(Items.COOKED_MEAT_2142)
list.add(RAW_BEEF_2132)
list.add(RAW_BEAR_MEAT_2136)
list.add(Items.BARLEY_6006)
list.add(SEAWEED_401)
list.add(Items.BOWL_OF_WATER_1921)
RAW_FOODS = list.toIntArray()
@ -76,7 +77,7 @@ class CookingRewrite : InteractionListener {
player.dialogueInterpreter.open(CookingDialogue(item.id,9436,true,obj,item.id))
return@onUseWith true
}
BREAD_DOUGH_2307, UNCOOKED_CAKE_1889 -> if (!range) {
BREAD_DOUGH_2307, UNCOOKED_CAKE_1889, Items.BARLEY_6006 -> if (!range) {
player.packetDispatch.sendMessage("You need to cook this on a range.")
return@onUseWith false
}
@ -104,7 +105,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, 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) {

View file

@ -200,6 +200,9 @@ public class StandardCookingPulse extends Pulse {
if (product.getId() == Items.SODA_ASH_1781) {
return "You burn the seaweed into soda ash.";
}
if (product.getId() == Items.BARLEY_MALT_6008) {
return null; // Both live branches of Jagexscape lack a message for this and I can't find period evidence of a message
}
if (CookableItems.intentionalBurn(food.getId())) {
return "You deliberately burn the perfectly good piece of meat.";
}

View file

@ -0,0 +1,187 @@
package content.global.skill.cooking.fermenting
import core.api.*
import core.game.interaction.QueueStrength
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.game.node.scenery.Scenery
import core.game.world.map.Location
import core.game.world.update.flag.context.Animation
import core.tools.ticksToCycles
import org.rs09.consts.Items
import org.rs09.consts.Scenery as SceneryIds
class AppleMushListener : InteractionListener {
private data class BarrelLocations(
val start: Location, // Where the player begins the mushing animation from
val jump: Location, // Where the player ends the mushing animation at
val tap: Location, // Where the apple barrel's tap is located
val stand: Location // Where the player stands to collect from the tap
)
private val barrelLocations = mapOf(
14747 to BarrelLocations( // Port Phasmatys
start = Location.create(3676, 9959, 0),
jump = Location.create(3676, 9957, 0),
tap = Location.create(3677, 9959, 0),
stand = Location.create(3678, 9959, 0)
),
11679 to BarrelLocations( // Keldagrim
start = Location.create(2914, 10193, 1),
jump = Location.create(2914, 10191, 1),
tap = Location.create(2915, 10193, 1),
stand = Location.create(2916, 10193, 1)
),
9780 to BarrelLocations( // Gnome Orchard
start = Location.create(2484, 3375, 0),
jump = Location.create(2484, 3373, 0),
tap = Location.create(2485, 3375, 0),
stand = Location.create(2486, 3375, 0)
)
)
private val emptyAppleBarrels = intArrayOf(
SceneryIds.APPLE_BARREL_7403,
SceneryIds.APPLE_BARREL_8807
)
private val fullAppleBarrels = intArrayOf(
SceneryIds.APPLE_BARREL_7404,
SceneryIds.APPLE_BARREL_8808
)
private val appleTaps = intArrayOf(
SceneryIds.BARREL_TAP_7406,
SceneryIds.BARREL_TAP_8737
)
private val MUSH_ANIM = Animation(2306)
override fun defineListeners() {
onUseWith(SCENERY, Items.COOKING_APPLE_1955, *emptyAppleBarrels) { player, _, with ->
if (getDynLevel(player, Skills.COOKING) < 14) {
sendDialogue(player, "You need a Cooking level of at least 14 in order to do this.")
return@onUseWith true
}
if (!inInventory(player, Items.COOKING_APPLE_1955, 4)) {
sendDialogue(player, "You need 4 cooking apples to make apple mush.")
return@onUseWith true
}
if (!inInventory(player, Items.BUCKET_1925)) {
sendDialogue(player, "You need a bucket to collect the apple mush.")
return@onUseWith true
}
if (inInventory(player, Items.COOKING_APPLE_1955, 4)) {
startMushing(player, with.asScenery())
}
return@onUseWith true
}
onUseWith(SCENERY, Items.BUCKET_1925, *appleTaps) { player, _, _ ->
if (getAttribute(player, "/save:cooking:brewing-apples", false)) {
if (removeItem(player, Item(Items.BUCKET_1925, 1))) {
addItem(player, Items.APPLE_MUSH_5992, 1)
sendMessage(player, "You fill a bucket with apple mush.")
}
removeAttribute(player, "/save:cooking:brewing-apples")
return@onUseWith true
}
sendMessage(player, "Nothing interesting happens.")
return@onUseWith false
}
}
private fun startMushing(player: Player, barrel: Scenery) {
val initLoc = player.location
val barrelLoc = barrel.location
val locations = barrelLocations[player.location.regionId]
val startLoc = locations?.start
val jumpLoc = locations?.jump
val standLoc = locations?.stand
val tapLoc = locations?.tap
val fastStart: Boolean = initLoc == startLoc
// https://www.youtube.com/watch?v=7I_Ec_tQ-tc
// Has to be soft or else you can log out and clog the barrel
queueScript(player, 1 ,QueueStrength.SOFT) { counter ->
when (counter) {
0 -> {
stopWalk(player)
forceWalk(player, startLoc!!, "smart")
lock(player, MUSH_ANIM.duration + 7)
return@queueScript delayScript(player, if (fastStart) 1 else 4)
}
1 -> {
face(player, barrelLoc)
if (!removeItem(player, Item(Items.COOKING_APPLE_1955, 4))) { return@queueScript stopExecuting(player) }
setAttribute(player, "/save:cooking:brewing-apples", true)
return@queueScript delayScript(player, 0)
}
2 -> {
forceMove(player, startLoc!!, barrelLoc, 0, ticksToCycles(1), anim = MUSH_ANIM.id)
if (barrel.id == SceneryIds.APPLE_BARREL_7403) sendGraphics(414, barrelLoc)
else sendGraphics(413, barrelLoc)
swapBarrel(barrel)
return@queueScript delayScript(player, MUSH_ANIM.duration - 1)
}
3 -> {
forceMove(player, barrelLoc, jumpLoc!!, 0, ticksToCycles(1))
return@queueScript delayScript(player, 2)
}
4 -> {
stopWalk(player)
return@queueScript delayScript(player, 1)
}
5 -> {
unlock(player)
stopWalk(player)
forceWalk(player, standLoc!!, "smart")
lock(player, 5)
return@queueScript delayScript(player, 4)
}
6 -> {
stopWalk(player)
face(player, tapLoc!!)
return@queueScript delayScript(player, 1)
}
7 -> {
if (removeItem(player, Item(Items.BUCKET_1925, 1))) {
addItem(player, Items.APPLE_MUSH_5992, 1)
sendMessage(player, "You fill a bucket with apple mush.")
}
removeAttribute(player, "/save:cooking:brewing-apples")
val currentBarrel = getScenery(barrelLoc)
currentBarrel?.let { swapBarrel(it) }
unlock(player)
return@queueScript stopExecuting(player)
}
else -> return@queueScript stopExecuting(player)
}
}
unlock(player)
}
private fun swapBarrel(barrel: Scenery) {
val emptyIndex = emptyAppleBarrels.indexOf(barrel.id)
val fullIndex = fullAppleBarrels.indexOf(barrel.id)
val newId = when {
emptyIndex != -1 -> fullAppleBarrels[emptyIndex]
fullIndex != -1 -> emptyAppleBarrels[fullIndex]
else -> return
}
replaceScenery(barrel, newId, -1)
}
}

View file

@ -0,0 +1,119 @@
package content.global.skill.cooking.fermenting
import core.api.log
import core.game.node.entity.Entity
import core.game.node.entity.player.Player
import core.game.system.timer.PersistTimer
import core.tools.Log
import core.tools.secondsToTicks
import org.json.simple.JSONArray
import org.json.simple.JSONObject
/**
* The timer for regular brewing operations
*/
const val brewCycleTime = 6 /*hours*/ * 60 /*minutes per hour*/ * 100 /*ticks per minute*/
class BrewGrowth : PersistTimer(brewCycleTime, "cooking:brewing", isSoft = true) {
private val vatMap = HashMap<Int, Vat>()
lateinit var player: Player
override fun onRegister(entity: Entity) {
player = (entity as? Player)!!
for (vat in vatMap.values.toList()){
catchUp(vat.nextBrew)
}
}
override fun save(root: JSONObject, entity: Entity) {
player = (entity as? Player)!!
val vats = JSONArray()
for ((key, vat) in vatMap.entries.toList()){
val isBrewingStage = vat.stage in listOf(
BrewingStage.MIXED,
BrewingStage.BREWING1,
BrewingStage.BREWING2,
BrewingStage.DONE,
BrewingStage.MATURE,
BrewingStage.BAD
)
if (isBrewingStage) {
val v = JSONObject()
v["brew-ordinal"] = key
v["brew-stuff"] = vat.theStuff
v["brewing-nextBrew"] = vat.nextBrew
v["brewing-stage"] = vat.stage.ordinal
v["brewing-item"] = vat.brewingItem?.ordinal
vats.add(v)
}
}
root["brewing"] = vats
}
override fun parse(root: JSONObject, entity: Entity) {
player = (entity as? Player)!!
val data = root["brewing"] as JSONArray
for (d in data){
val v = d as JSONObject
val vatOrdinal = v["brew-ordinal"].toString().toInt()
val vatStuff = v["brew-stuff"].toString().toBoolean()
val vatNextBrew = v["brewing-nextBrew"].toString().toLong()
val vatStage = BrewingStage.values()[v["brewing-stage"].toString().toInt()]
val vatItem = v["brewing-item"]?.toString()?.toIntOrNull()?.let {
Brewable.values().getOrNull(it)
}
if (vatMap[vatOrdinal] == null){
val brewingVat = BrewingVat.values()[vatOrdinal]
val vat = Vat(player, brewingVat, vatStuff, vatNextBrew, vatStage, vatItem)
vatMap[vatOrdinal] = vat
vat.updateVat()
}
}
}
fun getVat(brewingVat: BrewingVat, addVat: Boolean = true) : Vat {
return vatMap[brewingVat.ordinal] ?: (Vat(player, brewingVat).also { if (addVat) vatMap[brewingVat.ordinal] = it })
}
private fun catchUp(timeTillGrow: Long){
if (timeTillGrow < System.currentTimeMillis()){
val seconds = (System.currentTimeMillis() - timeTillGrow)/1000
if (seconds > Int.MAX_VALUE){
execute(1024)
}
else{
val cyclesToGrow = secondsToTicks(seconds.toInt()) / brewCycleTime
execute(cyclesToGrow)
}
}
}
override fun run(entity: Entity): Boolean {
player = (entity as? Player)!!
return execute(0)
}
private fun execute(count: Int) : Boolean {
for (vat in vatMap.entries.toList()){
if (vat.value.canBrew())
for (i in 0..count){
val resp = vat.value.brew()
if (!resp){
break
}
}
else {
vatMap.entries.removeIf { it.value.isVatEmpty() && it.value.isBarrelEmpty()}
}
}
return vatMap.isNotEmpty()
}
fun getVats(): MutableCollection<Vat>{
return vatMap.values
}
}

View file

@ -0,0 +1,32 @@
package content.global.skill.cooking.fermenting
import org.rs09.consts.Items
enum class Brewable (val displayName: String, val itemID: Int, val level: Int, val product: IntArray, val levelXP: Double, val vatVarBitOffset: Int,
val barrelVarBitOffset: Int, val ingredientAmount: Int = 4){
DWARVEN_STOUT("Dwarven Stout", Items.HAMMERSTONE_HOPS_5994, 19, intArrayOf(Items.DWARVEN_STOUT_1913, Items.DWARVEN_STOUT4_5777, Items.DWARVEN_STOUTM_5747, Items.DWARVEN_STOUTM4_5857), 215.0, 4, 8),
ASGARNIAN_ALE("Asgarnian Ale", Items.ASGARNIAN_HOPS_5996, 26, intArrayOf(Items.ASGARNIAN_ALE_1905, Items.ASGARNIAN_ALE4_5785, Items.ASGARNIAN_ALEM_5739, Items.ASGARNIAN_ALEM4_5865), 248.0, 10, 16),
GREENMANS_ALE("Greenman's Ale", Items.CLEAN_HARRALANDER_255, 29, intArrayOf(Items.GREENMANS_ALE_1909, Items.GREENMANS_ALE4_5793, Items.GREENMANS_ALEM_5743, Items.GREENMANS_ALEM4_5873), 281.0, 16, 24),
WIZARD_BOMB("Wizard's Mind Bomb", Items.YANILLIAN_HOPS_5998, 34, intArrayOf(Items.WIZARDS_MIND_BOMB_1907, Items.MIND_BOMB4_5801, Items.MATURE_WMB_5741, Items.MIND_BOMBM4_5881), 341.0, 22, 32),
DRAGON_BITTER("Dragon Bitter", Items.KRANDORIAN_HOPS_6000, 39, intArrayOf(Items.DRAGON_BITTER_1911, Items.DRAGON_BITTER4_5809, Items.DRAGON_BITTERM_5745, Items.DRAGON_BITTERM4_5889), 347.0, 28, 40),
MOONLIGHT_MEAD("Moonlight Mead", Items.MUSHROOM_6004, 44, intArrayOf(Items.MOONLIGHT_MEAD_2955, Items.MOONLIGHT_MEAD4_5817, Items.MOONLIGHT_MEADM_5749, Items.MLIGHT_MEADM4_5897), 380.0, 34, 48),
AXEMANS_FOLLY("Axeman's Folly", Items.OAK_ROOTS_6043, 49, intArrayOf(Items.AXEMANS_FOLLY_5751, Items.AXEMANS_FOLLY4_5825, Items.AXEMANS_FOLLYM_5753, Items.AXEMANS_FOLLYM4_5905), 413.0, 40, 56, 1),
CHEFS_DELIGHT("Chef's Delight", Items.CHOCOLATE_DUST_1975, 54, intArrayOf(Items.CHEFS_DELIGHT_5755, Items.CHEFS_DELIGHT4_5833, Items.CHEFS_DELIGHTM_5757, Items.CHEFS_DELIGHTM4_5913), 446.0, 46, 64),
SLAYERS_RESPITE("Slayer's Respite", Items.WILDBLOOD_HOPS_6002, 59, intArrayOf(Items.SLAYERS_RESPITE_5759, Items.SLAYERS_RESPITE4_5841, Items.SLAYERS_RESPITEM_5761, Items.SLAYER_RESPITEM4_5921), 479.0, 52, 72),
CIDER("cider", Items.APPLE_MUSH_5992, 14, intArrayOf(Items.CIDER_5763, Items.CIDER4_5849, Items.MATURE_CIDER_5765, Items.CIDERM4_5929), 182.0, 58, 80),
KELDA_STOUT("Kelda Stout", Items.KELDA_HOPS_6113, 22, intArrayOf(Items.KELDA_STOUT_6118), 0.0, 68, 3, 1);
companion object {
fun getIngredients() : IntArray{
return values().map { it.itemID }.toIntArray()
}
fun getBrewable(itemId: Int) : Brewable?{
return values().find { it.itemID == itemId }
}
}
}

View file

@ -0,0 +1,232 @@
package content.global.skill.cooking.fermenting
import core.api.*
import core.game.interaction.InteractionListener
import core.game.interaction.QueueStrength
import core.game.node.entity.player.Player
import core.game.node.item.Item
import core.game.world.update.flag.context.Animation
import org.rs09.consts.Items
import org.rs09.consts.Scenery
const val KELDAGRIM_LOC = 11679
const val PORT_PHASMATYS_LOC = 14747
val varbits = mapOf(
PORT_PHASMATYS_LOC to BrewingVat.PORT_PHAS,
KELDAGRIM_LOC to BrewingVat.KELDAGRIM
)
class BrewingListener : InteractionListener {
private val aleIngredientsId = Brewable.getIngredients()
private val vatsNeedingYeast = intArrayOf(
Scenery.FERMENTING_VAT_7444, // Hammerstone hops
Scenery.FERMENTING_VAT_7449, // Asgarnian hops
Scenery.FERMENTING_VAT_7454, // Harralander
Scenery.FERMENTING_VAT_7459, // Yanillian hops
Scenery.FERMENTING_VAT_7464, // Krandorian hops
Scenery.FERMENTING_VAT_7469, // Bittercap mushrooms
Scenery.FERMENTING_VAT_7474, // Oak roots
Scenery.FERMENTING_VAT_7479, // Chocolate dust
Scenery.FERMENTING_VAT_7484, // Wildblood hops
Scenery.FERMENTING_VAT_7489, // Apple mush
Scenery.FERMENTING_VAT_8871 // Kelda hops
)
// Const Lib is missing some of these
private val drainBarrels = intArrayOf(
Scenery.BARREL_7408,
Scenery.BARREL_7409,
Scenery.BARREL_7410,
)
private val barrels = intArrayOf(
Scenery.KELDA_STOUT_8870,
Scenery.DWARVEN_STOUT_7411,
Scenery.ASGARNIAN_ALE_7413,
Scenery.GREENMANS_ALE_7415,
Scenery.WIZARDS_MIND_BOMB_7417,
Scenery.DRAGON_BITTER_7419,
Scenery.MOONLIGHT_MEAD_7421,
Scenery.AXEMAN_S_FOLLY_7423,
Scenery.CHEF_S_DELIGHT_7425,
Scenery.SLAYER_S_RESPITE_7427,
Scenery.CIDER_7429,
Scenery.MATURE_DWARVEN_STOUT_7412,
Scenery.MATURE_ASGARNIAN_ALE_7414,
Scenery.MATURE_GREENMANS_ALE_7416,
Scenery.MATURE_WIZARDS_MIND_BOMB_7418,
Scenery.MATURE_DRAGON_BITTER_7420,
Scenery.MATURE_MOONLIGHT_MEAD_7422,
Scenery.MATURE_AXEMAN_S_FOLLY_7424,
Scenery.MATURE_CHEF_S_DELIGHT_7426,
Scenery.MATURE_SLAYER_S_RESPITE_7428,
Scenery.MATURE_CIDER_7430
)
override fun defineListeners() {
// Fill an empty vat with water - self-contained operation
onUseWith(SCENERY, Items.BUCKET_OF_WATER_1929, Scenery.FERMENTING_VAT_7437) { player, _, _ ->
if (removeItem(player, Item(Items.BUCKET_OF_WATER_1929, 2))) {
queueScript(player, 0) {
addItem(player, Items.BUCKET_1925, 2)
lock(player, Animation(2283).duration)
animate(player, Animation(2283))
sendMessage(player, "You add some water to the vat.")
val vatVarbit = varbits.getValue(player.location.regionId).varbit
setVarbit(player, vatVarbit, 1, true)
return@queueScript (stopExecuting(player))
}
}
else{
sendDialogue(player, "You need 2 buckets of water.")
}
return@onUseWith true
}
// Fill an empty vat with apple mush
onUseWith(SCENERY, Items.APPLE_MUSH_5992, Scenery.FERMENTING_VAT_7437) { player, used, _ ->
getVat(player).addIngredient(used as Item)
return@onUseWith true
}
// Add barley malt to a vat with water - self-contained operation
onUseWith(SCENERY, Items.BARLEY_MALT_6008, Scenery.FERMENTING_VAT_7438) { player, _, _ ->
if (inInventory(player, Items.BARLEY_MALT_6008, 2)) {
queueScript(player, 0, QueueStrength.SOFT) { counter ->
when (counter) {
0 -> {
if (!removeItem(player, Item(Items.BARLEY_MALT_6008, 1))) { return@queueScript stopExecuting(player) }
lock(player, Animation(2295).duration * 2 + 2)
animate(player, Animation(2295))
sendMessage(player, "You add some barley malt to the vat.")
return@queueScript delayScript(player, Animation(2295).duration + 1)
}
1 -> {
if (!removeItem(player, Item(Items.BARLEY_MALT_6008, 1))) { return@queueScript stopExecuting(player) }
stopWalk(player)
animate(player, Animation(2295))
val vatVarbit = varbits.getValue(player.location.regionId).varbit
setVarbit(player, vatVarbit, 2, true)
return@queueScript delayScript(player, Animation(2295).duration + 1)
}
2 -> {
stopWalk(player)
return@queueScript stopExecuting(player)
}
else -> return@queueScript stopExecuting(player)
}
}
}
else{
sendDialogue(player, "You need 2 barley malt.")
}
return@onUseWith true
}
// Add The Stuff to a vat with malt
onUseWith(SCENERY, Items.THE_STUFF_8988, Scenery.FERMENTING_VAT_7441) { player, _, _ ->
if (inInventory(player, Items.THE_STUFF_8988, 1)) {
getVat(player).addTheStuff()
}
return@onUseWith true
}
// Add ingredients to a vat with malt
onUseWith(SCENERY, aleIngredientsId, Scenery.FERMENTING_VAT_7441) { player, used, _ ->
if (used.id == Items.APPLE_MUSH_5992) {
sendDialogue(player, "Apple mush needs to be added to an empty vat.")
return@onUseWith false
}
// If you try to make Kelda Stout in Port Phasmatys the vat deletes itself forever
if (used.id == Items.KELDA_HOPS_6113 && player.location.regionId != KELDAGRIM_LOC) {
sendMessage(player, "Nothing interesting happens.")
return@onUseWith false
}
// Branch off into Kelda Stout logic
if (used.id == Items.KELDA_HOPS_6113 && player.location.regionId == KELDAGRIM_LOC) {
KeldaBypasses.keldaAddHops(player)
return@onUseWith true
}
getVat(player).addIngredient(used as Item)
return@onUseWith true
}
// Add yeast to a vat that's ready to brew
onUseWith(SCENERY, Items.ALE_YEAST_5767, *vatsNeedingYeast) { player, _, with ->
if (inInventory(player, Items.ALE_YEAST_5767, 1)) {
// Branch off into Kelda Stout logic
if (with.id == Scenery.FERMENTING_VAT_8871 &&
player.location.regionId == KELDAGRIM_LOC &&
getVarbit(player, 736) == 68) { // 68 = Kelda MIXED state
KeldaBypasses.keldaAddYeast(player)
return@onUseWith true
}
getVat(player).addYeast()
}
return@onUseWith true
}
// Turn the valve to empty a vat into a barrel
on(intArrayOf(Scenery.VALVE_7442, Scenery.VALVE_7443), SCENERY, "turn") { player, _ ->
// Branch off into Kelda Stout logic
when {
(player.location.regionId == KELDAGRIM_LOC && KeldaBypasses.keldaIsBrewing(player)) ->
sendMessage(player, "The contents of the vat haven't finished fermenting yet.")
(player.location.regionId == KELDAGRIM_LOC && KeldaBypasses.keldaIsComplete(player)) ->
KeldaBypasses.keldaTurnValve(player)
else -> getVat(player).turnValve()
}
return@on true
}
// Drain a barrel of bad ale
on(drainBarrels, SCENERY, "Drain") { player, _ ->
getVat(player).drainBarrel()
return@on true
}
// Level a barrel (clicking on it)
on(barrels, SCENERY, "Level") { player, _ ->
queueScript(player, 1) {
val goAgain : Boolean
if (inInventory(player, Items.BEER_GLASS_1919)) {
goAgain = getVat(player).levelBarrel(Items.BEER_GLASS_1919)
}
else if (inInventory(player, Items.CALQUAT_KEG_5769)) {
goAgain = getVat(player).levelBarrel(Items.CALQUAT_KEG_5769)
}
else{
goAgain = false
sendDialogue(player, "You need a container to level the barrel.")
}
return@queueScript if(goAgain) delayScript(player, 1) else stopExecuting(player)
}
return@on true
}
// Level a barrel (using a container on it)
onUseWith(SCENERY, intArrayOf(Items.BEER_GLASS_1919, Items.CALQUAT_KEG_5769), *barrels) { player, used, _ ->
getVat(player).levelBarrel(used.id)
return@onUseWith true
}
}
private fun getVat(player: Player) : Vat {
return varbits.getValue(player.location.regionId).getVat(player)
}
}

View file

@ -0,0 +1,217 @@
package content.global.skill.cooking.fermenting
import core.api.*
import core.game.node.entity.player.Player
import core.game.node.item.Item
import org.rs09.consts.Items
object CalquatDecant {
// Consumables.java hurts my eyes so I did this instead
data class CalquatKegs (
val emptyKeg: Int = Items.CALQUAT_KEG_5769,
val fourDose: Int, val threeDose: Int = fourDose - 2, val twoDose: Int = threeDose - 2, val oneDose: Int = twoDose - 2, val glass: Int,
val fourMature: Int, val threeMature: Int = fourMature - 2, val twoMature: Int = threeMature - 2, val oneMature: Int = twoMature - 2, val matureGlass: Int
)
val calquatDrinks =
Brewable.values()
.filter { it.product.size == 4 }
.associateWith { CalquatKegs (
fourDose = it.product[1],
glass = it.product[0],
fourMature = it.product[3],
matureGlass = it.product[2]
)
}
// Array of all kegs with nonzero contents
val allKegIds: IntArray by lazy {
calquatDrinks.values.flatMap { keg ->
listOf(
keg.fourDose, keg.threeDose, keg.twoDose, keg.oneDose,
keg.fourMature, keg.threeMature, keg.twoMature, keg.oneMature
)
}.distinct().toIntArray()
}
// Reverse lookup map
private val kegLookupMap: Map<Int, Pair<Brewable, CalquatKegs>> by lazy {
calquatDrinks.entries.flatMap { (brewable, kegs) ->
listOf(
kegs.fourDose, kegs.threeDose, kegs.twoDose, kegs.oneDose,
kegs.fourMature, kegs.threeMature, kegs.twoMature, kegs.oneMature
).map { itemId -> itemId to (brewable to kegs) }
}.toMap()
}
// Check what a keg is so it can be related to other kegs
fun getKeg(itemId: Int): Pair<Brewable, CalquatKegs>? = kegLookupMap[itemId]
// Check if an item is a keg
fun isKeg(itemId: Int): Boolean = kegLookupMap.containsKey(itemId)
// Check how full a keg is
fun getDoseByKeg(itemId: Int, kegs: CalquatKegs): Int {
return when (itemId) {
kegs.fourDose, kegs.fourMature -> 4
kegs.threeDose, kegs.threeMature -> 3
kegs.twoDose, kegs.twoMature -> 2
kegs.oneDose, kegs.oneMature -> 1
else -> 0
}
}
// Check if a keg contains matured ale
fun isMature(itemId: Int, kegs: CalquatKegs): Boolean {
return itemId == kegs.fourMature || itemId == kegs.threeMature ||
itemId == kegs.twoMature || itemId == kegs.oneMature
}
// Identifies a keg's category by its contents
fun getKegByDose(kegs: CalquatKegs, dose: Int, mature: Boolean): Int {
return if (!mature) {
when (dose) {
4 -> kegs.fourDose
3 -> kegs.threeDose
2 -> kegs.twoDose
1 -> kegs.oneDose
else -> kegs.emptyKeg
}
} else {
when (dose) {
4 -> kegs.fourMature
3 -> kegs.threeMature
2 -> kegs.twoMature
1 -> kegs.oneMature
else -> kegs.emptyKeg
}
}
}
// Decanting function (NPC) - should work the same as potions
fun decantCalquat(container: core.game.container.Container): Pair<List<Item>, List<Item>> {
val doseCount = HashMap<Pair<Brewable, Boolean>, Int>() // (Brewable, isMature), Total Doses
val toRemove = ArrayList<Item>()
val toAdd = ArrayList<Item>()
// Count up all doses grouped by keg type and maturity
for (item in container.toArray()) {
if (item == null) continue
val kegInfo = getKeg(item.id) ?: continue
val (brewable, kegs) = kegInfo
val dose = getDoseByKeg(item.id, kegs)
if (dose == 0) continue
val mature = isMature(item.id, kegs)
val key = brewable to mature
doseCount[key] = (doseCount[key] ?: 0) + dose
toRemove.add(item)
}
// Convert doses back into kegs
for ((key, totalDoses) in doseCount) {
val (brewable, mature) = key
val kegs = calquatDrinks[brewable] ?: continue
val fullKegs = totalDoses / 4 // Number of 4-dose kegs
val remainderDose = totalDoses % 4 // Leftover doses
if (fullKegs > 0)
toAdd.add(Item(getKegByDose(kegs, 4, mature), fullKegs))
if (remainderDose > 0)
toAdd.add(Item(getKegByDose(kegs, remainderDose, mature)))
}
// Add empty kegs for removed containers
val emptyKegs = toRemove.size - toAdd.sumOf { it.amount }
if (emptyKegs > 0) {
val firstKegs = calquatDrinks.values.firstOrNull()
if (firstKegs != null)
toAdd.add(Item(firstKegs.emptyKeg, emptyKegs))
}
return Pair(toRemove, toAdd)
}
// Decanting function (manual) - should work the same as potions
fun combineKegs(player: Player, used: Item, with: Item): Boolean {
// Identify both kegs
val (usedBrewable, kegs) = getKeg(used.id) ?: return false
val (withBrewable, _) = getKeg(with.id) ?: return false
// Verify the same drink is in both kegs
if (usedBrewable != withBrewable) return false
val usedMature = isMature(used.id, kegs)
val withMature = isMature(with.id, kegs)
// Verify the same maturity status is in both kegs
if (usedMature != withMature) return false
// Get doses
val usedDose = getDoseByKeg(used.id, kegs)
val withDose = getDoseByKeg(with.id, kegs)
// Disallow combining full or empty kegs
if (usedDose == 4 || withDose == 4 || usedDose == 0 || withDose == 0) {
return false
}
val totalDosage = usedDose + withDose
val fullDoses = totalDosage / 4
val leftoverDose = totalDosage % 4
// Replace the targeted keg with a (4) dose keg
if (fullDoses != 0) {
replaceSlot(player, with.slot, Item(getKegByDose(kegs, 4, usedMature)), with, Container.INVENTORY)
}
// Replace the targeted keg with the updated dosage amount
if (leftoverDose != 0 && fullDoses == 0) {
replaceSlot(player, with.slot, Item(getKegByDose(kegs, leftoverDose, usedMature)), with, Container.INVENTORY)
// Replace the used with keg with the updated dosage amount
} else if (leftoverDose != 0) {
replaceSlot(player, used.slot, Item(getKegByDose(kegs, leftoverDose, usedMature)), used, Container.INVENTORY)
}
// Replace the used with keg with an empty keg
if (leftoverDose == 0 || fullDoses == 0) {
replaceSlot(player, used.slot, Item(kegs.emptyKeg), used, Container.INVENTORY)
}
// Send message/Play sound
val amountString = when {
totalDosage >= 4 -> "four"
totalDosage == 3 -> "three"
else -> "two"
}
sendMessage(player, "You have combined the kegs into $amountString pints.")
playAudio(player, 2401)
return true
}
// Decant keg into beer glass
fun pourIntoGlass(player: Player, glass: Item, keg: Item): Boolean {
// Find keg info
val kegInfo = getKeg(keg.id) ?: return false
val (_, kegs) = kegInfo
val currentDose = getDoseByKeg(keg.id, kegs)
val mature = isMature(keg.id, kegs)
if (currentDose == 0) {
return false
}
// Fill the glass
val glassItem = if (mature) kegs.matureGlass else kegs.glass
replaceSlot(player, glass.slot, Item(glassItem), glass, Container.INVENTORY)
// Drain the keg
val newDose = currentDose - 1
replaceSlot(player, keg.slot, Item(getKegByDose(kegs, newDose, mature)), keg, Container.INVENTORY)
return true
}
}

View file

@ -0,0 +1,108 @@
package content.global.skill.cooking.fermenting
import core.api.getVarbit
import core.api.sendMessage
import core.game.node.entity.Entity
import core.api.*
import core.game.interaction.QueueStrength
import core.game.node.entity.player.Player
import core.game.node.item.Item
import core.game.world.update.flag.context.Animation
import org.rs09.consts.Items
import core.game.system.timer.PersistTimer
import core.game.system.timer.RSTimer
/**
* Independent brewing timer and functions exclusively for Kelda Stout
* Varbit progression: 68 (mixed) -> 69 (brewing1) -> 70 (brewing2) -> 71 (done)
*/
class KeldaStoutGrowth : PersistTimer( 5 /*minutes*/ * 100 /*ticks per minute*/, "cooking:brewing-kelda", isSoft = true) {
lateinit var player: Player
override fun run(entity: Entity): Boolean {
player = entity as Player
when (val currentVarbit = getVarbit(player, 736)) {
69 -> {
setVarbit(player, 736, currentVarbit + 1)
return true
}
70 -> {
setVarbit(player, 736, currentVarbit + 1)
sendMessage(player, "Perhaps I should have a look and see if my Kelda Stout has brewed...")
return false
}
else -> {
return false
}
}
}
override fun getTimer(vararg args: Any): RSTimer {
return KeldaStoutGrowth()
}
}
object KeldaBypasses {
fun keldaAddHops(player: Player) {
queueScript(player, 0) {
stopWalk(player)
if (!removeItem(player, Item(Items.KELDA_HOPS_6113, 1))) { return@queueScript stopExecuting(player) }
sendMessage(player, "You add some Kelda hops to the vat.")
lock(player, Animation(2295).duration + 1)
animate(player, Animation(2295))
setVarbit(player, 736, getVarbit(player, 736) + 66)
return@queueScript stopExecuting(player)
}
}
fun keldaAddYeast(player: Player) {
//addYeast() override
queueScript(player, 0) {
if (!removeItem(player, Item(Items.ALE_YEAST_5767, 1))) { return@queueScript stopExecuting(player) }
addItem(player, Items.EMPTY_POT_1931)
lock(player, Animation(2283).duration)
animate(player, Animation(2295))
sendMessage(player, "You add a pot of ale yeast to the vat.")
sendMessage(player, "The contents of the vat have begun to ferment.")
setVarbit(player, 736, getVarbit(player, 736) + 1)
registerTimer(player, KeldaStoutGrowth())
return@queueScript true
}
}
fun keldaTurnValve(player: Player) {
queueScript(player, 0, QueueStrength.SOFT) {
lock(player, Animation(780).duration)
animate(player, Animation(780))
sendMessage(player, "You turn the valve.")
delayScript(player, 2)
// Fill the barrel, then empty the vat
setVarbit(player, 738, 3, true)
setVarbit(player, 736, 0, true)
sendMessage(player, "The barrel now contains 1 pint of Kelda Stout.")
return@queueScript stopExecuting(player)
}
}
fun keldaIsBrewing(player: Player): Boolean {
val vatVarbit = getVarbit(player, 736)
return vatVarbit in 69..70
}
fun keldaIsComplete(player: Player): Boolean {
val vatVarbit = getVarbit(player, 736)
return vatVarbit == 71
}
}

View file

@ -0,0 +1,366 @@
package content.global.skill.cooking.fermenting
import core.api.*
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.game.world.update.flag.context.Animation
import core.tools.RandomFunction
import org.rs09.consts.Items
class Vat (var player: Player, private val brewingVat : BrewingVat, var theStuff: Boolean,
var nextBrew: Long, var stage: BrewingStage,
var brewingItem : Brewable?) {
private var barrelItem: Brewable? = null
constructor(player: Player, brewingVat: BrewingVat) :
this(player, brewingVat, false, 0, BrewingStage.EMPTY, null)
fun addTheStuff() {
if (!theStuff && removeItem(player, Item(Items.THE_STUFF_8988, 1))) {
theStuff = true
lock(player, Animation(2283).duration)
animate(player, Animation(2283))
sendMessage(player, "You add The Stuff to the mixture in the vat.")
} else { sendMessage(player, "Nothing interesting happens.") }
}
fun addIngredient(item: Item): Boolean {
val ingredient = Brewable.getBrewable(item.id) ?: return false
if (getDynLevel(player, Skills.COOKING) < ingredient.level) {
sendDialogue(player, "You need a Cooking level of at least ${ingredient.level} in order to brew ${ingredient.displayName}.")
return false
}
if (!inInventory(player, item.id, ingredient.ingredientAmount)) {
val ingredientWord = if (ingredient.itemID == Items.MUSHROOM_6004) item.name+"s" else item.name
sendDialogue(player, "You need 4 $ingredientWord.")
return false
}
queueScript(player, 0, QueueStrength.SOFT) { counter ->
when (counter) {
0 -> {
if (!removeItem(player, Item(item.id, 1))) { return@queueScript stopExecuting(player) }
if (ingredient.itemID == Items.APPLE_MUSH_5992) {
addItem(player, Items.BUCKET_1925)
}
stopWalk(player)
animate(player, Animation(2295))
val ingredientWord = if (item.id == Items.MUSHROOM_6004) item.name+"s" else item.name
sendMessage(player, "You add some $ingredientWord to the vat.")
if (ingredient.ingredientAmount == 1) {
lock(player, Animation(2295).duration)
brewingItem = ingredient
stage = BrewingStage.MIXED
updateVat()
return@queueScript stopExecuting(player)
}
lock(player, Animation(2295).duration * 4 + 3)
return@queueScript delayScript(player, Animation(2295).duration + 1)
}
1, 2 -> {
if (!removeItem(player, Item(item.id, 1))) { return@queueScript stopExecuting(player) }
if (ingredient.itemID == Items.APPLE_MUSH_5992) {
addItem(player, Items.BUCKET_1925)
}
stopWalk(player)
animate(player, Animation(2295))
return@queueScript delayScript(player, Animation(2295).duration + 1)
}
3 -> {
if (!removeItem(player, Item(item.id, 1))) { return@queueScript stopExecuting(player) }
if (ingredient.itemID == Items.APPLE_MUSH_5992) {
addItem(player, Items.BUCKET_1925)
}
stopWalk(player)
animate(player, Animation(2295))
brewingItem = ingredient
stage = BrewingStage.MIXED
updateVat()
return@queueScript delayScript(player, Animation(2295).duration)
}
4 -> {
stopWalk(player)
return@queueScript stopExecuting(player)
}
else -> return@queueScript stopExecuting(player)
}
}
return true
}
fun addYeast() {
if (removeItem(player, Item(Items.ALE_YEAST_5767, 1))) {
addItem(player, Items.EMPTY_POT_1931)
lock(player, Animation(2283).duration)
animate(player, Animation(2295))
sendMessage(player, "You add a pot of ale yeast to the vat.")
sendMessage(player, "The contents of the vat have begun to ferment.")
stage = BrewingStage.BREWING1
nextBrew = System.currentTimeMillis() + (brewCycleTime * 600) // ticks to ms
updateVat()
} else {
sendMessage(player, "Nothing interesting happens.")
}
}
fun brew(forceStep: Boolean = false, forceGood: Boolean = false, forceMature: Boolean = false, forceBad: Boolean = false) : Boolean {
val resp = internalBrew(forceStep, forceGood, forceMature, forceBad)
updateVat()
return resp
}
fun canBrew() : Boolean {
return listOf(BrewingStage.BREWING1, BrewingStage.BREWING2).any { it == stage }
}
/** The meat of the brewing mechanic lives in the following internalBrew function
* No one has clearly documented how this should work
* Period sources are vague, unaccountable, and/or vary wildly in their claims
* https://x.com/JagexAsh/status/994867369460813824
* Ash states that it was challenging to follow when he was trying to do it in OSRS
*
* If a good source is uncovered, this section can easily be configured to match it
*
* Current config results in the following behavior:
* Cooking level does not play a factor
* Chance to advance a step (or go bad) - 20%
* Chance to go bad when advancing - 25%
* Chance to mature (w/o The Stuff) - 05%
* Chance to mature (w/ The Stuff) - 50%
* Time per cycle (in BrewGrowth.kt) - 6 hours, or 36000 ticks
*/
private fun internalBrew(forceStep: Boolean, forceGood: Boolean, forceMature: Boolean, forceBad: Boolean):Boolean {
nextBrew = System.currentTimeMillis() + (brewCycleTime * 600) // Ticks to ms
if (listOf(BrewingStage.BREWING1, BrewingStage.BREWING2).all{ it != stage}) {
return false
}
// Step advancement check
if (RandomFunction.random(5) == 0 || forceStep || forceGood || forceMature || forceBad) {
if ((RandomFunction.random(4) == 0 || forceBad) && !forceGood && !forceMature) {
stage = BrewingStage.BAD
return false
}
stage = if (stage == BrewingStage.BREWING1) BrewingStage.BREWING2 else BrewingStage.DONE
}
// Maturity check
if (stage == BrewingStage.DONE) {
sendMessage(player, "Perhaps I should have a look and see if my ${brewingItem!!.displayName} has brewed...")
val roll = if (theStuff) 2 else 20
if ((RandomFunction.random(roll) == 0 || forceMature) && !forceGood) {
stage = BrewingStage.MATURE
}
return false
}
return true
}
fun turnValve(){
if (stage == BrewingStage.EMPTY) {
queueScript(player, 1) {
sendMessage(player, "Nothing interesting happens.")
return@queueScript stopExecuting(player)
}
return
}
if (getVarbit(player,this.brewingVat.varbit + 2) > 0) {
queueScript(player, 1) {
sendMessage(player, "The barrel is already full.")
return@queueScript stopExecuting(player)
}
return
}
if (stage == BrewingStage.BREWING1 || stage == BrewingStage.BREWING2) {
queueScript(player,1) {
sendMessage(player, "The contents of the vat haven't finished fermenting yet.")
return@queueScript stopExecuting(player)
}
return
}
lock(player, Animation(780).duration)
animate(player, Animation(780))
sendMessage(player, "You turn the valve.")
if (stage == BrewingStage.MATURE || stage == BrewingStage.DONE) {
rewardXP(player, Skills.COOKING, brewingItem!!.levelXP)
queueScript(player, 2, QueueStrength.SOFT) {
sendMessage(player, "The barrel now contains 8 pints of ${brewingItem!!.displayName}.")
fillBarrel()
emptyVat()
updateVat()
return@queueScript stopExecuting(player)
}
} else {
fillBarrel()
emptyVat()
updateVat()
}
}
private fun emptyVat() {
brewingItem = null
theStuff = false
stage = BrewingStage.EMPTY
updateVat()
}
private fun fillBarrel() {
var varbitValue = 0
when (stage){
BrewingStage.EMPTY -> Unit
BrewingStage.WATER,
BrewingStage.MALT,
BrewingStage.MIXED,
BrewingStage.BREWING1,
BrewingStage.BREWING2 -> {
varbitValue = 4
}
BrewingStage.DONE,
BrewingStage.MATURE -> {
barrelItem = brewingItem
val barrelAmount = 8
varbitValue = barrelItem?.barrelVarBitOffset ?: 0
if (barrelItem != null){
varbitValue += (barrelAmount - 1)
if (stage == BrewingStage.MATURE) {
varbitValue += 128
}
}
}
BrewingStage.BAD ->{
varbitValue = if(brewingItem == Brewable.CIDER) 2 else 1
}
}
setVarbit(player, this.brewingVat.varbit + 2, varbitValue, true)
}
fun levelBarrel(container: Int): Boolean {
var value = getVarbit(player, this.brewingVat.varbit + 2)
if (value == 0) return false
// Check for mature bit (128) and strip it for calculations
val isMature = value >= 128
val baseValue = if (isMature) value - 128 else value
var servingsLeft = if(baseValue == 3) 1 else (baseValue % 8) + 1
val brewable =
if (baseValue == 3) Brewable.KELDA_STOUT
else Brewable.values()[(baseValue/8) - 1]
val product = brewable.product
if (container == Items.BEER_GLASS_1919) {
if(removeItem(player, container)) {
animate(player, Animation(2285))
value--
servingsLeft--
val productIndex = if (isMature) 2 else 0
sendMessage(player, "You pour a glass of ${brewable.displayName}.")
addItem(player, product[productIndex])
}
}
else if (brewable == Brewable.KELDA_STOUT) {
sendDialogue(player, "You must use a glass to level Kelda Stout.")
return false
}
else if (container == Items.CALQUAT_KEG_5769) {
if(removeItem(player, container)){
animate(player, Animation(2284))
val servingsToTake = minOf(servingsLeft, 4)
value -= servingsToTake
servingsLeft -= servingsToTake
val filledKeg = CalquatDecant.calquatDrinks[brewable]!!
addItem(player, CalquatDecant.getKegByDose(filledKeg, servingsToTake, isMature))
}
}
if (servingsLeft == 0) {
sendMessage(player, "The barrel is now empty.")
value = 0
}
setVarbit(player, this.brewingVat.varbit + 2, value, true)
return servingsLeft != 0
}
fun drainBarrel() {
sendMessage(player, "You drain the barrel.")
setVarbit(player, this.brewingVat.varbit + 2, 0, true)
}
fun isVatEmpty(): Boolean {
return stage == BrewingStage.EMPTY
}
fun isBarrelEmpty(): Boolean {
return getVarbit(player, (this.brewingVat.varbit + 2)) == 0
}
private fun getVatDisplay(): Int {
return when (stage){
BrewingStage.EMPTY -> 0
BrewingStage.WATER -> 1
BrewingStage.MALT -> 2
BrewingStage.MIXED -> brewingItem?.vatVarBitOffset ?: run { 0 }
BrewingStage.BREWING1 -> (brewingItem?.vatVarBitOffset?.plus(1)) ?: run { 0 }
BrewingStage.BREWING2 -> (brewingItem?.vatVarBitOffset?.plus(2)) ?: run { 0 }
BrewingStage.DONE -> (brewingItem?.vatVarBitOffset?.plus(3)) ?: run { 0 }
BrewingStage.MATURE -> (brewingItem?.vatVarBitOffset?.plus(4)) ?: run { 0 }
BrewingStage.BAD -> if (brewingItem == Brewable.CIDER) 65 else 64
}
}
fun updateVat(){
setVarbit(player, this.brewingVat.varbit, this.getVatDisplay(), true)
}
}
enum class BrewingStage {
EMPTY,
WATER,
MALT,
MIXED,
BREWING1,
BREWING2,
DONE,
MATURE,
BAD;
}
enum class BrewingVat(val varbit: Int) {
KELDAGRIM(736),
PORT_PHAS(737);
fun getVat(player: Player): Vat {
val vat = getOrStartTimer<BrewGrowth>(player)
return vat.getVat(this, true)
}
}

View file

@ -1,5 +1,6 @@
package content.global.skill.farming
import content.data.Quests
import core.api.*
import core.game.dialogue.DialogueFile
import core.game.dialogue.FacialExpression
@ -67,6 +68,8 @@ class FarmerPayOptionDialogue(val patch: Patch, val quickPay: Boolean = false):
val amount = if (item?.amount == 1) "one" else item?.amount
npc(FacialExpression.HAPPY, "I want $amount $protectionText, one monkey bar,", "and one ground tooth for that.")
stage = 200
} else if (patch.patch.type == PatchType.CALQUAT_TREE_PATCH && getQuestStage(player!!, Quests.JUNGLE_POTION) < 100) {
npc("Sorry, I won't protect that.").also { stage = END_DIALOGUE } // Cannot confirm authenticity of this line just that there should be a rejection here
} else if (quickPay && !(inInventory(player!!, item!!.id, item!!.amount) || inInventory(player!!, note(item!!).id, note(item!!).amount))) {
val amount = if (item?.amount == 1) "one" else item?.amount
npc(FacialExpression.HAPPY, "I want $amount $protectionText for that.")

View file

@ -24,7 +24,8 @@ enum class Farmers(val id: Int, val patches: Array<FarmingPatch>) {
TORRELL(2338, arrayOf(FarmingPatch.ARDOUGNE_BUSH)),
FRIZZY(4560, arrayOf(FarmingPatch.PORT_SARIM_SPIRIT_TREE)),
YULF(4561, arrayOf(FarmingPatch.ETCETERIA_SPIRIT_TREE)),
PRAISTAN(4562, arrayOf(FarmingPatch.KARAMJA_SPIRIT_TREE))
PRAISTAN(4562, arrayOf(FarmingPatch.KARAMJA_SPIRIT_TREE)),
IMIAGO(8041, arrayOf(FarmingPatch.CALQUAT_TREE))
;
companion object{

View file

@ -67,7 +67,8 @@ enum class FarmingPatch(val varbit: Int, val type: PatchType) {
DRAYNOR_BELLADONNA(748, PatchType.BELLADONNA_PATCH),
CANIFIS_MUSHROOM(746, PatchType.MUSHROOM_PATCH),
ALKHARID_CACTUS(744, PatchType.CACTUS_PATCH),
EVIL_TURNIP(4291, PatchType.EVIL_TURNIP_PATCH);
EVIL_TURNIP(4291, PatchType.EVIL_TURNIP_PATCH),
CALQUAT_TREE(726, PatchType.CALQUAT_TREE_PATCH);
companion object {

View file

@ -60,6 +60,10 @@ class HealthChecker : OptionHandler() {
patch.setCurrentState(patch.plantable!!.value + patch.plantable!!.stages + 3)
sendMessage(player, "You examine the cactus for signs of disease and find that it is in perfect health.")
}
PatchType.CALQUAT_TREE_PATCH -> {
patch.setCurrentState(patch.plantable!!.value + patch.plantable!!.stages + 6)
sendMessage(player, "You examine the calquat tree for signs of disease and find that it is in perfect health.")
}
else -> log(this::class.java, Log.ERR, "Unreachable patch type from when(type) switch in HealthChecker.kt")
}

View file

@ -16,7 +16,7 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
var compost = CompostType.NONE
var protectionPaid = false
var cropLives = 3
val checkablePatches = arrayOf(PatchType.TREE_PATCH, PatchType.BUSH_PATCH, PatchType.FRUIT_TREE_PATCH, PatchType.SPIRIT_TREE_PATCH, PatchType.CACTUS_PATCH)
val checkablePatches = arrayOf(PatchType.TREE_PATCH, PatchType.BUSH_PATCH, PatchType.FRUIT_TREE_PATCH, PatchType.SPIRIT_TREE_PATCH, PatchType.CACTUS_PATCH, PatchType.CALQUAT_TREE_PATCH)
fun setNewHarvestAmount() {
val compostMod = when(compost) {
@ -164,6 +164,7 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
PatchType.CACTUS_PATCH -> setVarbit(player, patch.varbit, 31)
PatchType.TREE_PATCH -> setVarbit(player, patch.varbit, plantable!!.value + plantable!!.stages)
PatchType.SPIRIT_TREE_PATCH -> setVarbit(player, patch.varbit, plantable!!.value + plantable!!.stages + 24)
PatchType.CALQUAT_TREE_PATCH -> setVarbit(player, patch.varbit, 34)
else -> log(this::class.java, Log.WARN, "Invalid setting of isCheckHealth for patch type: " + patch.type.name + "at" + patch.name)
}
} else {
@ -223,7 +224,13 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
if(isDead) setVisualState(getMushroomDeathValue())
else if(isDiseased && !isDead) setVisualState(getMushroomDiseaseValue())
}
else -> {}
PatchType.CALQUAT_TREE_PATCH -> {
if(isDead) setVisualState(getCalquatDeathValue())
else if(isDiseased && !isDead) setVisualState(getCalquatDiseaseValue())
}
else -> {
log(this::class.java, Log.WARN, "Invalid patch type attempted to be displayed: " + patch.type.name)
}
}
}
}
@ -310,6 +317,14 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
return (plantable?.value ?: 0) + currentGrowthStage + 16
}
private fun getCalquatDiseaseValue(): Int {
return (plantable?.value ?: 0) + currentGrowthStage + 14
}
private fun getCalquatDeathValue(): Int {
return (plantable?.value ?: 0) + currentGrowthStage + 21
}
private fun getHerbDiseaseValue(): Int {
return if (plantable?.value ?: -1 <= 103) {
128 + (((plantable?.ordinal ?: 0) - Plantable.GUAM_SEED.ordinal) * 3) + currentGrowthStage - 1
@ -364,8 +379,12 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
isCheckHealth = true
}
if((patch.type == PatchType.FRUIT_TREE_PATCH || patch.type == PatchType.BUSH_PATCH || patch.type == PatchType.CACTUS_PATCH) && plantable?.stages == currentGrowthStage){
if((patch.type == PatchType.BUSH_PATCH && getFruitOrBerryCount() < 4) || (patch.type == PatchType.FRUIT_TREE_PATCH && getFruitOrBerryCount() < 6) || (patch.type == PatchType.CACTUS_PATCH && getFruitOrBerryCount() < 3)){
if(arrayOf(PatchType.FRUIT_TREE_PATCH, PatchType.BUSH_PATCH, PatchType.CACTUS_PATCH, PatchType.CALQUAT_TREE_PATCH).any{ patch.type == it} && plantable?.stages == currentGrowthStage){
if((patch.type == PatchType.BUSH_PATCH && getFruitOrBerryCount() < 4) ||
(patch.type == PatchType.FRUIT_TREE_PATCH && getFruitOrBerryCount() < 6) ||
(patch.type == PatchType.CACTUS_PATCH && getFruitOrBerryCount() < 3) ||
(patch.type == PatchType.CALQUAT_TREE_PATCH && getFruitOrBerryCount() < 6)
){
setCurrentState(getCurrentState() + 1)
}
}
@ -376,7 +395,7 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
}
}
if(plantable?.stages ?: 0 > currentGrowthStage && !isGrown()){
if((plantable?.stages ?: 0) > currentGrowthStage && !isGrown()){
currentGrowthStage++
setCurrentState(getCurrentState() + 1)
isWatered = false
@ -427,7 +446,7 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
fun getStageGrowthMinutes() : Int {
var minutes = patch.type.stageGrowthTime
if(patch.type == PatchType.FRUIT_TREE_PATCH && isGrown()) {
if(arrayOf(PatchType.FRUIT_TREE_PATCH, PatchType.CALQUAT_TREE_PATCH).any{ it == patch.type } && isGrown()) {
// Fruit trees take 160 minutes per stage to grow, but
// restocking their fruit should take 40 minutes per fruit
minutes = 40

View file

@ -12,7 +12,8 @@ enum class PatchType(val stageGrowthTime: Int) {
MUSHROOM_PATCH(30),
BELLADONNA_PATCH(80),
CACTUS_PATCH(60),
EVIL_TURNIP_PATCH(5);
EVIL_TURNIP_PATCH(5),
CALQUAT_TREE_PATCH(160);
/**
* Returns the display name of this PatchType.

View file

@ -81,6 +81,7 @@ enum class Plantable(val itemID: Int, val displayName: String, val value: Int, v
MUSHROOM_SPORE(Items.MUSHROOM_SPORE_5282, "mushroom spore", 4, 6, 61.5, 57.7, 0.0, 53, PatchType.MUSHROOM_PATCH, Items.MUSHROOM_6004),
CACTUS_SEED(Items.CACTUS_SEED_5280, "cactus seed", 8, 7, 66.5, 25.0, 374.0, 55, PatchType.CACTUS_PATCH, Items.CACTUS_SPINE_6016),
EVIL_TURNIP_SEED(Items.EVIL_TURNIP_SEED_12148, "evil turnip seed", 4, 1, 41.0, 46.0, 0.0, 42, PatchType.EVIL_TURNIP_PATCH, Items.EVIL_TURNIP_12134),
CALQUAT_SAPLING(Items.CALQUAT_SAPLING_5503, "calquat tree sapling", 4, 8, 130.5, 48.5, 12096.0, 72, PatchType.CALQUAT_TREE_PATCH, Items.CALQUAT_FRUIT_5980, Item(Items.POISON_IVY_BERRIES_6018, 8)),
SPIRIT_SAPLING(Items.SPIRIT_SAPLING_5375, "spirit tree sapling", 8, 12, 199.5, 0.0, 19301.0, 83, PatchType.SPIRIT_TREE_PATCH, 0, Item(Items.MONKEY_NUTS_4012, 5), null, Item(Items.MONKEY_BAR_4014, 1), Item(Items.GROUND_TOOTH_9082, 1) )
;

View file

@ -61,7 +61,7 @@ class SackBasketOptionHandler : OptionHandler() {
appropriateProduce.amount = (max - specific)
}
if(player.inventory.remove(item) && player.inventory.remove(appropriateProduce))
if(player.inventory.remove(appropriateProduce) && player.inventory.remove(item))
player.inventory.add(Item(container.containers[specific + appropriateProduce.amount]))
}

View file

@ -57,19 +57,20 @@ class SeedlingListener : InteractionListener {
fun getSeedling(id: Int) : Int {
return when (id) {
Items.ACORN_5312 -> Items.OAK_SEEDLING_5358
Items.WILLOW_SEED_5313 -> Items.WILLOW_SEEDLING_5359
Items.MAPLE_SEED_5314 -> Items.MAPLE_SEEDLING_5360
Items.YEW_SEED_5315 -> Items.YEW_SEEDLING_5361
Items.MAGIC_SEED_5316 -> Items.MAGIC_SEEDLING_5362
Items.APPLE_TREE_SEED_5283 -> Items.APPLE_SEEDLING_5480
Items.BANANA_TREE_SEED_5284 -> Items.BANANA_SEEDLING_5481
Items.ORANGE_TREE_SEED_5285 -> Items.ORANGE_SEEDLING_5482
Items.CURRY_TREE_SEED_5286 -> Items.CURRY_SEEDLING_5483
Items.PINEAPPLE_SEED_5287 -> Items.PINEAPPLE_SEEDLING_5484
Items.PAPAYA_TREE_SEED_5288 -> Items.PAPAYA_SEEDLING_5485
Items.PALM_TREE_SEED_5289 -> Items.PALM_SEEDLING_5486
Items.SPIRIT_SEED_5317 -> Items.SPIRIT_SEEDLING_5363
Items.ACORN_5312 -> Items.OAK_SEEDLING_5358
Items.WILLOW_SEED_5313 -> Items.WILLOW_SEEDLING_5359
Items.MAPLE_SEED_5314 -> Items.MAPLE_SEEDLING_5360
Items.YEW_SEED_5315 -> Items.YEW_SEEDLING_5361
Items.MAGIC_SEED_5316 -> Items.MAGIC_SEEDLING_5362
Items.APPLE_TREE_SEED_5283 -> Items.APPLE_SEEDLING_5480
Items.BANANA_TREE_SEED_5284 -> Items.BANANA_SEEDLING_5481
Items.ORANGE_TREE_SEED_5285 -> Items.ORANGE_SEEDLING_5482
Items.CURRY_TREE_SEED_5286 -> Items.CURRY_SEEDLING_5483
Items.PINEAPPLE_SEED_5287 -> Items.PINEAPPLE_SEEDLING_5484
Items.PAPAYA_TREE_SEED_5288 -> Items.PAPAYA_SEEDLING_5485
Items.PALM_TREE_SEED_5289 -> Items.PALM_SEEDLING_5486
Items.SPIRIT_SEED_5317 -> Items.SPIRIT_SEEDLING_5363
Items.CALQUAT_TREE_SEED_5290 -> Items.CALQUAT_SEEDLING_5487
else -> -1
}
}
@ -87,7 +88,8 @@ class SeedlingListener : InteractionListener {
Items.PINEAPPLE_SEED_5287,
Items.PAPAYA_TREE_SEED_5288,
Items.PALM_TREE_SEED_5289,
Items.SPIRIT_SEED_5317
Items.SPIRIT_SEED_5317,
Items.CALQUAT_TREE_SEED_5290
)
val TREE_SEEDLINGS = intArrayOf(
@ -103,7 +105,8 @@ class SeedlingListener : InteractionListener {
Items.PINEAPPLE_SEEDLING_5484,
Items.PAPAYA_SEEDLING_5485,
Items.PALM_SEEDLING_5486,
Items.SPIRIT_SEEDLING_5363
Items.SPIRIT_SEEDLING_5363,
Items.CALQUAT_SEEDLING_5487,
)
private val WATERING_CANS = intArrayOf(Items.WATERING_CAN8_5340,Items.WATERING_CAN7_5339,Items.WATERING_CAN6_5338,Items.WATERING_CAN5_5337,Items.WATERING_CAN4_5336,Items.WATERING_CAN3_5335,Items.WATERING_CAN2_5334,Items.WATERING_CAN1_5333)

View file

@ -38,7 +38,7 @@ class UseWithPatchHandler : InteractionListener {
onUseWith(IntType.SCENERY, allowedNodes.toIntArray(), *FarmingPatch.patchNodes.toIntArray()) { player, used, with ->
val patch = FarmingPatch.forObject(with.asScenery()) ?: return@onUseWith true
val usedItem = used.asItem()
val trimmablePatches = arrayOf(PatchType.TREE_PATCH, PatchType.BUSH_PATCH, PatchType.FRUIT_TREE_PATCH, PatchType.SPIRIT_TREE_PATCH)
val trimmablePatches = arrayOf(PatchType.TREE_PATCH, PatchType.BUSH_PATCH, PatchType.FRUIT_TREE_PATCH, PatchType.SPIRIT_TREE_PATCH, PatchType.CALQUAT_TREE_PATCH)
if (patch == FarmingPatch.TROLL_STRONGHOLD_HERB) {
if (!hasRequirement(player, Quests.MY_ARMS_BIG_ADVENTURE))
@ -261,7 +261,7 @@ class UseWithPatchHandler : InteractionListener {
}*/
val requiredItem = when (patch.type) {
PatchType.TREE_PATCH, PatchType.FRUIT_TREE_PATCH, PatchType.SPIRIT_TREE_PATCH -> Items.SPADE_952
PatchType.TREE_PATCH, PatchType.FRUIT_TREE_PATCH, PatchType.SPIRIT_TREE_PATCH, PatchType.CALQUAT_TREE_PATCH -> Items.SPADE_952
PatchType.FLOWER_PATCH -> if (plantable == Plantable.SCARECROW) null else Items.SEED_DIBBER_5343
else -> Items.SEED_DIBBER_5343
}
@ -284,7 +284,8 @@ class UseWithPatchHandler : InteractionListener {
}
animate(player, anim)
playAudio(player, sound)
val delay = if (patch.type == PatchType.TREE_PATCH || patch.type == PatchType.FRUIT_TREE_PATCH || patch.type == PatchType.SPIRIT_TREE_PATCH || plantable == Plantable.SCARECROW) 3 else 0
val delay = if (patch.type == PatchType.TREE_PATCH || patch.type == PatchType.FRUIT_TREE_PATCH || patch.type == PatchType.SPIRIT_TREE_PATCH ||
patch.type == PatchType.CALQUAT_TREE_PATCH || plantable == Plantable.SCARECROW) 3 else 0
return@queueScript delayScript(player,anim.duration + delay)
}
1 -> {

View file

@ -152,7 +152,7 @@ class FishingListener : InteractionListener{
}
return false
}
return node.isActive && node.location.withinDistance(player.location, 1)
return node.isActive && node.location.withinMaxnormDistance(player.location, 1)
}

View file

@ -35,6 +35,7 @@ import java.util.ArrayList;
import java.util.List;
import static core.api.ContentAPIKt.*;
import static core.api.ContentAPIKt.sendMessage;
/**
* Represents a familiar.
@ -258,7 +259,10 @@ public abstract class Familiar extends NPC implements Plugin<Object> {
if (victim == null) {
victim = owner.getAttribute("combat-attacker");
}
if (combat.getVictim() != this && victim != null && !victim.isInvisible() && getProperties().isMultiZone() && owner.getProperties().isMultiZone() && isCombatFamiliar() && !isBurdenBeast() && !isPeacefulFamiliar()) {
if (combat.getVictim() != this && victim != null && !victim.isInvisible()
&& getProperties().isMultiZone() && owner.getProperties().isMultiZone()
&& victim.getProperties().isMultiZone() && isCombatFamiliar()
&& !isBurdenBeast() && !isPeacefulFamiliar()) { // Are you serious my brotha?
getProperties().getCombatPulse().attack(victim);
}
}
@ -271,58 +275,120 @@ public abstract class Familiar extends NPC implements Plugin<Object> {
}
handleFamiliarTick();
}
@Override
public boolean isAttackable(Entity entity, CombatStyle style, boolean message) {
if (isInvalidTarget(entity, style, message)) return false;
if (isAttackableBy(entity, style, message)) return false;
return super.isAttackable(entity, style, message);
}
@Override
public boolean continueAttack(Entity target, CombatStyle style, boolean message) {
return !isInvalidTarget(target, style, message);
if (getProperties().getCombatPulse().getVictim() == target) {
return canAttackTarget(target, style, message);
}
if (target == owner) {
if (message) {
sendMessage(owner, "You can't just betray your own familiar like that!");
}
return false;
}
return true;
}
private boolean isInvalidTarget(Entity entity, CombatStyle style, boolean message) {
if (entity == owner) {
/**
* Checks if the familiar can attack the target (familiar as attacker).
* Called from continueAttack() when familiar initiates combat.
*
* @param target The target to attack.
* @param style The combat style.
* @param message Whether to send messages.
*
* @return true if the familiar can attack the target.
*/
private boolean canAttackTarget(Entity target, CombatStyle style, boolean message) {
if (target == owner) {
if (message) {
sendMessage(owner, "You can't just betray your own familiar like that!");
}
return false;
}
if (target instanceof Player) {
Player targetPlayer = (Player) target;
if (!targetPlayer.getSkullManager().isWilderness()) {
if (message) {
sendMessage(owner, "You have to be in the wilderness to attack a player's familiar.");
}
return false;
}
if (!owner.getSkullManager().isWilderness()) {
if (message) {
sendMessage(owner, "This familiar's owner is not in the wilderness.");
}
return false;
}
if (!owner.isAttackable(target, style, false)) {
return false;
}
if (!owner.getProperties().isMultiZone()) {
if (message) {
sendMessage(owner, "You have to be in a multicombat zone to attack another player with your familiar.");
}
return false;
}
}
return true;
}
/**
* Checks if the entity can attack this familiar (familiar as target).
* Called from isAttackable() when someone attacks the familiar.
*
* @param attacker The entity trying to attack the familiar.
* @param style The combat style.
* @param message Whether to send messages.
*
* @return true if the attacker cannot attack the familiar (invalid target).
*/
private boolean isAttackableBy(Entity attacker, CombatStyle style, boolean message) {
if (attacker == owner) {
if (message) {
sendMessage(owner, "You can't just betray your own familiar like that!");
}
return true;
}
if (entity instanceof Player) {
if (!owner.isAttackable(entity, style, message)) {
return true;
}
}
if (!getProperties().isMultiZone()) {
if (entity instanceof Player && !((Player) entity).getProperties().isMultiZone()) {
if (attacker instanceof Player) {
Player attackingPlayer = (Player) attacker;
if (!attackingPlayer.getSkullManager().isWilderness()) {
if (message) {
sendMessage((Player) entity, "You have to be in multicombat to attack a player's familiar.");
}
return true;
}
if (entity instanceof Player) {
if (message) {
sendMessage((Player) entity, "This familiar is not in the a multicombat zone.");
}
}
return true;
}
if (entity instanceof Player) {
if (!((Player) entity).getSkullManager().isWilderness()) {
if (message) {
sendMessage((Player) entity, "You have to be in the wilderness to attack a player's familiar.");
sendMessage(attackingPlayer, "You have to be in the wilderness to attack a player's familiar.");
}
return true;
}
if (!owner.getSkullManager().isWilderness()) {
if (message) {
sendMessage((Player) entity, "This familiar's owner is not in the wilderness.");
sendMessage(attackingPlayer, "This familiar's owner is not in the wilderness.");
}
return true;
}
if (!owner.isAttackable(attackingPlayer, style, false)) {
return true;
}
if (!attackingPlayer.getProperties().isMultiZone()) {
if (message) {
sendMessage(attackingPlayer, "You have to be in a multicombat zone to attack a player's familiar.");
}
return true;
}
if (!this.getProperties().isMultiZone()) {
if (message) {
sendMessage(attackingPlayer, "This familiar is not in a multicombat zone.");
}
return true;
}
}
return false;
}
@ -440,9 +506,10 @@ public abstract class Familiar extends NPC implements Plugin<Object> {
public void visualizeSpecialMove() {
owner.visualize(Animation.create(7660), Graphics.create(1316));
}
/**
* Sends a familiar hit.
*
* @param target the target.
* @param maxHit the max hit.
* @param graphics the graphics.
@ -459,6 +526,13 @@ public abstract class Familiar extends NPC implements Plugin<Object> {
hit = RandomFunction.randomize(maxHit);
}
state.setEstimatedHit(hit);
if (target instanceof NPC && ((NPC) target).getTask() != null && ((NPC) target).getTask().levelReq > owner.getSkills().getLevel(Skills.SLAYER)) {
state.neutralizeHits();
state.setPoisonDamage(-1);
if (state.getStyle() == CombatStyle.MAGIC) {
state.setEstimatedHit(-1);
}
}
target.getImpactHandler().handleImpact(owner, hit, CombatStyle.MELEE, state);
if (graphics != null) {
target.graphics(graphics);
@ -484,7 +558,7 @@ public abstract class Familiar extends NPC implements Plugin<Object> {
public void sendFamiliarHit(final Entity target, final int maxHit) {
sendFamiliarHit(target, maxHit, null);
}
/**
* Checks if this familiar can attack the target (used mainly for special
* moves).
@ -495,11 +569,17 @@ public abstract class Familiar extends NPC implements Plugin<Object> {
}
if (target.getLocation().getDistance(getLocation()) > 8) {
if (message) {
owner.getPacketDispatch().sendMessage("That target is too far.");
sendMessage(owner, "That target is too far.");
}
return false;
}
if (target.getLocks().isInteractionLocked() || !target.isAttackable(this, CombatStyle.MAGIC, true)) {
if (target instanceof Player && !owner.getProperties().isMultiZone()) {
if (message) {
sendMessage(owner, "You have to be in a multicombat zone to attack another player with your familiar.");
}
return false;
}
if (target.getLocks().isInteractionLocked() || !target.isAttackable(this, CombatStyle.MAGIC, message)) {
return false;
}
return isCombatFamiliar();
@ -509,11 +589,13 @@ public abstract class Familiar extends NPC implements Plugin<Object> {
public boolean canAttack(Entity target) {
return canAttack(target, true);
}
/**
* Checks if a familiar can perform a combat special attack.
*
* @param target the target.
* @param message show message.
*
* @return {@code True} if so.
*/
public boolean canCombatSpecial(Entity target, boolean message) {
@ -526,6 +608,10 @@ public abstract class Familiar extends NPC implements Plugin<Object> {
if (getAttribute("special-delay", 0) > GameWorld.getTicks()) {
return false;
}
// The special move probably should go through but with all harmful effects neutralized, but for now just block the damn thing
if (target instanceof NPC && ((NPC) target).getTask() != null && ((NPC) target).getTask().levelReq > owner.getSkills().getLevel(Skills.SLAYER)) {
return false;
}
return true;
}

View file

@ -40,7 +40,7 @@ public class IbisNPC extends Forager {
* @param id The id.
*/
public IbisNPC(Player owner, int id) {
super(owner, id, 3800, 12531, 12, new Item(361), new Item(373));
super(owner, id, 3800, 12531, 12, new Item(359), new Item(371));
boosts.add(new SkillBonus(Skills.FISHING, 3));
}
@ -54,7 +54,7 @@ public class IbisNPC extends Forager {
@Override
public boolean produceItem(Item item) {
if (super.produceItem(item)) {
if (item.getId() == 373) {
if (item.getId() == 371) {
owner.getSkills().addExperience(Skills.FISHING, 10);
}
return true;

View file

@ -3,6 +3,7 @@ package content.global.skill.summoning.familiar;
import java.util.List;
import core.cache.def.impl.NPCDefinition;
import core.game.node.entity.combat.equipment.WeaponInterface;
import core.plugin.Initializable;
import core.game.interaction.OptionHandler;
import core.game.node.Node;
@ -36,7 +37,7 @@ public class SmokeDevilNPC extends Familiar {
* @param id The id.
*/
public SmokeDevilNPC(Player owner, int id) {
super(owner, id, 4800, 12085, 6);
super(owner, id, 4800, 12085, 6, WeaponInterface.STYLE_CAST);
}
@Override

View file

@ -2,36 +2,35 @@ package content.minigame.allfiredup
import core.api.*
import core.game.node.entity.player.Player
import core.tools.SystemLogger
import core.game.world.map.Location
import core.tools.Log
/**
* Various data for beacons, such as varp and offset, required FM level, etc
* @author Ceikry
* @author Ceikry, Player Name
*/
enum class AFUBeacon(val title: String, val fmLevel: Int, val varbit: Int, val location: Location, val experience: Double, val keeper: Int = 0) {
RIVER_SALVE("",43,5146,Location.create(3396, 3464, 0),216.2,8065),
RAG_AND_BONE("",43,5147,Location.create(3343, 3510, 0),235.8,8066),
JOLLY_BOAR("",48,5148,Location.create(3278, 3525, 0), 193.8,8067),
NORTH_VARROCK_CASTLE("",53,5149,Location.create(3236, 3527, 0),178.5,8068),
GRAND_EXCHANGE("",59,5150,Location.create(3170, 3536, 0),194.3,8069),
EDGEVILLE("",62,5151,Location.create(3087, 3516, 0),86.7,8070),
MONASTERY("",68,5152,Location.create(3034, 3518, 0),224.4,8071),
GOBLIN_VILLAGE("",72,5153,Location.create(2968, 3516, 0),194.8,8072),
BURTHORPE("",76,5154,Location.create(2940, 3565, 0),195.3,8073),
DEATH_PLATEAU("",79,5155,Location.create(2944, 3622, 0),249.9,8074),
TROLLHEIM("",83,5156,Location.create(2939, 3680, 0),201.0,8075),
GWD("",87,5157,Location.create(2937, 3773, 0),255.0,8076),
TEMPLE("",89,5158,Location.create(2946, 3836, 0),198.9),
PLATEAU("",92,5159,Location.create(2964, 3931, 0),147.9);
RIVER_SALVE("near the River Salve", 43, 5146, Location.create(3396, 3464, 0), 216.2, 8065),
RAG_AND_BONE("near the Rag and Bone Man", 43, 5147, Location.create(3343, 3510, 0), 235.8, 8066),
JOLLY_BOAR("north of the Jolly Boar", 48, 5148, Location.create(3278, 3525, 0), 193.8, 8067),
NORTH_VARROCK_CASTLE("north of Varrock Palace", 53, 5149, Location.create(3236, 3527, 0), 178.5, 8068),
GRAND_EXCHANGE("north of the Grand Exchange", 59, 5150, Location.create(3170, 3536, 0), 194.3, 8069),
EDGEVILLE("near Edgeville", 62, 5151, Location.create(3087, 3516, 0), 86.7, 8070),
MONASTERY("near the Black Knights' Fortress", 68, 5152, Location.create(3034, 3518, 0), 224.4, 8071),
GOBLIN_VILLAGE("near Goblin Village", 72, 5153, Location.create(2968, 3516, 0), 194.8, 8072),
BURTHORPE("near Burthorpe", 76, 5154, Location.create(2940, 3565, 0), 195.3, 8073),
DEATH_PLATEAU("east of Death Plateau", 79, 5155, Location.create(2944, 3622, 0), 249.9, 8074),
TROLLHEIM("east of Trollheim", 83, 5156, Location.create(2939, 3680, 0), 201.0, 8075),
GWD("east of the God Wars dungeon temple", 87, 5157, Location.create(2937, 3773, 0), 255.0, 8076),
TEMPLE("north of the small temple in the Wilderness", 89, 5158, Location.create(2946, 3836, 0), 198.9),
PLATEAU("on the Frozen Waste Plateau", 92, 5159, Location.create(2964, 3931, 0), 147.9);
companion object {
fun forLocation(location: Location): AFUBeacon {
for (beacon in values()) {
if (beacon.location.equals(location)) return beacon
}
return AFUBeacon.RIVER_SALVE.also { log(this::class.java, Log.WARN, "Unhandled Beacon Location ${location.toString()}") }
return AFUBeacon.RIVER_SALVE.also { log(this::class.java, Log.WARN, "Unhandled Beacon Location ${location.toString()}") }
}
fun resetAllBeacons(player: Player){
@ -42,19 +41,19 @@ enum class AFUBeacon(val title: String, val fmLevel: Int, val varbit: Int, val l
}
fun light(player: Player){
setVarbit(player, varbit, 2)
setVarbit(player, varbit, 2, true)
}
fun diminish(player: Player){
setVarbit(player, varbit, 3)
setVarbit(player, varbit, 3, true)
}
fun extinguish(player: Player){
setVarbit(player, varbit, 0)
setVarbit(player, varbit, 0, true)
}
fun lightGnomish(player: Player){
setVarbit(player, varbit, 4)
setVarbit(player, varbit, 4, true)
}
fun fillWithLogs(player: Player){

View file

@ -4,13 +4,23 @@ 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.system.task.Pulse
import core.game.world.update.flag.context.Animation
import org.rs09.consts.Items
import core.game.interaction.InteractionListener
import core.game.interaction.IntType
import core.game.world.GameWorld
import content.data.Quests
import core.api.delayScript
import core.api.getAttribute
import core.api.getOrStartTimer
import core.api.getQuestStage
import core.api.lock
import core.api.queueScript
import core.api.removeAttribute
import core.api.sendMessage
import core.api.setAttribute
import core.api.setQuestStage
import core.api.stopExecuting
import core.game.interaction.QueueStrength
private val VALID_LOGS = intArrayOf(Items.LOGS_1511, Items.OAK_LOGS_1521,Items.WILLOW_LOGS_1519,Items.MAPLE_LOGS_1517,Items.YEW_LOGS_1515,Items.MAGIC_LOGS_1513)
private val FILL_ANIM = Animation(9136)
@ -18,7 +28,7 @@ private val LIGHT_ANIM = Animation(7307)
/**
* Handles interactions for beacons
* @author Ceikry
* @author Ceikry, Player Name
*/
class AFUBeaconListeners : InteractionListener {
@ -86,112 +96,80 @@ class AFUBeaconListeners : InteractionListener {
val logs = getLogs(player,20)
if (logs.id != 0 && player.inventory.remove(logs)) {
player.lock()
var session: AFUSession? = null
if(questComplete){
session = player.getAttribute("afu-session", null)
if(session == null) {
session = AFUSession(player)
session.init()
lock(player, FILL_ANIM.duration)
queueScript(player, 0, QueueStrength.SOFT) { stage ->
if (stage == 1) {
player.animator.animate(FILL_ANIM)
return@queueScript delayScript(player, FILL_ANIM.duration)
}
setAttribute(player, "/save:beacon:${beacon.ordinal}:logsId", logs.id)
beacon.fillWithLogs(player)
return@queueScript stopExecuting(player)
}
GameWorld.Pulser.submit(object : Pulse() {
var counter = 0
override fun pulse(): Boolean {
when (counter++) {
0 -> player.animator.animate(FILL_ANIM)
1 -> {
beacon.fillWithLogs(player)
if(questComplete){
session?.setLogs(beacon.ordinal,logs)
}
}
2 -> player.unlock().also {player.animator.animate(Animation.RESET); return true }
}
return false
}
})
} else {
player.dialogueInterpreter.sendDialogue("You need some logs to do this.")
}
}
fun lightBeacon(player: Player, beacon: AFUBeacon, questComplete: Boolean){
var session: AFUSession? = null
if(questComplete){
session = player.getAttribute("afu-session",null)
if(session == null) return
}
fun lightBeacon(player: Player, beacon: AFUBeacon, questComplete: Boolean) {
if(player.inventory.contains(Items.TINDERBOX_590,1)){
player.lock()
GameWorld.Pulser.submit(object: Pulse(){
var counter = 0
override fun pulse(): Boolean {
when(counter++){
0 -> player.animator.animate(LIGHT_ANIM)
1 -> {
beacon.light(player)
if(questComplete){
session?.startTimer(beacon.ordinal)
if(session?.getLitBeacons() == 6 && !player.hasFireRing()){
player.sendMessage("Congratulations on lighting 6 beacons at once! King Roald has something for you.")
player.setAttribute("/save:afu-mini:ring",true)
}
if(session?.getLitBeacons() == 10 && !player.hasFlameGloves()){
player.sendMessage("Congratulations on lighting 10 beacons at once! King Roald has something for you.")
player.setAttribute("/save:afu-mini:gloves",true)
}
if(session?.getLitBeacons() == 14 && !player.hasInfernoAdze()){
player.sendMessage("Congratulations on lighting all 14 beacons! King Roald has something special for you.")
player.setAttribute("/save:afu-mini:adze",true)
}
var experience = beacon.experience
experience += session?.getBonusExperience() ?: 0.0
player.skills.addExperience(Skills.FIREMAKING,experience)
} else {
player.questRepository.getQuest(Quests.ALL_FIRED_UP).setStage(player, player.questRepository.getStage(Quests.ALL_FIRED_UP) + 10)
}
}
2 -> player.unlock().also { return true }
}
return false
lock(player, LIGHT_ANIM.duration)
queueScript(player, 0, QueueStrength.SOFT) { stage ->
if (stage == 1) {
player.animator.animate(LIGHT_ANIM)
return@queueScript delayScript(player, LIGHT_ANIM.duration)
}
})
val logsId = getAttribute(player, "/save:beacon:${beacon.ordinal}:logsId", Items.LOGS_1511)
removeAttribute(player, "/save:beacon:${beacon.ordinal}:logsId")
beacon.light(player)
if (!questComplete) {
setQuestStage(player, Quests.ALL_FIRED_UP, getQuestStage(player, Quests.ALL_FIRED_UP) + 10)
return@queueScript stopExecuting(player)
}
val timer = getOrStartTimer<AFUTimer>(player)
timer.addTime(beacon.ordinal, logsId, 20)
val lit = timer.getLitBeacons()
if (lit == 6 && !player.hasFireRing()) {
sendMessage(player, "Congratulations on lighting 6 beacons at once! King Roald has something for you.")
setAttribute(player, "/save:afu-mini:ring", true)
}
if (lit == 10 && !player.hasFlameGloves()) {
sendMessage(player, "Congratulations on lighting 10 beacons at once! King Roald has something for you.")
setAttribute(player, "/save:afu-mini:gloves", true)
}
if (lit == 14 && !player.hasInfernoAdze()) {
sendMessage(player, "Congratulations on lighting all 14 beacons! King Roald has something special for you.")
setAttribute(player, "/save:afu-mini:adze", true)
}
val xp = beacon.experience + timer.getBonusExperience()
player.skills.addExperience(Skills.FIREMAKING, xp)
return@queueScript stopExecuting(player)
}
} else {
player.dialogueInterpreter.sendDialogue("You need a tinderbox to light this.")
}
}
fun restoreBeacon(player: Player, beacon: AFUBeacon, questComplete: Boolean){
var session: AFUSession? = null
if(questComplete){
session = player.getAttribute("afu-session",null)
if(session == null) return
}
val logs = getLogs(player, 5)
if (logs.id != 0 && player.inventory.remove(logs)) {
player.lock()
GameWorld.Pulser.submit(object: Pulse(){
var counter = 0
override fun pulse(): Boolean {
when(counter++){
0 -> player.animator.animate(FILL_ANIM)
1 -> beacon.light(player).also {
if(questComplete){
session?.refreshTimer(beacon,logs.id)
} else {
player.questRepository.getQuest(Quests.ALL_FIRED_UP).setStage(player, 80)
}
}
2 -> player.unlock().also { return true }
}
return false
lock(player, FILL_ANIM.duration)
queueScript(player, 0, QueueStrength.SOFT) { stage ->
if (stage == 1) {
player.animator.animate(LIGHT_ANIM)
return@queueScript delayScript(player, LIGHT_ANIM.duration)
}
})
val backupLogsId = getAttribute(player, "/save:beacon:${beacon.ordinal}:backupLogsId", Items.LOGS_1511)
removeAttribute(player, "/save:beacon:${beacon.ordinal}:backupLogsId")
beacon.light(player)
if (!questComplete) {
setQuestStage(player, Quests.ALL_FIRED_UP, 80)
return@queueScript stopExecuting(player)
}
val timer = getOrStartTimer<AFUTimer>(player)
timer.addTime(beacon.ordinal, backupLogsId, 5)
return@queueScript stopExecuting(player)
}
} else {
player.dialogueInterpreter.sendDialogue("You need some logs to do this.")
}

View file

@ -1,121 +0,0 @@
package content.minigame.allfiredup
import core.api.LogoutListener
import core.game.node.entity.player.Player
import core.game.node.item.Item
import core.game.system.task.Pulse
import core.game.world.GameWorld
import org.rs09.consts.Items
import core.tools.colorize
/**
* Handles keeping track of lit beacons and their burn time remaining
* @author Ceikry
*/
class AFUSession(val player: Player? = null) : LogoutListener {
private val beaconTimers = Array(14){i -> BeaconTimer(0, AFUBeacon.values()[i]) }
private val logInventories = Array(14){Item(0,0)}
private val beaconWatched = Array(14){false}
private var isActive = false
fun init() {
isActive = true
GameWorld.Pulser.submit(object: Pulse(){
override fun pulse(): Boolean {
player!!.setAttribute("afu-pulse",this)
beaconTimers.forEach {timer ->
timer.ticks--
if(timer.ticks == 300) timer.beacon.diminish(player).also {
if(beaconWatched[timer.beacon.ordinal]){
beaconWatched[timer.beacon.ordinal] = false
timer.ticks += (getTicks(logInventories[timer.beacon.ordinal].id) * 5)
timer.beacon.light(player)
player.sendMessage(colorize("%RThe ${timer.beacon.name.toLowerCase().replace("_"," ")} watcher has used your backup logs."))
} else {
player.sendMessage(colorize("%RThe ${timer.beacon.name.toLowerCase().replace("_", " ")} beacon is dying!"))
}
}
if(timer.ticks == 0) timer.beacon.extinguish(player).also { player.sendMessage(colorize("%RThe ${timer.beacon.name.toLowerCase().replace("_"," ")} beacon has gone out!")) }
}
return !isActive
}
})
player!!.setAttribute("afu-session",this)
}
fun getLitBeacons(): Int{
return beaconTimers.count { it.ticks > 0 }
}
fun end(){
isActive = false
}
fun setLogs(beaconIndex: Int, logs: Item){
logInventories[beaconIndex] = logs
}
fun startTimer(beaconIndex: Int){
val ticks = getTicks(logInventories[beaconIndex].id) * 20
logInventories[beaconIndex] = Item(0,0)
beaconTimers[beaconIndex].ticks = ticks
}
fun refreshTimer(beacon: AFUBeacon, logID: Int){
val ticks = getTicks(logID) * 5
beaconTimers.forEach {
if(it.beacon.ordinal == beacon.ordinal) it.ticks += ticks
}
}
fun setWatcher(index: Int, logs: Item){
beaconWatched[index] = true
logInventories[index] = logs
}
fun isWatched(index: Int): Boolean{
return beaconWatched[index]
}
fun getTicks(logID: Int): Int{
val ticks = when(logID){
Items.LOGS_1511 -> 65
Items.OAK_LOGS_1521 -> 68
Items.WILLOW_LOGS_1519 -> 73
Items.MAPLE_LOGS_1517 -> 79
Items.YEW_LOGS_1515 -> 83
Items.MAGIC_LOGS_1513 -> 90
else -> 0
}
return ticks
}
fun getBonusExperience(): Double{
return when(getLitBeacons()){
1 -> 608.4
2 -> 1622.4
3 -> 1987.4
4 -> 2149.6
5 -> 2149.6
6 -> 2514.6
7 -> 2555.2
8 -> 2758.0
9 -> 2839.1
10 -> 3041.9
11 -> 3123.0
12 -> 3244.7
13 -> 3366.4
14 -> 4867.4
else -> 0.0
}
}
override fun logout(player: Player) {
AFUBeacon.resetAllBeacons(player)
val session: AFUSession? = player.getAttribute("afu-session",null)
session?.end()
player.removeAttribute("afu-session")
}
internal class BeaconTimer(var ticks: Int, val beacon: AFUBeacon)
}

View file

@ -0,0 +1,104 @@
package content.minigame.allfiredup
import core.api.getAttribute
import core.api.log
import core.api.removeAttribute
import core.api.sendMessage
import core.game.node.entity.Entity
import core.game.node.entity.player.Player
import core.game.system.timer.PersistTimer
import core.tools.Log
import core.tools.colorize
import org.json.simple.JSONArray
import org.json.simple.JSONObject
import org.rs09.consts.Items
class AFUTimer : PersistTimer(1, "AFU timer", isSoft = true) {
private val burnTicks = Array(14) { 0 }
fun addTime(index: Int, logsId: Int, numLogs: Int) {
val ticksPerLog = when (logsId) {
Items.LOGS_1511 -> 65
Items.OAK_LOGS_1521 -> 68
Items.WILLOW_LOGS_1519 -> 73
Items.MAPLE_LOGS_1517 -> 79
Items.YEW_LOGS_1515 -> 83
Items.MAGIC_LOGS_1513 -> 90
else -> 0
}
burnTicks[index] += numLogs * ticksPerLog
}
fun getBonusExperience(): Double {
return when (getLitBeacons()) {
1 -> 608.4
2 -> 1622.4
3 -> 1987.4
4 -> 2149.6
5 -> 2149.6
6 -> 2514.6
7 -> 2555.2
8 -> 2758.0
9 -> 2839.1
10 -> 3041.9
11 -> 3123.0
12 -> 3244.7
13 -> 3366.4
14 -> 4867.4
else -> 0.0
}
}
fun getLitBeacons(): Int {
return burnTicks.count { it > 0 }
}
override fun save(root: JSONObject, entity: Entity) {
val beaconArray = JSONArray()
for (i in 0..13) {
beaconArray.add(burnTicks[i])
}
root["beacons"] = beaconArray
}
override fun parse(root: JSONObject, entity: Entity) {
val beacons = root["beacons"] as JSONArray
for (i in 0..13) {
burnTicks[i] = beacons[i].toString().toInt()
}
}
override fun run(entity: Entity) : Boolean {
if (entity !is Player) {
log(this::class.java, Log.ERR, "How did a non-Player light an AFU beacon?")
return false
}
val player = entity
var keepRunning = false
for (i in 0..13) {
val beacon = AFUBeacon.values()[i]
if (burnTicks[i] > 0) {
burnTicks[i]--
if (burnTicks[i] > 0) {
keepRunning = true
}
if (burnTicks[i] == 300) {
val backupLogsId = getAttribute(player, "/save:beacon:$i:backupLogsId", 0)
if (backupLogsId != 0) {
addTime(i, backupLogsId, 5)
removeAttribute(player, "/save:beacon:$i:backupLogsId")
sendMessage(player, colorize("%RThe beacon watcher ${beacon.title} has used your backup logs."))
} else {
beacon.diminish(player)
sendMessage(player, colorize("%RThe beacon ${beacon.title} is dying!"))
}
} else if (burnTicks[i] == 0) {
beacon.extinguish(player)
removeAttribute(player, "/save:beacon:$i:logsId")
sendMessage(player, colorize("%RThe beacon ${beacon.title} has gone out!"))
}
}
}
return keepRunning
}
}

View file

@ -1,13 +1,13 @@
package content.minigame.allfiredup
import core.ServerConstants
import core.game.dialogue.*
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.plugin.Initializable
import content.region.wilderness.dialogue.*
import core.api.getAttribute
import core.api.setAttribute
import org.rs09.consts.Items
private val VALID_LOGS = arrayOf(Items.LOGS_1511, Items.OAK_LOGS_1521, Items.WILLOW_LOGS_1519, Items.MAPLE_LOGS_1517, Items.YEW_LOGS_1515, Items.MAGIC_LOGS_1513)
@ -41,10 +41,9 @@ class BeaconTenderDialogue(player: Player? = null) : DialoguePlugin(player) {
override fun handle(interfaceId: Int, buttonId: Int): Boolean {
val beacon = AFUBeacon.values()[index]
val logs = getLogs(player,5)
val session: AFUSession? = player.getAttribute("afu-session",null)
when(stage){
0 -> player("Hello!").also { stage++ }
1 -> if(beacon.getState(player) == BeaconState.LIT && session?.isWatched(index) == false){
1 -> if (beacon.getState(player) == BeaconState.LIT && getAttribute(player, "/save:beacon:${beacon.ordinal}:backupLogsId", 0) == 0) {
options("Can you watch this beacon for me?","Nevermind.").also { stage = 10 }
} else {
npc("Carry on, adventurer.").also { stage = 1000 }
@ -62,7 +61,7 @@ class BeaconTenderDialogue(player: Player? = null) : DialoguePlugin(player) {
13 -> npc("Great, hand them over.").also { stage++ }
14 -> player("Here you go!").also {
player.inventory.remove(logs)
session?.setWatcher(index,logs)
setAttribute(player, "/save:beacon:${beacon.ordinal}:backupLogsId", logs.id)
stage = 1000
}

View file

@ -6,14 +6,14 @@ import core.game.node.entity.combat.CombatStyle;
import core.game.node.entity.combat.CombatSwingHandler;
import core.game.node.entity.combat.InteractionType;
import core.game.node.entity.combat.equipment.ArmourSet;
import core.game.node.entity.impl.Projectile;
import core.game.node.entity.impl.Animator.Priority;
import core.game.node.entity.impl.Projectile;
import core.game.node.entity.player.Player;
import core.game.world.update.flag.context.Animation;
import core.game.world.update.flag.context.Graphics;
import core.tools.RandomFunction;
import static core.api.ContentAPIKt.*;
import static core.api.ContentAPIKt.applyPoison;
/**
* Handles K'ril Tsutsaroth's combat.
@ -62,11 +62,13 @@ public final class GWDTsutsarothSwingHandler extends CombatSwingHandler {
if (RandomFunction.randomize(10) < 4) {
ticks += (int) Math.ceil(entity.getLocation().getDistance(victim.getLocation()) * 0.3);
style = CombatStyle.MAGIC;
} else if (RandomFunction.randomize(10) == 0) {
if (special = (victim instanceof Player)) {
((Player) victim).getPacketDispatch().sendMessage("K'ril Tsutsaroth slams through your protection prayer, leaving you feeling drained.");
}
} else if (victim instanceof Player && RandomFunction.randomize(10) == 0) {
Player player = (Player) victim;
special = player.hasProtectionPrayer(CombatStyle.MELEE);
if (special) {
player.getPacketDispatch().sendMessage("K'ril Tsutsaroth slams through your protection prayer, leaving you feeling drained.");
entity.sendChat("YARRRRRRR!");
}
}
if (style.getSwingHandler().isAccurateImpact(entity, victim)) {
int max = style.getSwingHandler().calculateHit(entity, victim, special ? 1.08 : 1.0);
@ -76,7 +78,7 @@ public final class GWDTsutsarothSwingHandler extends CombatSwingHandler {
applyPoison(victim, entity, 80);
}
if (special) {
((Player) victim).getSkills().decrementPrayerPoints((double) hit / 2);
victim.getSkills().decrementPrayerPoints((double) hit / 2);
}
}
state.setEstimatedHit(hit);

View file

@ -30,6 +30,7 @@ public class SophanemPlugin extends OptionHandler {
SceneryDefinition.forId(20275).getHandlers().put("option:climb-down", this);
SceneryDefinition.forId(20391).getHandlers().put("option:open", this);
SceneryDefinition.forId(28514).getHandlers().put("option:open", this);
SceneryDefinition.forId(20341).getHandlers().put("option:open", this);
return this;
}
@ -53,6 +54,8 @@ public class SophanemPlugin extends OptionHandler {
}
DoorActionHandler.handleDoor(player, (Scenery) node);
break;
case 20341:
break;
}
return true;
}

View file

@ -356,9 +356,8 @@ class PlagueCityListeners : InteractionListener {
on(Scenery.DOOR_2054, IntType.SCENERY, "open"){ player, node ->
if (isQuestComplete(player, Quests.PLAGUE_CITY)){
DoorActionHandler.handleDoor(player, node as SceneryNode)
}
else{
DoorActionHandler.handleAutowalkDoor(player, node as SceneryNode)
} else {
sendMessage(player, "This door is locked")
}
return@on true

View file

@ -1,51 +1,110 @@
package content.region.misc.keldagrim.dialogue
import core.game.dialogue.DialoguePlugin
import core.game.dialogue.FacialExpression
import core.game.dialogue.Topic
import core.game.node.entity.player.Player
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.entity.npc.NPC
import core.game.node.item.Item
import core.plugin.Initializable
import core.tools.END_DIALOGUE
import core.tools.START_DIALOGUE
import org.rs09.consts.Items
import org.rs09.consts.NPCs
@Initializable
class BlandebirDialogue(player: Player? = null) : DialoguePlugin(player) {
override fun handle(interfaceId: Int, buttonId: Int): Boolean {
when(stage){
START_DIALOGUE -> playerl(FacialExpression.FRIENDLY, "Hello, I wonder if you could help me on this whole brewing thing...").also { stage++ }
1 -> npcl(FacialExpression.OLD_DEFAULT, "I might be able to - what do you need to know?").also { stage++ }
2 -> showTopics(
Topic(FacialExpression.FRIENDLY, "How do I brew ales?", 3),
Topic(FacialExpression.FRIENDLY, "How do I brew cider?", 6),
Topic(FacialExpression.FRIENDLY, "What do I do once my ale has matured?", 8),
Topic(FacialExpression.FRIENDLY, "Do you have any spare ale yeast?", 9),
Topic(FacialExpression.FRIENDLY, "That's all I need to know, thanks.", END_DIALOGUE),
class BlandebirDialogue : InteractionListener {
override fun defineListeners() {
on(NPCs.BLANDEBIR_2321, IntType.NPC, "talk-to") { player, node ->
DialogueLabeller.open(player, BlandebirDialogueFile(), node as NPC)
return@on true
}
}
}
class BlandebirDialogueFile : DialogueLabeller() {
override fun addConversation() {
assignToIds(NPCs.BLANDEBIR_2321)
player(ChatAnim.FRIENDLY, "Hello, I wonder if you could help me on this whole brewing thing...")
npc(ChatAnim.OLD_DEFAULT, "I might be able to - what do you need to know?")
goto("main_menu")
label("main_menu") {
options(
DialogueOption("how_brew_ales", "How do I brew ales?", expression = ChatAnim.HALF_ASKING, spokenText = "How do I brew ales?"),
DialogueOption("how_brew_cider", "How do I brew cider?", expression = ChatAnim.HALF_ASKING, spokenText = "How do I brew cider?"),
DialogueOption("what_when_matured", "What do I do once my ale has matured?", expression = ChatAnim.HALF_ASKING, spokenText = "What do I do once my ale has matured?"),
DialogueOption("buy_yeast", "Do you have any spare ale yeast?", expression = ChatAnim.HALF_ASKING, spokenText = "Do you have any spare ale yeast?"),
DialogueOption("end", "That's all I need to know, thanks.")
)
3 -> npcl(FacialExpression.OLD_DEFAULT, "Well first off you need to fill the vat with water - two bucketfuls should do the trick. Then you'll need to put in two handfuls of barley malt - that's roasted barley by the way.").also { stage++ }
4 -> npcl(FacialExpression.OLD_DEFAULT, "After that you'll be putting your main ingredient in - this will decide which ale it is you're brewing. There should be some good guides around with recipes in.").also { stage++ }
5 -> npcl(FacialExpression.OLD_DEFAULT, "Lastly you pour a pot full of ale yeast in, which'll start it off fermenting. Then all you have to do is wait for the good stuff.").also {
stage = 2
}
6 -> npcl(FacialExpression.OLD_DEFAULT, "First you'll need some apples. You'll need to crush them using this cider-press - four apples should make a full bucket of apple-mush. Take four buckets of mush and fill up the vat.").also { stage++ }
7 -> npcl(FacialExpression.OLD_DEFAULT, "After that you pour a pot full of ale yeast in, which'll start it off fermenting. Then all you have to do is wait for the good stuff.").also {
stage = 2
}
8 -> npcl(FacialExpression.OLD_DEFAULT, "Well, once you've got a full vat of the good stuff, just turn the valve and your barrel will fill up with eight pints of whatever your chosen tipple is. Mind it's an empty barrel, though.").also {
stage = 2
}
9 -> npcl(FacialExpression.OLD_DEFAULT, "Well, as a matter of fact I do, although I wouldn't describe it as spare. This ale yeast I've got is the best money can buy, but if you've got a pot I'll fill it for you for 25GP - very cheap as it happens.").also {
stage = 2
}
label("how_brew_ales") {
npc(ChatAnim.OLD_DEFAULT, "Well first off you need to fill the vat with water - two bucketfuls should do the trick. Then you'll need to put in two handfuls of barley malt - that's roasted barley by the way.")
npc(ChatAnim.OLD_DEFAULT, "After that you'll be putting your main ingredient in - this will decide which ale it is you're brewing. There should be some good guides around with recipes in.")
npc(ChatAnim.OLD_DEFAULT, "Lastly you pour a pot full of ale yeast in, which'll start it off fermenting. Then all you have to do is wait for the good stuff.")
goto("main_menu")
}
label("how_brew_cider") {
npc(ChatAnim.OLD_DEFAULT, "First you'll need some apples. You'll need to crush them using this cider-press - four apples should make a full bucket of apple-mush. Take four buckets of mush and fill up the vat.")
npc(ChatAnim.OLD_DEFAULT, "After that you pour a pot full of ale yeast in, which'll start it off fermenting. Then all you have to do is wait for the good stuff.")
goto("main_menu")
}
label("what_when_matured") {
npc(ChatAnim.OLD_DEFAULT, "Well, once you've got a full vat of the good stuff, just turn the valve and your barrel will fill up with eight pints of whatever your chosen tipple is. Mind it's an empty barrel, though.")
goto("main_menu")
}
label("buy_yeast") {
npc(ChatAnim.OLD_DEFAULT, "Well, as a matter of fact I do, although I wouldn't describe it as spare. This ale yeast I've got is the best money can buy, but if you've got a pot I'll fill it for you for 25GP - very cheap as it happens.")
options(
DialogueOption("confirm_buy", "That's a good deal - please fill my pot with ale yeast for 25GP."),
DialogueOption("end", "No, that's too much I'm afraid.")
)
}
label("confirm_buy") {
exec { player, npc ->
if (!inInventory(player, Items.EMPTY_POT_1931, 1)) {
loadLabel(player, "no_pot")
} else if (!inInventory(player, Items.COINS_995, 25)) {
loadLabel(player, "no_coins")
} else {
loadLabel(player, "process_purchase")
}
}
}
return true
}
override fun newInstance(player: Player?): DialoguePlugin {
return BlandebirDialogue(player)
}
label("no_pot") {
npc(ChatAnim.OLD_DEFAULT, "I'm afraid you'll need a pot for me to put the yeast in.")
goto("end")
}
override fun getIds(): IntArray {
return intArrayOf(NPCs.BLANDEBIR_2321)
label("no_coins") {
line("You can not afford that.")
goto("end")
}
label("process_purchase") {
exec { player, npc ->
val emptyPots = amountInInventory(player, Items.EMPTY_POT_1931)
val coins = amountInInventory(player, Items.COINS_995)
val canAfford = coins / 25
val quantity = minOf(emptyPots, canAfford)
if (removeItem(player, Item(Items.EMPTY_POT_1931, quantity)) && removeItem(player, Item(Items.COINS_995, quantity * 25))) {
addItem(player, Items.ALE_YEAST_5767, quantity)
}
}
goto("main_menu")
}
label("end") {
}
}
}
}

View file

@ -52,9 +52,10 @@ public final class GrandExchangeClerk extends DialoguePlugin {
@Override
public boolean handle(int interfaceId, int buttonId) {
String salutation = player.isMale() ? "sir" : "madam";
switch (stage) {
case 0:
npc("Good day to you, sir, How can I help?");
npc("Good day to you, " + salutation + ", how can I help?");
stage = 1;
break;
case 1:
@ -80,13 +81,13 @@ public final class GrandExchangeClerk extends DialoguePlugin {
stage = 40;
break;
case 5:
interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "I'm fine actually.");
interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "I'm fine, actually.");
stage = 50;
break;
}
break;
case 10:
npc("Only too happy to help you, sir.");
npc("Only too happy to help you, " + salutation + ".");
stage = 11;
break;
case 11:
@ -94,7 +95,7 @@ public final class GrandExchangeClerk extends DialoguePlugin {
StockMarket.openFor(player);
break;
case 20:
npc("As you wish, sir.");
npc("As you wish, " + salutation + ".");
stage = 21;
break;
case 21:
@ -110,7 +111,7 @@ public final class GrandExchangeClerk extends DialoguePlugin {
GrandExchangeRecords.getInstance(player).openHistoryLog(player);
break;
case 40:
npc("It would be my pleasure, sir.");
npc("It would be my pleasure, " + salutation + ".");
stage = 41;
break;
case 41:
@ -118,7 +119,7 @@ public final class GrandExchangeClerk extends DialoguePlugin {
ExchangeItemSets.openFor(player);
break;
case 50:
npc("If you say so, sir.");
npc("If you say so, " + salutation + ".");
stage = 51;
break;
case 51:

View file

@ -15,7 +15,10 @@ import org.rs09.consts.Items
* @author Ceikry
*/
@Initializable
class AllFiredUp : Quest(Quests.ALL_FIRED_UP, 157, 156, 1){
class AllFiredUp : Quest(Quests.ALL_FIRED_UP, 157, 156, 1,
1282 /*this is actually unused*/,
5133 /*source: ::varbits 1282, after trial and error confirmed that I need ::setvarp 1282 0 QUEST_STAGE*/,
0, 1, 90) { //not started -- in progress -- completed
override fun newInstance(`object`: Any?): Quest {
return this
}
@ -150,13 +153,6 @@ class AllFiredUp : Quest(Quests.ALL_FIRED_UP, 157, 156, 1){
player.skills.addExperience(Skills.FIREMAKING, 5500.0)
player.inventory.add(Item(995,20000))
AFUBeacon.resetAllBeacons(player)
setVarbit(player, 1283, 0)
player.questRepository.syncronizeTab(player)
}
override fun getConfig(player: Player?, stage: Int): IntArray {
if(stage == 100) return intArrayOf(1282, 90)
if(stage > 0) return intArrayOf(1282, 1)
else return intArrayOf(1282, 0)
}
}

View file

@ -124,10 +124,4 @@ class FamilyCrest: Quest(Quests.FAMILY_CREST, 59, 58, 1, 148, 0, 1, 11) {
}
setAttribute(player, "/save:family-crest:gauntlets", Items.FAMILY_GAUNTLETS_778)
}
/*override fun getConfig(player: Player?, stage: Int): IntArray {
if(stage == 100) return intArrayOf(1282, 90)
if(stage > 0) return intArrayOf(1282, 1)
else return intArrayOf(1282, 0)
}*/
}

View file

@ -0,0 +1,127 @@
package content.region.morytania.phas.dialogue
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.entity.npc.NPC
import core.game.node.item.Item
import core.plugin.Initializable
import org.rs09.consts.Items
import org.rs09.consts.NPCs
@Initializable
class MetarialusDialogue : InteractionListener {
override fun defineListeners() {
on(NPCs.METARIALUS_2322, IntType.NPC, "talk-to") { player, node ->
if (inEquipment(player, Items.GHOSTSPEAK_AMULET_552) || inEquipment(player, Items.GHOSTSPEAK_AMULET_4250)) {
DialogueLabeller.open(player, MetarialusDialogueFile(), node as NPC)
return@on true
} else {
DialogueLabeller.open(player, MetarialusDialogueNoGhostSpeakFile(), node as NPC)
return@on true
}
}
}
}
class MetarialusDialogueFile : DialogueLabeller() {
override fun addConversation() {
assignToIds(NPCs.METARIALUS_2322)
player(ChatAnim.FRIENDLY, "Hello, I wonder if you could help me on this whole brewing thing...")
npc(ChatAnim.NEUTRAL, "I might be able to - what do you need to know?")
goto("main_menu")
label("main_menu") {
options(
DialogueOption("how_brew_ales", "How do I brew ales?", expression = ChatAnim.HALF_ASKING, spokenText = "How do I brew ales?"),
DialogueOption("how_brew_cider", "How do I brew cider?", expression = ChatAnim.HALF_ASKING, spokenText = "How do I brew cider?"),
DialogueOption("what_when_matured", "What do I do once my ale has matured?", expression = ChatAnim.HALF_ASKING, spokenText = "What do I do once my ale has matured?"),
DialogueOption("buy_yeast", "Do you have any spare ale yeast?", expression = ChatAnim.HALF_ASKING, spokenText = "Do you have any spare ale yeast?"),
DialogueOption("end", "That's all I need to know, thanks.")
)
}
label("how_brew_ales") {
npc(ChatAnim.NEUTRAL, "Well first off you need to fill the vat with water - two bucketfuls should do the trick. Then you'll need to put in two handfuls of barley malt - that's roasted barley by the way.")
npc(ChatAnim.NEUTRAL, "After that you'll be putting your main ingredient in - this will decide which ale it is you're brewing. There should be some good guides around with recipes in.")
npc(ChatAnim.NEUTRAL, "Lastly you pour a pot full of ale yeast in, which'll start it off fermenting. Then all you have to do is wait for the good stuff.")
goto("main_menu")
}
label("how_brew_cider") {
npc(ChatAnim.NEUTRAL, "First you'll need some apples. You'll need to crush them using this cider-press - four apples should make a full bucket of apple-mush. Take four buckets of mush and fill up the vat.")
npc(ChatAnim.NEUTRAL, "After that you pour a pot full of ale yeast in, which'll start it off fermenting. Then all you have to do is wait for the good stuff.")
goto("main_menu")
}
label("what_when_matured") {
npc(ChatAnim.NEUTRAL, "Well, once you've got a full vat of the good stuff, just turn the valve and your barrel will fill up with eight pints of whatever your chosen tipple is. Mind it's an empty barrel, though.")
goto("main_menu")
}
label("buy_yeast") {
npc(ChatAnim.NEUTRAL, "Well, as a matter of fact I do, although I wouldn't describe",
"it as spare. This ale yeast I've got is the best money can",
"buy, but if you've got a pot I'll fill it for you for",
"5 ecto-tokens - very cheap as it happens.")
options(
DialogueOption("confirm_buy", "That's a good deal - please fill my pot with ale yeast for 5 ecto-tokens."),
DialogueOption("end", "No, that's too much I'm afraid.")
)
}
label("confirm_buy") {
exec { player, npc ->
if (!inInventory(player, Items.EMPTY_POT_1931, 1)) {
loadLabel(player, "no_pot")
} else if (!inInventory(player, Items.ECTO_TOKEN_4278, 5)) {
loadLabel(player, "no_tokens")
} else {
loadLabel(player, "process_purchase")
}
}
}
label("no_pot") {
npc(ChatAnim.NEUTRAL, "I'm afraid you'll need a pot for me to put the yeast in.")
goto("end")
}
label("no_tokens") {
line("You can not afford that.")
goto("end")
}
label("process_purchase") {
exec { player, npc ->
val emptyPots = amountInInventory(player, Items.EMPTY_POT_1931)
val tokens = amountInInventory(player, Items.ECTO_TOKEN_4278)
val canAfford = tokens / 5
val quantity = minOf(emptyPots, canAfford)
if (removeItem(player, Item(Items.EMPTY_POT_1931, quantity)) && removeItem(player, Item(Items.ECTO_TOKEN_4278, quantity * 5))) {
addItem(player, Items.ALE_YEAST_5767, quantity)
}
}
goto("main_menu")
}
label("end") {
}
}
}
class MetarialusDialogueNoGhostSpeakFile : DialogueLabeller() {
override fun addConversation() {
assignToIds(NPCs.METARIALUS_2322)
npc(ChatAnim.WORRIED, "Wooo Woo ooo wooowoo!") // Placeholder taken from restless ghost, can't find source for his no-ghostspeak text
}
}

View file

@ -418,7 +418,7 @@ fun replaceSlot(player: Player, slot: Int, item: Item, currentItem: Item? = null
}
if (cont.remove(currentItem, slot, true)) {
return cont.replace(item, slot)
return cont.replace(item, slot) ?: currentItem
}
PlayerMonitor.log(player, LogType.DUPE_ALERT, "Potential slot-replacement-based dupe attempt, slot: $slot, item: $item")
@ -426,8 +426,9 @@ fun replaceSlot(player: Player, slot: Int, item: Item, currentItem: Item? = null
Container.INVENTORY -> Container.EQUIPMENT
else -> Container.INVENTORY
}
if (removeItem(player, currentItem, other))
return cont.replace(item, slot)
if (removeItem(player, currentItem, other)) {
return cont.replace(item, slot) ?: currentItem
}
return null
}
@ -3026,7 +3027,7 @@ fun queueScript(entity: Entity, delay: Int = 1, strength: QueueStrength = QueueS
}
/**
* Sets the clock to the value of WORLD_TICSK + ticks.
* Sets the clock to the value of WORLD_TICKS + ticks.
* @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

View file

@ -221,6 +221,12 @@ public abstract class MovementPulse extends Pulse {
clearInferiorScripts();
mover.face(null);
if (canInteractWithoutMoving()) {
if (interactImmediately()) {
stop();
return true;
}
}
updatePath();
if (tryInteract()) {
@ -237,7 +243,24 @@ public abstract class MovementPulse extends Pulse {
int radius = destination instanceof Entity && ((Entity)destination).getWalkingQueue().hasPath() ? 1 : 0;
if (interactLocation == null)
return false;
if (Math.max(Math.abs(ml.getX() - interactLocation.getX()), Math.abs(ml.getY() - interactLocation.getY())) <= radius) {
boolean atInteractLocation = Math.max(Math.abs(ml.getX() - interactLocation.getX()), Math.abs(ml.getY() - interactLocation.getY())) <= radius;
// Check if already in a valid interaction position for entity destinations
boolean canInteractFromCurrentPosition = false;
if (!atInteractLocation && destination instanceof Entity) {
Entity target = (Entity) destination;
Location dl = target.getLocation();
boolean onSameTile = ml.getX() == dl.getX() && ml.getY() == dl.getY() && ml.getZ() == dl.getZ();
if (!onSameTile) {
canInteractFromCurrentPosition = Pathfinder.canInteract(
ml.getX(), ml.getY(), mover.size(),
dl.getX(), dl.getY(), target.size(), target.size(),
0, // walkFlag - "can interact from any unblocked direction"
ml.getZ(),
RegionManager::getClippingFlag
);
}
}
if (atInteractLocation || canInteractFromCurrentPosition) {
try {
if (near || pulse()) {
if (mover instanceof Player) {
@ -257,6 +280,66 @@ public abstract class MovementPulse extends Pulse {
return false;
}
/**
* Checks if the mover can interact with an entity destination from their
* current position without needing to move.
*
* @return true if the mover can interact without moving
*/
private boolean canInteractWithoutMoving() {
if (!(destination instanceof Entity)) {
return false;
}
Entity target = (Entity) destination;
Location ml = mover.getLocation();
Location dl = target.getLocation();
if (ml.getX() == dl.getX() && ml.getY() == dl.getY() && ml.getZ() == dl.getZ()) {
return false;
}
if (isInsideEntity(mover.getLocation())) {
return false;
}
if (target.getWalkingQueue().hasPath()) { // For moving entities, allow interaction from 1 tile away
int distance = Math.max(
Math.abs(ml.getX() - dl.getX()),
Math.abs(ml.getY() - dl.getY())
);
if (distance <= target.size()) {
return true;
}
}
return Pathfinder.canInteract(
ml.getX(), ml.getY(), mover.size(),
dl.getX(), dl.getY(), target.size(), target.size(),
0, // walkFlag - "can interact from any unblocked direction"
ml.getZ(),
RegionManager::getClippingFlag
);
}
/**
* Immediately performs the interaction without pathfinding.
* Called when the player is already in a valid interaction position.
*
* @return true if interaction was successful and pulse should stop
*/
private boolean interactImmediately() {
if (destination instanceof Entity) {
mover.face((Entity) destination);
}
try {
if (pulse()) {
if (mover instanceof Player) {
PacketRepository.send(ClearMinimapFlag.class, new PlayerContext((Player) mover));
}
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
private boolean validate() {
if (mover == null || destination == null || mover.getViewport().getRegion() == null || hasInactiveNode()) {
return false;

View file

@ -450,6 +450,9 @@ public abstract class Entity extends Node {
if (!entity.getZoneMonitor().continueAttack(this, style, message)) {
return false;
}
if (!this.getZoneMonitor().continueAttack(entity, style, message)) {
return false;
}
return true;
}

View file

@ -212,7 +212,7 @@ public final class ImpactHandler {
int percentage = (int) (entity.getSkills().getStaticLevel(Skills.HITPOINTS) * 0.10);
if (p.getSkills().getLifepoints() <= percentage) {
Item rolItem = new Item(Items.RING_OF_LIFE_2570);
if (EnchantedJewellery.RING_OF_LIFE.attemptTeleport(p, rolItem, 0, true)) {
if (EnchantedJewellery.RING_OF_LIFE.attemptTeleport(p, rolItem, 0, true, true)) {
p.sendMessage("Your ring of life saves you and in the process is destroyed.");
}
}

View file

@ -1,6 +1,7 @@
package core.game.node.entity.combat
import content.global.skill.skillcapeperks.SkillcapePerks
import content.global.skill.summoning.SummoningPouch
import core.game.node.entity.Entity
import core.game.node.entity.combat.equipment.ArmourSet
import core.game.node.entity.combat.spell.SpellType
@ -200,6 +201,7 @@ open class MagicSwingHandler (vararg flags: SwingHandlerFlag)
effectiveMagicLevel = floor(effectiveMagicLevel + (victim.prayer.getSkillBonus(Skills.MAGIC) * effectiveMagicLevel))
effectiveDefenceLevel = effectiveDefenceLevel * 0.3 + effectiveMagicLevel * 0.7 + 8
effectiveDefenceLevel *= familiarDefenceBonus(victim)
return effectiveDefenceLevel.toInt() * styleDefenceBonus
}
is NPC -> {
@ -220,6 +222,20 @@ open class MagicSwingHandler (vararg flags: SwingHandlerFlag)
return 1.0
}
/**
* Check to see whether the player is receiving a defensive bonus from an active familiar.
* - Wolpertinger => +5%
*/
private fun familiarDefenceBonus(e: Entity?): Double {
if (e !is Player) return 1.0
val fam = try {
e.familiarManager?.familiar
} catch (ex: Exception) {
null
} ?: return 1.0
return if (fam.pouchId == SummoningPouch.WOLPERTINGER_POUCH.pouchId) 1.05 else 1.0
}
override fun addExperience(entity: Entity?, victim: Entity?, state: BattleState?) {
if (state?.spell != null && entity is Player) {
state.spell.addExperience(entity, state.totalDamage)

View file

@ -4,6 +4,7 @@ import content.global.skill.skillcapeperks.SkillcapePerks
import content.global.skill.slayer.SlayerEquipmentFlags
import content.global.skill.slayer.SlayerManager
import content.global.skill.slayer.Tasks
import content.global.skill.summoning.SummoningPouch
import core.api.*
import core.api.EquipmentSlot
import core.game.container.impl.EquipmentContainer
@ -235,6 +236,7 @@ open class MeleeSwingHandler (vararg flags: SwingHandlerFlag)
else if (victim.properties.attackStyle.style == WeaponInterface.STYLE_CONTROLLED) effectiveDefenceLevel += 1
effectiveDefenceLevel += 8
effectiveDefenceLevel *= getSetMultiplier(victim, Skills.DEFENCE)
effectiveDefenceLevel *= familiarDefenceBonus(victim)
return effectiveDefenceLevel.toInt() * styleDefenceBonus
}
is NPC -> {
@ -282,6 +284,25 @@ open class MeleeSwingHandler (vararg flags: SwingHandlerFlag)
|| name == "Skeleton" || name == "Zogre" || name == "Spiritual Mage")
}
/**
* Check to see whether the player is receiving a defensive bonus from an active familiar.
* - Iron Titan => +10%
* - Steel Titan => +15%
*/
private fun familiarDefenceBonus(e: Entity?): Double {
if (e !is Player) return 1.0
val fam = try {
e.familiarManager?.familiar
} catch (ex: Exception) {
null
} ?: return 1.0
return when (fam.pouchId) {
SummoningPouch.IRON_TITAN_POUCH.pouchId -> 1.10
SummoningPouch.STEEL_TITAN_POUCH.pouchId -> 1.15
else -> 1.0
}
}
companion object {
/**
* Checks if the entity is using a halberd.

View file

@ -1,6 +1,7 @@
package core.game.node.entity.combat
import content.global.skill.skillcapeperks.SkillcapePerks
import content.global.skill.summoning.SummoningPouch
import core.api.*
import core.game.container.impl.EquipmentContainer
import core.game.node.entity.Entity
@ -207,6 +208,7 @@ open class RangeSwingHandler (vararg flags: SwingHandlerFlag) : CombatSwingHandl
effectiveRangedLevel = floor(effectiveRangedLevel + (entity.prayer.getSkillBonus(Skills.RANGE) * effectiveRangedLevel))
if(entity.properties.attackStyle.style == WeaponInterface.STYLE_RANGE_ACCURATE) effectiveRangedLevel += 3
effectiveRangedLevel += 8
effectiveRangedLevel += familiarOffenceBonus(entity)
effectiveRangedLevel *= getSetMultiplier(entity, Skills.RANGE)
if(SkillcapePerks.isActive(SkillcapePerks.ACCURATE_MARKSMAN,entity)) effectiveRangedLevel *= 1.1
@ -245,6 +247,7 @@ open class RangeSwingHandler (vararg flags: SwingHandlerFlag) : CombatSwingHandl
effectiveStrengthLevel = floor(effectiveStrengthLevel + (entity.prayer.getSkillBonus(Skills.RANGE) * effectiveStrengthLevel))
if(entity.properties.attackStyle.style == WeaponInterface.STYLE_RANGE_ACCURATE) effectiveStrengthLevel += 3
effectiveStrengthLevel += 8
effectiveStrengthLevel += familiarOffenceBonus(entity)
effectiveStrengthLevel *= getSetMultiplier(entity, Skills.RANGE)
effectiveStrengthLevel = floor(effectiveStrengthLevel)
if (!flags.contains(SwingHandlerFlag.IGNORE_STAT_BOOSTS_DAMAGE))
@ -301,6 +304,22 @@ open class RangeSwingHandler (vararg flags: SwingHandlerFlag) : CombatSwingHandl
} else super.getArmourSet(e)
}
/**
* Check to see whether the player is receiving an offensive bonus from an active familiar.
* - Geyser Titan => 1 + 3% of ranged level
*/
private fun familiarOffenceBonus(e: Entity?): Double {
if (e !is Player) return 0.0
val fam = try {
e.familiarManager?.familiar
} catch (ex: Exception) {
null
} ?: return 0.0
return if (fam.pouchId == SummoningPouch.GEYSER_TITAN_POUCH.pouchId) {
1.0 + ceil(e.skills.getLevel(Skills.RANGE) * 0.03)
} else 0.0
}
companion object {
/**
* Checks if the entity has the ammunition needed to proceed.

View file

@ -226,14 +226,22 @@ public enum BoltEffect {
victim.graphics(graphics);
}
}
/**
* Checks if the effect can fire.
*
* @param state the state.
*
* @return {@code True} if so.
*/
public boolean canFire(BattleState state) {
return RandomFunction.random(13) == 5;
// Never fires on slayer monster that player lacks level requirement for. Inauthentic(?)
// and should be considered a TODO: temporary abuse prevention measure (bolt effect on slayer monsters)
boolean rollSuccess = RandomFunction.random(13) == 5;
if (!(state.getVictim() instanceof NPC)) return rollSuccess;
if (!(state.getAttacker() instanceof Player) && ((NPC) state.getVictim()).getTask() == null) return rollSuccess;
if (state.getVictim().asNpc().getTask().levelReq > state.getAttacker().asPlayer().getSkills().getLevel(Skills.SLAYER)) return false;
return rollSuccess;
}
/**

View file

@ -383,7 +383,7 @@ public class NPC extends Entity {
this.movementIndex = 0;
this.pathBoundMovement = true;
}
@Override
public void checkImpact(BattleState state) {
super.checkImpact(state);
@ -391,21 +391,31 @@ public class NPC extends Entity {
behavior.beforeDamageReceived(this, entity, state);
if (task != null && entity instanceof Player && task.levelReq > entity.getSkills().getLevel(Skills.SLAYER)) {
state.neutralizeHits();
state.setPoisonDamage(-1);
if (state.getStyle() == CombatStyle.MAGIC) {
state.setEstimatedHit(-1);
}
}
if (task != null && entity instanceof Familiar && task.levelReq > ((Familiar) entity).getOwner().getSkills().getLevel(Skills.SLAYER)) {
state.neutralizeHits();
state.setPoisonDamage(-1);
if (state.getStyle() == CombatStyle.MAGIC) {
state.setEstimatedHit(-1);
}
}
}
@Override
public boolean isAttackable(Entity entity, CombatStyle style, boolean message) {
if (isInvisible()) {
return false;
}
if (task != null && entity instanceof Player && task.levelReq > entity.getSkills().getLevel(Skills.SLAYER)) {
if(message) {
((Player) entity).getPacketDispatch().sendMessage("You need a higher slayer level to know how to wound this monster.");
}
if (message) {
((Player) entity).getPacketDispatch().sendMessage("You need a higher slayer level to know how to wound this monster.");
}
}
if (!behavior.canBeAttackedBy(this, entity, style, message))
return false;
if (!behavior.canBeAttackedBy(this, entity, style, message)) return false;
return super.isAttackable(entity, style, message);
}
@ -414,13 +424,7 @@ public class NPC extends Entity {
if (isInvisible()) {
return false;
}
if (task != null && target instanceof Player && task.levelReq > target.getSkills().getLevel(Skills.SLAYER)) {
if (message) {
((Player) target).getPacketDispatch().sendMessage("You need a higher slayer level to know how to wound this monster.");
}
return false;
}
return behavior.canBeAttackedBy(this, target, style, message);
return true;
}
@Override

View file

@ -764,7 +764,7 @@ public class Player extends Entity {
return false;
if (skullManager.hasWildernessProtection())
return false;
return true;
return super.isAttackable(entity, style, message);
} else return false;
}
return super.isAttackable(entity, style, message);

View file

@ -97,6 +97,7 @@ public class TeleportManager {
if (teleportType != -1) {
if (entity instanceof Player) {
Player p = (Player) entity;
p.scripts.setDelay(GameWorld.getTicks() + 12);
p.getDialogueInterpreter().close();
}
}

View file

@ -224,18 +224,6 @@ public class AchievementDiary {
drawStatus(player);
}
public boolean checkComplete(DiaryLevel level){
if(type != DiaryType.LUMBRIDGE && level == DiaryLevel.BEGINNER){
return false;
}
if(level == DiaryLevel.BEGINNER){
return completedLevels.contains(level.ordinal());
}
return completedLevels.contains(level.ordinal() - 1);
}
/**
* Sends a string on the diary interface.
* @param player the player.

View file

@ -337,14 +337,6 @@ public abstract class Quest implements Plugin<Object> {
return questPoints;
}
/**
* Gets the configs.
* @return the configs.
*/
public int[] getConfigs() {
return configs;
}
@Override
public String toString() {
return "Quest [name=" + quest + ", index=" + index + ", buttonId=" + buttonId + ", questPoints=" + questPoints + ", configs=" + Arrays.toString(configs) + "]";

View file

@ -3,11 +3,13 @@ package core.game.system.command.sets
import content.global.handlers.item.SpadeDigListener
import content.region.misc.tutisland.handlers.iface.CharacterDesign
import core.api.*
import core.ServerStore
import core.game.dialogue.DialogueFile
import core.game.node.entity.combat.ImpactHandler
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import core.game.node.entity.player.info.login.PlayerSaver
import core.game.node.item.Item
import core.game.system.command.Privilege
import core.game.system.task.Pulse
import core.game.world.GameWorld
@ -17,8 +19,9 @@ import core.game.world.repository.Repository.getPlayerByName
import core.game.world.update.flag.context.Animation
import core.game.world.update.flag.context.Graphics
import core.plugin.Initializable
import core.tools.END_DIALOGUE
import core.tools.RandomFunction
import core.tools.Log
import org.json.simple.JSONArray
import org.json.simple.JSONObject
import org.rs09.consts.Sounds
import java.awt.HeadlessException
@ -30,6 +33,60 @@ import kotlin.streams.toList
class FunCommandSet : CommandSet(Privilege.ADMIN) {
var npcs: List<NPC> = ArrayList()
private val TREASURE_KEY = "fun_command_buried_treasures"
private val treasures = ArrayList<BuriedTreasure>()
init {
loadTreasures()
}
/**
* I hope ceikry doesn't punish me for this ~ used SkillcapePerks for a base idea tried to make it modular.
* Supports persistence across reboots for bury, buryitem, and burymessage supporting all command arguments.
*/
private fun loadTreasures() {
val archive = ServerStore.getArchive(TREASURE_KEY)
if (!archive.containsKey("data")) return
try {
val array = archive["data"] as JSONArray
for (obj in array) {
val json = obj as JSONObject
val x = (json["x"] as Long).toInt()
val y = (json["y"] as Long).toInt()
val z = (json["z"] as Long).toInt()
val loc = Location.create(x, y, z)
val type = TreasureType.valueOf(json["type"] as String)
val hiderName = json["hiderName"] as? String ?: "Unknown"
val hideName = json["hideName"] as? Boolean ?: false
val customMessage = json["customMessage"] as? String
val message = json["message"] as? String ?: ""
val items = ArrayList<Item>()
if (json.containsKey("items")) {
val itemsJson = json["items"] as JSONArray
for (i in itemsJson) {
val itemObj = i as JSONObject
val id = (itemObj["id"] as Long).toInt()
val amount = (itemObj["amount"] as Long).toInt()
items.add(Item(id, amount))
}
}
val treasure = BuriedTreasure(loc, type, items, message, hiderName, hideName, customMessage)
treasures.add(treasure)
registerTreasureListener(treasure)
}
log(FunCommandSet::class.java, Log.DEBUG, "Loaded ${treasures.size} buried treasures.")
} catch (e: Exception) {
log(FunCommandSet::class.java, Log.ERR, "Failed to load buried treasures: ${e.message}")
e.printStackTrace()
}
}
override fun defineCommands() {
@ -175,19 +232,39 @@ class FunCommandSet : CommandSet(Privilege.ADMIN) {
/**
* Bury inventory at current location
*/
define("bury", description = "Buries your entire inventory at your current location."){player, _ ->
define("bury", description = "Buries your entire inventory at your current location for treasure hunters. Required true/false to hide players name and an optional custom message."){player, args ->
if(player.inventory.isEmpty){
reject(player, "You have no items to bury.")
}
if (args.size < 2) {
reject(player, "Usage: ::bury <lt>true | false<gt> [custom news message]")
return@define
}
val hideName = when(args[1].lowercase()) {
"true" -> true
"false" -> false
else -> {
reject(player, "Invalid argument. Usage: ::bury <lt>true | false<gt> [custom news message]")
return@define
}
}
val customMessage = if (args.size > 2) {
args.slice(2 until args.size).joinToString(" ")
} else {
null
}
player.dialogueInterpreter.open(object : DialogueFile(){
override fun handle(componentID: Int, buttonID: Int) {
when(stage){
0 -> dialogue("This will bury your whole inventory in this spot.","Are you sure?").also { stage++ }
0 -> dialogue("This will bury your whole inventory at this spot.","Are you sure?").also { stage++ }
1 -> options("Yes","No").also { stage++ }
2 -> when(buttonID){
1 -> bury(player).also { end() }
2 -> stage = END_DIALOGUE
1 -> bury(player, hideName, customMessage).also { end() }
2 -> end()
}
}
}
@ -195,6 +272,78 @@ class FunCommandSet : CommandSet(Privilege.ADMIN) {
}
/**
* Bury a secret message at current location for a player to find.
*/
define("burymessage", description = "Buries a secret message at your current location."){ player, args ->
if (args.size < 2) {
reject(player, "Usage: ::burymessage [message]")
return@define
}
val message = args.slice(1 until args.size).joinToString(" ")
val loc = Location.create(player.location)
if(SpadeDigListener.listeners.containsKey(loc)){
reject(player, "There is already something buried here.")
return@define
}
val treasure = BuriedTreasure(loc, TreasureType.MESSAGE, emptyList(), message, player.username, false, null)
treasures.add(treasure)
saveTreasures()
registerTreasureListener(treasure)
notify(player, "You have buried the message '$message' at $loc")
log(FunCommandSet::class.java, Log.DEBUG, "Message buried by ${player.username} at $loc: \"$message\"")
}
/**
* Bury a specific item at current location
*/
define("buryitem", description = "Buries a specific item at your current location."){ player, args ->
if (args.size < 3) {
reject(player, "Usage: ::buryitem <lt>item id<gt> <lt>true | false<gt> [custom message]")
return@define
}
val itemId = args[1].toIntOrNull()
if (itemId == null) {
reject(player, "Invalid item ID.")
return@define
}
val hideName = when(args[2].lowercase()) {
"true" -> true
"false" -> false
else -> {
reject(player, "Invalid argument. Usage: ::buryitem <lt>item id<gt> <lt>true | false<gt> [custom message]")
return@define
}
}
val customMessage = if (args.size > 3) {
args.slice(3 until args.size).joinToString(" ")
} else {
null
}
player.dialogueInterpreter.open(object : DialogueFile(){
override fun handle(componentID: Int, buttonID: Int) {
when(stage){
0 -> dialogue("This will bury a ${Item(itemId).name} at this spot.","Are you sure?").also { stage++ }
1 -> options("Yes","No").also { stage++ }
2 -> when(buttonID){
1 -> buryItem(player, itemId, hideName, customMessage).also { end() }
2 -> end()
}
}
}
})
}
define("appearance", Privilege.ADMIN, "", "Allows you to change your appearance."){ player, _ ->
CharacterDesign.reopen(player)
}
@ -249,19 +398,109 @@ class FunCommandSet : CommandSet(Privilege.ADMIN) {
}
fun bury(player: Player){
fun bury(player: Player, hideName: Boolean = false, customMessage: String? = null){
val loc = Location.create(player.location)
val inv = player.inventory.toArray().filterNotNull()
SpadeDigListener.registerListener(player.location){ p ->
for(item in inv){
addItemOrDrop(p, item.id, item.amount)
sendMessage(p, "You dig and find ${if(item.amount > 1) "some" else "a"} ${item.name}")
}
sendNews("${player.username} has found the hidden treasure! Congratulations!!!")
SpadeDigListener.listeners.remove(loc)
if(SpadeDigListener.listeners.containsKey(loc)){
notify(player, "There is already something buried here.")
return
}
val inv = player.inventory.toArray().filterNotNull().toList()
if (inv.isEmpty()) {
notify(player, "You have nothing to bury.")
return
}
val treasure = BuriedTreasure(loc, TreasureType.ITEM, inv, "", player.username, hideName, customMessage)
treasures.add(treasure)
saveTreasures()
registerTreasureListener(treasure)
player.inventory.clear()
notify(player, "You have buried your loot at ${loc.toString()}")
log(FunCommandSet::class.java, Log.DEBUG, "Loot buried by ${player.username} at $loc. Masked: $hideName")
}
fun buryItem(player: Player, itemId: Int, hideName: Boolean, customMessage: String? = null) {
val loc = Location.create(player.location)
if(SpadeDigListener.listeners.containsKey(loc)){
notify(player, "There is already something buried here.")
return
}
val treasure = BuriedTreasure(loc, TreasureType.ITEM, listOf(Item(itemId, 1)), "", player.username, hideName, customMessage)
treasures.add(treasure)
saveTreasures()
registerTreasureListener(treasure)
notify(player, "You have buried item ${Item(itemId).name} at $loc")
log(FunCommandSet::class.java, Log.DEBUG, "Item $itemId buried by ${player.username} at $loc. Masked: $hideName")
}
private fun registerTreasureListener(t: BuriedTreasure) {
SpadeDigListener.registerListener(t.location) { p ->
log(FunCommandSet::class.java, Log.DEBUG, "SpadeDigListener triggered for ${p.username} at ${t.location}")
if (t.type == TreasureType.ITEM) {
for(item in t.items){
addItemOrDrop(p, item.id, item.amount)
sendMessage(p, "You dig and find ${if(item.amount > 1) "some" else "a"} ${item.name}")
}
val finderName = if (t.hideName) "Someone" else p.username
val message = t.customMessage ?: "$finderName has found the hidden treasure! Congratulations!!!"
sendNews(message)
} else {
sendMessage(p, t.message)
}
SpadeDigListener.listeners.remove(t.location)
treasures.remove(t)
saveTreasures()
}
}
private fun saveTreasures() {
val array = JSONArray()
for (t in treasures) {
val json = JSONObject()
json["x"] = t.location.x
json["y"] = t.location.y
json["z"] = t.location.z
json["type"] = t.type.name
json["hiderName"] = t.hiderName
json["hideName"] = t.hideName
if (t.customMessage != null) json["customMessage"] = t.customMessage
json["message"] = t.message
val itemsArray = JSONArray()
for (i in t.items) {
val itemObj = JSONObject()
itemObj["id"] = i.id
itemObj["amount"] = i.amount
itemsArray.add(itemObj)
}
json["items"] = itemsArray
array.add(json)
}
val archive = ServerStore.getArchive(TREASURE_KEY)
archive["data"] = array
}
private data class BuriedTreasure(
val location: Location,
val type: TreasureType,
val items: List<Item>,
val message: String,
val hiderName: String,
val hideName: Boolean,
val customMessage: String?
)
private enum class TreasureType { ITEM, MESSAGE }
}

View file

@ -1,6 +1,7 @@
package core.game.system.command.sets
import content.global.handlers.iface.*
import content.global.skill.cooking.fermenting.BrewGrowth
import content.global.skill.farming.timers.*
import content.minigame.fishingtrawler.TrawlerLoot
import content.region.misthalin.draynor.quest.anma.AnmaCutscene
@ -26,12 +27,14 @@ import core.game.system.communication.CommunicationInfo
import core.game.world.map.RegionManager
import core.game.world.map.build.DynamicRegion
import core.game.world.repository.Repository
import core.game.node.entity.combat.equipment.WeaponInterface
import core.plugin.Initializable
import core.tools.Log
import core.tools.StringUtils
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.rs09.consts.Components
import org.rs09.consts.Items
import java.awt.HeadlessException
import java.awt.Toolkit
import java.awt.datatransfer.StringSelection
@ -129,6 +132,58 @@ class MiscCommandSet : CommandSet(Privilege.ADMIN){
notify(player, "max hit: ${hit} (${(swingHandler as Object).getClass().getName()})")
}
define("calcdefence", Privilege.STANDARD, "::calcdefence <lt>NPC ID<gt>", "Calculates and prints your current raw defence values for Melee, Ranged and Magic against a given NPC.") { player, args ->
val meleeHandler = core.game.node.entity.combat.MeleeSwingHandler()
val rangeHandler = core.game.node.entity.combat.RangeSwingHandler()
val magicHandler = core.game.node.entity.combat.MagicSwingHandler()
var attacker: core.game.node.entity.Entity = player
if (args.size > 1) {
val npcId = args[1].toInt()
val npc = NPC(npcId)
npc.initConfig()
attacker = npc
notify(player, "Calculating defence against NPC: ${npc.name} (ID=$npcId)")
val bonuses = npc.properties.bonuses
var maxBonus = Int.MIN_VALUE
var maxIndex = 0
for (i in 0..4) {
if (i in bonuses.indices) {
val v = bonuses[i]
if (v > maxBonus) {
maxBonus = v
maxIndex = i
}
}
}
if (maxBonus == Int.MIN_VALUE) {
maxBonus = 0
maxIndex = 0
}
npc.properties.attackStyle = WeaponInterface.AttackStyle(WeaponInterface.STYLE_CONTROLLED,maxIndex)
} else {
notify(player, "Calculating defence against yourself (no NPC specified).")
}
fun hitChance(accuracy: Int, defence: Int): Double {
return if (accuracy > defence) {
1.0 - ((defence + 2.0) / (2.0 * (accuracy + 1.0)))
} else {
accuracy / (2.0 * (defence + 1.0))
}
}
val meleeAccuracy = meleeHandler.calculateAccuracy(attacker)
val meleeDefence = meleeHandler.calculateDefence(player, attacker)
val meleeChance = hitChance(meleeAccuracy, meleeDefence)
notify(player, "Your melee defence: $meleeDefence; Attacker accuracy: $meleeAccuracy; Chance to be hit: $meleeChance")
val rangeAccuracy = rangeHandler.calculateAccuracy(attacker)
val rangeDefence = rangeHandler.calculateDefence(player, attacker)
val rangeChance = hitChance(rangeAccuracy, rangeDefence)
notify(player, "Your range defence: $rangeDefence; Attacker accuracy: $rangeAccuracy; Chance to be hit: $rangeChance")
val magicAccuracy = magicHandler.calculateAccuracy(attacker)
val magicDefence = magicHandler.calculateDefence(player, attacker)
val magicChance = hitChance(magicAccuracy, magicDefence)
notify(player, "Your magic defence: $magicDefence; Attacker accuracy: $magicAccuracy; Chance to be hit: $magicChance")
}
/**
* Empty a player's inventory
* ADMIN only (for obvious reasons)
@ -698,6 +753,64 @@ class MiscCommandSet : CommandSet(Privilege.ADMIN){
notify(player, "Setting plaques read to: ${args[1]}")
}
define("brewkit", Privilege.ADMIN, "brewkit <lt>0-10<gt>",
"Give yourself everything you need to brew ales."){ player, args ->
if (args.size == 1) {
sendMessage(player, "Kit numbers: 0 - Dwarven Stout, 1 - Asgarnian Ale, 2 - Greenman's Ale,")
sendMessage(player, "Kit numbers: 3 - Wizard's Mind Bomb, 4 - Dragon Bitter, 5 - Moonlight Mead,")
sendMessage(player, "Kit numbers: 6 - Axeman's Folly, 7 - Chef's Delight, 8 - Slayer's Respite,")
sendMessage(player, "Kit numbers: 9 - Cider, 10 - Kelda Stout")
return@define
}
val item =
when (args[1].toInt()) {
0 -> Items.HAMMERSTONE_HOPS_5994
1 -> Items.ASGARNIAN_HOPS_5996
2 -> Items.CLEAN_HARRALANDER_255
3 -> Items.YANILLIAN_HOPS_5998
4 -> Items.KRANDORIAN_HOPS_6000
5 -> Items.MUSHROOM_6004
6 -> Items.OAK_ROOTS_6043
7 -> Items.CHOCOLATE_DUST_1975
8 -> Items.WILDBLOOD_HOPS_6002
9 -> Items.APPLE_MUSH_5992
10 -> Items.KELDA_HOPS_6113
else -> {
reject(player, "Invalid brew kit specified.")
return@define
}
}
addItemOrDrop(player, Items.BUCKET_OF_WATER_1929, if(args[1].toInt() == 9) 0 else 2)
addItemOrDrop(player, Items.BARLEY_MALT_6008, if(args[1].toInt() == 9) 0 else 2)
addItemOrDrop(player, item, if(args[1].toInt() == 6 || args[1].toInt() == 10) 1 else 4)
addItemOrDrop(player, Items.ALE_YEAST_5767)
return@define
}
define("brew", Privilege.ADMIN, "brew <lt>mode<gt>",
"0: force natural step, 1: force good step without maturity, 2: force good step with maturity, 3: force bad step"){ player, args ->
var forceStep = false
var forceGood = false
var forceMature = false
var forceBad = false
if (args.size == 1){
forceStep = true
}
if (args.size != 1){
when(args[1].toIntOrNull()){
0 -> forceStep = true
1 -> forceGood = true
2 -> forceMature = true
3 -> forceBad = true
else -> reject(player, "${args[1]} is not a valid option.")
}
}
val vats = getOrStartTimer<BrewGrowth>(player).getVats()
for (vat in vats){
vat.brew(forceStep, forceGood, forceMature, forceBad)
}
}
}
fun setupScrollInterface(player: Player, title: String): Int {

View file

@ -1,11 +1,13 @@
package core.game.system.command.sets
import core.api.log
import core.game.component.Component
import core.game.node.entity.player.Player
import core.game.node.entity.player.link.quest.QuestRepository
import core.plugin.Initializable
import core.game.system.command.Privilege
import core.game.world.repository.Repository
import core.tools.Log
@Initializable
class QuestCommandSet : CommandSet(Privilege.ADMIN){
@ -15,7 +17,12 @@ class QuestCommandSet : CommandSet(Privilege.ADMIN){
*/
define("allquest", description = "Instantly completes every quest for your account."){player,_->
for (quest in QuestRepository.getQuests().values) {
quest.finish(player)
try {
quest.finish(player)
} catch (e: Exception){
e.message?.let { log(this.javaClass, Log.WARN, it) }
continue
}
}
}

View file

@ -1,10 +1,13 @@
package core.game.system.timer.impl
import content.global.skill.summoning.familiar.Familiar
import core.game.system.timer.*
import core.api.*
import core.game.node.entity.Entity
import core.game.node.entity.player.Player
import core.game.node.entity.combat.ImpactHandler
import core.game.node.entity.npc.NPC
import core.game.node.entity.skill.Skills
import core.game.world.repository.Repository
import org.json.simple.*
@ -30,15 +33,28 @@ class Poison : PersistTimer (30, "poison", flags = arrayOf(TimerFlag.ClearOnDeat
damageSource = Repository.getPlayerByUid (uid) ?: entity
severity = root["severity"].toString().toInt()
}
override fun onRegister (entity: Entity) {
if (entity is Player) {
sendMessage(entity, "You have been poisoned.")
entity.debug ("[Poison] -> Received for $severity severity.")
}
if (damageSource is Player)
(damageSource as? Player)?.debug ("[Poison] -> Applied for $severity severity.")
}
override fun onRegister(entity : Entity) {
if (entity is Player) {
sendMessage(entity, "You have been poisoned.")
entity.debug("[Poison] -> Received for $severity severity.")
}
if (damageSource is Player) {
(damageSource as? Player)?.debug("[Poison] -> Applied for $severity severity.")
// this is ass, and preventing poisoning slayer monster player lacks lv req for should be done but prob not here & not like this. same for familiars
if (entity is NPC && entity.task != null && entity.task.levelReq > damageSource.skills.getLevel(Skills.SLAYER)) {
(damageSource as? Player)?.debug("[Poison] -> Removed poison from ${entity.name} ${entity.location} due to Slayer reqs!")
removeTimer(entity, this)
}
}
if (damageSource is Familiar) {
val owner : Player? = (damageSource as Familiar).owner
if (owner != null && entity is NPC && entity.task != null && entity.task.levelReq > owner.skills.getLevel(Skills.SLAYER)) {
owner.debug("[Poison] -> Removed poison from ${entity.name} ${entity.location} due to Slayer reqs!")
removeTimer(entity, this)
}
}
}
override fun run (entity: Entity) : Boolean {
entity.impactHandler.manualHit (

View file

@ -177,14 +177,23 @@ public abstract class MapZone implements Zone {
if (multi || e.isIgnoreMultiBoundaries(t) || e.getZoneMonitor().isIgnoreMultiBoundaries(t)) {
return true;
}
Entity attacker = e instanceof Familiar ? ((Familiar) e).getOwner() : e;
Entity target = t.getAttribute("combat-attacker", e);
if (t.getAttribute("combat-time", -1L) > time && target != e && target.isActive()) {
if (target instanceof Familiar) {
target = ((Familiar) target).getOwner();
}
if (t.getAttribute("combat-time", -1L) > time && target != attacker && target.isActive()) {
if (message && e instanceof Player) {
((Player) e).getPacketDispatch().sendMessage("Someone else is already fighting this" + (t instanceof Player ? " player." : "."));
}
return false;
}
if (e.getAttribute("combat-time", -1L) > time && (target = e.getAttribute("combat-attacker", t)) != t && target.isActive()) {
Entity attackerOnE = e.getAttribute("combat-attacker", t);
if (attackerOnE instanceof Familiar) {
attackerOnE = ((Familiar) attackerOnE).getOwner();
}
Entity tResolved = t instanceof Familiar ? ((Familiar) t).getOwner() : t;
if (e.getAttribute("combat-time", -1L) > time && attackerOnE != tResolved && attackerOnE.isActive()) {
if (t.getId() == 1614 || t.getId() == 1613) {
return true;
}

View file

@ -263,6 +263,23 @@ public final class WildernessZone extends MapZone {
return false;
}
}
if (e instanceof Player && target instanceof NPC && ((NPC) target).getName().contains("Revenant")) {
Player p = (Player) e;
NPC npc = (NPC) target;
int level = p.getSkullManager().getLevel();
int npcWildLevel = getWilderness(npc);
if (npcWildLevel < level) {
level = npcWildLevel;
}
int combat = p.getProperties().getCurrentCombatLevel();
int npcCombat = npc.getDefinition().getCombatLevel();
if (combat - level > npcCombat || combat + level < npcCombat) {
if (message) {
p.getPacketDispatch().sendMessage("The level difference between you and your opponent is too great.");
}
return false;
}
}
return true;
}

View file

@ -35,6 +35,7 @@ public class RandomFunction {
/**
* Method used to ease the access of the random class.
* In certain cases, it is inclusive from the min value and exclusive from the max values specified.
* @param a the minimum random value.
* @param b the maximum random value.
* @return the value as an {@link Integer}.