From 11963ede80438a16524b7e0033d5c330e1a2e063 Mon Sep 17 00:00:00 2001 From: Ceikry Date: Mon, 16 Aug 2021 02:22:10 +0000 Subject: [PATCH] Nature Spirit quest --- .../main/java/org/runite/client/Class39.java | 6 +- .../java/org/runite/client/PacketParser.java | 1 + Server/data/configs/ground_spawns.json | 8 + Server/data/configs/npc_configs.json | 242 +----------------- Server/data/configs/npc_spawns.json | 12 +- Server/data/configs/shops.json | 9 + .../game/content/consumable/Consumable.java | 4 + .../game/content/dialogue/DrezelDialogue.java | 2 +- .../dialogue/DrezelMonumentDialogue.java | 53 +++- .../content/dialogue/RugMerchantDialogue.java | 2 +- .../game/interaction/item/CasketPlugin.java | 19 +- .../interaction/item/SilverSicklePlugin.java | 36 +-- .../object/PriestInPerilOptionPlugin.java | 3 +- .../java/core/game/node/entity/Entity.java | 1 - .../game/node/entity/combat/DeathTask.java | 3 +- .../game/node/entity/impl/Properties.java | 3 + .../java/core/game/node/entity/npc/NPC.java | 4 + .../core/game/node/entity/player/Player.java | 3 +- .../entity/player/link/PacketDispatch.java | 5 +- .../link/request/trade/TradeContainer.java | 2 +- .../decoration/chapel/BoneOfferPlugin.java | 7 +- .../entity/skill/herblore/FinishedPotion.java | 3 +- .../java/core/game/world/map/Location.java | 22 +- .../context/PositionedGraphicContext.java | 9 +- .../core/net/packet/in/InteractionPacket.java | 2 +- .../net/packet/out/PositionedGraphic.java | 15 +- Server/src/main/kotlin/api/ContentAPI.kt | 23 ++ .../naturespirit/FillimanTarlockNPC.kt | 32 +++ .../members/naturespirit/NSDrezelDialogue.kt | 120 +++++++++ .../quest/members/naturespirit/NSListeners.kt | 234 +++++++++++++++++ .../members/naturespirit/NSTarlockDialogue.kt | 241 +++++++++++++++++ .../quest/members/naturespirit/NSUtils.kt | 125 +++++++++ .../naturespirit/NatureSpiritDialogue.kt | 164 ++++++++++++ .../members/naturespirit/NatureSpiritQuest.kt | 122 +++++++++ .../game/interaction/InteractionListener.kt | 1 + .../game/interaction/InteractionListeners.kt | 1 + .../interaction/region/MorytaniaListeners.kt | 6 +- .../node/entity/npc/other/MortMyreGhastNPC.kt | 104 ++++++++ .../game/system/config/NPCConfigParser.kt | 1 + .../StringTools.kt => Globals.kt} | 0 40 files changed, 1349 insertions(+), 301 deletions(-) create mode 100644 Server/src/main/kotlin/rs09/game/content/quest/members/naturespirit/FillimanTarlockNPC.kt create mode 100644 Server/src/main/kotlin/rs09/game/content/quest/members/naturespirit/NSDrezelDialogue.kt create mode 100644 Server/src/main/kotlin/rs09/game/content/quest/members/naturespirit/NSListeners.kt create mode 100644 Server/src/main/kotlin/rs09/game/content/quest/members/naturespirit/NSTarlockDialogue.kt create mode 100644 Server/src/main/kotlin/rs09/game/content/quest/members/naturespirit/NSUtils.kt create mode 100644 Server/src/main/kotlin/rs09/game/content/quest/members/naturespirit/NatureSpiritDialogue.kt create mode 100644 Server/src/main/kotlin/rs09/game/content/quest/members/naturespirit/NatureSpiritQuest.kt create mode 100644 Server/src/main/kotlin/rs09/game/node/entity/npc/other/MortMyreGhastNPC.kt rename Server/src/main/kotlin/rs09/tools/{stringtools/StringTools.kt => Globals.kt} (100%) diff --git a/Client/src/main/java/org/runite/client/Class39.java b/Client/src/main/java/org/runite/client/Class39.java index ad540fca2..fadaff41f 100644 --- a/Client/src/main/java/org/runite/client/Class39.java +++ b/Client/src/main/java/org/runite/client/Class39.java @@ -346,13 +346,13 @@ public final class Class39 { } else if (Unsorted.incomingOpcode == 17) { var1 = BufferedDataStream.incomingBuffer.readUnsignedByte(); var2 = Class65.currentChunkX + (var1 >> 4 & 7); - var3 = currentChunkY - -(var1 & 7); + var3 = currentChunkY + (var1 & 7); var4 = BufferedDataStream.incomingBuffer.readUnsignedShort(); var5 = BufferedDataStream.incomingBuffer.readUnsignedByte(); var6 = BufferedDataStream.incomingBuffer.readUnsignedShort(); if (var2 >= 0 && var3 >= 0 && var2 < 104 && var3 < 104) { - var2 = var2 * 128 - -64; - var3 = var3 * 128 - -64; + var2 = var2 * 128 + 64; + var3 = var3 * 128 + 64; Class140_Sub2 var32 = new Class140_Sub2(var4, WorldListCountry.localPlane, var2, var3, -var5 + Class121.method1736(WorldListCountry.localPlane, 1, var2, var3), var6, Class44.anInt719); TextureOperation17.aLinkedList_3177.method1215(new Class3_Sub28_Sub2(var32)); } diff --git a/Client/src/main/java/org/runite/client/PacketParser.java b/Client/src/main/java/org/runite/client/PacketParser.java index 4b929db00..a0735f56a 100644 --- a/Client/src/main/java/org/runite/client/PacketParser.java +++ b/Client/src/main/java/org/runite/client/PacketParser.java @@ -2,6 +2,7 @@ package org.runite.client; import org.rs09.Discord; import org.rs09.SlayerTracker; +import org.rs09.SystemLogger; import org.rs09.XPGainDraw; import java.nio.charset.StandardCharsets; diff --git a/Server/data/configs/ground_spawns.json b/Server/data/configs/ground_spawns.json index 92c2281d0..a08750389 100644 --- a/Server/data/configs/ground_spawns.json +++ b/Server/data/configs/ground_spawns.json @@ -530,5 +530,13 @@ { "item_id": "4619", "loc_data": "{1,2713,4913,0,2}" + }, + { + "item_id": "2964", + "loc_data": "{1,3437,3337,0,100}" + }, + { + "item_id": "2957", + "loc_data": "{1,3443,9742,0,100}-{1,3443,9742,1,100}" } ] diff --git a/Server/data/configs/npc_configs.json b/Server/data/configs/npc_configs.json index 400ae302f..e6270d816 100644 --- a/Server/data/configs/npc_configs.json +++ b/Server/data/configs/npc_configs.json @@ -10595,28 +10595,31 @@ "lifepoints": "10", "strength_level": "1", "id": "1052", - "aggressive": "true", + "aggressive": "false", "range_level": "1", "attack_level": "1" }, { "examine": "Arrghhh... A Ghast.", "range_animation": "0", - "attack_speed": "5", + "melee_animation": "1087", + "attack_speed": "4", + "magic_level": "1", "respawn_delay": "60", - "defence_animation": "0", + "defence_animation": "1088", "weakness": "9", "magic_animation": "0", - "death_animation": "9467", + "death_animation": "1089", + "death_gfx": "265", "name": "Ghast", - "defence_level": "25", + "defence_level": "18", "safespot": null, - "lifepoints": "71", - "strength_level": "25", + "lifepoints": "45", + "strength_level": "22", "id": "1053", "aggressive": "true", "range_level": "1", - "attack_level": "25" + "attack_level": "22" }, { "examine": "Ticket trader for the Brimhaven Agility Arena.", @@ -69050,22 +69053,6 @@ "range_level": "1", "attack_level": "55" }, - { - "examine": "The oldest man in Nardah.", - "melee_animation": "0", - "range_animation": "0", - "defence_animation": "0", - "magic_animation": "0", - "death_animation": "0", - "name": "Ghaslor the Elder", - "defence_level": "1", - "safespot": null, - "lifepoints": "10", - "strength_level": "1", - "id": "3029", - "range_level": "1", - "attack_level": "1" - }, { "examine": "A water salesman from Pollnivneach.", "melee_animation": "0", @@ -84891,43 +84878,6 @@ "projectile": "100", "attack_level": "25" }, - { - "examine": "Arrghhh... A Ghast.", - "melee_animation": "0", - "range_animation": "0", - "defence_animation": "0", - "weakness": "9", - "magic_animation": "0", - "death_animation": "0", - "name": "Ghast", - "defence_level": "1", - "safespot": null, - "lifepoints": "10", - "strength_level": "1", - "id": "1052", - "aggressive": "true", - "range_level": "1", - "attack_level": "1" - }, - { - "examine": "Arrghhh... A Ghast.", - "range_animation": "0", - "attack_speed": "5", - "respawn_delay": "60", - "defence_animation": "0", - "weakness": "9", - "magic_animation": "0", - "death_animation": "9467", - "name": "Ghast", - "defence_level": "25", - "safespot": null, - "lifepoints": "71", - "strength_level": "25", - "id": "1053", - "aggressive": "true", - "range_level": "1", - "attack_level": "25" - }, { "examine": "Ticket trader for the Brimhaven Agility Arena.", "melee_animation": "0", @@ -102186,22 +102136,6 @@ "range_level": "1", "attack_level": "55" }, - { - "examine": "The oldest man in Nardah.", - "melee_animation": "0", - "range_animation": "0", - "defence_animation": "0", - "magic_animation": "0", - "death_animation": "0", - "name": "Ghaslor the Elder", - "defence_level": "1", - "safespot": null, - "lifepoints": "10", - "strength_level": "1", - "id": "3029", - "range_level": "1", - "attack_level": "1" - }, { "examine": "A water salesman from Pollnivneach.", "melee_animation": "0", @@ -143465,22 +143399,6 @@ "range_level": "1", "attack_level": "55" }, - { - "examine": "The oldest man in Nardah.", - "melee_animation": "0", - "range_animation": "0", - "defence_animation": "0", - "magic_animation": "0", - "death_animation": "0", - "name": "Ghaslor the Elder", - "defence_level": "1", - "safespot": null, - "lifepoints": "10", - "strength_level": "1", - "id": "3029", - "range_level": "1", - "attack_level": "1" - }, { "examine": "A water salesman from Pollnivneach.", "melee_animation": "0", @@ -159314,43 +159232,6 @@ "projectile": "100", "attack_level": "25" }, - { - "examine": "Arrghhh... A Ghast.", - "melee_animation": "0", - "range_animation": "0", - "defence_animation": "0", - "weakness": "9", - "magic_animation": "0", - "death_animation": "0", - "name": "Ghast", - "defence_level": "1", - "safespot": null, - "lifepoints": "10", - "strength_level": "1", - "id": "1052", - "aggressive": "true", - "range_level": "1", - "attack_level": "1" - }, - { - "examine": "Arrghhh... A Ghast.", - "range_animation": "0", - "attack_speed": "5", - "respawn_delay": "60", - "defence_animation": "0", - "weakness": "9", - "magic_animation": "0", - "death_animation": "9467", - "name": "Ghast", - "defence_level": "25", - "safespot": null, - "lifepoints": "71", - "strength_level": "25", - "id": "1053", - "aggressive": "true", - "range_level": "1", - "attack_level": "25" - }, { "examine": "Ticket trader for the Brimhaven Agility Arena.", "melee_animation": "0", @@ -176609,22 +176490,6 @@ "range_level": "1", "attack_level": "55" }, - { - "examine": "The oldest man in Nardah.", - "melee_animation": "0", - "range_animation": "0", - "defence_animation": "0", - "magic_animation": "0", - "death_animation": "0", - "name": "Ghaslor the Elder", - "defence_level": "1", - "safespot": null, - "lifepoints": "10", - "strength_level": "1", - "id": "3029", - "range_level": "1", - "attack_level": "1" - }, { "examine": "A water salesman from Pollnivneach.", "melee_animation": "0", @@ -217640,22 +217505,6 @@ "range_level": "1", "attack_level": "55" }, - { - "examine": "The oldest man in Nardah.", - "melee_animation": "0", - "range_animation": "0", - "defence_animation": "0", - "magic_animation": "0", - "death_animation": "0", - "name": "Ghaslor the Elder", - "defence_level": "1", - "safespot": null, - "lifepoints": "10", - "strength_level": "1", - "id": "3029", - "range_level": "1", - "attack_level": "1" - }, { "examine": "A water salesman from Pollnivneach.", "melee_animation": "0", @@ -233481,43 +233330,6 @@ "projectile": "100", "attack_level": "25" }, - { - "examine": "Arrghhh... A Ghast.", - "melee_animation": "0", - "range_animation": "0", - "defence_animation": "0", - "weakness": "9", - "magic_animation": "0", - "death_animation": "0", - "name": "Ghast", - "defence_level": "1", - "safespot": null, - "lifepoints": "10", - "strength_level": "1", - "id": "1052", - "aggressive": "true", - "range_level": "1", - "attack_level": "1" - }, - { - "examine": "Arrghhh... A Ghast.", - "range_animation": "0", - "attack_speed": "5", - "respawn_delay": "60", - "defence_animation": "0", - "weakness": "9", - "magic_animation": "0", - "death_animation": "9467", - "name": "Ghast", - "defence_level": "25", - "safespot": null, - "lifepoints": "71", - "strength_level": "25", - "id": "1053", - "aggressive": "true", - "range_level": "1", - "attack_level": "25" - }, { "examine": "Ticket trader for the Brimhaven Agility Arena.", "melee_animation": "0", @@ -250776,22 +250588,6 @@ "range_level": "1", "attack_level": "55" }, - { - "examine": "The oldest man in Nardah.", - "melee_animation": "0", - "range_animation": "0", - "defence_animation": "0", - "magic_animation": "0", - "death_animation": "0", - "name": "Ghaslor the Elder", - "defence_level": "1", - "safespot": null, - "lifepoints": "10", - "strength_level": "1", - "id": "3029", - "range_level": "1", - "attack_level": "1" - }, { "examine": "A water salesman from Pollnivneach.", "melee_animation": "0", @@ -292055,22 +291851,6 @@ "range_level": "1", "attack_level": "55" }, - { - "examine": "The oldest man in Nardah.", - "melee_animation": "0", - "range_animation": "0", - "defence_animation": "0", - "magic_animation": "0", - "death_animation": "0", - "name": "Ghaslor the Elder", - "defence_level": "1", - "safespot": null, - "lifepoints": "10", - "strength_level": "1", - "id": "3029", - "range_level": "1", - "attack_level": "1" - }, { "examine": "A water salesman from Pollnivneach.", "melee_animation": "0", diff --git a/Server/data/configs/npc_spawns.json b/Server/data/configs/npc_spawns.json index 043dc96e4..41e8744dc 100644 --- a/Server/data/configs/npc_spawns.json +++ b/Server/data/configs/npc_spawns.json @@ -2484,7 +2484,7 @@ "loc_data": "{3409,3489,0,1,4}-{3413,3485,0,1,4}-{3414,3491,1,1,6}" }, { - "npc_id": "1047", + "npc_id": "7690", "loc_data": "{3415,3489,2,0,0}" }, { @@ -2492,7 +2492,7 @@ "loc_data": "{3437,3486,0,0,0}" }, { - "npc_id": "1049", + "npc_id": "7707", "loc_data": "{3440,9895,0,0,0}" }, { @@ -10734,5 +10734,13 @@ { "npc_id": "1164", "loc_data": "{2780,3058,0,1,0}" + }, + { + "npc_id": "1051", + "loc_data": "{3440,9738,1,1,0}" + }, + { + "npc_id": "1052", + "loc_data": "{3420,3442,0,1,0}-{3429,3436,0,1,0}-{3439,3437,0,1,0}-{3448,3444,0,1,0}-{3458,3433,0,1,0}-{3459,3419,0,1,0}-{3449,3420,0,1,0}-{3441,3421,0,1,0}-{3427,3421,0,1,0}-{3414,3422,0,1,0}-{3411,3423,0,1,0}-{3415,3414,0,1,0}-{3410,3409,0,1,0}-{3422,3405,0,1,0}-{3431,3402,0,1,0}-{3444,3405,0,1,0}-{3453,3397,0,1,0}-{3467,3402,0,1,0}-{3473,3395,0,1,0}-{3470,3385,0,1,0}-{3463,3379,0,1,0}-{3451,3380,0,1,0}-{3445,3374,0,1,0}-{3438,3365,0,1,0}-{3429,3367,0,1,0}-{3417,3367,0,1,0}-{3413,3362,0,1,0}-{3417,3358,0,1,0}-{3426,3352,0,1,0}-{3435,3349,0,1,0}-{3442,3353,0,1,0}-{3448,3358,0,1,0}-{3457,3355,0,1,0}-{3461,3348,0,1,0}-{3457,3343,0,1,0}-{3471,3344,0,1,0}-{3426,3338,0,1,0}-{3423,3335,0,1,0}" } ] \ No newline at end of file diff --git a/Server/data/configs/shops.json b/Server/data/configs/shops.json index 1373c4579..2dfe0ffa0 100644 --- a/Server/data/configs/shops.json +++ b/Server/data/configs/shops.json @@ -2131,5 +2131,14 @@ "id": "240", "title": "Tiadeche's Karambwan Stall", "stock": "{3142,10}-{3157,10}" + }, + { + "npcs": "", + "high_alch": "0", + "currency": "995", + "general_store": "false", + "id": "241", + "title": "Wishing Well", + "stock": "{12204,10}-{12207,10}-{12210,0}-{12213,0}-{12216,0}-{12219,0}-{12222,0}-{12183,65000}-{12155,5000}" } ] \ No newline at end of file diff --git a/Server/src/main/java/core/game/content/consumable/Consumable.java b/Server/src/main/java/core/game/content/consumable/Consumable.java index 4936fa978..789099637 100644 --- a/Server/src/main/java/core/game/content/consumable/Consumable.java +++ b/Server/src/main/java/core/game/content/consumable/Consumable.java @@ -109,6 +109,10 @@ public abstract class Consumable implements Plugin { return effect.getHealthEffectValue(player); } + public ConsumableEffect getEffect() { + return effect; + } + public int[] getIds() { return ids; } diff --git a/Server/src/main/java/core/game/content/dialogue/DrezelDialogue.java b/Server/src/main/java/core/game/content/dialogue/DrezelDialogue.java index 65adacc77..3dd359c6c 100644 --- a/Server/src/main/java/core/game/content/dialogue/DrezelDialogue.java +++ b/Server/src/main/java/core/game/content/dialogue/DrezelDialogue.java @@ -381,6 +381,6 @@ public final class DrezelDialogue extends DialoguePlugin { @Override public int[] getIds() { - return new int[] { 1047 }; + return new int[] { 7690 }; } } diff --git a/Server/src/main/java/core/game/content/dialogue/DrezelMonumentDialogue.java b/Server/src/main/java/core/game/content/dialogue/DrezelMonumentDialogue.java index 1af228462..6d4f62163 100644 --- a/Server/src/main/java/core/game/content/dialogue/DrezelMonumentDialogue.java +++ b/Server/src/main/java/core/game/content/dialogue/DrezelMonumentDialogue.java @@ -1,10 +1,16 @@ package core.game.content.dialogue; +import api.ContentAPI; import core.game.node.entity.npc.NPC; import core.game.node.entity.player.Player; import core.game.node.entity.player.link.quest.Quest; import core.plugin.Initializable; import core.game.node.item.Item; +import org.rs09.consts.NPCs; +import rs09.game.content.quest.members.naturespirit.NSDrezelDialogue; +import rs09.game.system.SystemLogger; + +import static rs09.tools.DialogueConstKt.END_DIALOGUE; /** * Represents the dialogue plugin used for the drezel monument. @@ -44,7 +50,7 @@ public final class DrezelMonumentDialogue extends DialoguePlugin { @Override public boolean open(Object... args) { npc = (NPC) args[0]; - final Quest quest = player.getQuestRepository().getQuest("Priest in Peril"); + Quest quest = player.getQuestRepository().getQuest("Priest in Peril"); if (quest.getStage(player) == 17) { interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "Ah, " + player.getUsername() + ". I see you finally made it down here.", "Things are worse than I feared. I'm not sure if I will", "be able to repair the damage."); stage = 900; @@ -64,9 +70,23 @@ public final class DrezelMonumentDialogue extends DialoguePlugin { interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "So can I pass through that barrier now?"); stage = 400; return true; - } else { + } + + /*else { interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "Greetings again adventurer, How go your travels in", "Morytania? Is it as evil as I have heard?"); stage = 420; + }*/ + + quest = player.getQuestRepository().getQuest("Nature Spirit"); + + if(quest.getStage(player) <= 5){ + interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "Greetings again adventurer, How go your travels in", "Morytania? Is it as evil as I have heard?"); + stage = 420; + } else if (quest.getStage(player) < 100){ + ContentAPI.openDialogue(player, new NSDrezelDialogue(), npc); + } else { + npcl(FacialExpression.NEUTRAL, "I heard you finished your quest with Filliman! Great work!"); + stage = END_DIALOGUE; } return true; } @@ -108,16 +128,37 @@ public final class DrezelMonumentDialogue extends DialoguePlugin { end(); break; case 420: - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "Well, I'm going to look around a bit more."); + options("Well, I'm going to look around a bit more.", "Is there anything else interesting to do around here?"); stage = 421; break; case 421: - interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "Well, that sounds like a good idea. Don't get into any", "trouble though!"); - stage = 422; + switch(buttonId){ + case 1: + playerl(FacialExpression.FRIENDLY, "Well, I'm going to look around a bit more."); + stage++; + break; + case 2: + playerl(FacialExpression.HALF_THINKING, "Is there anything else interesting to do around here?"); + stage = 425; + break; + } break; case 422: + interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "Well, that sounds like a good idea. Don't get into any", "trouble though!"); + stage = 423; + break; + case 423: end(); break; + case 425: + npcl(FacialExpression.HALF_THINKING, "Well, not a great deal... but there is something you can do for me if you're interested. Though it is quite dangerous."); + stage++; + break; + case 426: + end(); + player.getDialogueInterpreter().open(new NSDrezelDialogue(), npc); + player.getDialogueInterpreter().handle(0,0); + break; case 120: interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "I need " + player.getGameAttributes().getAttribute("priest-in-peril:rune", 50) + " more."); stage = 121; @@ -239,6 +280,6 @@ public final class DrezelMonumentDialogue extends DialoguePlugin { @Override public int[] getIds() { - return new int[] { 1049 }; + return new int[] { NPCs.DREZEL_7707 }; } } diff --git a/Server/src/main/java/core/game/content/dialogue/RugMerchantDialogue.java b/Server/src/main/java/core/game/content/dialogue/RugMerchantDialogue.java index 97fa3263f..23ce43b07 100644 --- a/Server/src/main/java/core/game/content/dialogue/RugMerchantDialogue.java +++ b/Server/src/main/java/core/game/content/dialogue/RugMerchantDialogue.java @@ -20,7 +20,7 @@ import core.plugin.Plugin; import rs09.plugin.PluginManager; import org.rs09.consts.Items; -import static rs09.tools.stringtools.StringToolsKt.colorize; +import static rs09.tools.stringtools.GlobalsKt.colorize; /** diff --git a/Server/src/main/java/core/game/interaction/item/CasketPlugin.java b/Server/src/main/java/core/game/interaction/item/CasketPlugin.java index 9291f5e2d..2c8f9cbf8 100644 --- a/Server/src/main/java/core/game/interaction/item/CasketPlugin.java +++ b/Server/src/main/java/core/game/interaction/item/CasketPlugin.java @@ -3,6 +3,7 @@ package core.game.interaction.item; import java.util.ArrayList; import java.util.List; +import api.ContentAPI; import core.cache.def.impl.ItemDefinition; import core.game.interaction.OptionHandler; import core.game.node.Node; @@ -13,6 +14,9 @@ import core.plugin.Initializable; import core.plugin.Plugin; import core.tools.RandomFunction; import core.tools.StringUtils; +import org.rs09.consts.Items; +import rs09.game.content.global.WeightBasedTable; +import rs09.game.content.global.WeightedItem; /** * Represents the casket handling plugin. @@ -25,7 +29,16 @@ public final class CasketPlugin extends OptionHandler { /** * Represents the casket rewards. */ - private static final ChanceItem[] CASKET_REWARD = new ChanceItem[] { new ChanceItem(995, 8, 3000, 30), new ChanceItem(1623, 1, 30), new ChanceItem(1621, 1, 70), new ChanceItem(1619, 1, 70), new ChanceItem(1617, 1, 97), new ChanceItem(987, 1, 97), new ChanceItem(985, 1, 97), new ChanceItem(1454, 1, 30), new ChanceItem(1452, 1, 70), new ChanceItem(1462, 1, 97) }; + private WeightBasedTable table = WeightBasedTable.create( + new WeightedItem(Items.COINS_995, 20, 640, 55, false), + new WeightedItem(Items.UNCUT_SAPPHIRE_1623, 1, 1, 32, false), + new WeightedItem(Items.UNCUT_EMERALD_1621, 1, 1, 16, false), + new WeightedItem(Items.UNCUT_RUBY_1619, 1, 1, 9, false), + new WeightedItem(Items.UNCUT_DIAMOND_1617, 1, 1, 2, false), + new WeightedItem(Items.COSMIC_TALISMAN_1454, 1, 1, 8, false), + new WeightedItem(Items.LOOP_HALF_OF_A_KEY_987, 1, 1, 1, false), + new WeightedItem(Items.TOOTH_HALF_OF_A_KEY_985, 1, 1, 1, false) + ); @Override public Plugin newInstance(Object arg) throws Throwable { @@ -35,10 +48,10 @@ public final class CasketPlugin extends OptionHandler { @Override public boolean handle(Player player, Node node, String option) { - final ChanceItem reward = getChanceItem(CASKET_REWARD); + final Item reward = table.roll().get(0); player.getInventory().remove((Item) node); player.getDialogueInterpreter().sendItemMessage(reward, "You open the casket. Inside you find " + (reward.getAmount() > 1 ? "some" : (StringUtils.isPlusN(reward.getName()) ? "an" : "a")) + " " + reward.getName().toLowerCase() + "."); - player.getInventory().add(reward.getAmount() == 1 ? reward : new Item(reward.getId(), RandomFunction.random(reward.getMinimumAmount(), reward.getMaximumAmount()))); + ContentAPI.addItemOrDrop(player, reward.getId(), reward.getAmount()); return true; } diff --git a/Server/src/main/java/core/game/interaction/item/SilverSicklePlugin.java b/Server/src/main/java/core/game/interaction/item/SilverSicklePlugin.java index 8e3b1a41d..3c0713edd 100644 --- a/Server/src/main/java/core/game/interaction/item/SilverSicklePlugin.java +++ b/Server/src/main/java/core/game/interaction/item/SilverSicklePlugin.java @@ -12,6 +12,7 @@ import core.game.world.map.RegionManager; import core.plugin.Initializable; import core.plugin.Plugin; import core.tools.RandomFunction; +import rs09.game.content.quest.members.naturespirit.NSUtils; /** * Handles the Silver Sickle (b) to collect Mort Myre Fungus. @@ -29,45 +30,14 @@ public final class SilverSicklePlugin extends OptionHandler { @Override public boolean handle(Player player, Node node, String option) { - Region region = RegionManager.forId(player.getLocation().getRegionId()); switch (option) { case "operate": case "cast bloom": - if (player.getSkills().getPrayerPoints() < 1) { - player.getPacketDispatch().sendMessage("You don't have enough prayer points to do this."); - } - for (Scenery[] o : region.getPlanes()[0].getObjects()) { - for (Scenery obj : o) { - if (obj != null) { - if (obj.getName().equalsIgnoreCase("Rotting log") && player.getSkills().getPrayerPoints() >= 1) { - if (player.getLocation().withinDistance(obj.getLocation(), 2)) { - handleVisuals(player, node); - SceneryBuilder.add(new Scenery(3509, obj.getLocation(), obj.getRotation())); - } - } - } - } - } - RegionManager.getLock().unlock(); + player.getPacketDispatch().sendAnimation(9021); + NSUtils.castBloom(player); return true; } return false; } - /** - * Handles the draining of prayer points and physical graphics and - * animation. - */ - public void handleVisuals(Player player, Node node) { - player.getSkills().decrementPrayerPoints(RandomFunction.random(1, 3)); - player.getPacketDispatch().sendAnimation(9021); - final Location[] AROUND_YOU = new Location[] { Location.create(player.getLocation().getX() - 1, player.getLocation().getY(), 0), Location.create(player.getLocation().getX() + 1, player.getLocation().getY(), 0), Location.create(player.getLocation().getX(), player.getLocation().getY() - 1, 0), Location.create(player.getLocation().getX(), player.getLocation().getY() + 1, 0), Location.create(player.getLocation().getX() + 1, player.getLocation().getY() + 1, 0), Location.create(player.getLocation().getX() - 1, player.getLocation().getY() + 1, 0), Location.create(player.getLocation().getX() + 1, player.getLocation().getY() - 1, 0), Location.create(player.getLocation().getX() - 1, player.getLocation().getY() - 1, 0), Location.create(player.getLocation().getX() + 1, player.getLocation().getY() + 1, 0), }; - for (Location location : AROUND_YOU) { - // The graphic is meant to play on a 3x3 radius around you, but not - // including the tile you are on. - player.getPacketDispatch().sendGlobalPositionGraphic(263, location); - } - - } - } diff --git a/Server/src/main/java/core/game/interaction/object/PriestInPerilOptionPlugin.java b/Server/src/main/java/core/game/interaction/object/PriestInPerilOptionPlugin.java index 47e17a152..ba1b8d79f 100644 --- a/Server/src/main/java/core/game/interaction/object/PriestInPerilOptionPlugin.java +++ b/Server/src/main/java/core/game/interaction/object/PriestInPerilOptionPlugin.java @@ -14,6 +14,7 @@ import core.game.node.object.Scenery; import core.game.world.map.Location; import core.plugin.Initializable; import core.plugin.Plugin; +import org.rs09.consts.NPCs; /** * Represents the quest node plugin handler. @@ -200,7 +201,7 @@ public class PriestInPerilOptionPlugin extends OptionHandler { } break; case "talk-through": - player.getDialogueInterpreter().open(1047, NPC.create(1047, player.getLocation())); + player.getDialogueInterpreter().open(NPCs.DREZEL_7690, NPC.create(NPCs.DREZEL_7690, player.getLocation())); break; } break; diff --git a/Server/src/main/java/core/game/node/entity/Entity.java b/Server/src/main/java/core/game/node/entity/Entity.java index 68f826c75..8d2046eba 100644 --- a/Server/src/main/java/core/game/node/entity/Entity.java +++ b/Server/src/main/java/core/game/node/entity/Entity.java @@ -638,7 +638,6 @@ public abstract class Entity extends Node { public Properties getProperties() { return properties; } - /** * Gets the updateMasks. * @return The updateMasks. diff --git a/Server/src/main/java/core/game/node/entity/combat/DeathTask.java b/Server/src/main/java/core/game/node/entity/combat/DeathTask.java index d2286f619..83922c552 100644 --- a/Server/src/main/java/core/game/node/entity/combat/DeathTask.java +++ b/Server/src/main/java/core/game/node/entity/combat/DeathTask.java @@ -55,8 +55,9 @@ public final class DeathTask extends NodeTask { } } } - e.getAnimator().forceAnimation(e.getProperties().getDeathAnimation()); e.graphics(Animator.RESET_G); + e.visualize(e.getProperties().getDeathAnimation(), e.getProperties().deathGfx); + e.getAnimator().forceAnimation(e.getProperties().getDeathAnimation()); e.commenceDeath(killer); e.getImpactHandler().setDisabledTicks(50); } diff --git a/Server/src/main/java/core/game/node/entity/impl/Properties.java b/Server/src/main/java/core/game/node/entity/impl/Properties.java index e2438f442..081ca3c4f 100644 --- a/Server/src/main/java/core/game/node/entity/impl/Properties.java +++ b/Server/src/main/java/core/game/node/entity/impl/Properties.java @@ -12,6 +12,7 @@ import core.game.node.entity.player.Player; import core.game.node.item.Item; import core.game.world.map.Location; import core.game.world.update.flag.context.Animation; +import core.game.world.update.flag.context.Graphics; import rs09.game.node.entity.combat.CombatPulse; import rs09.game.system.config.ItemConfigParser; import rs09.game.system.config.NPCConfigParser; @@ -92,6 +93,8 @@ public final class Properties { */ private Animation deathAnimation = new Animation(9055, Animator.Priority.HIGH); + public Graphics deathGfx = new Graphics(-1); + /** * The range animation. */ diff --git a/Server/src/main/java/core/game/node/entity/npc/NPC.java b/Server/src/main/java/core/game/node/entity/npc/NPC.java index b7cd9fc23..9486ce73f 100644 --- a/Server/src/main/java/core/game/node/entity/npc/NPC.java +++ b/Server/src/main/java/core/game/node/entity/npc/NPC.java @@ -25,6 +25,7 @@ import core.game.world.map.RegionManager; import core.game.world.map.build.DynamicRegion; import core.game.world.map.path.Pathfinder; import core.game.world.update.flag.context.Animation; +import core.game.world.update.flag.context.Graphics; import core.game.world.update.flag.npc.NPCFaceEntity; import core.game.world.update.flag.npc.NPCFaceLocation; import core.game.world.update.flag.npc.NPCForceChat; @@ -305,6 +306,9 @@ public class NPC extends Entity { if (definition.getConfiguration("movement_radius") != null) { this.setWalkRadius(definition.getConfiguration("movement_radius")); } + if(definition.getConfiguration("death_gfx") != null) { + getProperties().deathGfx = new Graphics(definition.getConfiguration("death_gfx")); + } } /** diff --git a/Server/src/main/java/core/game/node/entity/player/Player.java b/Server/src/main/java/core/game/node/entity/player/Player.java index e119932bc..f0602227b 100644 --- a/Server/src/main/java/core/game/node/entity/player/Player.java +++ b/Server/src/main/java/core/game/node/entity/player/Player.java @@ -80,7 +80,6 @@ import rs09.game.ge.PlayerGrandExchange; import rs09.game.node.entity.combat.CombatSwingHandler; import rs09.game.node.entity.combat.equipment.EquipmentDegrader; import rs09.game.node.entity.skill.runecrafting.PouchManager; -import rs09.game.node.entity.skill.skillcapeperks.SkillcapePerks; import rs09.game.node.entity.state.newsys.State; import rs09.game.node.entity.state.newsys.StateRepository; import rs09.game.world.GameWorld; @@ -95,7 +94,7 @@ import java.util.*; import static rs09.game.node.entity.player.info.stats.StatAttributeKeysKt.STATS_BASE; import static rs09.game.node.entity.player.info.stats.StatAttributeKeysKt.STATS_DEATHS; -import static rs09.tools.stringtools.StringToolsKt.colorize; +import static rs09.tools.stringtools.GlobalsKt.colorize; /** * Represents a player entity. diff --git a/Server/src/main/java/core/game/node/entity/player/link/PacketDispatch.java b/Server/src/main/java/core/game/node/entity/player/link/PacketDispatch.java index 85db4e86f..3b9076515 100644 --- a/Server/src/main/java/core/game/node/entity/player/link/PacketDispatch.java +++ b/Server/src/main/java/core/game/node/entity/player/link/PacketDispatch.java @@ -322,7 +322,7 @@ public final class PacketDispatch { * @param location the location. */ public void sendPositionedGraphic(int id, int height, int delay, Location location) { - PacketRepository.send(PositionedGraphic.class, new PositionedGraphicContext(player, new Graphics(id, height, delay), location)); + PacketRepository.send(PositionedGraphic.class, new PositionedGraphicContext(player, new Graphics(id, height, delay), location, 0, 0)); } /** @@ -342,8 +342,7 @@ public final class PacketDispatch { * @param location the location. */ public void sendPositionedGraphics(Graphics graphics, Location location) { - PacketRepository.send(PositionedGraphic.class, new PositionedGraphicContext(player, graphics, location)); - + PacketRepository.send(PositionedGraphic.class, new PositionedGraphicContext(player, graphics, location, 0, 0)); } /** diff --git a/Server/src/main/java/core/game/node/entity/player/link/request/trade/TradeContainer.java b/Server/src/main/java/core/game/node/entity/player/link/request/trade/TradeContainer.java index 25a0d86cc..0a62d001b 100644 --- a/Server/src/main/java/core/game/node/entity/player/link/request/trade/TradeContainer.java +++ b/Server/src/main/java/core/game/node/entity/player/link/request/trade/TradeContainer.java @@ -11,7 +11,7 @@ import core.net.packet.PacketRepository; import core.net.packet.context.ContainerContext; import core.net.packet.out.ContainerPacket; -import static rs09.tools.stringtools.StringToolsKt.colorize; +import static rs09.tools.stringtools.GlobalsKt.colorize; /** * Represents the container during a trade session. diff --git a/Server/src/main/java/core/game/node/entity/skill/construction/decoration/chapel/BoneOfferPlugin.java b/Server/src/main/java/core/game/node/entity/skill/construction/decoration/chapel/BoneOfferPlugin.java index 8cccb5592..28139bea9 100644 --- a/Server/src/main/java/core/game/node/entity/skill/construction/decoration/chapel/BoneOfferPlugin.java +++ b/Server/src/main/java/core/game/node/entity/skill/construction/decoration/chapel/BoneOfferPlugin.java @@ -87,9 +87,12 @@ public class BoneOfferPlugin extends UseWithHandler { return; } final Location start = player.getLocation(); + + Location gfxLoc = player.getLocation().transform(player.getDirection(), 1); + ContentAPI.submitIndividualPulse(player, new Pulse(1) { int counter = 0; - + @Override public boolean pulse() { counter++; @@ -97,7 +100,7 @@ public class BoneOfferPlugin extends UseWithHandler { if (player.getInventory().remove(new Item(b.getItemId()))) { player.animate(ANIM); player.getAudioManager().send(new Audio(958)); - player.getPacketDispatch().sendPositionedGraphics(GFX, altar.getLocation()); + player.getPacketDispatch().sendPositionedGraphics(GFX, gfxLoc); player.sendMessage(getMessage(isLit(left), isLit(right))); player.getSkills().addExperience(Skills.PRAYER, b.getExperience() * getMod(altar, isLit(left), isLit(right))); } diff --git a/Server/src/main/java/core/game/node/entity/skill/herblore/FinishedPotion.java b/Server/src/main/java/core/game/node/entity/skill/herblore/FinishedPotion.java index b0ddfeae8..dc808863a 100644 --- a/Server/src/main/java/core/game/node/entity/skill/herblore/FinishedPotion.java +++ b/Server/src/main/java/core/game/node/entity/skill/herblore/FinishedPotion.java @@ -1,6 +1,7 @@ package core.game.node.entity.skill.herblore; import core.game.node.item.Item; +import org.rs09.consts.Items; /** * Represents a finished potion. @@ -21,7 +22,7 @@ public enum FinishedPotion { SUPER_ANTIPOISON(UnfinishedPotion.IRIT, new Item(235), 48, 106.3, new Item(181)), FISHING_POTION(UnfinishedPotion.AVANTOE, new Item(231), 50, 112.5, new Item(151)), SUPER_ENERGY(UnfinishedPotion.AVANTOE, new Item(2970), 52, 117.5, new Item(3018)), - HUNTING_POTION(UnfinishedPotion.AVANTOE, new Item(10109), 53, 120, new Item(10000)), + HUNTING_POTION(UnfinishedPotion.AVANTOE, new Item(Items.KEBBIT_TEETH_DUST_10111), 53, 120, new Item(10000)), SUPER_STRENGTH(UnfinishedPotion.KWUARM, new Item(225), 55, 125, new Item(157)), WEAPON_POISON(UnfinishedPotion.KWUARM, new Item(241), 60, 137.5, new Item(187)), SUPER_RESTORE(UnfinishedPotion.SNAPDRAGON, new Item(223), 63, 142.5, new Item(3026)), diff --git a/Server/src/main/java/core/game/world/map/Location.java b/Server/src/main/java/core/game/world/map/Location.java index 8b7f8ddbc..17b22e7c0 100644 --- a/Server/src/main/java/core/game/world/map/Location.java +++ b/Server/src/main/java/core/game/world/map/Location.java @@ -6,6 +6,8 @@ import core.game.world.map.path.Path; import core.game.world.map.path.Pathfinder; import core.tools.RandomFunction; +import java.util.ArrayList; + /** * Represents a location on the world map. * @author Emperor @@ -250,6 +252,24 @@ public final class Location extends Node { return Math.sqrt(xdiff * xdiff + ydiff * ydiff); } + /** + * Gets the 8 tiles surrounding this location as an ArrayList + */ + public ArrayList getSurroundingTiles() { + ArrayList locs = new ArrayList<>(); + + locs.add(transform(0,1,0)); //N + locs.add(transform(1,1,0)); //NE + locs.add(transform(1,0,0)); //E + locs.add(transform(1,-1,0)); //SE + locs.add(transform(0,-1,0)); //S + locs.add(transform(-1,-1,0));//SW + locs.add(transform(-1,0,0));//W + locs.add(transform(-1,1,0));//NW + + return locs; + } + /** * Gets the x position on the region chunk. * @return The x position on the region chunk. @@ -339,7 +359,7 @@ public final class Location extends Node { * @return The local y-coordinate. */ public int getSceneY(Location loc) { - return y - ((loc.getRegionY() - 6) * 8); + return y - ((loc.getRegionY() - 6) << 3); } /** diff --git a/Server/src/main/java/core/net/packet/context/PositionedGraphicContext.java b/Server/src/main/java/core/net/packet/context/PositionedGraphicContext.java index d26116617..738eeea0b 100644 --- a/Server/src/main/java/core/net/packet/context/PositionedGraphicContext.java +++ b/Server/src/main/java/core/net/packet/context/PositionedGraphicContext.java @@ -26,16 +26,23 @@ public final class PositionedGraphicContext implements Context { */ private final Location location; + public int sceneX, sceneY; + public int offsetX, offsetY; + /** * Constructs a new {@code PositionedGraphicContext} {@code Object}. * @param player The player. * @param graphic The graphic to display on the given location. * @param location The location to display the graphic on. */ - public PositionedGraphicContext(Player player, Graphics graphic, Location location) { + public PositionedGraphicContext(Player player, Graphics graphic, Location location, int offsetX, int offsetY) { this.player = player; this.graphic = graphic; this.location = location; + this.sceneX = location.getSceneX(player.getPlayerFlags().getLastSceneGraph()); + this.sceneY = location.getSceneY(player.getPlayerFlags().getLastSceneGraph()); + this.offsetX = offsetX; + this.offsetY = offsetY; } @Override diff --git a/Server/src/main/java/core/net/packet/in/InteractionPacket.java b/Server/src/main/java/core/net/packet/in/InteractionPacket.java index 9b93745f9..8973c4049 100644 --- a/Server/src/main/java/core/net/packet/in/InteractionPacket.java +++ b/Server/src/main/java/core/net/packet/in/InteractionPacket.java @@ -338,7 +338,7 @@ public final class InteractionPacket implements IncomingPacket { player.debug("Handled by quest interaction manager."); return; } - if(InteractionListeners.run(item.getId(), InteractionListener.Companion.getITEM(),option.getName(),player,item)){ + if(InteractionListeners.run(item.getId(), InteractionListener.Companion.getGROUNDITEM(), option.getName(),player,item)){ return; } item.getInteraction().handle(player, option); diff --git a/Server/src/main/java/core/net/packet/out/PositionedGraphic.java b/Server/src/main/java/core/net/packet/out/PositionedGraphic.java index 2652632eb..2b7289157 100644 --- a/Server/src/main/java/core/net/packet/out/PositionedGraphic.java +++ b/Server/src/main/java/core/net/packet/out/PositionedGraphic.java @@ -5,6 +5,7 @@ import core.game.world.update.flag.context.Graphics; import core.net.packet.IoBuffer; import core.net.packet.OutgoingPacket; import core.net.packet.context.PositionedGraphicContext; +import rs09.game.system.SystemLogger; /** * The positioned graphic outgoing packet. @@ -16,8 +17,16 @@ public final class PositionedGraphic implements OutgoingPacket 100) ContentAPI.poofClear(this) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.FILLIMAN_TARLOCK_1050) + } +} \ No newline at end of file diff --git a/Server/src/main/kotlin/rs09/game/content/quest/members/naturespirit/NSDrezelDialogue.kt b/Server/src/main/kotlin/rs09/game/content/quest/members/naturespirit/NSDrezelDialogue.kt new file mode 100644 index 000000000..26a0872a3 --- /dev/null +++ b/Server/src/main/kotlin/rs09/game/content/quest/members/naturespirit/NSDrezelDialogue.kt @@ -0,0 +1,120 @@ +package rs09.game.content.quest.members.naturespirit + +import api.ContentAPI +import core.game.content.dialogue.FacialExpression +import core.game.node.entity.npc.NPC +import core.game.node.entity.player.Player +import core.game.node.entity.player.link.audio.Audio +import core.game.system.task.Pulse +import core.game.world.update.flag.context.Animation +import core.game.world.update.flag.context.Graphics +import org.rs09.consts.Items +import rs09.game.content.dialogue.DialogueFile +import rs09.tools.END_DIALOGUE +import rs09.tools.stringtools.colorize + +class NSDrezelDialogue : DialogueFile() { + var questStage = 0 + override fun handle(componentID: Int, buttonID: Int) { + questStage = player!!.questRepository.getStage("Nature Spirit") + + if(questStage <= 5){ + when(stage){ + 0 -> options("Sorry, not interested...", "Well, what is it, I may be able to help?").also { stage++ } + 1 -> when(buttonID){ + 1 -> playerl(FacialExpression.NEUTRAL, "Sorry, not interested.").also { stage = END_DIALOGUE } + 2 -> playerl(FacialExpression.FRIENDLY, "Well, what is it, I may be able to help?").also { stage++ } + } + + 2 -> npcl(FacialExpression.HALF_THINKING, "There's a man called Filliman who lives in Mort Myre, I wonder if you could look for him? The swamps of Mort Myre are dangerous though, they're infested with Ghasts!").also { stage++ } + 3 -> options("Who is this Filliman?", "Where's Mort Myre?", "What's a Ghast?", "Yes, I'll go and look for him.", "Sorry, I don't think I can help.").also { stage++ } + 4 -> when(buttonID){ + 1 -> npcl(FacialExpression.NEUTRAL, "Filliman Tarlock is his full name and he's a Druid. He lives in Mort Myre much like a hermit, but there's many a traveller who he's helped.").also { stage-- } + 2 -> npcl(FacialExpression.NEUTRAL, "Mort Myre is a decayed and dangerous swamp to the south. It was once a beautiful forest but has since become filled with vile emanations from within Morytania.").also { stage = 6 } + 3 -> npcl(FacialExpression.NEUTRAL, "A Ghast is a poor soul who died in Mort Myre. They're undead of a special class, they're untouchable as far as I'm aware!").also { stage = 5 } + 4 -> playerl(FacialExpression.FRIENDLY, "Yes, I'll go and look for him.").also { stage = 10 } + 5 -> playerl(FacialExpression.NEUTRAL, "Sorry, I don't think I can help.").also { stage = END_DIALOGUE } + } + 5 -> npcl(FacialExpression.NEUTRAL, "Filliman knew how to tackle them, but I've not heard from him in a long time. Ghasts, when they attack, will devour any food you have. If you have no food, they'll draw their nourishment from you!").also { stage = 3 } + 6 -> npcl(FacialExpression.NEUTRAL, " We put a fence around it to stop unwary travellers going in. Anyone who dies in the swamp is forever cursed to haunt it as a Ghast. Ghasts attack travellers, turning food to rotten filth.").also { stage = 3 } + + 10 -> npcl(FacialExpression.NEUTRAL, "That's great, but it is very dangerous. Are you sure you want to do this?").also { stage++ } + 11 -> options("Yes, I'm sure.", "Sorry, I don't think I can help.").also { stage++ } + 12 -> when(buttonID){ + 1 -> playerl(FacialExpression.FRIENDLY, "Yes, I'm sure.").also { stage = 20 } + 2 -> playerl(FacialExpression.NEUTRAL, "Sorry, I don't think I can help.").also { stage = END_DIALOGUE } + } + + 20 -> npcl(FacialExpression.NEUTRAL, "That's great! Many Thanks! Now then, please be aware of the Ghasts, you cannot attack them, only Filliman knew how to take them on.").also { stage++ } + 21 -> npcl(FacialExpression.NEUTRAL, "Just run from them if you can. If you start to get lost, try to make your way back to the temple.").also { stage++ } + 22 -> { + ContentAPI.sendDoubleItemDialogue(player!!, Items.MEAT_PIE_2327, Items.APPLE_PIE_2323, "The cleric hands you some food.") + if(questStage == 0){ + repeat(3) { ContentAPI.addItemOrDrop(player!!, Items.MEAT_PIE_2327, 1) } + repeat(3) { ContentAPI.addItemOrDrop(player!!, Items.APPLE_PIE_2323, 1) } + player!!.questRepository.getQuest("Nature Spirit").setStage(player!!, 5) + } + stage++ + } + 23 -> npcl(FacialExpression.NEUTRAL, "Please take this food to Filliman, he'll probably appreciate a bit of cooked food. Now, he's never revealed where he lives in the swamps but I guess he'd be to the south, search for him won't you?").also { stage++ } + 24 -> playerl(FacialExpression.FRIENDLY, "I'll do my very best, don't worry, if he's in there and he's still alive I'll definitely find him.").also { stage = END_DIALOGUE; player!!.questRepository.getQuest("Nature Spirit").start(player!!) } + } + } + + else if(questStage == 15) { + when(stage){ + 0 -> playerl(FacialExpression.HALF_GUILTY, "I've found Filliman and you should prepare for some sad news.").also { stage++ } + 1 -> npcl(FacialExpression.HALF_GUILTY, "You mean... he's dead?").also { stage++ } + 2 -> playerl(FacialExpression.NEUTRAL, "Well, er sort of. I got to his camp and I encountered a spirit of some kind. I don't think it was a Ghast, it tried to communicate with me, but made no sense, it was all 'ooooh' this and 'oooh' that.").also { stage++ } + 3 -> npcl(FacialExpression.NEUTRAL, "Hmmm, that's very interesting, I seem to remember Father Aereck in Lumbridge and his predecessor Father Urhney having a similar issue. Though this is probably not related to your problem.").also { stage++ } + 4 -> npcl(FacialExpression.NEUTRAL, " I will pray that it wasn't the spirit of my friend Filliman, but some lost soul who needs some help. Please do let me know how you get on with it.").also { stage = END_DIALOGUE } + } + } + + else if(questStage == 35){ + when(stage){ + 0 -> playerl(FacialExpression.FRIENDLY, "Hello again! I'm helping Filliman, he plans to become a nature spirit. I have a spell to cast but first I need to be blessed. Can you bless me?").also { stage++ } + 1 -> npcl(FacialExpression.NEUTRAL, "But you haven't sneezed!").also { stage++ } + 2 -> playerl(FacialExpression.FRIENDLY, "You're so funny! But can you bless me?").also { stage++ } + 3 -> npcl(FacialExpression.NEUTRAL, "Very well my friend, prepare yourself for the blessings of Saradomin. Here we go!").also { stage++ } + 4 -> { + end() + player!!.lock(); + ContentAPI.submitIndividualPulse(player!!, BlessingPulse(npc!!, player!!)) + } + } + } + + else if(questStage == 40){ + npcl(FacialExpression.NEUTRAL, "There you go my friend, you're now blessed. It's funny, now I look at you, there seems to be something of the faith about you. Anyway, good luck with your quest!").also { stage = END_DIALOGUE; player!!.questRepository.getQuest("Nature Spirit").setStage(player!!, 45) } + } + + else { + when(stage){ + 0 -> npcl(FacialExpression.NEUTRAL, "Hello, friend, how goes your quest with Filliman?").also { stage++ } + 1 -> playerl(FacialExpression.NEUTRAL, "Still working at it.").also { stage++ } + 2 -> npcl(FacialExpression.NEUTRAL, "Well enough! Do let me know when something develops!").also { stage = END_DIALOGUE } + } + } + } + +} + +private class BlessingPulse(val drezel: NPC, val player: Player) : Pulse(){ + var ticks = 0 + + override fun pulse(): Boolean { + when(ticks){ + 0 -> ContentAPI.animate(drezel, 1162).also { ContentAPI.spawnProjectile(drezel, player, 268); ContentAPI.playAudio(player, Audio(2674)) } + 2 -> ContentAPI.visualize(player, Animation(645), Graphics(267, 100)) + 4 -> ContentAPI.unlock(player).also { player.questRepository.getQuest("Nature Spirit").setStage(player, 40); return true } + } + ticks++ + return false + } + + override fun stop() { + super.stop() + ContentAPI.openDialogue(player, NSDrezelDialogue(), drezel) + } +} \ No newline at end of file diff --git a/Server/src/main/kotlin/rs09/game/content/quest/members/naturespirit/NSListeners.kt b/Server/src/main/kotlin/rs09/game/content/quest/members/naturespirit/NSListeners.kt new file mode 100644 index 000000000..70e619568 --- /dev/null +++ b/Server/src/main/kotlin/rs09/game/content/quest/members/naturespirit/NSListeners.kt @@ -0,0 +1,234 @@ +package rs09.game.content.quest.members.naturespirit + +import api.Container +import api.ContentAPI +import api.DialUtils +import core.game.content.dialogue.FacialExpression +import core.game.node.entity.npc.NPC +import core.game.node.entity.player.Player +import core.game.node.item.GroundItem +import core.game.node.item.GroundItemManager +import core.game.node.item.Item +import core.game.system.task.Pulse +import core.game.world.map.Location +import org.rs09.consts.Items +import org.rs09.consts.NPCs +import rs09.game.content.dialogue.DialogueFile +import rs09.game.content.global.action.PickupHandler +import rs09.game.interaction.InteractionListener +import rs09.game.node.entity.npc.other.MortMyreGhastNPC +import rs09.game.system.SystemLogger +import rs09.game.system.config.ShopParser +import rs09.tools.END_DIALOGUE + +class NSListeners : InteractionListener() { + + val GROTTO_TREE = 3517 + val GROTTO_ENTRANCE = 3516 + val GROTTO_ALTAR = 3520 + val NATURE_ALTAR = 3521 + val JOURNAL = Items.JOURNAL_2967 + val NATURE_STONE = 3527 + val FAITH_STONE = 3528 + val FREELY_GIVEN_STONE = 3529 + val WASHING_BOWL = Items.WASHING_BOWL_2964 + val MIRROR = Items.MIRROR_2966 + val SPELLCARD = Items.DRUIDIC_SPELL_2968 + val USED_SPELLCARD = Items.A_USED_SPELL_2969 + val FUNGUS = Items.MORT_MYRE_FUNGUS_2970 + val MIRROR_TAKEN = "/save:ns:mirror_taken" + val GROTTO_SEARCHED = "/save:ns:grotto_searched" + val WISHING_WELL = 28715 + val DRUID_POUCH = Items.DRUID_POUCH_2958 + val DRUID_POUCH_EMPTY = Items.DRUID_POUCH_2957 + val stones = intArrayOf(NATURE_STONE, FAITH_STONE, FREELY_GIVEN_STONE) + val items = intArrayOf(USED_SPELLCARD, FUNGUS) + + override fun defineListeners() { + on(GROTTO_TREE, SCENERY, "look-at"){player, _ -> + ContentAPI.sendMessage(player, "It looks like a tree on a large rock with roots trailing down to the ground.") + return@on true + } + + on(GROTTO_TREE, SCENERY, "search"){player, _ -> + if(!ContentAPI.getAttribute(player, GROTTO_SEARCHED, false) || !(ContentAPI.inInventory(player, JOURNAL) || ContentAPI.inBank(player, JOURNAL))){ + ContentAPI.sendItemDialogue(player, JOURNAL, "You search the strange rock. You find a knot and inside of it you discover a small tome. The words on the front are a bit vague, but you can make out the words 'Tarlock' and 'journal'.") + ContentAPI.addItemOrDrop(player, JOURNAL, 1) + ContentAPI.setAttribute(player, GROTTO_SEARCHED, true) + return@on true + } + return@on false + } + + on(GROTTO_ENTRANCE, SCENERY, "enter"){player, node -> + val questStage = player.questRepository.getQuest("Nature Spirit").getStage(player) + if(questStage < 55) { + val npc = core.game.node.entity.npc.NPC.create(NPCs.FILLIMAN_TARLOCK_1050, Location.create(3440, 3336, 0)) + npc.init() + } else if(questStage < 60) { + player.teleport(Location.create(3442, 9734, 0)) + } else if (questStage >= 60){ + player.teleport(Location.create(3442, 9734, 1)) + } + return@on true + } + + on(GROTTO_ALTAR, SCENERY,"search"){player, node -> + val stage = player.questRepository.getStage("Nature Spirit") + if(stage == 55){ + ContentAPI.openDialogue(player, FillimanCompletionDialogue(), NPC(NPCs.FILLIMAN_TARLOCK_1050)) + return@on true + } + + return@on false + } + + on(NATURE_STONE, SCENERY, "search"){player, _ -> + ContentAPI.sendDialogue(player, "You search the stone and find that it has some sort of nature symbol scratched into it.") + return@on true + } + + on(FAITH_STONE, SCENERY, "search"){player, _ -> + ContentAPI.sendDialogue(player, "You search the stone and find that it has some sort of faith symbol scratched into it.") + return@on true + } + + on(FREELY_GIVEN_STONE, SCENERY, "search"){player, _ -> + ContentAPI.sendDialogue(player, "You search the stone and find it has some sort of spirit symbol scratched into it.") + return@on true + } + + on(WISHING_WELL, SCENERY, "make-wish"){player, node -> + if(player.questRepository.isComplete("Nature Spirit") && player.questRepository.isComplete("Wolf Whistle")) + ShopParser.openUid(player, 241) + else + ContentAPI.sendDialogue(player, "You can't do that yet.") + + return@on true + } + + on(JOURNAL, ITEM, "read"){player, _ -> + player.dialogueInterpreter.open(NSJournalDialogue()) + return@on true + } + + on(WASHING_BOWL, GROUNDITEM, "take"){player, node -> + SystemLogger.logInfo("Running listener") + GroundItemManager.create(Item(MIRROR), node.location, player) + PickupHandler.take(player, node as GroundItem) + return@on true + } + + on(MIRROR, GROUNDITEM, "take"){player, node -> + if(ContentAPI.getAttribute(player, MIRROR_TAKEN, false) && (ContentAPI.inInventory(player, MIRROR) || ContentAPI.inBank(player, MIRROR))){ + ContentAPI.sendDialogue(player, "I don't need another one of these.") + return@on true + } + ContentAPI.setAttribute(player, MIRROR_TAKEN, true) + PickupHandler.take(player, node as GroundItem) + return@on true + } + + on(SPELLCARD, ITEM, "cast"){player, node -> + if(NSUtils.castBloom(player)){ + ContentAPI.removeItem(player, node.asItem(), Container.INVENTORY) + ContentAPI.addItem(player, Items.A_USED_SPELL_2969) + } + return@on true + } + + on(intArrayOf(DRUID_POUCH, DRUID_POUCH_EMPTY), ITEM, "fill"){player, node -> + + if(player.questRepository.getStage("Nature Spirit") >= 75) { + if (ContentAPI.amountInInventory(player, Items.MORT_MYRE_FUNGUS_2970) >= 3) { + if (node.id != Items.DRUID_POUCH_2958) { + ContentAPI.removeItem(player, node, Container.INVENTORY) + } + ContentAPI.removeItem(player, Item(Items.MORT_MYRE_FUNGUS_2970, 3), Container.INVENTORY) + ContentAPI.addItem(player, Items.DRUID_POUCH_2958, 3) + } else { + ContentAPI.sendDialogue(player, "You need 3 fungus before you can do that.") + } + } else { + ContentAPI.sendDialogue(player, "I don't know how to use that yet.") + } + + return@on true + } + + onUseWith(SCENERY, Items.SILVER_SICKLE_2961, NATURE_ALTAR){player, used, with -> + ContentAPI.sendItemDialogue(player, Items.SILVER_SICKLEB_2963, "You dump the sickle into the waters.") + if(ContentAPI.removeItem(player, Items.SILVER_SICKLE_2961, Container.INVENTORY)){ + ContentAPI.addItem(player, Items.SILVER_SICKLEB_2963, 1) + } + return@onUseWith true + } + + onUseWith(NPC, DRUID_POUCH, NPCs.GHAST_1052){player, used, with -> + NSUtils.activatePouch(player, with as MortMyreGhastNPC) + } + + onUseWith(SCENERY, items, *stones) { player, used, with -> + when (used.id) { + USED_SPELLCARD -> { + if (with.id == FREELY_GIVEN_STONE) { + if(ContentAPI.removeItem(player, used, Container.INVENTORY)){ + ContentAPI.sendNPCDialogue(player, NPCs.FILLIMAN_TARLOCK_1050, "Aha, yes, that seems right well done!") + ContentAPI.sendMessage(player, "The stone seems to absorb the used spell scroll.") + NSUtils.flagCardPlaced(player) + } + } else ContentAPI.sendMessage(player, "You try to put the item on the stone, but it just moves off.") + } + + FUNGUS -> { + if (with.id == NATURE_STONE) { + if(ContentAPI.removeItem(player, used, Container.INVENTORY)){ + ContentAPI.sendNPCDialogue(player, NPCs.FILLIMAN_TARLOCK_1050, "Aha, yes, that seems right well done!") + ContentAPI.sendMessage(player, "The stone seems to absorb the used fungus.") + NSUtils.flagFungusPlaced(player) + } + } else ContentAPI.sendMessage(player, "You try to put the item on the stone, but it just moves off.") + } + } + return@onUseWith true + } + } +} + +class NSJournalDialogue : DialogueFile() { + override fun handle(componentID: Int, buttonID: Int) { + when(stage){ + 0 -> dialogue(*DialUtils.splitLines("Most of the writing is pretty uninteresting, but something inside refers to a nature spirit. The requirements for which are,")).also { stage++ } + 1 -> dialogue(*DialUtils.splitLines("'Something from nature', 'something with faith' and 'something of the spirit-to-become freely given'. It's all pretty vague.")).also { stage = END_DIALOGUE } + } + } +} + +class FillimanCompletionDialogue : DialogueFile() { + override fun handle(componentID: Int, buttonID: Int) { + when(stage){ + 0 -> npcl(FacialExpression.NEUTRAL, "Well, hello there again. I was just enjoying the grotto. Many thanks for your help, I couldn't have become a Spirit of nature without you.").also { stage++ } + 1 -> npcl(FacialExpression.NEUTRAL, "I must complete the transformation now. Just stand there and watch the show, apparently it's quite good!").also { stage++ } + 2 -> { + end() + ContentAPI.lock(player!!, 10) + ContentAPI.submitWorldPulse(CompleteSpellPulse(player!!)) + } + } + } +} + +class CompleteSpellPulse(val player: Player) : Pulse(2){ + var counter = 0 + val locations = arrayOf(Location.create(3444, 9740, 0), Location.create(3439, 9740, 0), Location.create(3439, 9737, 0), Location.create(3444, 9737, 0), Location.create(3444, 9735, 0), Location.create(3438, 9735, 0)) + val dest = Location.create(3441, 9738, 0) + override fun pulse(): Boolean { + when(counter++){ + 0 -> repeat(6) { ContentAPI.spawnProjectile(locations[it], dest, 268, 0, 1000, 0, 40, 20) } + 1 -> player.questRepository.getQuest("Nature Spirit").setStage(player, 60) + 2 -> player.teleport(player.location.transform(0,0,1)) + 3 -> ContentAPI.openDialogue(player, NPCs.NATURE_SPIRIT_1051, ContentAPI.findLocalNPC(player, NPCs.NATURE_SPIRIT_1051) as NPC).also { ContentAPI.unlock(player); return true } + } + return false + } +} \ No newline at end of file diff --git a/Server/src/main/kotlin/rs09/game/content/quest/members/naturespirit/NSTarlockDialogue.kt b/Server/src/main/kotlin/rs09/game/content/quest/members/naturespirit/NSTarlockDialogue.kt new file mode 100644 index 000000000..31f0b8260 --- /dev/null +++ b/Server/src/main/kotlin/rs09/game/content/quest/members/naturespirit/NSTarlockDialogue.kt @@ -0,0 +1,241 @@ +package rs09.game.content.quest.members.naturespirit + +import api.Container +import api.ContentAPI +import api.DialUtils +import core.game.content.dialogue.DialoguePlugin +import core.game.content.dialogue.FacialExpression +import core.game.node.entity.npc.NPC +import core.game.node.entity.player.Player +import core.game.node.entity.player.link.quest.Quest +import core.game.system.task.Pulse +import core.game.world.map.Location +import core.game.world.update.flag.context.Graphics +import core.plugin.Initializable +import org.rs09.consts.Items +import org.rs09.consts.NPCs +import rs09.tools.END_DIALOGUE + +@Initializable +class NSTarlockDialogue(player: Player? = null) : DialoguePlugin(player) { + var questStage = 0 + + override fun newInstance(player: Player?): DialoguePlugin { + return NSTarlockDialogue(player) + } + + override fun open(vararg args: Any?): Boolean { + npc = args[0] as NPC + val quest = player.questRepository.getQuest("Nature Spirit") + questStage = quest.getStage(player) + + if(questStage > 10 && !ContentAPI.inEquipment(player, Items.GHOSTSPEAK_AMULET_552)){ + npcl(FacialExpression.HALF_GUILTY, "OooOOOOOOoooOoOOoOOOo") + setQuest(15) + return false + } + + when(questStage){ + 10, 15 -> sendDialogue("A shifting apparition appears in front of you.").also { stage = 1000 } + 20 -> npcl(FacialExpression.HALF_GUILTY, "Oh, hello there, do you still think I'm dead? It's hard to see how I could be dead when I'm still in the world. I can see everything quite clearly. And nothing of what you say reflects the truth.") + 25 -> npcl(FacialExpression.HALF_GUILTY, "Oh, hello... Sorry, you've caught me at a bad time, it's just that I've had a sign you see and I need to find my journal.").also { stage = 7 } + 30 -> npcl(FacialExpression.HALF_GUILTY, "Thanks for the journal, I've been reading it. It looks like I came to a violent and bitter end but that's not really important. I just have to figure out what I am going to do now?").also { stage = 14 } + 35 -> npcl(FacialExpression.NEUTRAL, "Hello there, have you been blessed yet?").also { stage = 60 } + 45 -> { + if(ContentAPI.inInventory(player, Items.MORT_MYRE_FUNGUS_2970)){ + npcl(FacialExpression.NEUTRAL, "Did you manage to get something from nature?").also { stage = 80 } + } else { + playerl( + FacialExpression.NEUTRAL, + "Hello, I've been blessed but I don't know what to do now." + ).also { stage = 70 } + } + } + 50 -> npcl(FacialExpression.NEUTRAL, " Hello again! I don't suppose you've found out what the other components of the Nature spell are have you?").also { stage = 90 } + else -> npcl(FacialExpression.NEUTRAL, ".......").also { stage = END_DIALOGUE } + } + return true + } + + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + when(stage){ + 0 -> if(ContentAPI.inInventory(player, Items.MIRROR_2966)){ + sendDialogue("You use the mirror on the spirit","of the dead Filliman Tarlock.").also { stage++ } + } else { + playerl(FacialExpression.NEUTRAL, "Yes, I do think you're dead and I'll prove it somehow.").also { stage = 1002 } + } + + 1 -> playerl(FacialExpression.NEUTRAL, "Here take a look at this, perhaps you can see that you're utterly transparent now!").also { stage++ } + 2 -> sendDialogue("The spirit of Filliman reaches forwards and takes the mirror.").also { stage++ } + 3 -> npcl(FacialExpression.HALF_GUILTY, "Well, that is the most peculiar thing I've ever experienced. Strange how well it reflects the stagnant swamp behind me, but there is nothing of my own visage apparent.").also { stage++ } + 4 -> playerl(FacialExpression.NEUTRAL, "That's because you're dead! Dead as a door nail... Deader in fact... You bear a remarkable resemblance to wormbait! Err... No offence...").also { stage++ } + 5 -> npcl(FacialExpression.HALF_GUILTY, "I think you might be right my friend, though I still feel very much alive. It is strange how I still come to be here and yet I've not turned into a Ghast.").also { stage++ } + 6 -> npcl(FacialExpression.HALF_GUILTY, " It must be a sign... Yes a sign... I must try to find out what it means. Now, where did I put my journal?").also { stage++ } + 7 -> if(!ContentAPI.inInventory(player, Items.JOURNAL_2967)){ + playerl(FacialExpression.NEUTRAL, "Where did you put it?").also { stage++; setQuest(25) } + } else sendDialogue("You give the journal to Filliman Tarlock").also { ContentAPI.removeItem(player, Items.JOURNAL_2967, Container.INVENTORY); stage = 10; setQuest(30) } + + //no journal + 8 -> npcl(FacialExpression.HALF_GUILTY, "Well, if I knew that, I wouldn't still be looking for it. However, I do remember something about a knot? Perhaps I was meant to tie a knot or something?").also { stage = END_DIALOGUE } + + //has journal + 10 -> playerl(FacialExpression.NEUTRAL, "Here, I found this, maybe you can use it?").also { stage++ } + 11 -> npcl(FacialExpression.FRIENDLY, "My journal! That should help to collect my thoughts.").also { stage++ } + 12 -> sendDialogue("~ The spirit starts leafing through the journal. ~", "~ He seems quite distant as he regards the pages. ~", "~ After some time the druid faces you again. ~").also {stage++} + 13 -> npcl(FacialExpression.HALF_GUILTY, "It's all coming back to me now. It looks like I came to a violent and bitter end but that's not important now. I just have to figure out what I am going to do now?").also { stage++ } + 14 -> options("Being dead, what options do you think you have?", "So, what's your plan?", "Well, good luck with that.", "How can I help?", "Ok thanks.").also { stage++ } + 15 -> when(buttonId){ + 1 -> playerl(FacialExpression.NEUTRAL, "Being dead, what options do you think you have? I'm not trying to be rude or anything, but it's not like you have many options is it? I mean, it's either up or down for you isn't it?").also { stage = 20 } + 2 -> playerl(FacialExpression.NEUTRAL, "So, what's your plan?").also { stage = 30 } + 3 -> playerl(FacialExpression.NEUTRAL, "Well, good luck with that.").also { stage = 40 } + 4 -> playerl(FacialExpression.NEUTRAL, "How can I help?").also { stage = 50 } + 5 -> playerl(FacialExpression.NEUTRAL, "Ok thanks.").also { stage = END_DIALOGUE } + } + + //Being dead, what options + 20 -> npcl(FacialExpression.HALF_GUILTY, "Hmm, well you're a poetic one aren't you. Your material world logic stands you in good stead... If you're standing in the material world...").also { stage = 14 } + + //what's your plan? + 30 -> npcl(FacialExpression.HALF_GUILTY, "In my former incarnation I was Filliman Tarlock, a great druid of some power. I spent many years in this place, which was once a forest and I would wish to protect it as a nature spirit.").also { stage = 14 } + + //good luck with that + 40 -> npcl(FacialExpression.HALF_GUILTY, "Won't you help me to become a nature spirit? I could really use your help!").also { stage = 14 } + + //How can I help? + 50 -> npcl(FacialExpression.HALF_GUILTY, "Will you help me to become a nature spirit? The directions for becoming one are a bit vague, I need three things but I know how to get one of them. Perhaps you can help collect the rest?").also { stage++ } + 51 -> playerl(FacialExpression.NEUTRAL, "I might be interested, what's involved?").also { stage++ } + 52 -> npcl(FacialExpression.HALF_GUILTY, "Well, the book says, that I need, and I quote:- 'Something with faith', 'something from nature' and the 'spirit-to-become' freely given'. Hmm, I know how to get something from nature.").also { stage++ } + 53 -> playerl(FacialExpression.NEUTRAL, "Well, that does seem a bit vague.").also { stage++ } + 54 -> npcl(FacialExpression.HALF_GUILTY, "Hmm, it does and I could understand if you didn't want to help. However, if you could perhaps at least get the item from nature, that would be a start. Perhaps we can figure out the rest as we go along.").also { stage++ } + 55 -> sendDialogue(*DialUtils.splitLines("The druid produces a small sheet of papyrus with some writing on it.")).also { ContentAPI.addItemOrDrop(player, Items.DRUIDIC_SPELL_2968); setQuest(35); stage++ } + 56 -> npcl(FacialExpression.NEUTRAL, "This spell needs to be cast in the swamp after you have been blessed. I'm afraid you'll need to go to the temple to the North and ask a member of the clergy to bless you.").also { stage++ } + 57 -> playerl(FacialExpression.NEUTRAL, "Blessed, what does that do?").also { stage++ } + 58 -> npcl(FacialExpression.NEUTRAL, "It is required if you're to cast this druid spell. Once you've cast the spell, you should find something from nature. Bring it back to me and then we'll try to figure out the other things we need.").also { stage = END_DIALOGUE } + + //have you been blessed yet + 60 -> playerl(FacialExpression.NEUTRAL, "No, not yet.").also { stage++ } + 61 -> npcl(FacialExpression.NEUTRAL, "Well, hurry up!").also { stage++ } + 62 -> if(ContentAPI.inInventory(player, Items.DRUIDIC_SPELL_2968) || ContentAPI.inBank(player, Items.DRUIDIC_SPELL_2968)) end() + else playerl(FacialExpression.NEUTRAL, "Could I have another bloom scroll please?").also { stage++ } + 63 -> npcl(FacialExpression.NEUTRAL, "Sure, but please look after this one.").also { stage++ } + 64 -> sendDialogue("The spirit of Filliman Tarlock gives you another bloom spell.").also { ContentAPI.addItemOrDrop(player, Items.DRUIDIC_SPELL_2968); stage = END_DIALOGUE } + + //I've been blessed + 70 -> npcl(FacialExpression.NEUTRAL, "Well, you need to bring 'something from nature', 'something with faith' and 'something of the spirit-to- become freely given.'").also { stage++ } + 71 -> playerl(FacialExpression.NEUTRAL, "Yeah, but what does that mean?").also { stage++ } + 72 -> npcl(FacialExpression.NEUTRAL, "Hmm, it is a conundrum, however, if you use that spell I gave you, you should be able to get from nature. Once you have that, we may be puzzle the rest out.").also { stage++ } + 73 -> if(!ContentAPI.inInventory(player, Items.DRUIDIC_SPELL_2968) && !ContentAPI.inBank(player, Items.DRUIDIC_SPELL_2968)){ + playerl(FacialExpression.NEUTRAL, "Could I have another bloom scroll please?").also { stage++ } + } else end() + 74 -> npcl(FacialExpression.NEUTRAL, "Sure, but please look after this one.").also { stage++ } + 75 -> sendDialogue("The spirit of Filliman Tarlock gives you","another bloom spell.").also { ContentAPI.addItem(player, Items.DRUIDIC_SPELL_2968); stage = END_DIALOGUE } + + //has fungus + 80 -> sendDialogue("You show the fungus to Filliman.").also { stage++ } + 81 -> playerl(FacialExpression.NEUTRAL, "Yes, I have a fungus here that I picked.").also { stage++ } + 82 -> npcl(FacialExpression.NEUTRAL, "Wonderful, the mushroom represents 'something from nature'. Now we need to work out what the other components of the spell are!").also { stage = 90; setQuest(50) } + + //pre-spell options + 90 -> options("What are the things that are needed?", "What should I do when I have those things?", "I think I've solved the puzzle!", "Could I have another bloom scroll please?", "Ok, thanks.").also { stage++ } + 91 -> when(buttonId){ + 1 -> playerl(FacialExpression.NEUTRAL, "What are the things that are needed?").also { stage = 100 } + 2 -> playerl(FacialExpression.NEUTRAL, "What should I do when I have those things?").also { stage = 110 } + 3 -> playerl(FacialExpression.FRIENDLY, "I think I've solved the puzzle!").also { stage = 120 } + 4 -> playerl(FacialExpression.FRIENDLY, "Can I have another bloom scroll please?").also { stage = 130 } + 5 -> playerl(FacialExpression.NEUTRAL, "Ok, thanks.").also { stage = END_DIALOGUE } + } + + //What things are needed? + 100 -> npcl(FacialExpression.NEUTRAL, "The three things are: 'Something with faith', 'something from nature' and 'something of the spirit-to-become freely given'.").also { stage++ } + 101 -> playerl(FacialExpression.FRIENDLY, " Ok, and 'something from nature' is the mushroom from the bloom spell you gave me?").also { stage++ } + 102 -> npcl(FacialExpression.FRIENDLY, "Yes, that's correct, that seems right to me. The other things we need are 'something with faith' and 'something of the spirit-to-become freely given.").also { stage++ } + 103 -> playerl(FacialExpression.NEUTRAL, "Do you have any ideas what those things are?").also { stage++ } + 104 -> npcl(FacialExpression.HALF_GUILTY, "I'm sorry my friend, but I do not.").also { stage = 90 } + + //What should I do when I have them? + 110 -> npcl(FacialExpression.NEUTRAL, "It says,.. 'to arrange upon three rocks around the spirit-to-become...'. Then I must cast a spell. As you can see, I've already placed the rocks.").also { stage++ } + 111 -> playerl(FacialExpression.NEUTRAL, "Can we just place the components on any rock?").also { stage++ } + 112 -> npcl(FacialExpression.NEUTRAL, "Well, the only thing the journal says is that 'something with faith stands south of the spirit-to-become', but I'm so confused now I don't really know what that means.").also { stage = 90 } + + //I think I've solved the puzzle! + 120 -> npcl(FacialExpression.NEUTRAL, "Oh really.. Have you placed all the items on the stones? Ok, well, let's try!").also { stage++ } + 121 -> sendDialogue("~ The druid attempts to cast a spell. ~").also { stage++ } + 122 -> { + ContentAPI.animate(npc, 812) + if(NSUtils.hasPlacedCard(player) && NSUtils.hasPlacedFungus(player) && NSUtils.onStone(player)){ + end() + player.lock() + val locations = arrayOf(Location.create(3439, 3336, 0), Location.create(3441, 3336, 0), Location.create(3440, 3335, 0)) + repeat(3) {i -> ContentAPI.spawnProjectile(locations[i], Location.create(3440, 3336, 0), 268, 0, 35, 0, 100, 20) } + ContentAPI.submitIndividualPulse(player, object : Pulse(4){ + override fun pulse(): Boolean { + ContentAPI.sendNPCDialogue(player, npc.originalId, "Aha, everything seems to be in place! You can come through now into the grotto for the final section of my transformation.") + setQuest(55) + ContentAPI.unlock(player) + return true + } + + override fun stop() { + ContentAPI.visualize(npc, -1, Graphics(266, 80)) + super.stop() + } + }) + } else { + npcl(FacialExpression.NEUTRAL, "Hmm, something still doesn't seem right. I think we need something more before we can continue.") + } + + stage = END_DIALOGUE + } + + 130 -> if(ContentAPI.inInventory(player, Items.DRUIDIC_SPELL_2968) || ContentAPI.inBank(player, Items.DRUIDIC_SPELL_2968)){ + npcl(FacialExpression.NEUTRAL, "No, you've already got one!").also { stage = END_DIALOGUE } + } else { + npcl(FacialExpression.NEUTRAL, "Sure, but look after this one.") + ContentAPI.addItem(player, Items.DRUIDIC_SPELL_2968) + stage = END_DIALOGUE + } + + //Initial dialogue + 1000 -> playerl(FacialExpression.HALF_ASKING, "Hello?").also { stage++ } + 1001 -> if(ContentAPI.inEquipment(player, Items.GHOSTSPEAK_AMULET_552)){ + npcl(FacialExpression.EXTREMELY_SHOCKED, "Oh, I understand you! At last, someone who doesn't just mumble. I understand what you're saying!").also { stage++ } + } else npcl(FacialExpression.HALF_GUILTY, "OooOOoOOoOOOOo.") + + 1002 -> options("I'm wearing an amulet of ghost speak!","How long have you been a ghost?", "What's it like being a ghost?", "Ok, thanks.").also { stage++ } + 1003 -> when(buttonId){ + 1 -> playerl(FacialExpression.NEUTRAL, "I'm wearing an amulet of ghost speak!").also { stage = 1010; setQuest(20) } + 2 -> playerl(FacialExpression.NEUTRAL, "How long have you been a ghost?").also { stage = 1020; setQuest(20) } + 3 -> playerl(FacialExpression.NEUTRAL, "What's it like being a ghost?").also { stage = 1030; setQuest(20) } + 4 -> playerl(FacialExpression.NEUTRAL, "Ok, thanks.").also { stage = END_DIALOGUE } + } + + 1010 -> npcl(FacialExpression.HALF_GUILTY, "Why you poor fellow, have you passed away and you want to send a message back to a loved one?").also { stage++ } + 1011 -> playerl(FacialExpression.HALF_THINKING, "Err.. Not exactly...").also { stage++ } + 1012 -> npcl(FacialExpression.HALF_GUILTY, "You have come to haunt my dreams until I pass on your message to a dearly loved one. I understand. Pray, tell me who would you like me to pass a message on to?").also { stage++ } + 1013 -> playerl(FacialExpression.NEUTRAL, "Ermm, you don't understand... It's just that...").also { stage++ } + 1014 -> npcl(FacialExpression.HALF_GUILTY, "Yes!").also { stage++ } + 1015 -> playerl(FacialExpression.NEUTRAL, "Well please don't be upset or anything... But you're the ghost!").also { stage++ } + 1016 -> npcl(FacialExpression.HALF_GUILTY, "Don't be silly now! That in no way reflects the truth!").also { stage = 1002 } + + 1020 -> npcl(FacialExpression.HALF_GUILTY, "What?! Don't be preposterous! I'm not a ghost! How could you say something like that?").also { stage++ } + 1021 -> playerl(FacialExpression.NEUTRAL, "But it's true, you're a ghost... well, at least that is to say, you're sort of not alive anymore.").also { stage++ } + 1022 -> npcl(FacialExpression.HALF_GUILTY, "Don't be silly, I can see you. I can see that tree. If I were dead, I wouldn't be able to see anything. What you say just doesn't reflect the truth. You'll have to try harder to put one over on me!").also { stage = 1002 } + + 1030 -> npcl(FacialExpression.HALF_GUILTY, "Oh, it's quite.... Oh... Trying to catch me out were you! Anyone can clearly see that I am not a ghost!").also { stage++ } + 1031 -> playerl(FacialExpression.NEUTRAL, "But you are a ghost, look at yourself! I can see straight through you! You're as dead as this swamp! Err... No offence or anything...").also { stage++ } + 1032 -> npcl(FacialExpression.HALF_GUILTY, "No I won't take offence because I'm not dead and I'm afraid you'll have to come up with some pretty conclusive proof before I believe it. What a strange dream this is.").also { stage = 1002 } + + + } + return true + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.FILLIMAN_TARLOCK_1050) + } + + fun setQuest(stage: Int){ + player.questRepository.getQuest("Nature Spirit").setStage(player, stage) + } + +} \ No newline at end of file diff --git a/Server/src/main/kotlin/rs09/game/content/quest/members/naturespirit/NSUtils.kt b/Server/src/main/kotlin/rs09/game/content/quest/members/naturespirit/NSUtils.kt new file mode 100644 index 000000000..0f07c86e2 --- /dev/null +++ b/Server/src/main/kotlin/rs09/game/content/quest/members/naturespirit/NSUtils.kt @@ -0,0 +1,125 @@ +package rs09.game.content.quest.members.naturespirit + +import api.Container +import api.ContentAPI +import core.game.node.Node +import core.game.node.`object`.Scenery +import core.game.node.`object`.SceneryBuilder +import core.game.node.entity.npc.NPC +import core.game.node.entity.player.Player +import core.game.system.task.Pulse +import core.game.world.map.Location +import core.game.world.map.RegionManager +import core.game.world.map.RegionManager.forId +import core.game.world.map.RegionManager.lock +import core.game.world.update.flag.context.Graphics +import core.tools.RandomFunction +import org.rs09.consts.Items +import rs09.game.node.entity.npc.other.MortMyreGhastNPC + +object NSUtils { + fun flagFungusPlaced(player: Player) { + ContentAPI.setAttribute(player, "/save:ns:fungus_placed", true) + } + + fun flagCardPlaced(player: Player){ + ContentAPI.setAttribute(player, "/save:ns:card_placed", true) + } + + fun hasPlacedFungus(player: Player): Boolean { + return ContentAPI.getAttribute(player, "ns:fungus_placed", false) + } + + fun hasPlacedCard(player: Player): Boolean { + return ContentAPI.getAttribute(player, "ns:card_placed", false) + } + + fun onStone(player: Player): Boolean { + return player.location.equals(3440, 3335, 0) + } + + fun getGhastKC(player: Player): Int { + return ContentAPI.getAttribute(player,"ns:ghasts_killed", 0) as Int + } + + fun incrementGhastKC(player: Player){ + ContentAPI.setAttribute(player, "/save:ns:ghasts_killed", getGhastKC(player) + 1) + val msg = when(getGhastKC(player)) { + 1 -> "That's one down, two more to go." + 2 -> "Two down, only one more to go." + 3 -> "That's it! I've killed all 3 Ghasts!" + else -> "" + } + + if(!msg.isEmpty()){ + ContentAPI.sendMessage(player, msg) + } + } + + fun activatePouch(player: Player, attacker: MortMyreGhastNPC): Boolean { + var shouldAddEmptyPouch = false + val pouchAmt = ContentAPI.amountInInventory(player, Items.DRUID_POUCH_2958) + if(pouchAmt == 1) shouldAddEmptyPouch = true + if(pouchAmt > 0 && ContentAPI.removeItem(player, Items.DRUID_POUCH_2958, Container.INVENTORY)){ + if(shouldAddEmptyPouch){ + ContentAPI.addItem(player, Items.DRUID_POUCH_2957) + } + ContentAPI.spawnProjectile(player, attacker, 268) + ContentAPI.submitWorldPulse(object : Pulse(){ + var ticks = 0 + override fun pulse(): Boolean { + when(ticks++){ + 2 -> ContentAPI.visualize(attacker, -1, Graphics(269, 125)) + 3 -> attacker.transform(attacker.id + 1).also { attacker.attack(player); attacker.setAttribute("woke", ContentAPI.getWorldTicks()); return true } + } + return false + } + }) + return true + } + return false + } + + fun cleanupAttributes(player: Player){ + player.removeAttribute("ns:fungus_placed") + player.removeAttribute("ns:card_placed") + } + + @JvmStatic + fun castBloom(player: Player): Boolean{ + var success = false + val region = forId(player.location.regionId) + if (player.skills.prayerPoints < 1) { + player.packetDispatch.sendMessage("You don't have enough prayer points to do this.") + return false + } + handleVisuals(player) + val locs = player.location.surroundingTiles + for (o in locs) { + val obj = RegionManager.getObject(o) + if (obj != null) { + if (obj.name.equals("Rotting log", ignoreCase = true) && player.skills.prayerPoints >= 1) { + if (player.location.withinDistance(obj.location, 2)) { + SceneryBuilder.replace(obj, obj.transform(3509)) + success = true + } + } + } + } + return success + } + + /** + * Handles the draining of prayer points and physical graphics and + * animation. + */ + private fun handleVisuals(player: Player) { + player.skills.decrementPrayerPoints(RandomFunction.random(1, 3).toDouble()) + val AROUND_YOU = player.location.surroundingTiles + for (location in AROUND_YOU) { + // The graphic is meant to play on a 3x3 radius around you, but not + // including the tile you are on. + player.packetDispatch.sendGlobalPositionGraphic(263, location) + } + } +} \ No newline at end of file diff --git a/Server/src/main/kotlin/rs09/game/content/quest/members/naturespirit/NatureSpiritDialogue.kt b/Server/src/main/kotlin/rs09/game/content/quest/members/naturespirit/NatureSpiritDialogue.kt new file mode 100644 index 000000000..052b699d5 --- /dev/null +++ b/Server/src/main/kotlin/rs09/game/content/quest/members/naturespirit/NatureSpiritDialogue.kt @@ -0,0 +1,164 @@ +package rs09.game.content.quest.members.naturespirit + +import api.Container +import api.ContentAPI +import core.game.content.dialogue.DialoguePlugin +import core.game.content.dialogue.FacialExpression +import core.game.node.entity.npc.NPC +import core.game.node.entity.player.Player +import core.game.system.task.Pulse +import core.game.world.map.Location +import core.plugin.Initializable +import org.rs09.consts.Items +import org.rs09.consts.NPCs +import rs09.game.content.dialogue.DialogueFile +import rs09.tools.END_DIALOGUE + +@Initializable +class NatureSpiritDialogue(player: Player? = null) : DialoguePlugin(player){ + + val questStage = player?.questRepository?.getStage("Nature Spirit") ?: 0 + override fun newInstance(player: Player?): DialoguePlugin { + return NatureSpiritDialogue(player) + } + + override fun open(vararg args: Any?): Boolean { + npc = args[0] as NPC + + when(questStage){ + 60 -> npcl(FacialExpression.NEUTRAL, "Hmm, good, the transformation is complete. Now, my friend, in return for your assistance, I will help you to kill the Ghasts. First bring to me a silver sickle so that I can bless it for you.").also { return true } + 65 -> npcl(FacialExpression.NEUTRAL, "Have you brought me a silver sickle?").also { stage = 100; return true } + 70 -> npcl(FacialExpression.NEUTRAL, "Now you can go forth and make the swamp bloom. Collect nature's bounty to fill a druids pouch. So armed will the Ghasts be bound to you until, you flee or they are defeated.").also { stage = 200 } + 75 -> npcl(FacialExpression.NEUTRAL, "Hello again, my friend. Have you defeated three ghasts as I asked you?").also { stage = 300 } + else -> npcl(FacialExpression.FRIENDLY, "Welcome to my grotto, friend. Enjoy your visit.").also { stage = END_DIALOGUE } + } + return true + } + + override fun handle(componentID: Int, buttonID: Int): Boolean { + when(stage){ + 0 -> playerl(FacialExpression.NEUTRAL,"A silver sickle? What's that?").also { stage++ } + 1 -> npcl(FacialExpression.NEUTRAL, "The sickle is the symbol and weapon of the Druid, you need to construct one of silver so that I can bless it, with its powers you will be able to defeat the Ghasts of Mort Myre.").also { stage++; setQuest(65) } + 2 -> options("Where would I get a silver sickle?", "What will you do to the silver sickle?", "How can a blessed sickle help me to defeat the Ghasts?", "Ok, thanks.").also { stage++ } + 3 -> when(buttonID){ + 1 -> playerl(FacialExpression.NEUTRAL, "Where would I get a silver sickle?").also { stage = 10 } + 2 -> playerl(FacialExpression.NEUTRAL, "What will you do to the silver sickle?").also { stage = 20 } + 3 -> playerl(FacialExpression.NEUTRAL, "How can a blessed sickle help me to defeat the Ghasts?").also { stage = 30 } + 4 -> playerl(FacialExpression.NEUTRAL, "Ok, thanks.").also { stage = END_DIALOGUE } + } + + //where sickle + 10 -> npcl(FacialExpression.NEUTRAL, "You could make one yourself if you're artisan enough. I've heard of a distant sandy place where you can buy the mould that you require, it's similar in many respects to the creating of a holy symbol.").also { stage = 2 } + + //What you gonna do to my sickle bro + 20 -> npcl(FacialExpression.NEUTRAL, "Why, I will give it my blessings so that the very swamp in which you stand will blossom and bloom!").also { stage = 2 } //pompous git + + //bruh how does a silver sickle help me tho + 30 -> npcl(FacialExpression.NEUTRAL, "My blessings will entice nature to bloom in Mort Myre! And then with nature's harvest you can fill a druids pouch and release the Ghasts from their torment.").also { stage = 2 } //this dude kinda weird + + //have you brought me a sickle yet bro + 100 -> if(ContentAPI.inInventory(player, Items.SILVER_SICKLE_2961)){ + playerl(FacialExpression.FRIENDLY, "Yes, here it is. What are you going to do with it?").also { stage = 110 } + } else { + playerl(FacialExpression.NEUTRAL, "No sorry, not yet!").also { stage++ } + } + 101 -> npcl(FacialExpression.NEUTRAL, "Well, come to me when you have it.").also { stage = 2 } + + /** + * This dialogue drags on so much man this quest has been like 95% dialogue. + * Nature Spirit dude also talks like an uppity self-righteous deity looking dude + */ + + //yeah bro I got it + 110 -> npcl(FacialExpression.NEUTRAL, "My friend, I will bless it for you and you will then be able to accomplish great things. Now then, I must cast the enchantment. You can bless a new sickle by dipping it in the holy water of the grotto.").also { stage++ } + 111 -> sendDialogue("- The Nature Spirit casts a spell on the player. -").also { stage++ } + + /** + * Here we go uoooh + */ + 112 -> end().also { ContentAPI.lock(player, 10); ContentAPI.submitWorldPulse(SickleBlessPulse(player, npc)) } + + //go kill some ghasts bro + 200 -> npcl(FacialExpression.NEUTRAL, "Go forth into Mort Myre and slay three Ghasts. You'll be releasing their souls from Mort Myre.").also { stage++ } + 201 -> ContentAPI.sendItemDialogue(player, Items.DRUID_POUCH_2957, "The nature spirit gives you an empty pouch.").also { stage++; setQuest(75) } + 202 -> npcl(FacialExpression.NEUTRAL, "You'll need this in order to collect together nature's bounty. It will bind the Ghast to you until you flee or it is defeated.").also { stage = END_DIALOGUE } + + //Have you killed the ghasts yet bro + 300 -> if(NSUtils.getGhastKC(player) >= 3){ + playerl(FacialExpression.NEUTRAL, "Yes, I've killed all three and their spirits have been released!").also { stage = 350 } + } else { + playerl(FacialExpression.NEUTRAL, "Not yet.").also { stage++ } + } + + //nah bro + 301 -> npcl(FacialExpression.NEUTRAL, "Well, when you do, please come to me and I'll reward you!").also { stage++ } + 302 -> options("How do I get to attack the Ghasts?", "What's this pouch for?", "What can I do with this sickle?", "I've lost my sickle.", "Ok, thanks.").also { stage++ } + 303 -> when(buttonID){ + 1 -> playerl(FacialExpression.NEUTRAL, "How do I get to attack the Ghasts?").also { stage = 310 } + 2 -> playerl(FacialExpression.NEUTRAL, "What's this pouch for?").also { stage = 320 } + 3 -> playerl(FacialExpression.NEUTRAL, "What can I do with this sickle?").also { stage = 330 } + 4 -> playerl(FacialExpression.NEUTRAL, "I've lost my sickle.").also { stage = 340 } + 5 -> playerl(FacialExpression.NEUTRAL, "Ok, thanks.").also { stage = END_DIALOGUE } + } + + //How do I attack duh ghosty bois + 310 -> npcl(FacialExpression.NEUTRAL, "Go forth and with the sickle make the swamp bloom. Collect natures bounty to fill a druids pouch. So armed will the Ghasts be bound to you until, you flee or they are defeated.").also { stage = 302 } + + //What's dis funny pouch for? + 320 -> npcl(FacialExpression.NEUTRAL, "It is for collecting natures bounty, once it contains the blossomed items of the swamp, it will make the Ghasts appear and you can then attack them.").also { stage = 302 } + + //What can I do wif da sickle m8 + 330 -> npcl(FacialExpression.NEUTRAL, "You may use it wisely within the area of Mort Myre to affect natures balance and bring forth a bounty of natures harvest. Once collected into the druid pouch will the Ghast be apparent.").also { stage = 302 } + + //oi I lost it bruv + 340 -> npcl(FacialExpression.NEUTRAL, "If you should lose the blessed sickle, simply bring another to my altar of nature and refresh it in the grotto waters.").also { stage = 302 } + + //killed all dem buggers bruv + 350 -> npcl(FacialExpression.NEUTRAL, "Many thanks my friend, you have completed your quest!").also { stage++ } + 351 -> end().also { player.questRepository.getQuest("Nature Spirit").finish(player) } + } + + return true + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.NATURE_SPIRIT_1051) + } + + /** + * Needs to spawn 4ish of those green projectiles and the player needs to lift up the sickle + * then needs to open the dialogue back up with the quest stage at 70 + */ + internal class SickleBlessPulse(val player: Player, val spirit: NPC) : Pulse() { + var ticks = 0 + val locs: MutableList = player.location.surroundingTiles + + override fun pulse(): Boolean { + when(ticks++){ + 0 -> ContentAPI.animate(spirit, 812) + 1 -> repeat(4) { + val loc = locs.random() + locs.remove(loc) + + ContentAPI.spawnProjectile(loc, player.location, 268, 0, 400, 0, 125, 180) + ContentAPI.animate(player, 9021) + } + 4 -> { + if(ContentAPI.removeItem(player, Items.SILVER_SICKLE_2961, Container.INVENTORY)){ + ContentAPI.addItem(player, Items.SILVER_SICKLEB_2963) + ContentAPI.unlock(player) + player.questRepository.getQuest("Nature Spirit").setStage(player, 70) + ContentAPI.openDialogue(player, NPCs.NATURE_SPIRIT_1051, ContentAPI.findLocalNPC(player, NPCs.NATURE_SPIRIT_1051) as NPC) + ContentAPI.sendMessage(player, "Your sickle has been blessed! You can bless a new sickle by dipping it into the grotto waters.") + } + } + 6 -> return true + } + return false + } + } + + fun setQuest(stage: Int){ + player!!.questRepository.getQuest("Nature Spirit").setStage(player!!, stage) + } +} \ No newline at end of file diff --git a/Server/src/main/kotlin/rs09/game/content/quest/members/naturespirit/NatureSpiritQuest.kt b/Server/src/main/kotlin/rs09/game/content/quest/members/naturespirit/NatureSpiritQuest.kt new file mode 100644 index 000000000..05ac35cbc --- /dev/null +++ b/Server/src/main/kotlin/rs09/game/content/quest/members/naturespirit/NatureSpiritQuest.kt @@ -0,0 +1,122 @@ +package rs09.game.content.quest.members.naturespirit + +import api.ContentAPI +import core.game.node.entity.player.Player +import core.game.node.entity.player.link.quest.Quest +import core.game.node.entity.skill.Skills +import core.plugin.Initializable +import org.rs09.consts.Items + +@Initializable +class NatureSpiritQuest : Quest("Nature Spirit", 95, 94, 2, 307, 0, 1, 110 ) { + override fun newInstance(`object`: Any?): Quest { + return this + } + + override fun drawJournal(player: Player?, stage: Int) { + super.drawJournal(player, stage) + player ?: return + var line = 11 + if(stage == 0){ + line(player, "I can start this quest by speaking to !!Drezel?? in the !!temple of Saradomin??.", line++) + } else { + if(stage >= 10){ + line(player, "After talking to Drezel in the temple of Saradomin I've",line++, true) + line(player,"agreed to look for a Druid called Filliman Tarlock.", line++, true) + } + + if(stage == 10){ + line(player, "I need to look for !!Filliman Tarlock?? in the !!Swamps?? of Mort",line++) + line(player, "Myre. I should be wary of !!Ghasts??.", line++) + } + + if(stage == 15){ + line(player, "I located a !!spirit?? in the swamp. I believe he's", line++, false) + line(player, "!!Filliman Tarlock?? but I can't understand him.",line++, false) + } + + if(stage == 20){ + line(player, "I located !!Filliman Tarlock?? in the swamp. I believe he's",line++) + line(player, "dead but he doesn't believe me. I need to convince him.", line++) + } + + if(stage >= 25){ + line(player, "I located Filliman Tarlock in the swamp and managed to",line++,true) + line(player, "convince him that he is in fact a ghost. ", line++, true) + } + + if(stage == 25){ + line(player, "Filliman needs his !!journal?? to figure out what to do",line++) + line(player, "next. He mentioned something about a !!knot??.", line++) + } + + if(stage >= 30){ + line(player, "I recovered Filliman's journal for him.", line++, true) + } + + if(stage == 30) { + line(player, "I should speak to !!Filliman Tarlock?? to see what I can",line++) + line(player, "do to help.", line++) + } + + if(stage >= 40){ + line(player, "I've gone and gotten blessed by Drezel.", line++, true) + } + + if(stage >= 35) { + line(player, "I've agreed to help Filliman become a Nature Spirit.",line++, true) + } + + if(stage == 35){ + line(player, "The first thing Filliman needs me to do is go and get",line++) + line(player, "blessed by !!Drezel?? in the temple of Saradomin.",line++) + } + + if (stage == 40){ + line(player, "I should return to !!Filliman?? to see what I need to do.", line++, false) + } + + if(stage in 45 until 55){ + line(player, "In order to help Filliman I need to find 3 things:", line++, false) + line(player, "Something of !!Faith??.",line++, false) + line(player, "Something of !!Nature??.", line++, stage >= 50) + line(player, "Something of the !!spirit-to-be freely given??.", line++, false) + } + + if(stage == 50){ + line(player, "I know for a fact the fungus is !!something of Nature??.", line++, false) + } + + if(stage >= 55){ + line(player, "I've helped Filliman complete the spell.", line++, true) + } + + if(stage == 55){ + line(player, "Filliman has asked me to meet him back inside the !!grotto??.", line++, false) + } + + if(stage == 75){ + line(player, "I need to go and kill !!3 Ghasts?? for Filliman.", line++, false) + } + + if(stage >= 100){ + line(player,"!!QUEST COMPLETE!??",line++) + } + } + } + + override fun finish(player: Player?) { + super.finish(player) + player ?: return + var ln = 10 + player.packetDispatch.sendItemZoomOnInterface(Items.SILVER_SICKLEB_2963,230,277,5) + drawReward(player, "2 Quest Points", ln++) + drawReward(player, "3,000 Crafting XP",ln++) + drawReward(player, "2,000 Hitpoints XP", ln++) + drawReward(player, "2,000 Defence XP", ln++) + ContentAPI.rewardXP(player, Skills.CRAFTING, 3000.0) + ContentAPI.rewardXP(player, Skills.HITPOINTS, 2000.0) + ContentAPI.rewardXP(player, Skills.DEFENCE, 2000.0) + NSUtils.cleanupAttributes(player) + } +} diff --git a/Server/src/main/kotlin/rs09/game/interaction/InteractionListener.kt b/Server/src/main/kotlin/rs09/game/interaction/InteractionListener.kt index 7526cc706..6f93c9334 100644 --- a/Server/src/main/kotlin/rs09/game/interaction/InteractionListener.kt +++ b/Server/src/main/kotlin/rs09/game/interaction/InteractionListener.kt @@ -9,6 +9,7 @@ abstract class InteractionListener : Listener{ val ITEM = 0 val SCENERY = 1 val NPC = 2 + val GROUNDITEM = 3 } fun on(id: Int, type: Int, vararg option: String,handler: (player: Player, node: Node) -> Boolean){ InteractionListeners.add(id,type,option,handler) diff --git a/Server/src/main/kotlin/rs09/game/interaction/InteractionListeners.kt b/Server/src/main/kotlin/rs09/game/interaction/InteractionListeners.kt index 415b2b56c..65fdcac52 100644 --- a/Server/src/main/kotlin/rs09/game/interaction/InteractionListeners.kt +++ b/Server/src/main/kotlin/rs09/game/interaction/InteractionListeners.kt @@ -170,6 +170,7 @@ object InteractionListeners { @JvmStatic fun run(id: Int, type: Int, option: String, player: Player, node: Node): Boolean{ val flag = when(type){ + 3 -> DestinationFlag.ITEM 2 -> DestinationFlag.ENTITY 1 -> DestinationFlag.OBJECT else -> DestinationFlag.OBJECT diff --git a/Server/src/main/kotlin/rs09/game/interaction/region/MorytaniaListeners.kt b/Server/src/main/kotlin/rs09/game/interaction/region/MorytaniaListeners.kt index 8d301b34e..3547a1ed3 100644 --- a/Server/src/main/kotlin/rs09/game/interaction/region/MorytaniaListeners.kt +++ b/Server/src/main/kotlin/rs09/game/interaction/region/MorytaniaListeners.kt @@ -15,7 +15,7 @@ import rs09.game.interaction.InteractionListener class MorytaniaListeners : InteractionListener() { val GROTTO_ENTRANCE = 3516 - val GROTTO_EXIT = 3526 + val GROTTO_EXIT = intArrayOf(3525, 3526) val GROTTO_BRIDGE = 3522 val outside = Location.create(3439, 3337, 0) val inside = Location.create(3442, 9734, 1) @@ -23,10 +23,10 @@ class MorytaniaListeners : InteractionListener() { private val JUMP_ANIM = Animation(1603) override fun defineListeners() { - on(GROTTO_ENTRANCE,SCENERY,"enter"){ player, node -> +/* on(GROTTO_ENTRANCE,SCENERY,"enter"){ player, node -> player.teleport(inside) return@on true - } + }*/ on(GROTTO_EXIT,SCENERY,"exit"){ player, node -> player.teleport(outside) diff --git a/Server/src/main/kotlin/rs09/game/node/entity/npc/other/MortMyreGhastNPC.kt b/Server/src/main/kotlin/rs09/game/node/entity/npc/other/MortMyreGhastNPC.kt new file mode 100644 index 000000000..5aa192d43 --- /dev/null +++ b/Server/src/main/kotlin/rs09/game/node/entity/npc/other/MortMyreGhastNPC.kt @@ -0,0 +1,104 @@ +package rs09.game.node.entity.npc.other + +import api.Container +import api.ContentAPI +import core.game.content.consumable.Consumables +import core.game.content.consumable.Food +import core.game.interaction.MovementPulse +import core.game.node.entity.Entity +import core.game.node.entity.combat.ImpactHandler +import core.game.node.entity.npc.AbstractNPC +import core.game.node.entity.npc.NPC +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.world.map.Location +import core.game.world.map.RegionManager +import core.game.world.update.flag.context.Animation +import core.plugin.Initializable +import core.tools.RandomFunction +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import org.rs09.consts.Items +import org.rs09.consts.NPCs +import rs09.game.content.quest.members.naturespirit.NSUtils + +@Initializable +class MortMyreGhastNPC : AbstractNPC { + //Constructor spaghetti because Arios I guess + constructor() : super(NPCs.GHAST_1052, null, true) {} + private constructor(id: Int, location: Location) : super(id, location) {} + + override fun construct(id: Int, location: Location, vararg objects: Any?): AbstractNPC { + isAggressive = id != ids[0] + return MortMyreGhastNPC(id, location) + } + + override fun handleTickActions() { + super.handleTickActions() + if(id == ids[0] && RandomFunction.roll(35)){ + val players = RegionManager.getLocalPlayers(this, 5).filter { !it.inCombat() } + if(players.isNotEmpty()){ + val player = players.random() + ContentAPI.submitIndividualPulse(this, object : MovementPulse(this, player){ + override fun pulse(): Boolean { + animate(Animation(1093)) + attemptLifeSiphon(player) + return true + } + }) + } + } else { + val ticksTransformed = ContentAPI.getWorldTicks() - ContentAPI.getAttribute(this, "woke", 0) + if(!inCombat() && ticksTransformed > 10){ + reTransform() + } + } + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.GHAST_1052, NPCs.GHAST_1053) + } + + fun attemptLifeSiphon(player: Player){ + var hasFood = false + + if(NSUtils.activatePouch(player, this)) return + + GlobalScope.launch { + for(i in player.inventory.toArray()){ + if(i == null) continue + val consumable = Consumables.getConsumableById(i.id) + if(consumable != null && consumable is Food) { + hasFood = true + ContentAPI.removeItem(player, i, Container.INVENTORY) + ContentAPI.addItem(player, Items.ROTTEN_FOOD_2959) + ContentAPI.sendMessage(player, "You feel something attacking your backpack, and smell a terrible stench.") + break + } + } + + if(!hasFood && RandomFunction.roll(3)) { + ContentAPI.sendMessage(player, "An attacking Ghast just misses you.") + } else if(!hasFood){ + ContentAPI.impact(player, RandomFunction.random(3,6), ImpactHandler.HitsplatType.NORMAL) + ContentAPI.sendMessage(player, "A supernatural force draws energy from you.") + } + } + } + + override fun commenceDeath(killer: Entity?) { + super.commenceDeath(killer) + } + + override fun finalizeDeath(killer: Entity?) { + super.finalizeDeath(killer) + if(id == ids[1]) { + reTransform() + if(killer is Player){ + NSUtils.incrementGhastKC(killer) + ContentAPI.rewardXP(killer, Skills.PRAYER, 30.0) + removeAttribute("woke") + } + } + } +} \ No newline at end of file diff --git a/Server/src/main/kotlin/rs09/game/system/config/NPCConfigParser.kt b/Server/src/main/kotlin/rs09/game/system/config/NPCConfigParser.kt index 88e5ece5c..7200d3c7d 100644 --- a/Server/src/main/kotlin/rs09/game/system/config/NPCConfigParser.kt +++ b/Server/src/main/kotlin/rs09/game/system/config/NPCConfigParser.kt @@ -245,6 +245,7 @@ NPCConfigParser { "prj_height", "end_height", "spell_id", + "death_gfx", "magic_level" -> configs.put(it.key.toString(), if (it.value.toString().isEmpty()) Unit else it.value.toString().toInt()) //doubles diff --git a/Server/src/main/kotlin/rs09/tools/stringtools/StringTools.kt b/Server/src/main/kotlin/rs09/tools/Globals.kt similarity index 100% rename from Server/src/main/kotlin/rs09/tools/stringtools/StringTools.kt rename to Server/src/main/kotlin/rs09/tools/Globals.kt