From 5c4134e2e3d7d14c6a7101807dce948381d84d38 Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Sat, 17 Sep 2022 04:20:50 +0000 Subject: [PATCH] Implemented The Golem quest Added new DialogueBuilder system Fixed bug where examinable scenery with no left-click options were null from RegionManager's perspective --- Server/data/configs/ground_spawns.json | 4 + Server/data/configs/music_configs.json | 8 + Server/data/configs/npc_spawns.json | 9 + .../cache/def/impl/SceneryDefinition.java | 27 +- .../core/game/content/dialogue/book/Book.java | 3 + .../CuratorHaigHalenDialogue.java | 100 +--- .../CuratorHaigHalenSOADialogue.java | 93 ++++ .../game/interaction/item/BookreadOption.java | 3 +- .../node/entity/player/link/quest/Quest.java | 3 + .../player/link/quest/QuestRepository.java | 1 + .../game/world/map/build/LandscapeParser.java | 6 +- .../core/net/packet/in/ItemActionPacket.java | 26 +- .../game/content/dialogue/DialogueBuilder.kt | 149 ++++++ .../members/thegolem/TheGolemDialogue.kt | 445 +++++++++++++++++ .../quest/members/thegolem/TheGolemQuest.kt | 453 ++++++++++++++++++ .../members/thelosttribe/PickaxeOnRubble.kt | 6 +- .../game/interaction/InteractionListeners.kt | 1 + 17 files changed, 1220 insertions(+), 117 deletions(-) create mode 100644 Server/src/main/java/core/game/content/quest/free/shieldofarrav/CuratorHaigHalenSOADialogue.java create mode 100644 Server/src/main/kotlin/rs09/game/content/dialogue/DialogueBuilder.kt create mode 100644 Server/src/main/kotlin/rs09/game/content/quest/members/thegolem/TheGolemDialogue.kt create mode 100644 Server/src/main/kotlin/rs09/game/content/quest/members/thegolem/TheGolemQuest.kt diff --git a/Server/data/configs/ground_spawns.json b/Server/data/configs/ground_spawns.json index b895671ad..f06349de7 100644 --- a/Server/data/configs/ground_spawns.json +++ b/Server/data/configs/ground_spawns.json @@ -602,5 +602,9 @@ { "item_id": "2128", "loc_data": "{1,2648,2963,0,327690}-{1,2647,2956,0,327690}-{1,2638,2949,0,327690}" + }, + { + "item_id": "4615", + "loc_data": "{1,3479,3092,0,0}" } ] diff --git a/Server/data/configs/music_configs.json b/Server/data/configs/music_configs.json index 9b8f98b8b..1fb75f150 100644 --- a/Server/data/configs/music_configs.json +++ b/Server/data/configs/music_configs.json @@ -1540,5 +1540,13 @@ { "id": "578", "borders": "" + }, + { + "id": "378", + "borders": "{2688,4864,2752,4928}" + }, + { + "id": "379", + "borders": "{3520,4928,3584,4992}" } ] diff --git a/Server/data/configs/npc_spawns.json b/Server/data/configs/npc_spawns.json index c4189059b..73521b0b6 100644 --- a/Server/data/configs/npc_spawns.json +++ b/Server/data/configs/npc_spawns.json @@ -11038,5 +11038,14 @@ { "npc_id": "1012", "loc_data": "{2650,9393,0,1,0}" + }, + { + "npc_id": "1907", + "loc_data": "{3488,3091,0,1,0}" + }, + { + "npc_id": "1911", + "loc_data": "{3416,3155,0,1,0}" } + ] diff --git a/Server/src/main/java/core/cache/def/impl/SceneryDefinition.java b/Server/src/main/java/core/cache/def/impl/SceneryDefinition.java index 7e7aa41cf..26f05fd90 100644 --- a/Server/src/main/java/core/cache/def/impl/SceneryDefinition.java +++ b/Server/src/main/java/core/cache/def/impl/SceneryDefinition.java @@ -312,7 +312,7 @@ public class SceneryDefinition extends Definition { /** * The second integer. */ - public int secondInt; + public int interactable; /** * A unknown boolean. @@ -493,7 +493,7 @@ public class SceneryDefinition extends Definition { aByte3914 = (byte) 0; anInt3915 = 0; anInt3900 = 0; - secondInt = -1; + interactable = -1; aBoolean3894 = false; aByte3912 = (byte) 0; anInt3921 = 0; @@ -648,7 +648,7 @@ public class SceneryDefinition extends Definition { } else if (opcode == 18) { def.projectileClipped = false; } else if (opcode == 19) { - def.secondInt = buffer.get() & 0xFF; + def.interactable = buffer.get() & 0xFF; } else if (opcode == 21) { def.aByte3912 = (byte) 1; } else if (opcode == 22) { @@ -818,14 +818,14 @@ public class SceneryDefinition extends Definition { * Configures the object definitions. */ final void configureObject() { - if (secondInt == -1) { - secondInt = 0; + if (interactable == -1) { + interactable = 0; if (modelIds != null && (getModelConfiguration() == null || getModelConfiguration()[0] == 10)) { - secondInt = 1; + interactable = 1; } for (int i = 0; i < 5; i++) { if (options[i] != null) { - secondInt = 1; + interactable = 1; break; } } @@ -853,6 +853,9 @@ public class SceneryDefinition extends Definition { * @return {@code True} if so. */ public boolean hasActions() { + if(interactable > 0) { + return true; + } if (childrenIds == null) { return hasOptions(false); } @@ -1360,11 +1363,11 @@ public class SceneryDefinition extends Definition { } /** - * Get the secondInt. - * @return the secondInt + * Get the interactable. + * @return the interactable */ - public int getSecondInt() { - return secondInt; + public int getInteractable() { + return interactable; } /** @@ -1678,4 +1681,4 @@ public class SceneryDefinition extends Definition { public static int getContainerId(int id) { return id >>> 8; } -} \ No newline at end of file +} diff --git a/Server/src/main/java/core/game/content/dialogue/book/Book.java b/Server/src/main/java/core/game/content/dialogue/book/Book.java index 561284a40..c78ce364e 100644 --- a/Server/src/main/java/core/game/content/dialogue/book/Book.java +++ b/Server/src/main/java/core/game/content/dialogue/book/Book.java @@ -142,6 +142,9 @@ public abstract class Book extends DialoguePlugin { * @param set the set. */ public void display(Page[] set) { + // TODO: It seems like different subclasses instantiate this by + // copy-paste, this should probably be made final, and should switch between a few + // implementations by component id } /** diff --git a/Server/src/main/java/core/game/content/quest/free/shieldofarrav/CuratorHaigHalenDialogue.java b/Server/src/main/java/core/game/content/quest/free/shieldofarrav/CuratorHaigHalenDialogue.java index 8f9b52b09..01a30974e 100644 --- a/Server/src/main/java/core/game/content/quest/free/shieldofarrav/CuratorHaigHalenDialogue.java +++ b/Server/src/main/java/core/game/content/quest/free/shieldofarrav/CuratorHaigHalenDialogue.java @@ -2,10 +2,13 @@ package core.game.content.quest.free.shieldofarrav; import core.game.content.dialogue.DialoguePlugin; import core.game.content.dialogue.FacialExpression; +import core.game.content.quest.free.shieldofarrav.CuratorHaigHalenSOADialogue; import core.game.node.entity.npc.NPC; import core.game.node.entity.player.Player; import core.game.node.entity.player.link.diary.DiaryType; import core.game.node.item.Item; +import rs09.game.content.dialogue.IfTopic; +import rs09.game.content.quest.members.thegolem.CuratorHaigHalenGolemDialogue; /** * Represents the curator haig halen dialogue. @@ -51,90 +54,17 @@ public final class CuratorHaigHalenDialogue extends DialoguePlugin { @Override public boolean handle(int interfaceId, int buttonId) { - if (player.getQuestRepository().getQuest("Shield of Arrav").getStage(player) == 70 && player.getInventory().containsItem(ShieldofArrav.PHOENIX_SHIELD) || player.getInventory().containsItem(ShieldofArrav.BLACKARM_SHIELD)) { - switch (stage) { - case 0: - player("I have half the shield of Arrav here. Can I get a", "reward?"); - stage = 1; - break; - case 1: - npc("The Shield of Arrav! Goodness, the Museum has been", "searching for that for years! The late King Roald II", "offered a reward for it years ago!"); - stage = 2; - break; - case 2: - player("Well, I'm here to claim it."); - stage = 3; - break; - case 3: - npc("Let me have a look at it first."); - stage = 4; - break; - case 4: - interpreter.sendItemMessage(ShieldofArrav.getShield(player), "The curator peers at the shield."); - stage = 5; - break; - case 5: - npc("This is incredible!"); - stage = 6; - break; - case 6: - npc("That shield has been missing for over twenty-five years!"); - stage = 7; - break; - case 7: - npc("Leave the shield here with me and I'll write you out a", "certificate saying that you have returned the shield, so", "that you can claim your reward from the King."); - stage = 8; - break; - case 8: - player("Can I have two certificates please?"); - stage = 9; - break; - case 9: - npc("Yes, certainly. Please hand over the shield."); - stage = 10; - break; - case 10: - interpreter.sendItemMessage(ShieldofArrav.getShield(player), "You hand over the shield half."); - stage = 11; - break; - case 11: - final Item shield = ShieldofArrav.getShield(player); - final Item certificate = shield == ShieldofArrav.BLACKARM_SHIELD ? ShieldofArrav.BLACKARM_CERTIFICATE : ShieldofArrav.PHOENIX_CERTIFICATE; - if (player.getInventory().remove(shield)) { - player.getInventory().add(certificate); - interpreter.sendItemMessage(certificate, "The curator writes out two half-certificates."); - stage = 12; - } - break; - } - return true; - } else { - switch (stage) { - case 12: - npc("Of course you won't actually be able to claim the", "reward with only half the reward certificate..."); - stage = 13; - break; - case 13: - player("What? I went through a lot of trouble to get that shield", "piece and now you tell me it was for nothing? That's", "not very fair!"); - stage = 14; - break; - case 14: - npc("Well, if you were to get me the other half of the shield,", "I could give you the other half of the reward certificate.", "It's rumoured to be in the possession of the infamous", "Blackarm Gang, beyond that I can't help you."); - stage = 15; - break; - case 15: - player("Okay, I'll see what I can do."); - stage = 16; - break; - case 16: - end(); - break; - } - } - switch (stage) { - case 0: - end(); - break; + switch(stage) { + case 0: + showTopics( + new IfTopic("I have the Shield of Arrav", new CuratorHaigHalenSOADialogue(), + player.getQuestRepository().getQuest("Shield of Arrav").getStage(player) == 70 + ), + new IfTopic("I'm looking for a statuette recovered from the city of Uzer.", new CuratorHaigHalenGolemDialogue(), + player.getQuestRepository().getQuest("The Golem").getStage(player) >= 3 + ) + ); + break; } return true; } @@ -143,4 +73,4 @@ public final class CuratorHaigHalenDialogue extends DialoguePlugin { public int[] getIds() { return new int[] { 646 }; } -} \ No newline at end of file +} diff --git a/Server/src/main/java/core/game/content/quest/free/shieldofarrav/CuratorHaigHalenSOADialogue.java b/Server/src/main/java/core/game/content/quest/free/shieldofarrav/CuratorHaigHalenSOADialogue.java new file mode 100644 index 000000000..822691d69 --- /dev/null +++ b/Server/src/main/java/core/game/content/quest/free/shieldofarrav/CuratorHaigHalenSOADialogue.java @@ -0,0 +1,93 @@ +package core.game.content.quest.free.shieldofarrav; + +import core.game.node.entity.player.Player; +import core.game.node.item.Item; +import rs09.game.content.dialogue.DialogueFile; + +public final class CuratorHaigHalenSOADialogue extends DialogueFile { + @Override + public void handle(int componentID, int buttonID) { + Player player = getPlayer(); + if (player.getInventory().containsItem(ShieldofArrav.PHOENIX_SHIELD) || player.getInventory().containsItem(ShieldofArrav.BLACKARM_SHIELD)) { + switch (getStage()) { + case 0: + player("I have half the shield of Arrav here. Can I get a", "reward?"); + setStage(1); + break; + case 1: + npc("The Shield of Arrav! Goodness, the Museum has been", "searching for that for years! The late King Roald II", "offered a reward for it years ago!"); + setStage(2); + break; + case 2: + player("Well, I'm here to claim it."); + setStage(3); + break; + case 3: + npc("Let me have a look at it first."); + setStage(4); + break; + case 4: + getInterpreter().sendItemMessage(ShieldofArrav.getShield(player), "The curator peers at the shield."); + setStage(5); + break; + case 5: + npc("This is incredible!"); + setStage(6); + break; + case 6: + npc("That shield has been missing for over twenty-five years!"); + setStage(7); + break; + case 7: + npc("Leave the shield here with me and I'll write you out a", "certificate saying that you have returned the shield, so", "that you can claim your reward from the King."); + setStage(8); + break; + case 8: + player("Can I have two certificates please?"); + setStage(9); + break; + case 9: + npc("Yes, certainly. Please hand over the shield."); + setStage(10); + break; + case 10: + getInterpreter().sendItemMessage(ShieldofArrav.getShield(player), "You hand over the shield half."); + setStage(11); + break; + case 11: + final Item shield = ShieldofArrav.getShield(player); + final Item certificate = shield == ShieldofArrav.BLACKARM_SHIELD ? ShieldofArrav.BLACKARM_CERTIFICATE : ShieldofArrav.PHOENIX_CERTIFICATE; + if (player.getInventory().remove(shield)) { + player.getInventory().add(certificate); + getInterpreter().sendItemMessage(certificate, "The curator writes out two half-certificates."); + setStage(12); + } + break; + } + return; + } + switch (getStage()) { + case 0: + case 12: + npc("Of course you won't actually be able to claim the", "reward with only half the reward certificate..."); + setStage(13); + break; + case 13: + player("What? I went through a lot of trouble to get that shield", "piece and now you tell me it was for nothing? That's", "not very fair!"); + setStage(14); + break; + case 14: + npc("Well, if you were to get me the other half of the shield,", "I could give you the other half of the reward certificate.", "It's rumoured to be in the possession of the infamous", "Blackarm Gang, beyond that I can't help you."); + setStage(15); + break; + case 15: + player("Okay, I'll see what I can do."); + setStage(16); + break; + case 16: + end(); + break; + } + } +} + diff --git a/Server/src/main/java/core/game/interaction/item/BookreadOption.java b/Server/src/main/java/core/game/interaction/item/BookreadOption.java index dcc395449..92bb0be88 100644 --- a/Server/src/main/java/core/game/interaction/item/BookreadOption.java +++ b/Server/src/main/java/core/game/interaction/item/BookreadOption.java @@ -25,6 +25,7 @@ public class BookreadOption extends OptionHandler { ItemDefinition.forId(1856).getHandlers().put("option:read", this); ItemDefinition.forId(Items.BATTERED_BOOK_2886).getHandlers().put("option:read", this); ItemDefinition.forId(Items.SLASHED_BOOK_9715).getHandlers().put("option:read", this); + ItemDefinition.forId(Items.VARMENS_NOTES_4616).getHandlers().put("option:read", this); ItemDefinition.forId(9003).getHandlers().put("option:read", this); ItemDefinition.forId(9004).getHandlers().put("option:read", this); ItemDefinition.forId(11710).getHandlers().put("option:read", this); @@ -68,6 +69,6 @@ public class BookreadOption extends OptionHandler { case 1856: return 387454; } - return -1; + return item; } } diff --git a/Server/src/main/java/core/game/node/entity/player/link/quest/Quest.java b/Server/src/main/java/core/game/node/entity/player/link/quest/Quest.java index 0cdbe77bb..1c6aa9efa 100644 --- a/Server/src/main/java/core/game/node/entity/player/link/quest/Quest.java +++ b/Server/src/main/java/core/game/node/entity/player/link/quest/Quest.java @@ -206,6 +206,9 @@ public abstract class Quest implements Plugin { } return new int[] {configs[0], stage == 0 ? configs[1] : stage >= 100 ? configs[3] : configs[2]}; } + + public void updateVarps(Player player) { + } /** * Checks if the quest is in progress. diff --git a/Server/src/main/java/core/game/node/entity/player/link/quest/QuestRepository.java b/Server/src/main/java/core/game/node/entity/player/link/quest/QuestRepository.java index af10beebf..e7f8737c3 100644 --- a/Server/src/main/java/core/game/node/entity/player/link/quest/QuestRepository.java +++ b/Server/src/main/java/core/game/node/entity/player/link/quest/QuestRepository.java @@ -84,6 +84,7 @@ public final class QuestRepository { } config = quest.getConfig(player,getStage(quest)); player.varpManager.get(config[0]).setVarbit(0,config[1]).send(player); + quest.updateVarps(player); } } } diff --git a/Server/src/main/java/core/game/world/map/build/LandscapeParser.java b/Server/src/main/java/core/game/world/map/build/LandscapeParser.java index 6f20e0fc4..cd4b12e37 100644 --- a/Server/src/main/java/core/game/world/map/build/LandscapeParser.java +++ b/Server/src/main/java/core/game/world/map/build/LandscapeParser.java @@ -110,7 +110,7 @@ public final class LandscapeParser { int type = object.getType(); if (type == 22) { //Tile plane.getFlags().getLandscape()[localX][localY] = true; - if (def.secondInt != 0 || def.clipType == 1 || def.secondBool) { + if (def.interactable != 0 || def.clipType == 1 || def.secondBool) { if (def.clipType == 1) { plane.getFlags().flagTileObject(localX, localY); if (def.isProjectileClipped()) { @@ -190,7 +190,7 @@ public final class LandscapeParser { } int type = object.getType(); if (type == 22) { //Tile - if (def.secondInt != 0 || def.clipType == 1 || def.secondBool) { + if (def.interactable != 0 || def.clipType == 1 || def.secondBool) { if (def.clipType == 1) { plane.getFlags().unflagTileObject(localX, localY); if (def.isProjectileClipped()) { @@ -215,4 +215,4 @@ public final class LandscapeParser { } return current; } -} \ No newline at end of file +} diff --git a/Server/src/main/java/core/net/packet/in/ItemActionPacket.java b/Server/src/main/java/core/net/packet/in/ItemActionPacket.java index 40153ab31..81a891e73 100644 --- a/Server/src/main/java/core/net/packet/in/ItemActionPacket.java +++ b/Server/src/main/java/core/net/packet/in/ItemActionPacket.java @@ -49,6 +49,7 @@ public class ItemActionPacket implements IncomingPacket { int childId2 = -1; NodeUsageEvent event = null; Item used = null; + player.debug(String.format("ItemActionPacket.decode(%d)", opcode)); switch (buffer.opcode()) { case 115: // Item on NPC int interfaceId = buffer.getIntA() >> 16; @@ -169,19 +170,13 @@ public class ItemActionPacket implements IncomingPacket { int objectId = buffer.getShortA(); int z = player.getLocation().getZ(); Scenery object = RegionManager.getObject(z, x, y); - if(objectId != 6898) { - if (object == null || object.getId() != objectId) { - PacketRepository.send(ClearMinimapFlag.class, new PlayerContext(player)); - return; - } - object = object.getChild(player); - if (object == null) { - PacketRepository.send(ClearMinimapFlag.class, new PlayerContext(player)); - break; - } - } else { - object = new Scenery(6898,x,y,z); - } + //player.debug(String.format("opcode 134: oid %d iid %d", objectId, id)); + //player.debug(String.format("opcode 134: %d %d", object != null ? object.getId() : -1, object != null && object.getChild(player) != null ? object.getChild(player).getId() : -1)); + Scenery child = object != null ? object.getChild(player) : null; + if(object == null || (object.getId() != objectId && (child == null || child.getId() != objectId))) { + PacketRepository.send(ClearMinimapFlag.class, new PlayerContext(player)); + return; + } used = player.getInventory().get(slot); if (used == null || used.getId() != id) { return; @@ -190,6 +185,9 @@ public class ItemActionPacket implements IncomingPacket { RottenPotatoUseWithHandler.handle(object,player); return; } + if(InteractionListeners.run(used,child,1,player)){ + return; + } if(InteractionListeners.run(used,object,1,player)){ return; } @@ -245,4 +243,4 @@ public class ItemActionPacket implements IncomingPacket { return; } } -} \ No newline at end of file +} diff --git a/Server/src/main/kotlin/rs09/game/content/dialogue/DialogueBuilder.kt b/Server/src/main/kotlin/rs09/game/content/dialogue/DialogueBuilder.kt new file mode 100644 index 000000000..db531f8a5 --- /dev/null +++ b/Server/src/main/kotlin/rs09/game/content/dialogue/DialogueBuilder.kt @@ -0,0 +1,149 @@ +package rs09.game.content.dialogue +import core.game.node.entity.player.Player +import rs09.game.content.dialogue.DialogueFile +import rs09.tools.END_DIALOGUE + +abstract class DialogueBuilderFile : DialogueFile() { + var data: ArrayList = ArrayList() + //var stages: ArrayList = ArrayList() + abstract fun create(b: DialogueBuilder) + init { + create(DialogueBuilder(this)) + } + override fun handle(componentID: Int, buttonID: Int) { + for((i, clause) in data.iterator().withIndex()) { + if(clause.predicate(player!!)) { + stage = clause.handle(this, componentID, buttonID, stage) + if(stage == END_DIALOGUE) { + end() + } + return + } + } + } +} + +interface DialogueNode { + fun handle(df: DialogueFile, componentID: Int, buttonID: Int, stage: Int): Int +} + +class NpcLNode(val value: String): DialogueNode { + override fun handle(df: DialogueFile, componentID: Int, buttonID: Int, stage: Int): Int { + df.npcl(value) + return stage + 1 + } +} +class NpcNode(val values: Array): DialogueNode { + override fun handle(df: DialogueFile, componentID: Int, buttonID: Int, stage: Int): Int { + df.npc(*values) + return stage + 1 + } +} +class PlayerLNode(val value: String): DialogueNode { + override fun handle(df: DialogueFile, componentID: Int, buttonID: Int, stage: Int): Int { + df.playerl(value) + return stage + 1 + } +} +class ClosureNode(val f: (Player) -> Int): DialogueNode { + override fun handle(df: DialogueFile, componentID: Int, buttonID: Int, stage: Int): Int { + return f(df.player!!) + } +} + +class OptionEntry(val text: String, val nextStage: Int, val predicate: (Player) -> Boolean = { _ -> true }) {} + +class OptionsNode(var options: ArrayList): DialogueNode { + fun optionNames(player: Player): Array { + return options.asSequence().filter({ it.predicate(player) }).map({ it.text }).toList().toTypedArray() + } + override fun handle(df: DialogueFile, componentID: Int, buttonID: Int, stage: Int): Int { + val tmp: Array = optionNames(df.player!!) + if(tmp.size > 1) { + df.options(*tmp) + return stage + 1 + } else if(tmp.size == 1) { + val tmp: List = options.asSequence().filter({ it.predicate(df.player!!) }).toList() + df.stage = tmp[0].nextStage + df.handle(componentID, 0) + return df.stage + } else { + return END_DIALOGUE + } + } +} +class OptionsDispatchNode(var options: ArrayList): DialogueNode { + override fun handle(df: DialogueFile, componentID: Int, buttonID: Int, stage: Int): Int { + val tmp: List = options.asSequence().filter({ it.predicate(df.player!!) }).toList() + df.stage = tmp[buttonID-1].nextStage + df.handle(componentID, 0) + return df.stage + } +} + +class DialogueClause(val predicate: (player: Player) -> Boolean, val nodes: ArrayList) { + fun handle(df: DialogueFile, componentID: Int, buttonID: Int, stage: Int): Int { + if(stage < nodes.size) { + return nodes[stage].handle(df, componentID, buttonID, stage) + } else { + return END_DIALOGUE + } + } +} + +class DialogueOptionsBuilder(var target: DialogueBuilderFile, val clauseIndex: Int, var options: ArrayList) { + fun option(value: String): DialogueBuilder { + options.add(OptionEntry(value, target.data[clauseIndex].nodes.size)) + return DialogueBuilder(target, clauseIndex) + } + + fun optionIf(value: String, predicate: (Player) -> Boolean): DialogueBuilder { + options.add(OptionEntry(value, target.data[clauseIndex].nodes.size, predicate)) + return DialogueBuilder(target, clauseIndex) + } +} + +class DialogueBuilder(var target: DialogueBuilderFile, var clauseIndex: Int = -1) { + fun onPredicate(predicate: (player: Player) -> Boolean): DialogueBuilder { + target.data.add(DialogueClause(predicate, ArrayList())) + clauseIndex = target.data.size - 1 + return this + } + fun onQuestStages(name: String, vararg stages: Int): DialogueBuilder { + return onPredicate() { player -> + val questStage = player.questRepository.getStage(name) + return@onPredicate stages.contains(questStage) + } + } + fun playerl(value: String): DialogueBuilder { + target.data[clauseIndex].nodes.add(PlayerLNode(value)) + return this + } + fun npcl(value: String): DialogueBuilder { + target.data[clauseIndex].nodes.add(NpcLNode(value)) + return this + } + fun npc(vararg values: String): DialogueBuilder { + target.data[clauseIndex].nodes.add(NpcNode(values as Array)) + return this + } + fun endWith(f: (Player) -> Unit) { + target.data[clauseIndex].nodes.add(ClosureNode({ player -> + f(player) + return@ClosureNode END_DIALOGUE + })) + } + fun end() { + target.data[clauseIndex].nodes.add(ClosureNode({ _ -> + return@ClosureNode END_DIALOGUE + })) + } + fun options(): DialogueOptionsBuilder { + var options: ArrayList = ArrayList() + val node = OptionsNode(options) + val dispatchNode = OptionsDispatchNode(options) + target.data[clauseIndex].nodes.add(node) + target.data[clauseIndex].nodes.add(dispatchNode) + return DialogueOptionsBuilder(target, clauseIndex, options) + } +} diff --git a/Server/src/main/kotlin/rs09/game/content/quest/members/thegolem/TheGolemDialogue.kt b/Server/src/main/kotlin/rs09/game/content/quest/members/thegolem/TheGolemDialogue.kt new file mode 100644 index 000000000..d5fdce519 --- /dev/null +++ b/Server/src/main/kotlin/rs09/game/content/quest/members/thegolem/TheGolemDialogue.kt @@ -0,0 +1,445 @@ +package rs09.game.content.quest.members.thegolem + +import api.* +import core.game.content.dialogue.DialoguePlugin +import core.game.content.dialogue.book.Book +import core.game.content.dialogue.book.BookLine +import core.game.content.dialogue.book.Page +import core.game.content.dialogue.book.PageSet +import core.game.node.Node +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.node.entity.skill.Skills +import core.game.node.item.Item +import core.game.world.map.Location +import core.plugin.Initializable +import org.rs09.consts.Items +import org.rs09.consts.NPCs +import rs09.game.content.dialogue.DialogueBuilder +import rs09.game.content.dialogue.DialogueBuilderFile +import rs09.game.content.dialogue.DialogueFile +import rs09.tools.END_DIALOGUE + +@Initializable +public final class ClayGolemDialoguePlugin(player: Player? = null) : DialoguePlugin(player) { + override fun newInstance(player: Player): DialoguePlugin { + return ClayGolemDialoguePlugin(player) + } + override fun open(vararg objects: Any?): Boolean { + npc = objects[0] as NPC + player.dialogueInterpreter.open(ClayGolemDialogueFile(), npc) + return true + } + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + return false + } + override fun getIds(): IntArray { + return intArrayOf(1907, NPCs.BROKEN_CLAY_GOLEM_1908, NPCs.DAMAGED_CLAY_GOLEM_1909, NPCs.CLAY_GOLEM_1910) + } +} + +class ClayGolemDialogueFile : DialogueBuilderFile() { + override fun create(b: DialogueBuilder) { + val opt1 = b.onQuestStages("The Golem", 0) + .npc("Damage... severe...", "task... incomplete...") + .options() + opt1 + .optionIf("Shall I try to repair you?") { player -> return@optionIf player.questRepository.getQuest("The Golem").hasRequirements(player) } + .playerl("Shall I try to repair you?") + .npcl("Repairs... needed...") + .endWith(){ player -> if (player.questRepository.getStage("The Golem") < 1 ) { setQuestStage(player, "The Golem", 1) } } + opt1 + .option("I'm not going to find a conversation here!") + .playerl("I'm not going to find a conversation here!") + .end() + b.onQuestStages("The Golem", 1) + .npcl("Repairs... needed...") + .end() + b.onQuestStages("The Golem", 2) + .npcl("Damage repaired...") + .npcl("Thank you. My body and mind are fully healed.") + .npcl("Now I must complete my task by defeating the great enemy.") + .playerl("What enemy?") + .npcl("A great demon. It broke through from its dimension to attack the city.") + .npcl("The golem army was created to fight it. Many were destroyed, but we drove the demon back!") + .npcl("The demon is still wounded. You must open the portal so that I can strike the final blow and complete my task.") + .endWith() { player -> setQuestStage(player, "The Golem", 3) } + b.onQuestStages("The Golem", 3) + .npcl("The demon is still wounded. You must open the portal so that I can strike the final blow and complete my task.") + .end() + b.onQuestStages("The Golem", 4) + .npcl("My task is incomplete. You must open the portal so I can defeat the great demon.") + .playerl("It's ok, the demon is dead!") + .npcl("The demon must be defeated...") + .playerl("No, you don't understand. I saw the demon's skeleton. It must have died of its wounds.") + .npcl("Demon must be defeated! Task incomplete.") + .endWith() { player -> setQuestStage(player, "The Golem", 5) } + b.onQuestStages("The Golem", 5) + .npcl("Task incomplete.") + .playerl("Oh, how am I going to convince you?") + .endWith() { player -> setQuestStage(player, "The Golem", 6) } + b.onQuestStages("The Golem", 6, 7) + .npcl("My task is incomplete. You must open the portal so I can defeat the great demon.") + .playerl("I already told you, he's dead!") + .npcl("Task incomplete.") + .playerl("Oh, how am I going to convince you?") + .endWith() { player -> if(player.questRepository.getStage("The Golem") < 7) { setQuestStage(player, "The Golem", 7) } } + } +} + +class ClayGolemProgramDialogueFile : DialogueBuilderFile() { + override fun create(b: DialogueBuilder) { + b.onQuestStages("The Golem", 8) + .npc("New instructions...", "Updating program...") + .npcl("Task complete!") + .npcl("Thank you. Now my mind is at rest.") + .endWith() { player -> finishQuest(player, "The Golem") } + } +} + +class CuratorHaigHalenGolemDialogue : DialogueBuilderFile() { + override fun create(b: DialogueBuilder) { + val opt1 = b.onQuestStages("The Golem", 3) + .npcl("Ah yes, a very impressive artefact. The people of that city were excellent sculptors.") + .npcl("It's in the display case upstairs.") + .playerl("No, I need to take it away with me.") + .npcl("What do you want it for?") + .options() + opt1 + .option("I want to open a portal to the lair of an elder-demon.") + .playerl("I want to open a portal to the lair of an elder-demon.") + .npcl("Good heavens! I'd never let you do such a dangerous thing.") + .end() + opt1 + .option("Well, I, er, just want it.") + .playerl("Well, I, er, just want it.") + .end() + } +} + +val LETTER_LINES = arrayOf( + "", + "", + "Dearest Varmen,", + "I hope this finds you well. Here are the books you asked for", + "There has been an exciting development closer to home --", + "another city from the same period has been discovered east", + "of Varrock, and we are starting a huge excavation project", + "here. I don't know if the museum will be able to finance your", + "expedition as well as this one, so I fear your current trip will be", + "the last.", + "May Saradomin grant you a safe journey home", + "Your loving Elissa.", + ) + +val VARMEN_NOTES = arrayOf( + PageSet( + Page( + BookLine("Septober 19:", 55), + BookLine("The nomads were right:", 56), + BookLine("there is a city here,", 57), + BookLine("probably buried for", 58), + BookLine("millenia and revealed by", 59), + BookLine("the random motions of", 60), + BookLine("the sand. The", 61), + BookLine("architecture is impressive", 62), + BookLine("even in ruin, and must", 63), + BookLine("once have been amazing.", 64), + BookLine("One puzzling factor is the", 65), + ), + Page( + BookLine("pottery -- there are", 66), + BookLine("fragments all over the", 67), + BookLine("ruins, surely too much", 68), + BookLine("for a city even of this", 69), + BookLine("size. We have set up", 70), + BookLine("camp and will do a proper", 71), + BookLine("survey tomorrow.", 72), + ) + ), + PageSet( + Page( + BookLine("Septober 20:", 55), + BookLine("The meaning of the", 56), + BookLine("pottery was explained", 57), + BookLine("today in a most", 58), + BookLine("surprising manner. We", 59), + BookLine("found a mostly-intact clay", 60), + BookLine("statue buried up to its", 61), + BookLine("waist in sand, and as", 62), + BookLine("soon as we dug it out, it", 63), + BookLine("started to walk around! It", 64), + BookLine("is a clay golem, built by", 65), + ), + Page( + BookLine("the city's inhabitants and", 66), + BookLine("dormant all this time. Its", 67), + BookLine("head is badly damaged", 68), + BookLine("and it is", 69), + BookLine("uncommunicative, but its", 70), + BookLine("existence tells us that the", 71), + BookLine("city's inhabitants were", 72), + BookLine("expert magical craftsmen.", 73), + BookLine("The huge kilns in some", 74), + BookLine("of the buildings indicate", 75), + BookLine("that at some point before", 76), + ) + ), + PageSet( + Page( + BookLine("its destruction the whole", 55), + BookLine("city was converted to the", 56), + BookLine("manufacture of these", 57), + BookLine("golems.", 58), + BookLine("", 59), + BookLine("We have also examined", 60), + BookLine("the carvings on the large", 61), + BookLine("building in the centre.", 62), + BookLine("These are symbols", 63), + BookLine("depicting several of the", 64), + BookLine("ancient gods, including", 65), + ), + Page( + BookLine("Saradomin, Zamorak, and", 66), + BookLine("Armadyl, but there is", 67), + BookLine("another prominent symbol", 68), + BookLine("that I cannot identify. As", 69), + BookLine("it seems we will need to", 70), + BookLine("be here for longer than I", 71), + BookLine("had thought, I have sent", 72), + BookLine("to Elissa for books on", 73), + BookLine("golems and religious", 74), + BookLine("symbols.", 75), + ) + ), + PageSet( + Page( + BookLine("Septober 21:", 55), + BookLine("As we examine the ruins", 56), + BookLine("one thing becomes", 57), + BookLine("increasingly clear: most", 58), + BookLine("of the damage was not", 59), + BookLine("due to weathering. The", 60), + BookLine("buildings were destroyed", 61), + BookLine("by force, as if torn down", 62), + BookLine("by giant hands.", 63), + ), + Page( + BookLine("Septober 22:", 66), + BookLine("A breakthrough! We have", 67), + BookLine("found the staircase into", 68), + BookLine("the lower levels of the", 69), + BookLine("temple. This part has", 70), + BookLine("been untouched by the", 71), + BookLine("elements, and the", 72), + BookLine("carvings here are more", 73), + BookLine("intact, especially four", 74), + BookLine("beautiful statuettes in", 75), + BookLine("alcoves framing the large", 76), + ) + ), + PageSet( + Page( + BookLine("door. I have removed one", 55), + BookLine("of them. The door will", 56), + BookLine("not open. I am glad I", 57), + BookLine("sent for a book on", 58), + BookLine("symbols, as the", 59), + BookLine("unidentified symbol is", 60), + BookLine("even more prominent", 61), + BookLine("here, especially on the", 62), + BookLine("door.", 63), + ), + Page( + BookLine("Septober 23:", 66), + BookLine("Our messenger returned", 67), + BookLine("with the books I asked for", 68), + BookLine("and a letter from Elissa.", 69), + BookLine("It is unfortunate that the", 70), + BookLine("museum will not be able", 71), + BookLine("to finance a full-scale", 72), + BookLine("excavation here as well as", 73), + BookLine("the one closer to Varrock,", 74), + BookLine("although I am of course", 75), + BookLine("pleased that the other city", 76), + ) + ), + PageSet( + Page( + BookLine("has been uncovered. But", 55), + BookLine("with the books I am able", 56), + BookLine("to piece together more of", 57), + BookLine("the story of this city.", 58), + BookLine("", 59), + BookLine("The unidentified symbol", 60), + BookLine("in the ruins is that of the", 61), + BookLine("demon Thammaron, who", 62), + BookLine("was Zamorak's chief", 63), + BookLine("lieutenant during the", 64), + BookLine("godwars of the Third", 65), + ), + Page( + BookLine("Age. With that", 66), + BookLine("information I can say", 67), + BookLine("with confidence that these", 68), + BookLine("are the ruins of Uzer, an", 69), + BookLine("advanced human", 70), + BookLine("civilization said to have", 71), + BookLine("been destroyed towards", 72), + BookLine("the end of the Third Age", 73), + BookLine("(roughly 2,500 years", 74), + BookLine("ago). It was allied with", 75), + BookLine("Saradomin and enjoyed", 76), + ) + ), + PageSet( + Page( + BookLine("his protection, as well as", 55), + BookLine("that of its own mages and", 56), + BookLine("warriors. Thammaron was", 57), + BookLine("able to open a portal from", 58), + BookLine("his own domain straight", 59), + BookLine("into the heart of the city,", 60), + BookLine("bypassing its defences.", 61), + BookLine("With Saradomin's help the", 62), + BookLine("army of Uzer was able to", 63), + BookLine("drive Thammaron back,", 64), + BookLine("but the record ends at", 65), + ), + Page( + BookLine("that point and it has", 66), + BookLine("always been assumed that", 67), + BookLine("a later attack, either by", 68), + BookLine("Thammaron or by", 69), + BookLine("Zamorak's other forces,", 70), + BookLine("finished the city off.", 71), + BookLine("", 72), + BookLine("Examining the door", 73), + BookLine("again, I now see that it is", 74), + BookLine("exactly the sort of door", 75), + BookLine("that could be used to seal", 76), + ) + ), + PageSet( + Page( + BookLine("Thammaron's portal. I am", 55), + BookLine("suddently glad I was not", 56), + BookLine("able to open it! I surmise", 57), + BookLine("that the army of golems", 58), + BookLine("was created in order to", 59), + BookLine("fight the demon, since", 60), + BookLine("Uzer's army had been", 61), + BookLine("wiped out and", 62), + BookLine("Saradomin's forces were", 63), + BookLine("increasingly stretched.", 64), + BookLine("However, this approach", 65), + ), + Page( + BookLine("evidently failed, since the", 66), + BookLine("city was eventually", 67), + BookLine("destroyed.", 68), + BookLine("", 69), + BookLine("The art of the", 70), + BookLine("construction of golems", 71), + BookLine("has been lost since the", 72), + BookLine("Third Age, and, although", 73), + BookLine("they are sometimes", 74), + BookLine("discovered lying dormant", 75), + BookLine("in the ground, no", 76), + ) + ), + PageSet( + Page( + BookLine("concerted effort has been", 55), + BookLine("made to regain it, thanks", 56), + BookLine("largely to the modern", 57), + BookLine("Saradomist Church's view", 58), + BookLine("of them as unnatural.", 59), + BookLine("This view is without", 60), + BookLine("foundation, as golems are", 61), + BookLine("neither good nor evil but", 62), + BookLine("follow instructions they", 63), + BookLine("are given to the letter", 64), + BookLine("and without imagination,", 65), + ), + Page( + BookLine("indeed experiencing", 66), + BookLine("extreme discomfort for as", 67), + BookLine("long as a task assigned to", 68), + BookLine("them remains incomplete.", 69), + BookLine("Some golems were", 70), + BookLine("constructed to obey", 71), + BookLine("verbal instructions, but", 72), + BookLine("the main method of", 73), + BookLine("instruction was to place", 74), + BookLine("magical words into the", 75), + BookLine("golem's skull cavity.", 76), + ) + ), + PageSet( + Page( + BookLine("These were written on", 55), + BookLine("papyrus using a naturally", 56), + BookLine("occurring source of ink,", 57), + BookLine("and their magical power", 58), + BookLine("derived from the use of a", 59), + BookLine("phoenix tail feather as a", 60), + BookLine("pen. These would be used", 61), + BookLine("for long-term or", 62), + BookLine("important tasks, and", 63), + BookLine("would override any verbal", 64), + BookLine("instructions.", 65), + ) + ) +) + +@Initializable +class VarmensNotesHandler : Book { + constructor() {} + constructor(player: Player?) : super(player, "The Ruins of Uzer", Items.VARMENS_NOTES_4616, *VARMEN_NOTES) {} + override fun finish() { + player.setAttribute("/save:the-golem:varmen-notes-read", true) + } + + override fun display(set: Array) { + player.interfaceManager.open(getInterface()) + + for (i in 55..76) { + player.packetDispatch.sendString("", getInterface().id, i) + } + player.packetDispatch.sendString("", getInterface().id, 77) + player.packetDispatch.sendString("", getInterface().id, 78) + player.packetDispatch.sendString(getName(), getInterface().id, 6) + for (page in set) { + for (line in page.lines) { + player.packetDispatch.sendString(line.message, getInterface().id, line.child) + } + } + player.packetDispatch.sendInterfaceConfig(getInterface().id, 51, index < 1) + val lastPage = index == sets.size - 1 + player.packetDispatch.sendInterfaceConfig(getInterface().id, 53, lastPage) + if (lastPage) { + finish() + } + } + + override fun newInstance(player: Player): DialoguePlugin { + return VarmensNotesHandler(player) + } + + override fun getIds(): IntArray { + return intArrayOf(Items.VARMENS_NOTES_4616) + } +} + +val DISPLAY_CASE_TEXT = arrayOf("3rd age - yr 3000-4000", + "", + "This statuette was found in an underground", + "temple in the ruined city of Uzer, which was", + "destroyed late in the 3rd Age, suddenly, due to", + "causes unknown. It probably represents one of", + "the clay golems that the craftsmen of the city", + "built as warriors and servants. The statuette", + "was originally part of a mechanism whose", + "purpose is unknown.") diff --git a/Server/src/main/kotlin/rs09/game/content/quest/members/thegolem/TheGolemQuest.kt b/Server/src/main/kotlin/rs09/game/content/quest/members/thegolem/TheGolemQuest.kt new file mode 100644 index 000000000..6980d4ed6 --- /dev/null +++ b/Server/src/main/kotlin/rs09/game/content/quest/members/thegolem/TheGolemQuest.kt @@ -0,0 +1,453 @@ +package rs09.game.content.quest.members.thegolem + +import api.* +import core.game.content.global.action.ClimbActionHandler +import core.game.content.global.action.SpecialLadders; +import core.game.node.Node +import core.game.node.entity.npc.AbstractNPC +import core.game.node.entity.npc.NPC +import core.game.node.entity.player.Player +import core.game.node.entity.player.link.quest.Quest +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import core.game.node.scenery.Scenery +import core.game.system.task.Pulse +import core.game.world.map.Location +import core.game.world.update.flag.context.Animation +import core.plugin.Initializable +import core.tools.RandomFunction +import org.rs09.consts.Items +import org.rs09.consts.NPCs +import rs09.game.content.dialogue.DialogueBuilder +import rs09.game.content.dialogue.DialogueBuilderFile +import rs09.game.content.dialogue.DialogueFile +import rs09.game.interaction.InteractionListener +import rs09.game.interaction.InterfaceListener +import rs09.game.world.GameWorld +import rs09.tools.END_DIALOGUE + +@Initializable +class TheGolemQuest : Quest("The Golem", 70, 69, 1, 437, 0, 1, 10) { + override fun newInstance(`object`: Any?): Quest { + return this + } + + override fun drawJournal(player: Player, stage: Int) { + super.drawJournal(player, stage) + var ln = 11 + if(stage == 0) { + line(player, "I can start this quest by talking to the golem who is in the:", ln++, false) + line(player, "Ruined city of !!Uzer??, which is in the desert to the east of", ln++, false) + line(player, "the !!Shantay Pass.", ln++, false) + line(player, "I will need to have !!level 20 crafting?? and !!level 25 thieving", ln++, false) + } + if(stage >= 1) { + line(player, "I've found the golem, and offered to repair it.", ln++, stage > 1) + } + if(stage >= 2) { + line(player, "I've repaired the golem with some soft clay.", ln++, stage > 2) + } + if(stage >= 3) { + line(player, "The golem wants me to open a portal to help it defeat", ln++, stage > 3) + line(player, "the demon that attacked its city.", ln++, stage > 3) + } + val readLetter = player.getAttribute("the-golem:read-elissa-letter", false) + val readBook = player.getAttribute("the-golem:varmen-notes-read", false) + if(readLetter) { + line(player, "I've found a letter that mentions !!The Digsite", ln++, readBook) + } + if(readBook) { + line(player, "I've found a book that mentions that golems are programmed by", ln++, stage > 7) + line(player, "writing instructions on papyrus with a phoenix quill pen.", ln++, stage > 7) + } + val hasStatuette = TheGolemListeners.hasStatuette(player) + val doorOpen = player.getAttribute("the-golem:door-open", false) + if(hasStatuette) { + line(player, "I've acquired a statuette that fits a mechanism in the ruins", ln++, doorOpen) + line(player, " of !!Uzer?? from the Varrock museum.", ln++, doorOpen) + } + val seenDemon = player.getAttribute("the-golem:seen-demon", false) + if(doorOpen) { + line(player, "I've opened the portal in the ruins of Uzer.", ln++, seenDemon) + } + if(seenDemon) { + line(player, "It turns out that the demon is already dead!", ln++, stage > 4) + line(player, "I should tell the golem the good news.", ln++, stage > 4) + } + if(stage > 4) { + line(player, "The demon doesn't think its task is complete.", ln++, stage > 7) + } + if(stage >= 100) { + line(player, "QUEST COMPLETE", ln++, false) + } + } + + override fun hasRequirements(player: Player): Boolean { + return player.skills.getStaticLevel(Skills.CRAFTING) >= 20 && player.skills.getStaticLevel(Skills.THIEVING) >= 25 + } + + override fun finish(player: Player?) { + super.finish(player) + player ?: return + var ln = 10 + player.packetDispatch.sendItemZoomOnInterface(Items.STATUETTE_4618,230,277,5) + drawReward(player, "1 quest point", ln++) + drawReward(player, "1,000 Crafting XP",ln++) + drawReward(player, "1,000 Theiving XP", ln++) + rewardXP(player, Skills.CRAFTING, 3000.0) + rewardXP(player, Skills.THIEVING, 2000.0) + } + + override fun updateVarps(player: Player) { + TheGolemListeners.updateVarps(player) + } +} + +@Initializable +class ClayGolemNPC : AbstractNPC { + constructor() : super(NPCs.BROKEN_CLAY_GOLEM_1908, null, true) {} + private constructor(id: Int, location: Location) : super(id, location) {} + + override fun construct(id: Int, location: Location, vararg objects: Any?): AbstractNPC { + return ClayGolemNPC(id, location) + } + + override fun getIds(): IntArray { + return intArrayOf(1907, NPCs.BROKEN_CLAY_GOLEM_1908, NPCs.DAMAGED_CLAY_GOLEM_1909, NPCs.CLAY_GOLEM_1910) + } +} + +class LetterListener : InterfaceListener { + override fun defineInterfaceListeners() { + onOpen(220) { player, component -> + val lines: Array = player.getAttribute("ifaces:220:lines", arrayOf()) + for(i in 0 until Math.min(lines.size, 15)) { + setInterfaceText(player, lines[i], 220, i+1) + //setInterfaceText(player, "${i}", 220, i+1) + } + return@onOpen true + } + } +} + +class DisplayCaseListener : InterfaceListener { + override fun defineInterfaceListeners() { + onOpen(534) { player, component -> + //player.packetDispatch.sendModelOnInterface(25576, 534, 5, 650) + val model = player.getAttribute("ifaces:534:model", 0) + player.packetDispatch.sendModelOnInterface(model, 534, 4, 461) + //player.packetDispatch.sendModelOnInterface(25576, 534, 12, 973) + setInterfaceText(player, DISPLAY_CASE_TEXT.joinToString("
"), 534, 2) + setInterfaceText(player, "30", 534, 85) + return@onOpen true + } + } +} + +class TheGolemListeners : InteractionListener { + fun repairGolem(player: Player): Boolean { + if(player.questRepository.getStage("The Golem") == 1) { + var clayUsed = player.getAttribute("the-golem:clay-used", 0) + val msg = when(clayUsed) { + 0 -> "You apply some clay to the golem's wounds. The clay begins to harden in the hot sun." + 1 -> "You fix the golem's legs." + 2 -> "The golem is nearly whole." + 3 -> "You repair the golem with a final piece of clay." + else -> null + } + if(removeItem(player, Item(Items.SOFT_CLAY_1761, 1))) { + if(msg != null) { + sendItemDialogue(player, Items.SOFT_CLAY_1761, msg) + } + clayUsed = Math.min(clayUsed + 1, 4) + player.setAttribute("/save:the-golem:clay-used", clayUsed) + updateVarps(player) + if(clayUsed == 4) { + setQuestStage(player, "The Golem", 2) + } + } + } + return true + } + + fun takeThroneGems(player: Player) { + if(player.getAttribute("the-golem:gems-taken", false)) { + return + } + if(!player.inventory.containsAtLeastOneItem(Items.HAMMER_2347) || !player.inventory.containsAtLeastOneItem(Items.CHISEL_1755)) { + player.sendMessage("You need a hammer and chisel to remove the gems.") + return + } + if(!(player.inventory.freeSlots() >= 6)) { + player.sendMessage("You don't have enough free space to remove all six gems.") + return + } + player.setAttribute("/save:the-golem:gems-taken", true) + updateVarps(player) + for(item in arrayOf(Items.SAPPHIRE_1607, Items.EMERALD_1605, Items.RUBY_1603)) { + player.inventory.add(Item(item, 2)) + } + sendItemDialogue(player, Items.RUBY_1603, "You prize the gems from the demon's throne.") + } + + companion object { + @JvmStatic + fun hasStatuette(player: Player): Boolean { + return player.inventory.containsAtLeastOneItem(4618) || player.bank.containsAtLeastOneItem(4618) || player.getAttribute("the-golem:placed-statuette", false) + } + + @JvmStatic + fun initializeStatuettes(player: Player) { + if(!player.getAttribute("the-golem:statuette-rotation:initialized", false)) { + for(i in 0 until 4) { + player.setAttribute("/save:the-golem:statuette-rotation:${i}", RandomFunction.random(2)); + } + player.setAttribute("/save:the-golem:statuette-rotation:initialized", true) + } + } + + @JvmStatic + fun updateVarps(player: Player) { + val clayUsed = player.getAttribute("the-golem:clay-used", 0) + val gemsTaken = if(player.getAttribute("the-golem:gems-taken", false)) { 1 } else { 0 } + val statuetteTaken = if(hasStatuette(player)) { 1 } else { 0 } + val statuettePlaced = if(player.getAttribute("the-golem:placed-statuette", false)) { 1 } else { 0 } + initializeStatuettes(player) + val rotation1 = player.getAttribute("the-golem:statuette-rotation:1", 0) + val rotation2 = player.getAttribute("the-golem:statuette-rotation:2", 0) + val rotation3 = player.getAttribute("the-golem:statuette-rotation:3", 0) + val rotation4 = player.getAttribute("the-golem:statuette-rotation:4", 0) + val doorOpen = player.getAttribute("the-golem:door-open", false) + var clientStage = 0 + if(player.questRepository.getStage("The Golem") > 0) { + clientStage = Math.max(clientStage, 1) + } + if(doorOpen) { + clientStage = Math.max(clientStage, 5) + } + if(player.questRepository.getStage("The Golem") >= 100) { + clientStage = Math.max(clientStage, 10) + } + player.varpManager.get(437) + .setVarbit(0, clientStage) + .setVarbit(7, clayUsed) + .setVarbit(16, gemsTaken) + .setVarbit(17, statuetteTaken) + .setVarbit(10, rotation1) + .setVarbit(11, rotation2) + .setVarbit(12, rotation3) + .setVarbit(13, statuettePlaced * (rotation4 + 1)) + .send(player) + } + } + + fun pickpocketCurator(player: Player, node: Node): Boolean { + if(player.inventory.containsAtLeastOneItem(4617) || player.bank.containsAtLeastOneItem(4617)) { + player.sendMessage("You have no reason to do that.") + return true + } + sendItemDialogue(player, 4617, "You steal a tiny key.") + addItemOrDrop(player, 4617, 1) + return true + } + + fun displayCase(player: Player, node: Scenery): Boolean { + val model = node.definition.modelIds[0] + player.setAttribute("ifaces:534:model", model) + openInterface(player, 534) + return true + } + + fun openDisplayCase(player: Player, node: Node): Boolean { + if(!player.inventory.containsAtLeastOneItem(4617)) { + player.sendMessage("You can't open the display case without the key.") + return true + } + if(hasStatuette(player)) { + return true + } + addItemOrDrop(player, 4618, 1) + updateVarps(player) + return true + } + + fun placeStatuette(player: Player): Boolean { + System.out.println("Hello from placeStatuette") + if(player.inventory.remove(Item(4618))) { + player.sendMessage("You insert the statuette into the alcove.") + player.setAttribute("/save:the-golem:placed-statuette", true) + updateVarps(player) + } + return true + } + + fun turnStatuette(player: Player, node: Scenery): Boolean { + if(player.getAttribute("the-golem:door-open", false)) { + player.sendMessage("You've already opened the door.") + return true + } + val index = when(node.wrapper.id) { + 6303 -> 1 + 6304 -> 2 + 6305 -> 3 + 6306 -> 4 + else -> return true + } + initializeStatuettes(player) + val rotation = 1 - player.getAttribute("the-golem:statuette-rotation:${index}", 0) + val dir = if(rotation == 0) { "right" } else { "left" } + player.sendMessage("You turn the statuette to the ${dir}.") + player.setAttribute("the-golem:statuette-rotation:${index}", rotation) + checkDoor(player) + updateVarps(player) + return true + } + + fun checkDoor(player: Player) { + if(!player.getAttribute("the-golem:door-open", false)) { + val rotation1 = player.getAttribute("the-golem:statuette-rotation:1", 0) + val rotation2 = player.getAttribute("the-golem:statuette-rotation:2", 0) + val rotation3 = player.getAttribute("the-golem:statuette-rotation:3", 0) + val rotation4 = player.getAttribute("the-golem:statuette-rotation:4", 0) + val placed = player.getAttribute("the-golem:placed-statuette", false) + if(rotation1 == 1 && rotation2 == 1 && rotation3 == 0 && rotation4 == 0 && placed) { + player.sendMessage("The door grinds open.") + player.setAttribute("/save:the-golem:door-open", true) + } + } + } + + fun mortarOnMushroom(player: Player): Boolean { + if(!player.inventory.containsAtLeastOneItem(Items.VIAL_229)) { + player.sendMessage("You need a vial to do that.") + return true + } + if(player.inventory.remove(Item(Items.BLACK_MUSHROOM_4620, 1)) && player.inventory.remove(Item(Items.VIAL_229, 1))) { + sendItemDialogue(player, Items.BLACK_MUSHROOM_INK_4622, "You crush the mushroom and pour the juice into a vial.") + player.inventory.add(Item(Items.BLACK_MUSHROOM_INK_4622, 1)) + } + return true + } + + fun featherOnInk(player: Player): Boolean { + if(player.inventory.remove(Item(Items.PHOENIX_FEATHER_4621, 1))) { + sendItemDialogue(player, Items.PHOENIX_QUILL_PEN_4623, "You dip the phoenix feather into the ink.") + player.inventory.add(Item(Items.PHOENIX_QUILL_PEN_4623, 1)) + } + return true + } + + fun penOnPapyrus(player: Player): Boolean { + if(!player.getAttribute("the-golem:varmen-notes-read", false)) { + player.sendMessage("You don't know what to write."); + return true + } + if(player.inventory.remove(Item(Items.PAPYRUS_970, 1))) { + sendItemDialogue(player, Items.GOLEM_PROGRAM_4624, "You write on the papyrus:
YOUR TASK IS DONE") + player.inventory.add(Item(Items.GOLEM_PROGRAM_4624, 1)) + } + return true + } + + fun implementOnGolem(player: Player): Boolean { + if(!player.getAttribute("the-golem:varmen-notes-read", false)) { + player.sendMessage("You don't know what that would do."); + return true + } + if(player.questRepository.getStage("The Golem") == 7) { + player.sendMessage("You insert the key and the golem's skull hinges open.") + setQuestStage(player, "The Golem", 8) + } + return true + } + + fun programOnGolem(player: Player, used: Node, with: Node): Boolean { + player.dialogueInterpreter.open(ClayGolemProgramDialogueFile(), with) + return true + } + + + override fun defineDestinationOverrides() { + addClimbDest(Location.create(3492, 3089, 0), Location.create(2722, 4886, 0)) + addClimbDest(Location.create(2721, 4884, 0), Location.create(3491, 3090, 0)) + setDest(SCENERY, intArrayOf(34978), "climb-down") { _, _ -> return@setDest Location.create(3491, 3090, 0) } + setDest(SCENERY, intArrayOf(6372), "climb-up") { _, _ -> return@setDest Location.create(2722, 4886, 0) } + } + + override fun defineListeners() { + onUseWith(NPC, Items.SOFT_CLAY_1761, 1907) { player, used, with -> return@onUseWith repairGolem(player) } + on(34978, SCENERY, "climb-down") { player, node -> ClimbActionHandler.climb(player, ClimbActionHandler.CLIMB_DOWN, SpecialLadders.getDestination(node.location)); return@on true } + on(6372, SCENERY, "climb-up") { player, node -> ClimbActionHandler.climb(player, ClimbActionHandler.CLIMB_UP, SpecialLadders.getDestination(node.location)); return@on true } + on(4615, ITEM, "read") { player, node -> + player.setAttribute("ifaces:220:lines", LETTER_LINES) + player.setAttribute("/save:the-golem:read-elissa-letter", true) + openInterface(player, 220) + return@on true + } + on(35226, SCENERY, "search") { player, node -> + player.packetDispatch.sendMessage("You search the bookcase.") + val readLetter = player.getAttribute("the-golem:read-elissa-letter", false) + if(!player.inventory.containsAtLeastOneItem(4616) && !player.bank.containsAtLeastOneItem(4616) && readLetter) { + sendItemDialogue(player, 4616, "You find Varmen's expedition notes.") + addItemOrDrop(player, 4616, 1) + } else { + player.packetDispatch.sendMessage("You find nothing of interest.") + } + return@on true + } + on(6363, SCENERY, "open") { player, node -> player.sendMessage("The door doesn't open."); return@on true } + on(6364, SCENERY, "enter") { player, node -> + player.sendMessage("You step into the portal.") + if(!player.getAttribute("the-golem:seen-demon", false)) { + player.sendMessage("The room is dominated by a colossal horned skeleton!") + player.setAttribute("/save:the-golem:seen-demon", true) + setQuestStage(player, "The Golem", 4) + } + teleport(player, Location.create(3552, 4948, 0)) + return@on true + } + on(6282, SCENERY, "enter") { player, node -> + player.sendMessage("You step into the portal.") + teleport(player, Location.create(2722, 4911, 0)) + return@on true + } + onUseWith(SCENERY, Items.HAMMER_2347, 6301) { player, _, _ -> takeThroneGems(player); return@onUseWith true } + onUseWith(SCENERY, Items.CHISEL_1755, 6301) { player, _, _ -> takeThroneGems(player); return@onUseWith true } + on(646, NPC, "pickpocket") { player, node -> return@on pickpocketCurator(player, node) } + on(intArrayOf(24627, 24550), SCENERY, "study") { player, node -> return@on displayCase(player, node as Scenery) } + on(24627, SCENERY, "open") { player, node -> return@on openDisplayCase(player, node) } + onUseWith(SCENERY, 4618, 6309) { player, used, with -> return@onUseWith placeStatuette(player) } + on(intArrayOf(6307, 6308), SCENERY, "turn") { player, node -> turnStatuette(player, node as Scenery) } + onUseWith(ITEM, 233, 4620) { player, used, with -> return@onUseWith mortarOnMushroom(player) } + onUseWith(ITEM, 4621, 4622) { player, used, with -> return@onUseWith featherOnInk(player) } + onUseWith(ITEM, 970, 4623) { player, used, with -> return@onUseWith penOnPapyrus(player) } + onUseWith(NPC, 4619, 1907) { player, used, with -> return@onUseWith implementOnGolem(player) } + onUseWith(NPC, 4624, 1907) { player, used, with -> return@onUseWith programOnGolem(player, used, with) } + on(1911, NPC, "grab-feather") { player, node -> + if(player.getAttribute("the-golem:varmen-notes-read", false)) { + player.lock() + GameWorld.Pulser.submit(object : Pulse(){ + var counter = 0 + override fun pulse(): Boolean { + when(counter++){ + 0 -> { + player.sendMessage("You attempt to grab the pheonix's tail-feather.") + player.animator.animate(Animation(881)) + } + 3 -> { + player.sendMessage("You grab a tail-feather.") + player.inventory.add(Item(Items.PHOENIX_FEATHER_4621)) + player.unlock() + return true + } + } + return false + } + }) + } else { + player.sendMessage("You have no reason to take the phoenix's feathers.") + } + return@on true + } + } +} diff --git a/Server/src/main/kotlin/rs09/game/content/quest/members/thelosttribe/PickaxeOnRubble.kt b/Server/src/main/kotlin/rs09/game/content/quest/members/thelosttribe/PickaxeOnRubble.kt index c86aa3361..c15869a7d 100644 --- a/Server/src/main/kotlin/rs09/game/content/quest/members/thelosttribe/PickaxeOnRubble.kt +++ b/Server/src/main/kotlin/rs09/game/content/quest/members/thelosttribe/PickaxeOnRubble.kt @@ -16,7 +16,9 @@ import core.plugin.Plugin */ class PickaxeOnRubble : UseWithHandler(1265,1267,1269,1271,1273,1275){ override fun newInstance(arg: Any?): Plugin { - addHandler(6898, OBJECT_TYPE,this) + for(id in arrayOf(6898, 6903, 6904, 6905)) { + addHandler(id, OBJECT_TYPE,this) + } return this } @@ -66,4 +68,4 @@ class PickaxeOnRubble : UseWithHandler(1265,1267,1269,1271,1273,1275){ return true } -} \ No newline at end of file +} diff --git a/Server/src/main/kotlin/rs09/game/interaction/InteractionListeners.kt b/Server/src/main/kotlin/rs09/game/interaction/InteractionListeners.kt index ac75ff28c..e9a332e42 100644 --- a/Server/src/main/kotlin/rs09/game/interaction/InteractionListeners.kt +++ b/Server/src/main/kotlin/rs09/game/interaction/InteractionListeners.kt @@ -160,6 +160,7 @@ object InteractionListeners { @JvmStatic fun run(used: Node, with: Node, type: Int,player: Player): Boolean{ + player.debug("InteractionListeners.run(${used.id}, ${with.id}, ${type}, _)") val flag = when(type){ 2, 4 -> DestinationFlag.ENTITY 1 -> DestinationFlag.OBJECT